Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6f993a6
test(engine): the scripted Claude reports its running tasks stopped a…
fxck Oct 9, 2026
100f875
fix(engine): background work keeps whose it is, the call that started…
fxck Oct 9, 2026
90a5681
fix(client): an engine card reads whose its work is, its call and its…
fxck Oct 9, 2026
d924579
fix(client): a card waiting on its helpers never counts their own com…
fxck Oct 9, 2026
34c671b
fix(web): a helper's own helper is never among the helpers the Mate s…
fxck Oct 9, 2026
57a405e
fix(web): a helpers bubble marks a failure at once and says how its h…
fxck Oct 9, 2026
ff67ba3
fix(web): every call the Mate made counts in its card's effort, its Z…
fxck Oct 9, 2026
71f29ed
fix(web): a message steered into a run is drawn once, above its card
fxck Oct 9, 2026
b472a50
fix(web): a wake's answer appears under the reply the person was reading
fxck Oct 9, 2026
fb75e20
fix(web): a background job that ended ran in the background
fxck Oct 9, 2026
417ca23
fix(web): a check of a site's front page names the home page, never "/"
fxck Oct 9, 2026
ab28c4a
fix(claude): a turn Claude opens on its own owns the command it strea…
fxck Oct 9, 2026
02a16c6
test(web): a run-card oracle holds an engine Mate's card against what…
fxck Oct 9, 2026
44de1a1
test(web): an oracle tree's message hands the Mate what ended before …
fxck Oct 9, 2026
e4f5daa
test(web): the wake journey holds the reply the person read to the li…
fxck Oct 9, 2026
60a6e0c
fix(web): a person's own message, sent or steered, moves the conversa…
fxck Oct 9, 2026
6653edf
Merge remote-tracking branch 'origin/main' into engine/card-oracle
fxck Oct 9, 2026
47102f7
test(web): the wake journey keeps its sentence; the steer mark's sent…
fxck Oct 9, 2026
c7cdd37
fix(web): a run that only wrote to the person thought, live and after…
fxck Oct 9, 2026
43c3d74
test(web): the steering picture journey reads the picture on its mess…
fxck Oct 9, 2026
6d56cc5
Merge remote-tracking branch 'origin/main' into engine/card-oracle
fxck Oct 10, 2026
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
6 changes: 6 additions & 0 deletions apps/server/src/engine/bridge/spi3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,12 @@ export type SignalBody =
readonly kind: WorkKind;
readonly status: WorkStatus;
readonly title?: string;
/** The helper whose own tool started it; absent: the Mate's. */
readonly helper?: WorkKey;
/** The call that started it. */
readonly call?: ItemKey;
/** What it said as it ended, in a line. */
readonly report?: string;
}
// requests
| {
Expand Down
72 changes: 65 additions & 7 deletions apps/server/src/engine/bridge/translate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@
* - every item, request and piece of work has an app key, never a native id;
* - a late item never reopens a turn: it carries `afterEnd`;
* - an open call is closed `unreturned` at its turn's end, an open request
* `superseded`, and at a session's close every live work is `lost` and every
* open request `expired` — except a question asked by message, which no
* callback waits on: a person's message answers it after its turn.
* `superseded`, and at a session's close every live work is `lost` (`stopped`
* when a Stop closed it) and every open request `expired` — except a question
* asked by message, which no callback waits on: a person's message answers it
* after its turn.
*
* Its state lives in memory: a host restart kills every driver process, so the
* engine's own boot ends what was open and a new session starts a new fold.
Expand Down Expand Up @@ -156,6 +157,12 @@ interface WorkState {
kind?: WorkKind;
status?: WorkStatus;
title?: string | undefined;
/** The helper whose own tool started it; none: the Mate's. */
helper?: WorkKey;
/** The call that started it: the command sent to the background, the helper's launch. */
call?: ItemKey;
/** What it said as it ended, in a line. */
report?: string;
}

