From 41b628e6866fa1f32f964c3fb02211008f3394d9 Mon Sep 17 00:00:00 2001 From: BKF-Gitty Date: Sun, 19 Apr 2026 02:43:43 +0300 Subject: [PATCH] fix(subagent): include role, session key, and timing in error payloads Error payloads from sessions_spawn and subagent wait outcomes now carry the context a parent needs to retry or report clearly: - sessions-spawn-tool: add role (requested agentId) to early validation errors, to the ACP register-failure payload, and to forwarded error results from both the ACP and subagent spawn paths. childSessionKey and runId are already populated by the inner spawn for the errors that know them; this just plumbs role through alongside. - subagent-announce-output: extend SubagentRunOutcome with optional startedAt/endedAt/elapsedMs and populate them in applySubagentWaitOutcome so timeout and error outcomes convey how long the child ran before failing. Scoped verification: tsgo:core, tsgo:core:test, and 43 targeted tests in src/agents (sessions-spawn-tool, subagent-registry lifecycle retry grace, subagent-announce timeout, subagent-announce, and capture-completion-reply) all green. Repo-wide pnpm check is red on latest origin/main for unrelated extensions/discord and extensions/qa-lab surfaces (missing @buape/carbon and @copilotkit/aimock members); not addressed here. --- src/agents/subagent-announce-output.ts | 29 ++++++++++++++++++------- src/agents/tools/sessions-spawn-tool.ts | 18 +++++++++++++-- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/agents/subagent-announce-output.ts b/src/agents/subagent-announce-output.ts index b40b105cfb2..02e13cb0cdd 100644 --- a/src/agents/subagent-announce-output.ts +++ b/src/agents/subagent-announce-output.ts @@ -58,6 +58,9 @@ export type AgentWaitResult = { export type SubagentRunOutcome = { status: "ok" | "error" | "timeout" | "unknown"; error?: string; + startedAt?: number; + endedAt?: number; + elapsedMs?: number; }; function extractToolResultText(content: unknown): string { @@ -297,20 +300,30 @@ export function applySubagentWaitOutcome(params: { startedAt: params.startedAt, endedAt: params.endedAt, }; - const waitError = typeof params.wait?.error === "string" ? params.wait.error : undefined; - if (params.wait?.status === "timeout") { - next.outcome = { status: "timeout" }; - } else if (params.wait?.status === "error") { - next.outcome = { status: "error", error: waitError }; - } else if (params.wait?.status === "ok") { - next.outcome = { status: "ok" }; - } if (typeof params.wait?.startedAt === "number" && !next.startedAt) { next.startedAt = params.wait.startedAt; } if (typeof params.wait?.endedAt === "number" && !next.endedAt) { next.endedAt = params.wait.endedAt; } + const timing: Pick = {}; + if (typeof next.startedAt === "number") { + timing.startedAt = next.startedAt; + } + if (typeof next.endedAt === "number") { + timing.endedAt = next.endedAt; + } + if (typeof next.startedAt === "number" && typeof next.endedAt === "number") { + timing.elapsedMs = Math.max(0, next.endedAt - next.startedAt); + } + const waitError = typeof params.wait?.error === "string" ? params.wait.error : undefined; + if (params.wait?.status === "timeout") { + next.outcome = { status: "timeout", ...timing }; + } else if (params.wait?.status === "error") { + next.outcome = { status: "error", error: waitError, ...timing }; + } else if (params.wait?.status === "ok") { + next.outcome = { status: "ok", ...timing }; + } return next; } diff --git a/src/agents/tools/sessions-spawn-tool.ts b/src/agents/tools/sessions-spawn-tool.ts index fa05597c721..4fcc0359cad 100644 --- a/src/agents/tools/sessions-spawn-tool.ts +++ b/src/agents/tools/sessions-spawn-tool.ts @@ -201,10 +201,13 @@ export function createSessionsSpawnTool( }>) : undefined; + const roleContext = requestedAgentId ? { role: requestedAgentId } : {}; + if (streamTo && runtime !== "acp") { return jsonResult({ status: "error", error: `streamTo is only supported for runtime=acp; got runtime=${runtime}`, + ...roleContext, }); } @@ -212,6 +215,7 @@ export function createSessionsSpawnTool( return jsonResult({ status: "error", error: `resumeSessionId is only supported for runtime=acp; got runtime=${runtime}`, + ...roleContext, }); } @@ -222,6 +226,7 @@ export function createSessionsSpawnTool( status: "error", error: "attachments are currently unsupported for runtime=acp; use runtime=subagent or remove attachments", + ...roleContext, }); } const result = await spawnAcpDirect( @@ -304,10 +309,15 @@ export function createSessionsSpawnTool( error: `Failed to register ACP run: ${summarizeError(err)}. Cleanup was attempted, but the already-started ACP run may still finish in the background.`, childSessionKey, runId: childRunId, + ...roleContext, }); } } - return jsonResult(result); + return jsonResult( + result.status === "error" && requestedAgentId + ? { ...result, role: requestedAgentId } + : result, + ); } const result = await spawnSubagentDirect( @@ -345,7 +355,11 @@ export function createSessionsSpawnTool( }, ); - return jsonResult(result); + return jsonResult( + result.status === "error" && requestedAgentId + ? { ...result, role: requestedAgentId } + : result, + ); }, }; }