@@ -4085,7 +4085,12 @@ describe("ClaudeAdapterV2 background wake turns", () => {
40854085 function taskToolFrames(
40864086 tool: TaskToolCall,
40874087 nextUuid: () => string,
4088- options?: { parentToolUseId?: string; isError?: boolean; extraToolResult?: boolean },
4088+ options?: {
4089+ parentToolUseId?: string;
4090+ isError?: boolean;
4091+ extraToolResult?: boolean;
4092+ nonExecutionKind?: string;
4093+ },
40894094 ): ReadonlyArray<SDKMessage> {
40904095 const uuid = nextUuid();
40914096 const toolUseId = `tool-${uuid}`;
@@ -4120,15 +4125,24 @@ describe("ClaudeAdapterV2 background wake turns", () => {
41204125 {
41214126 type: "tool_result",
41224127 tool_use_id: toolUseId,
4123- content: "Task tool finished.",
4128+ content:
4129+ parentToolUseId !== null && tool.name === "TaskCreate"
4130+ ? `Task #${tool.output.task.id} created successfully: ${tool.output.task.subject}`
4131+ : "Task tool finished.",
41244132 is_error: options?.isError ?? false,
41254133 },
41264134 ...(options?.extraToolResult
41274135 ? [{ type: "tool_result", tool_use_id: "unrelated-tool", content: "ok" }]
41284136 : []),
41294137 ],
41304138 },
4131- tool_use_result: tool.output,
4139+ // Like real Claude, subagent results carry only the text, no `tool_use_result`.
4140+ ...(parentToolUseId === null ? { tool_use_result: tool.output } : {}),
4141+ ...(options?.nonExecutionKind === undefined
4142+ ? {}
4143+ : {
4144+ tool_result_meta: [{ id: toolUseId, non_execution_kind: options.nonExecutionKind }],
4145+ }),
41324146 parent_tool_use_id: parentToolUseId,
41334147 uuid: nextUuid(),
41344148 session_id: WAKE_NATIVE_SESSION,
@@ -4157,7 +4171,7 @@ describe("ClaudeAdapterV2 background wake turns", () => {
41574171 });
41584172 const call = Effect.fnUntraced(function* (
41594173 tool: TaskToolCall,
4160- options?: { parentToolUseId?: string; isError?: boolean; extraToolResult?: boolean } ,
4174+ options?: Parameters<typeof taskToolFrames>[2] ,
41614175 ) {
41624176 for (const frame of taskToolFrames(tool, nextUuid, options)) {
41634177 yield* harness.offerAndWait(frame);
@@ -4173,7 +4187,22 @@ describe("ClaudeAdapterV2 background wake turns", () => {
41734187 harness.events.flatMap((event) =>
41744188 event.type === "plan.updated" && event.plan.kind === "todo_list" ? [event.plan] : [],
41754189 );
4176- return { startTurn, call, finishTurn, plans };
4190+ // Registers a running subagent so frames carrying its tool_use_id as
4191+ // parent_tool_use_id are processed instead of held for an unknown owner.
4192+ const launchSubagent = (toolUseId: string) =>
4193+ harness.offerAndWait(
4194+ claudeSdkFrame({
4195+ type: "system",
4196+ subtype: "task_started",
4197+ task_id: `task-${toolUseId}`,
4198+ tool_use_id: toolUseId,
4199+ description: "Work on tasks",
4200+ task_type: "local_agent",
4201+ uuid: nextUuid(),
4202+ session_id: WAKE_NATIVE_SESSION,
4203+ }),
4204+ );
4205+ return { startTurn, call, launchSubagent, finishTurn, plans };
41774206 });
41784207
41794208 const createTask = (id: string, subject: string): TaskToolCall => ({
@@ -4404,20 +4433,31 @@ describe("ClaudeAdapterV2 background wake turns", () => {
44044433 ),
44054434 );
44064435
4407- it.effect("keeps subagent TaskCreate, TaskUpdate and TaskList out of the main plan ", () =>
4436+ it.effect("applies subagent task tools to the parent turn's shared task list ", () =>
44084437 Effect.gen(function* () {
44094438 const harness = yield* makeTaskToolHarness;
44104439 yield* harness.startTurn();
44114440 yield* harness.call(createTask("1", "Main task"));
4441+ yield* harness.launchSubagent("tool-parent-agent");
44124442 const child = { parentToolUseId: "tool-parent-agent" };
4413- yield* harness.call(createTask("2", "Child task"), child);
44144443 yield* harness.call(updateTask({ taskId: "1", status: "completed" }), child);
4415- yield* harness.call({ name: "TaskList", input: {}, output: { tasks: [] } }, child);
4416- yield* harness.call(updateTask({ taskId: "1", status: "in_progress" }));
4444+ yield* harness.call(createTask("2", "Child task"), child);
4445+ yield* harness.call(updateTask({ taskId: "2", status: "in_progress" }), {
4446+ ...child,
4447+ isError: true,
4448+ });
4449+ // A cancelled call never ran, even without `is_error`.
4450+ yield* harness.call(updateTask({ taskId: "2", status: "completed" }), {
4451+ ...child,
4452+ nonExecutionKind: "cancelled",
4453+ });
44174454 yield* harness.finishTurn();
4418- assert.lengthOf(harness.plans(), 2);
4419- assert.deepEqual(harness.plans().at(-1)?.steps, [
4420- { id: "task-1", text: "Main task", status: "running" },
4455+ const plans = harness.plans();
4456+ assert.lengthOf(plans, 3);
4457+ assert.equal(new Set(plans.map((plan) => plan.id)).size, 1);
4458+ assert.deepEqual(plans.at(-1)?.steps, [
4459+ { id: "task-1", text: "Main task", status: "completed" },
4460+ { id: "task-2", text: "Requested Child task", status: "pending" },
44214461 ]);
44224462 }).pipe(
44234463 Effect.scoped,
0 commit comments