diff --git a/packages/server/src/server/agent/agent-sdk-types.ts b/packages/server/src/server/agent/agent-sdk-types.ts index 46e694e89b..6570392311 100644 --- a/packages/server/src/server/agent/agent-sdk-types.ts +++ b/packages/server/src/server/agent/agent-sdk-types.ts @@ -314,7 +314,8 @@ export type ToolCallDetail = description?: string; childSessionId?: string; // COMPAT(subagent-log): added in v0.14.1, remove after 2027-04-30. Apps before v0.14.1 - // require `log`; every producer sends it empty. Drop the field and those producers' `log: ""`. + // require `log`; every producer sends it empty and carries the same tag. Optional here because + // protocol timeline items, where `log` is optional, flow into this type. log?: string; actions?: Array<{ index: number; diff --git a/packages/server/src/server/agent/providers/claude/agent.ts b/packages/server/src/server/agent/providers/claude/agent.ts index 3fa48c6c06..c675b1ed62 100644 --- a/packages/server/src/server/agent/providers/claude/agent.ts +++ b/packages/server/src/server/agent/providers/claude/agent.ts @@ -6276,6 +6276,7 @@ function buildClaudeSubagentCardDetail( type: "sub_agent", ...(facts.title ? { subAgentType: facts.title } : {}), ...(facts.description ? { description: facts.description } : {}), + // COMPAT(subagent-log): remove after 2027-04-30. log: "", actions: [], }; diff --git a/packages/server/src/server/agent/providers/claude/sidechain-tracker.ts b/packages/server/src/server/agent/providers/claude/sidechain-tracker.ts index f0596c22fd..4e52ea9f50 100644 --- a/packages/server/src/server/agent/providers/claude/sidechain-tracker.ts +++ b/packages/server/src/server/agent/providers/claude/sidechain-tracker.ts @@ -137,6 +137,7 @@ export class ClaudeSidechainTracker { type: "sub_agent", ...(state.subAgentType ? { subAgentType: state.subAgentType } : {}), ...(state.description ? { description: state.description } : {}), + // COMPAT(subagent-log): remove after 2027-04-30. log: "", actions: [], }; diff --git a/packages/server/src/server/agent/providers/codex/tool-call-mapper.ts b/packages/server/src/server/agent/providers/codex/tool-call-mapper.ts index 64629bc5d9..4e77dc9367 100644 --- a/packages/server/src/server/agent/providers/codex/tool-call-mapper.ts +++ b/packages/server/src/server/agent/providers/codex/tool-call-mapper.ts @@ -950,6 +950,7 @@ function mapCollabAgentToolCallItem( type: "sub_agent", subAgentType: "Sub-agent", ...(item.prompt ? { description: item.prompt } : {}), + // COMPAT(subagent-log): remove after 2027-04-30. log: "", actions: [], }; @@ -1006,6 +1007,7 @@ function mapSubAgentActivityItem( type: "sub_agent", subAgentType: nativeName, description, + // COMPAT(subagent-log): remove after 2027-04-30. log: "", actions: [], }, diff --git a/packages/server/src/server/agent/providers/omp/tool-call-mapper.ts b/packages/server/src/server/agent/providers/omp/tool-call-mapper.ts index 8610996814..500123758a 100644 --- a/packages/server/src/server/agent/providers/omp/tool-call-mapper.ts +++ b/packages/server/src/server/agent/providers/omp/tool-call-mapper.ts @@ -199,6 +199,7 @@ function mapOmpTaskDetail(args: unknown, result: OmpToolResult): ToolCallDetail argRecord.assignment, ), ...(childSessionId ? { childSessionId } : {}), + // COMPAT(subagent-log): remove after 2027-04-30. log: "", }; } diff --git a/packages/server/src/server/agent/providers/opencode/tool-call-detail-parser.ts b/packages/server/src/server/agent/providers/opencode/tool-call-detail-parser.ts index f8f7b2bfd2..b3eb432f86 100644 --- a/packages/server/src/server/agent/providers/opencode/tool-call-detail-parser.ts +++ b/packages/server/src/server/agent/providers/opencode/tool-call-detail-parser.ts @@ -199,6 +199,7 @@ function deriveOpencodeTaskDetail( ...(subAgentType ? { subAgentType } : {}), ...(description ? { description } : {}), ...(childSessionId ? { childSessionId } : {}), + // COMPAT(subagent-log): remove after 2027-04-30. log: "", actions: [], }; diff --git a/packages/server/src/server/agent/providers/pi/extensions/edxeth-pi-subagents/index.ts b/packages/server/src/server/agent/providers/pi/extensions/edxeth-pi-subagents/index.ts index 1b89c9407e..c58f0faf15 100644 --- a/packages/server/src/server/agent/providers/pi/extensions/edxeth-pi-subagents/index.ts +++ b/packages/server/src/server/agent/providers/pi/extensions/edxeth-pi-subagents/index.ts @@ -98,6 +98,7 @@ export const edxethPiSubagents: PiExtension = { detail: { type: "sub_agent", description, + // COMPAT(subagent-log): remove after 2027-04-30. log: "", }, subagents, diff --git a/packages/server/src/server/agent/providers/pi/extensions/gotgenes-pi-subagents/index.ts b/packages/server/src/server/agent/providers/pi/extensions/gotgenes-pi-subagents/index.ts index 5eef7433da..7d1b4c325c 100644 --- a/packages/server/src/server/agent/providers/pi/extensions/gotgenes-pi-subagents/index.ts +++ b/packages/server/src/server/agent/providers/pi/extensions/gotgenes-pi-subagents/index.ts @@ -63,6 +63,7 @@ export const gotgenesPiSubagents: PiExtension = { type: "sub_agent" as const, subAgentType: title, description, + // COMPAT(subagent-log): remove after 2027-04-30. log: "", }; if (call.status === "running") @@ -117,6 +118,7 @@ export const gotgenesPiSubagents: PiExtension = { const detail = { type: "sub_agent" as const, description: details.success ? details.data.description : undefined, + // COMPAT(subagent-log): remove after 2027-04-30. log: "", }; if (call.status === "running" || !details.success) return { detail }; diff --git a/packages/server/src/server/agent/providers/pi/extensions/pi-subagents/index.ts b/packages/server/src/server/agent/providers/pi/extensions/pi-subagents/index.ts index 4ec882e11c..058171e8df 100644 --- a/packages/server/src/server/agent/providers/pi/extensions/pi-subagents/index.ts +++ b/packages/server/src/server/agent/providers/pi/extensions/pi-subagents/index.ts @@ -131,6 +131,7 @@ export const piSubagents: PiExtension = { type: "sub_agent" as const, subAgentType: args.data.agent, description: args.data.task, + // COMPAT(subagent-log): remove after 2027-04-30. log: "", }; const base = { diff --git a/packages/server/src/server/agent/providers/pi/extensions/subagent-fixture-test.ts b/packages/server/src/server/agent/providers/pi/extensions/subagent-fixture-test.ts index 8a9ca61d08..96ba771594 100644 --- a/packages/server/src/server/agent/providers/pi/extensions/subagent-fixture-test.ts +++ b/packages/server/src/server/agent/providers/pi/extensions/subagent-fixture-test.ts @@ -54,8 +54,6 @@ export async function verifySubagentFixture( try { await session.startTurn("Delegate work"); for (const event of fixture.events) pi.latestSession().emit(event); - const immediate = live.filter((event) => event.type === "provider_subagent"); - expect(immediate.some((event) => event.event.type === "timeline")).toBe(false); pi.latestSession().finishTurn(); const replay: AgentStreamEvent[] = []; for await (const event of streamPiHistory("pi", fixture.messages)) replay.push(event); diff --git a/packages/server/src/server/agent/providers/pi/extensions/tintinweb-pi-subagents/index.test.ts b/packages/server/src/server/agent/providers/pi/extensions/tintinweb-pi-subagents/index.test.ts index 65499183b8..60f3b12869 100644 --- a/packages/server/src/server/agent/providers/pi/extensions/tintinweb-pi-subagents/index.test.ts +++ b/packages/server/src/server/agent/providers/pi/extensions/tintinweb-pi-subagents/index.test.ts @@ -36,6 +36,11 @@ describe("@tintinweb/pi-subagents adapter", () => { }); expect(mapping?.subagents).toEqual([ expect.objectContaining({ id: "call-1", status: "completed" }), + { + type: "timeline", + id: "call-1", + item: { type: "assistant_message", text: "Wrapped up (turn limit)" }, + }, ]); expect(mapping?.detail).toEqual({ type: "sub_agent", @@ -53,6 +58,18 @@ describe("@tintinweb/pi-subagents adapter", () => { .filter((event) => event.event.type === "upsert") .map((event) => (event.event.type === "upsert" ? event.event.status : null)), ).toEqual(["running", "completed"]); + expect( + events.flatMap((event) => (event.event.type === "timeline" ? [event.event] : [])), + ).toEqual([ + { + type: "timeline", + id: "call_458926", + item: { + type: "assistant_message", + text: "Agent completed in 6.6s (1 tool uses, 18.8k token).\n\nThe workspace directory `/tmp/paseo-pi-extension-phase3/workspace` is empty (no files or hidden dotfiles).", + }, + }, + ]); }); test("uses a structured notification to complete a background child", async () => { const source = readSubagentFixture(new URL("./fixtures/background.json", import.meta.url)); diff --git a/packages/server/src/server/agent/providers/pi/extensions/tintinweb-pi-subagents/index.ts b/packages/server/src/server/agent/providers/pi/extensions/tintinweb-pi-subagents/index.ts index 101035ad89..6858dec8be 100644 --- a/packages/server/src/server/agent/providers/pi/extensions/tintinweb-pi-subagents/index.ts +++ b/packages/server/src/server/agent/providers/pi/extensions/tintinweb-pi-subagents/index.ts @@ -1,4 +1,5 @@ import { z } from "zod"; +import { extractTextFromToolResult } from "../../tool-call-mapper.js"; import { outputFileFromToolResult } from "../child-session.js"; import type { PiExtension, PiExtensionToolCall } from "../contract.js"; @@ -56,6 +57,7 @@ export const tintinwebPiSubagents: PiExtension = { type: "sub_agent" as const, subAgentType: title, description, + // COMPAT(subagent-log): remove after 2027-04-30. log: "", }; if (call.status === "running") @@ -83,6 +85,10 @@ export const tintinwebPiSubagents: PiExtension = { const file = outputFileFromToolResult(call.result); const childSessions = file && !readSessions.has(file) ? [{ id: call.callId, file }] : []; if (file) readSessions.add(file); + const childStatus = status(details.data.status); + // A foreground child has no session file; its answer is the tool result. + const answer = + !file && childStatus !== "running" ? extractTextFromToolResult(call.result)?.trim() : ""; return { detail, subagents: [ @@ -92,8 +98,17 @@ export const tintinwebPiSubagents: PiExtension = { title, description, toolCallId: call.callId, - status: status(details.data.status), + status: childStatus, }, + ...(answer + ? [ + { + type: "timeline" as const, + id: call.callId, + item: { type: "assistant_message" as const, text: answer }, + }, + ] + : []), ], childSessions, }; @@ -109,6 +124,7 @@ export const tintinwebPiSubagents: PiExtension = { const detail = { type: "sub_agent" as const, description: details.success ? details.data.description : undefined, + // COMPAT(subagent-log): remove after 2027-04-30. log: "", }; if (call.status === "running" || !details.success) return { detail };