interface RequestState {
Expand Down Expand Up @@ -423,9 +430,11 @@ export function makeTranslator(options: TranslatorOptions): Translator {
undelivered: "unknown",
});
}
// A Stop that closed the session stopped what it ran: that work was stopped, not lost (Claude
// reports its tasks stopped only after the turn's end, from a session already closed).
for (const work of owner.work.values()) {
if (work.status !== undefined && isLiveWork(work.status)) {
work.status = "lost";
work.status = cause === "stopped-turn" ? "stopped" : "lost";
emitWork(owner, work);
}
}
Expand Down Expand Up @@ -516,6 +525,9 @@ export function makeTranslator(options: TranslatorOptions): Translator {
kind: work.kind ?? "other",
status: work.status ?? "running",
...(work.title === undefined ? {} : { title: work.title }),
...(work.helper === undefined ? {} : { helper: work.helper }),
...(work.call === undefined ? {} : { call: work.call }),
...(work.report === undefined ? {} : { report: work.report }),
});
};

Expand All @@ -532,7 +544,13 @@ export function makeTranslator(options: TranslatorOptions): Translator {
const nativeId = event.itemId === undefined ? undefined : String(event.itemId);
let item = nativeId === undefined ? undefined : owner.items.get(nativeId);
if (item === undefined) {
const turn = turnOf(owner, event);
// A helper's own call goes on under the turn that started its helper, whatever turn is open
// when it comes: it is that card's, never the next message's.
const launched = payload.agentId ?? payload.parentToolUseId;
const helper = launched === undefined ? undefined : owner.work.get(launched);
const helperTurn =
helper === undefined || helper.origin === "unknown" ? undefined : turns.get(helper.origin);
const turn = helperTurn ?? turnOf(owner, event);
if (turn === undefined) return;
item = newItem(turn, itemBody(event));
if (nativeId !== undefined) owner.items.set(nativeId, item);
Expand Down Expand Up @@ -757,11 +775,43 @@ export function makeTranslator(options: TranslatorOptions): Translator {
return;
}
const work = workFor(owner, nativeIds);
const before = JSON.stringify([work.kind, work.status, work.title, work.origin]);
const facts = () =>
JSON.stringify([
work.kind,
work.status,
work.title,
work.origin,
work.helper,
work.call,
work.report,
]);
const before = facts();
// Whose it is: the helper whose own tool started it, as each report of it repeats
// (Claude's `agentId`, Codex's `parentAgentId` of a nested helper). It is that helper's,
// and goes on under the run its helper served (Milo's second stress run drew a helper's
// sleep as the Mate's, and a nested helper among the Mate's own).
const owning =
payload.agentId ?? ("parentAgentId" in payload ? payload.parentAgentId : undefined);
if (owning !== undefined && work.helper === undefined) {
const helper = workFor(owner, [owning]);
if (helper !== work) {
work.helper = helper.key;
if (helper.origin !== "unknown") work.origin = helper.origin;
}
}
if (work.origin === "unknown" && event.turnId !== undefined) {
const origin = owner.nativeTurns.get(String(event.turnId));
if (origin !== undefined) work.origin = origin.handle;
}
// The call that started it, by the tool use the task names.
if (work.call === undefined && payload.toolUseId !== undefined) {
const started = owner.items.get(payload.toolUseId);
if (started !== undefined) work.call = started.key;
}
if (event.type === "task.completed" && event.payload.summary !== undefined) {
const line = reportLine(event.payload.summary);
if (line !== undefined) work.report = line;
}
if (
work.kind === undefined ||
payload.agentKind !== undefined ||
Expand All @@ -777,7 +827,7 @@ export function makeTranslator(options: TranslatorOptions): Translator {
payload.title ??
work.title;
work.status = taskStatus(event, work.status);
if (JSON.stringify([work.kind, work.status, work.title, work.origin]) !== before) {
if (facts() !== before) {
emitWork(owner, work);
}
return;
Expand Down Expand Up @@ -1262,6 +1312,14 @@ function itemStatus(
return payload.status === "completed" ? "completed" : "running";
}

/** What a report says first, in a line a row can hold: "… failed with exit code 3". */
const REPORT_LINE = 280;
function reportLine(summary: string): string | undefined {
const first = summary.trim().split("\n")[0]?.trim() ?? "";
if (first === "") return undefined;
return first.length <= REPORT_LINE ? first : `${first.slice(0, REPORT_LINE - 1)}…`;
}

function workKind(
stamped: "agent" | "background" | undefined,
taskType: string | undefined,
Expand Down
6 changes: 6 additions & 0 deletions apps/server/src/engine/domain/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ export type ProviderSignal =
readonly workKind: Extract<ItemBody, { readonly kind: "work" }>["workKind"];
readonly status: Extract<ItemBody, { readonly kind: "work" }>["status"];
readonly title?: string;
/** The helper (its work's key) whose own tool started it; absent: the Mate's. */
readonly helper?: string;
/** The key of the call that started it. */
readonly call?: string;
/** What it said as it ended, in a line. */
readonly report?: string;
}
/** A limit whose reset was unknown learned its reset time (the driver's rate-limit report). */
| { readonly kind: "usage-reset-known"; readonly resetsAt: number }
Expand Down
14 changes: 13 additions & 1 deletion apps/server/src/engine/domain/decide.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1961,12 +1961,20 @@ const signalOne = (b: StepBuilder, sessionId: SessionId, signal: ProviderSignal)
// session went) was stopped by the person, not lost.
const byPerson =
(signal.status === "lost" || signal.status === "stopped") && personStopped(b.state);
// The call that started it, by its key: still open, or closed in a run the state keeps.
const call =
signal.call === undefined
? undefined
: (b.state.closedItems[signal.call]?.itemId ??
Object.values(b.state.items).find((item) => item.key === signal.call)?.id);
const body: ItemBody = {
kind: "work",
work: signal.work,
workKind: signal.workKind,
status: byPerson ? "stopped" : signal.status,
title: signal.title ?? null,
...(call === undefined ? {} : { call }),
...(signal.report === undefined ? {} : { report: signal.report }),
};
const ends = WORK_ENDED.has(signal.status);
const closed = b.state.closedItems[signal.work];
Expand Down Expand Up @@ -2005,7 +2013,11 @@ const signalOne = (b: StepBuilder, sessionId: SessionId, signal: ProviderSignal)
runId: owner.id,
itemId: id,
key: signal.work,
by: { kind: "mate" },
// A helper's own work is the helper's, as its calls are.
by:
signal.helper === undefined
? { kind: "mate" }
: { kind: "helper", helperId: signal.helper },
body,
});
if (ends) b.emit({ _tag: "ItemClosed", runId: owner.id, itemId: id, body });
Expand Down
197 changes: 197 additions & 0 deletions apps/server/src/engine/engine.pump.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1274,3 +1274,200 @@ describe("the running engine", () => {
),
);
});

/**
* Claude's helpers and background work as its adapter reports them: a helper's own calls carry the
* launch they run under and the helper's task (`agentId`), a task a helper's tool started names that
* helper as its owner, and every report of a task repeats its linkage (`ClaudeAdapter`
* `taskLinkageFor`).
*/
describe("Claude's helpers and background work, as the engine records them", () => {
const claude = (w: EngineWorld) => ({
call: (
id: string,
args: Record<string, unknown>,
owner: { readonly helper: string; readonly launch: string } | null = null,
tool = "Bash",
result = "",
) =>
w.agent((agent, thread) =>
Effect.gen(function* () {
const head = {
itemType: tool === "Agent" ? "collab_agent_tool_call" : "command_execution",
title: tool === "Agent" ? "Subagent task" : "Command run",
...(owner === null ? {} : { agentId: owner.helper, parentToolUseId: owner.launch }),
};
yield* agent.emit("item.started", thread, {
itemId: id,
payload: { ...head, status: "inProgress", data: { toolName: tool, input: args } },
});
yield* agent.emit("item.completed", thread, {
itemId: id,
payload: {
...head,
status: "completed",
data: {
toolName: tool,
input: args,
result: { type: "tool_result", content: result },
},
},
});
}),
),
task: (type: "task.started" | "task.completed", payload: Record<string, unknown>) =>
w.agent((agent, thread) => agent.emit(type, thread, { payload })),
});

const works = (w: EngineWorld, n: number) =>
Effect.map(w.items(r(n)), (items) => items.filter((item) => item.kind === "work"));

// Milo's second stress run drew a helper's background sleep as the Mate's own job, and a nested
// helper among the Mate's helpers.
it.effect("a helper's own background job and helper are recorded as that helper's", () =>
scene(
Effect.gen(function* () {
const w = yield* world("claudeAgent");
const c = claude(w);
yield* send(w);
yield* c.call(
"toolu_h",
{ description: "Look into it", run_in_background: true },
null,
"Agent",
);
yield* c.task("task.started", {
taskId: "ah",
description: "Look into it",
taskType: "local_agent",
toolUseId: "toolu_h",
});
const owner = { helper: "ah", launch: "toolu_h" };
yield* c.call("toolu_j", { command: "sleep 30", run_in_background: true }, owner);
yield* c.task("task.started", {
taskId: "bj",
description: "Sleep",
taskType: "local_bash",
agentId: "ah",
toolUseId: "toolu_j",
});
yield* c.call("toolu_n", { description: "Dig deeper" }, owner, "Agent");
yield* c.task("task.started", {
taskId: "an",
description: "Dig deeper",
taskType: "local_agent",
agentId: "ah",
toolUseId: "toolu_n",
});
const [helper, job, nested] = yield* works(w, 1);
assert.deepStrictEqual(
[helper?.by, job?.by, nested?.by],
[
{ kind: "mate" },
{ kind: "helper", helperId: helper?.body.work },
{ kind: "helper", helperId: helper?.body.work },
],
);
yield* w.shutdown;
}),
),
);

it.effect(
"a helper's own call goes on under the run that started its helper, never the next message's",
() =>
scene(
Effect.gen(function* () {
const w = yield* world("claudeAgent");
const c = claude(w);
yield* send(w);
yield* c.call("toolu_h", { description: "Look into it" }, null, "Agent");
yield* c.task("task.started", {
taskId: "ah",
description: "Look into it",
taskType: "local_agent",
toolUseId: "toolu_h",
});
yield* w.agent((agent, thread) => agent.finish(thread));
yield* send(w, "second");
yield* c.call("toolu_x", { command: "ls" }, { helper: "ah", launch: "toolu_h" });
const calls = (n: number) =>
Effect.map(w.items(r(n)), (items) =>
items.flatMap((item) =>
item.kind === "call" ? [(item.body.tool as { readonly name: string }).name] : [],
),
);
assert.deepStrictEqual([yield* calls(1), yield* calls(2)], [["Agent", "Bash"], []]);
yield* w.shutdown;
}),
),
);

it.effect(
"a background job's record names the call that started it and the line it ended with",
() =>
scene(
Effect.gen(function* () {
const w = yield* world("claudeAgent");
const c = claude(w);
yield* send(w);
yield* c.call(
"toolu_j",
{ command: "exit 3", run_in_background: true },
null,
"Bash",
"Command running in background with ID: bj.",
);
yield* c.task("task.started", {
taskId: "bj",
description: "Fail on purpose",
taskType: "local_bash",
toolUseId: "toolu_j",
});
yield* c.task("task.completed", {
taskId: "bj",
status: "failed",
summary: 'Background command "Fail on purpose" failed with exit code 3',
taskType: "local_bash",
toolUseId: "toolu_j",
});
const items = yield* w.items(r(1));
const call = items.find((item) => item.kind === "call");
const [job] = yield* works(w, 1);
assert.deepStrictEqual(
[job?.body.call, job?.body.status, job?.body.report],
[
call?.item_id,
"failed",
'Background command "Fail on purpose" failed with exit code 3',
],
);
yield* w.shutdown;
}),
),
);

// Milo's run 2 rig: the job a person's Stop killed read "didn't report back".
it.effect(
"background work a person's Stop killed with Claude's session is recorded stopped",
() =>
scene(
Effect.gen(function* () {
const w = yield* world("claudeAgent");
const c = claude(w);
yield* send(w);
yield* c.call("toolu_j", { command: "sleep 120", run_in_background: true });
yield* c.task("task.started", {
taskId: "bj",
description: "Sleep two minutes",
taskType: "local_bash",
toolUseId: "toolu_j",
});
yield* stop(w);
const [job] = yield* works(w, 1);
assert.strictEqual(job?.body.status, "stopped");
yield* w.shutdown;
}),
),
);
});
Loading
Loading