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.
This commit is contained in:
BKF-Gitty
2026-04-19 02:43:43 +03:00
committed by Gustavo Madeira Santana
parent f9a1875127
commit 41b628e686
2 changed files with 37 additions and 10 deletions

View File

@@ -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<SubagentRunOutcome, "startedAt" | "endedAt" | "elapsedMs"> = {};
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;
}

View File

@@ -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,
);
},
};
}