Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 128 additions & 11 deletions apps/server/src/provider/Layers/CodexAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -512,24 +512,141 @@ function startLifecycleRuntime() {
});
}

const childEvent = (id: string, method: string, payload: Record<string, unknown>) => ({
id: asEventId(id),
kind: "notification" as const,
provider: ProviderDriverKind.make("codex"),
createdAt: "2026-01-01T00:00:00.000Z",
method,
threadId: asThreadId("thread-1"),
turnId: asTurnId("turn-1"),
payload,
});

lifecycleLayer("CodexAdapterLive lifecycle", (it) => {
it.effect("does not reactivate an idle child after a parent interaction", () =>
it.effect("preserves a nested spawning child as the parent", () =>
Effect.gen(function* () {
const { adapter, runtime } = yield* startLifecycleRuntime();
const firstEventFiber = yield* Stream.runHead(adapter.streamEvents).pipe(Effect.forkChild);

yield* runtime.emit(
childEvent("evt-child-b-started", "collabAgent/activity", {
agentThreadId: "child-b",
agentPath: "/root/child-a/child-b",
activityKind: "started",
parentThreadId: "child-a",
}),
);

const firstEvent = yield* Fiber.join(firstEventFiber);
NodeAssert.ok(firstEvent._tag === "Some");
NodeAssert.ok(firstEvent.value.type === "task.started");
NodeAssert.equal(firstEvent.value.payload.taskId, "child-b");
NodeAssert.equal(firstEvent.value.payload.parentAgentId, "child-a");
}),
);

it.effect("preserves the parent on collabAgent/started", () =>
Effect.gen(function* () {
const { adapter, runtime } = yield* startLifecycleRuntime();
const firstEventFiber = yield* Stream.runHead(adapter.streamEvents).pipe(Effect.forkChild);

yield* runtime.emit(
childEvent("evt-child-announced", "collabAgent/started", {
agentThreadId: "child-1",
agentPath: "/root/parent/child",
parentThreadId: "parent-1",
}),
);

const firstEvent = yield* Fiber.join(firstEventFiber);
NodeAssert.ok(firstEvent._tag === "Some");
NodeAssert.ok(firstEvent.value.type === "task.started");
NodeAssert.equal(firstEvent.value.payload.taskId, "child-1");
NodeAssert.equal(firstEvent.value.payload.parentAgentId, "parent-1");
}),
);

it.effect("does not start a task for an interacted activity", () =>
Effect.gen(function* () {
const { adapter, runtime } = yield* startLifecycleRuntime();
const firstEventFiber = yield* Stream.runHead(adapter.streamEvents).pipe(Effect.forkChild);

yield* runtime.emit(
childEvent("evt-child-interacted", "collabAgent/activity", {
agentThreadId: "child-1",
agentPath: "/root/parent/child",
activityKind: "interacted",
parentThreadId: "parent-1",
}),
);
yield* runtime.emit(
childEvent("evt-sentinel-running", "collabAgent/turnStarted", {
agentThreadId: "child-2",
agentPath: "/root/sentinel",
}),
);

const firstEvent = yield* Fiber.join(firstEventFiber);
NodeAssert.ok(firstEvent._tag === "Some");
NodeAssert.ok(firstEvent.value.type === "task.updated");
NodeAssert.equal(firstEvent.value.payload.taskId, "child-2");
NodeAssert.equal(firstEvent.value.payload.status, "running");
}),
);

it.effect("repeats the parent on child lifecycle rows", () =>
Effect.gen(function* () {
const { adapter, runtime } = yield* startLifecycleRuntime();
const eventsFiber = yield* Stream.runCollect(Stream.take(adapter.streamEvents, 3)).pipe(
Effect.forkChild,
);

const childEvent = (id: string, method: string, payload: Record<string, unknown>) => ({
id: asEventId(id),
kind: "notification" as const,
provider: ProviderDriverKind.make("codex"),
createdAt: "2026-01-01T00:00:00.000Z",
method,
threadId: asThreadId("thread-1"),
turnId: asTurnId("turn-1"),
payload,
});
yield* runtime.emit(
childEvent("evt-child-interrupted", "collabAgent/activity", {
agentThreadId: "child-1",
agentPath: "/root/parent/child",
activityKind: "interrupted",
parentThreadId: "parent-1",
}),
);
yield* runtime.emit(
childEvent("evt-child-usage", "collabAgent/tokenUsage", {
agentThreadId: "child-1",
agentPath: "/root/parent/child",
parentThreadId: "parent-1",
tokenUsage: { total: { totalTokens: 1 } },
}),
);
yield* runtime.emit(
childEvent("evt-child-item", "collabAgent/item", {
agentThreadId: "child-1",
agentPath: "/root/parent/child",
parentThreadId: "parent-1",
item: { type: "commandExecution", command: "pwd" },
}),
);

const events = Array.from(yield* Fiber.join(eventsFiber));
const updated = events[0];
NodeAssert.ok(updated?.type === "task.updated");
NodeAssert.equal(updated.payload.status, "interrupted");
NodeAssert.equal(updated.payload.parentAgentId, "parent-1");
const usageProgress = events[1];
NodeAssert.ok(usageProgress?.type === "task.progress");
NodeAssert.equal(usageProgress.payload.parentAgentId, "parent-1");
const itemProgress = events[2];
NodeAssert.ok(itemProgress?.type === "task.progress");
NodeAssert.equal(itemProgress.payload.parentAgentId, "parent-1");
}),
);

it.effect("does not reactivate an idle child after a parent interaction", () =>
Effect.gen(function* () {
const { adapter, runtime } = yield* startLifecycleRuntime();
const eventsFiber = yield* Stream.runCollect(Stream.take(adapter.streamEvents, 3)).pipe(
Effect.forkChild,
);

yield* runtime.emit(
childEvent("evt-child-running", "collabAgent/turnStarted", {
Expand Down
55 changes: 19 additions & 36 deletions apps/server/src/provider/Layers/CodexAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -530,35 +530,33 @@ function mapCollabAgentEvent(
// finding: progress rows renamed math_one to its UUID).
const knownName = nickname ?? pathLeaf;
const title = knownName ?? agentThreadId;
// Identity repeated on every status patch so rows are self-describing when
// Identity repeated on every lifecycle row so rows are self-describing when
// the start row ages out of activity retention (review finding: a
// reconstructed agent had a UUID name and no role/path).
const statusLinkage = {
role,
...(knownName ? { title: knownName } : {}),
...(agentPath ? { agentPath } : {}),
...(typeof payload.parentThreadId === "string"
? { parentAgentId: payload.parentThreadId }
: {}),
timelineBypass: true,
} as const;

const buildTaskStarted = (): ProviderRuntimeEvent => ({
...base,
type: "task.started",
payload: {
...statusLinkage,
taskId,
description: title,
title,
},
});

switch (event.method) {
case "collabAgent/started":
return [
{
...base,
type: "task.started",
payload: {
taskId,
description: title,
title,
role,
...(agentPath ? { agentPath } : {}),
...(typeof payload.parentThreadId === "string"
? { parentAgentId: payload.parentThreadId }
: {}),
timelineBypass: true,
},
},
];
return [buildTaskStarted()];
case "collabAgent/activity": {
const activityKind = typeof payload.activityKind === "string" ? payload.activityKind : "";
if (activityKind === "interrupted") {
Expand All @@ -575,20 +573,7 @@ function mapCollabAgentEvent(
// alone (no thread/started with a spawn source), so this is the one
// shot at a task.started with a real name — agentPath leaf beats a
// bare thread-id title.
return [
{
...base,
type: "task.started",
payload: {
taskId,
description: title,
title,
role,
...(agentPath ? { agentPath } : {}),
timelineBypass: true,
},
},
];
return [buildTaskStarted()];
}
// Reading a child's result also emits "interacted" after its turn is idle.
// Only the child's turn or thread lifecycle can prove it resumed work.
Expand Down Expand Up @@ -704,9 +689,8 @@ function mapCollabAgentEvent(
payload: {
taskId,
description: title,
...(knownName ? { title: knownName } : {}),
...statusLinkage,
typedUsage,
timelineBypass: true,
},
},
];
Expand Down Expand Up @@ -736,9 +720,8 @@ function mapCollabAgentEvent(
payload: {
taskId,
description: title,
...(knownName ? { title: knownName } : {}),
...statusLinkage,
summary,
timelineBypass: true,
},
},
];
Expand Down
Loading