diff --git a/packages/server/src/server/agent/providers/pi/extensions/tintinweb-pi-subagents/fixtures/background-group.json b/packages/server/src/server/agent/providers/pi/extensions/tintinweb-pi-subagents/fixtures/background-group.json new file mode 100644 index 0000000000..083e57744e --- /dev/null +++ b/packages/server/src/server/agent/providers/pi/extensions/tintinweb-pi-subagents/fixtures/background-group.json @@ -0,0 +1,419 @@ +{ + "provenance": { + "package": "@tintinweb/pi-subagents", + "version": "0.19.0", + "sourceCommit": "4f572eaa04c09d3dbc16e4a5f13a16b295e84e14", + "piVersion": "0.84.4", + "captureDate": "2026-10-01" + }, + "events": [ + { + "type": "tool_execution_start", + "toolCallId": "call_277173", + "toolName": "Agent", + "args": { + "run_in_background": true, + "description": "Test agent 1", + "prompt": "reply with exactly OK", + "subagent_type": "general-purpose" + } + }, + { + "type": "tool_execution_start", + "toolCallId": "call_277176", + "toolName": "Agent", + "args": { + "prompt": "reply with exactly OK", + "run_in_background": true, + "description": "Test agent 2", + "subagent_type": "general-purpose" + } + }, + { + "type": "tool_execution_start", + "toolCallId": "call_277179", + "toolName": "Agent", + "args": { + "subagent_type": "general-purpose", + "description": "Test agent 3", + "prompt": "reply with exactly OK", + "run_in_background": true + } + }, + { + "type": "tool_execution_end", + "toolCallId": "call_277173", + "toolName": "Agent", + "result": { + "content": [ + { + "type": "text", + "text": "Agent started in background.\nAgent ID: 4b4fd01d-aaa8-46d\nType: Agent\nDescription: Test agent 1\nOutput file: /tmp/pi-subagents-1000/tmp-paseo-pi-subagent-fixture/01a0f602-2dd3-7755-8969-96e4d126cd2e/tasks/4b4fd01d-aaa8-46d.output\n\nYou will be notified when this agent completes.\nUse get_subagent_result to retrieve full results, or steer_subagent to send it messages.\nDo not duplicate this agent's work." + } + ], + "details": { + "displayName": "Agent", + "description": "Test agent 1", + "subagentType": "general-purpose", + "modelName": "google: gemini 3.6 flash", + "tags": ["twin", "background"], + "toolUses": 0, + "tokens": "", + "durationMs": 0, + "status": "background", + "agentId": "4b4fd01d-aaa8-46d" + } + }, + "isError": false + }, + { + "type": "tool_execution_end", + "toolCallId": "call_277176", + "toolName": "Agent", + "result": { + "content": [ + { + "type": "text", + "text": "Agent started in background.\nAgent ID: a92b022c-cd9e-41e\nType: Agent\nDescription: Test agent 2\nOutput file: /tmp/pi-subagents-1000/tmp-paseo-pi-subagent-fixture/01a0f602-2dd3-7755-8969-96e4d126cd2e/tasks/a92b022c-cd9e-41e.output\n\nYou will be notified when this agent completes.\nUse get_subagent_result to retrieve full results, or steer_subagent to send it messages.\nDo not duplicate this agent's work." + } + ], + "details": { + "displayName": "Agent", + "description": "Test agent 2", + "subagentType": "general-purpose", + "modelName": "google: gemini 3.6 flash", + "tags": ["twin", "background"], + "toolUses": 0, + "tokens": "", + "durationMs": 0, + "status": "background", + "agentId": "a92b022c-cd9e-41e" + } + }, + "isError": false + }, + { + "type": "tool_execution_end", + "toolCallId": "call_277179", + "toolName": "Agent", + "result": { + "content": [ + { + "type": "text", + "text": "Agent started in background.\nAgent ID: 13336b9f-79c3-49e\nType: Agent\nDescription: Test agent 3\nOutput file: /tmp/pi-subagents-1000/tmp-paseo-pi-subagent-fixture/01a0f602-2dd3-7755-8969-96e4d126cd2e/tasks/13336b9f-79c3-49e.output\n\nYou will be notified when this agent completes.\nUse get_subagent_result to retrieve full results, or steer_subagent to send it messages.\nDo not duplicate this agent's work." + } + ], + "details": { + "displayName": "Agent", + "description": "Test agent 3", + "subagentType": "general-purpose", + "modelName": "google: gemini 3.6 flash", + "tags": ["twin", "background"], + "toolUses": 0, + "tokens": "", + "durationMs": 0, + "status": "background", + "agentId": "13336b9f-79c3-49e" + } + }, + "isError": false + }, + { + "type": "message_end", + "message": { + "role": "custom", + "customType": "subagent-notification", + "content": "Background agent group completed: 3 agent(s) finished\n\n\n4b4fd01d-aaa8-46d\ncall_277173\n/tmp/pi-subagents-1000/tmp-paseo-pi-subagent-fixture/01a0f602-2dd3-7755-8969-96e4d126cd2e/tasks/4b4fd01d-aaa8-46d.output\nDone\nAgent \"Test agent 1\" completed\nOK\n10186011054\n\n\n\na92b022c-cd9e-41e\ncall_277176\n/tmp/pi-subagents-1000/tmp-paseo-pi-subagent-fixture/01a0f602-2dd3-7755-8969-96e4d126cd2e/tasks/a92b022c-cd9e-41e.output\nDone\nAgent \"Test agent 2\" completed\nOK\n10186011182\n\n\n\n13336b9f-79c3-49e\ncall_277179\n/tmp/pi-subagents-1000/tmp-paseo-pi-subagent-fixture/01a0f602-2dd3-7755-8969-96e4d126cd2e/tasks/13336b9f-79c3-49e.output\nDone\nAgent \"Test agent 3\" completed\nOK\n10186011472\n\n\nUse get_subagent_result for full output.", + "display": true, + "details": { + "id": "4b4fd01d-aaa8-46d", + "description": "Test agent 1", + "status": "completed", + "toolUses": 0, + "turnCount": 0, + "totalTokens": 10186, + "totalCost": 0.007642500000000001, + "durationMs": 1054, + "outputFile": "/tmp/pi-subagents-1000/tmp-paseo-pi-subagent-fixture/01a0f602-2dd3-7755-8969-96e4d126cd2e/tasks/4b4fd01d-aaa8-46d.output", + "resultPreview": "OK", + "others": [ + { + "id": "a92b022c-cd9e-41e", + "description": "Test agent 2", + "status": "completed", + "toolUses": 0, + "turnCount": 0, + "totalTokens": 10186, + "totalCost": 0.007642500000000001, + "durationMs": 1182, + "outputFile": "/tmp/pi-subagents-1000/tmp-paseo-pi-subagent-fixture/01a0f602-2dd3-7755-8969-96e4d126cd2e/tasks/a92b022c-cd9e-41e.output", + "resultPreview": "OK" + }, + { + "id": "13336b9f-79c3-49e", + "description": "Test agent 3", + "status": "completed", + "toolUses": 0, + "turnCount": 0, + "totalTokens": 10186, + "totalCost": 0.007642500000000001, + "durationMs": 1472, + "outputFile": "/tmp/pi-subagents-1000/tmp-paseo-pi-subagent-fixture/01a0f602-2dd3-7755-8969-96e4d126cd2e/tasks/13336b9f-79c3-49e.output", + "resultPreview": "OK" + } + ] + }, + "timestamp": 1790833737082 + } + } + ], + "messages": [ + { + "role": "assistant", + "content": [ + { + "type": "thinking", + "thinking": "", + "thinkingSignature": "[{\"type\":\"reasoning.encrypted\",\"data\":\"AY89a19lxhI31c8Kjto2eEqQuEoAa1jXk+6WHw4RxcJiLq9eTkm6UtOU0r9mP+xFrs3A9GkNyIfpaIzLUGuPKjiYw648QdaOOVbR7jCqPVFA+CEYQH0V\",\"format\":\"google-gemini-v1\",\"id\":\"call_277173\",\"index\":0}]" + }, + { + "type": "toolCall", + "id": "call_277173", + "name": "Agent", + "arguments": { + "run_in_background": true, + "description": "Test agent 1", + "prompt": "reply with exactly OK", + "subagent_type": "general-purpose" + } + }, + { + "type": "toolCall", + "id": "call_277176", + "name": "Agent", + "arguments": { + "prompt": "reply with exactly OK", + "run_in_background": true, + "description": "Test agent 2", + "subagent_type": "general-purpose" + } + }, + { + "type": "toolCall", + "id": "call_277179", + "name": "Agent", + "arguments": { + "subagent_type": "general-purpose", + "description": "Test agent 3", + "prompt": "reply with exactly OK", + "run_in_background": true + } + } + ], + "api": "openai-completions", + "provider": "openrouter", + "model": "google/gemini-3.6-flash", + "usage": { + "input": 13147, + "output": 135, + "cacheRead": 0, + "cacheWrite": 0, + "reasoning": 0, + "totalTokens": 13282, + "cost": { + "input": 0.009860250000000001, + "output": 0.00050625, + "cacheRead": 0, + "cacheWrite": 0, + "total": 0.0103665 + } + }, + "stopReason": "toolUse", + "timestamp": 1790833733536, + "responseId": "gen-1790833733-Qo0mjOAU6129xnYfl8rM", + "rawStopReason": "tool_calls" + }, + { + "role": "toolResult", + "toolCallId": "call_277173", + "toolName": "Agent", + "content": [ + { + "type": "text", + "text": "Agent started in background.\nAgent ID: 4b4fd01d-aaa8-46d\nType: Agent\nDescription: Test agent 1\nOutput file: /tmp/pi-subagents-1000/tmp-paseo-pi-subagent-fixture/01a0f602-2dd3-7755-8969-96e4d126cd2e/tasks/4b4fd01d-aaa8-46d.output\n\nYou will be notified when this agent completes.\nUse get_subagent_result to retrieve full results, or steer_subagent to send it messages.\nDo not duplicate this agent's work." + } + ], + "details": { + "displayName": "Agent", + "description": "Test agent 1", + "subagentType": "general-purpose", + "modelName": "google: gemini 3.6 flash", + "tags": ["twin", "background"], + "toolUses": 0, + "tokens": "", + "durationMs": 0, + "status": "background", + "agentId": "4b4fd01d-aaa8-46d" + }, + "isError": false, + "timestamp": 1790833735411 + }, + { + "role": "toolResult", + "toolCallId": "call_277176", + "toolName": "Agent", + "content": [ + { + "type": "text", + "text": "Agent started in background.\nAgent ID: a92b022c-cd9e-41e\nType: Agent\nDescription: Test agent 2\nOutput file: /tmp/pi-subagents-1000/tmp-paseo-pi-subagent-fixture/01a0f602-2dd3-7755-8969-96e4d126cd2e/tasks/a92b022c-cd9e-41e.output\n\nYou will be notified when this agent completes.\nUse get_subagent_result to retrieve full results, or steer_subagent to send it messages.\nDo not duplicate this agent's work." + } + ], + "details": { + "displayName": "Agent", + "description": "Test agent 2", + "subagentType": "general-purpose", + "modelName": "google: gemini 3.6 flash", + "tags": ["twin", "background"], + "toolUses": 0, + "tokens": "", + "durationMs": 0, + "status": "background", + "agentId": "a92b022c-cd9e-41e" + }, + "isError": false, + "timestamp": 1790833735412 + }, + { + "role": "toolResult", + "toolCallId": "call_277179", + "toolName": "Agent", + "content": [ + { + "type": "text", + "text": "Agent started in background.\nAgent ID: 13336b9f-79c3-49e\nType: Agent\nDescription: Test agent 3\nOutput file: /tmp/pi-subagents-1000/tmp-paseo-pi-subagent-fixture/01a0f602-2dd3-7755-8969-96e4d126cd2e/tasks/13336b9f-79c3-49e.output\n\nYou will be notified when this agent completes.\nUse get_subagent_result to retrieve full results, or steer_subagent to send it messages.\nDo not duplicate this agent's work." + } + ], + "details": { + "displayName": "Agent", + "description": "Test agent 3", + "subagentType": "general-purpose", + "modelName": "google: gemini 3.6 flash", + "tags": ["twin", "background"], + "toolUses": 0, + "tokens": "", + "durationMs": 0, + "status": "background", + "agentId": "13336b9f-79c3-49e" + }, + "isError": false, + "timestamp": 1790833735412 + }, + { + "role": "assistant", + "content": [ + { + "type": "text", + "text": "I have spawned the 3 background agents and am waiting for their completion notifications." + } + ], + "api": "openai-completions", + "provider": "openrouter", + "model": "google/gemini-3.6-flash", + "usage": { + "input": 2004, + "output": 16, + "cacheRead": 11743, + "cacheWrite": 0, + "reasoning": 0, + "totalTokens": 13763, + "cost": { + "input": 0.001503, + "output": 6e-5, + "cacheRead": 0.000880725, + "cacheWrite": 0, + "total": 0.002443725 + } + }, + "stopReason": "stop", + "timestamp": 1790833735412, + "responseId": "gen-1790833735-adM3whTd4mLRXnKi2Xu0", + "rawStopReason": "stop" + }, + { + "role": "custom", + "customType": "subagent-notification", + "content": "Background agent group completed: 3 agent(s) finished\n\n\n4b4fd01d-aaa8-46d\ncall_277173\n/tmp/pi-subagents-1000/tmp-paseo-pi-subagent-fixture/01a0f602-2dd3-7755-8969-96e4d126cd2e/tasks/4b4fd01d-aaa8-46d.output\nDone\nAgent \"Test agent 1\" completed\nOK\n10186011054\n\n\n\na92b022c-cd9e-41e\ncall_277176\n/tmp/pi-subagents-1000/tmp-paseo-pi-subagent-fixture/01a0f602-2dd3-7755-8969-96e4d126cd2e/tasks/a92b022c-cd9e-41e.output\nDone\nAgent \"Test agent 2\" completed\nOK\n10186011182\n\n\n\n13336b9f-79c3-49e\ncall_277179\n/tmp/pi-subagents-1000/tmp-paseo-pi-subagent-fixture/01a0f602-2dd3-7755-8969-96e4d126cd2e/tasks/13336b9f-79c3-49e.output\nDone\nAgent \"Test agent 3\" completed\nOK\n10186011472\n\n\nUse get_subagent_result for full output.", + "display": true, + "details": { + "id": "4b4fd01d-aaa8-46d", + "description": "Test agent 1", + "status": "completed", + "toolUses": 0, + "turnCount": 0, + "totalTokens": 10186, + "totalCost": 0.007642500000000001, + "durationMs": 1054, + "outputFile": "/tmp/pi-subagents-1000/tmp-paseo-pi-subagent-fixture/01a0f602-2dd3-7755-8969-96e4d126cd2e/tasks/4b4fd01d-aaa8-46d.output", + "resultPreview": "OK", + "others": [ + { + "id": "a92b022c-cd9e-41e", + "description": "Test agent 2", + "status": "completed", + "toolUses": 0, + "turnCount": 0, + "totalTokens": 10186, + "totalCost": 0.007642500000000001, + "durationMs": 1182, + "outputFile": "/tmp/pi-subagents-1000/tmp-paseo-pi-subagent-fixture/01a0f602-2dd3-7755-8969-96e4d126cd2e/tasks/a92b022c-cd9e-41e.output", + "resultPreview": "OK" + }, + { + "id": "13336b9f-79c3-49e", + "description": "Test agent 3", + "status": "completed", + "toolUses": 0, + "turnCount": 0, + "totalTokens": 10186, + "totalCost": 0.007642500000000001, + "durationMs": 1472, + "outputFile": "/tmp/pi-subagents-1000/tmp-paseo-pi-subagent-fixture/01a0f602-2dd3-7755-8969-96e4d126cd2e/tasks/13336b9f-79c3-49e.output", + "resultPreview": "OK" + } + ] + }, + "timestamp": 1790833737082 + }, + { + "role": "assistant", + "content": [ + { + "type": "text", + "text": "DONE" + } + ], + "api": "openai-completions", + "provider": "openrouter", + "model": "google/gemini-3.6-flash", + "usage": { + "input": 2740, + "output": 1, + "cacheRead": 11762, + "cacheWrite": 0, + "reasoning": 0, + "totalTokens": 14503, + "cost": { + "input": 0.002055, + "output": 3.75e-6, + "cacheRead": 0.00088215, + "cacheWrite": 0, + "total": 0.0029408999999999998 + } + }, + "stopReason": "stop", + "timestamp": 1790833737083, + "responseId": "gen-1790833737-3IfsB2wLuE8DMNlJMgmI", + "rawStopReason": "stop" + } + ] +} 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 b464026ab1..b03c17c6ec 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 @@ -51,6 +51,20 @@ describe("@tintinweb/pi-subagents adapter", () => { ).toEqual(["running", "running", "completed"]); expect(events.filter((event) => event.event.type === "timeline").length).toBeGreaterThan(0); }); + test("completes every background child in a grouped notification", async () => { + const events = await verifySubagentFixture( + readSubagentFixture(new URL("./fixtures/background-group.json", import.meta.url)), + ); + const finalStatus = new Map(); + for (const { event } of events) { + if (event.type === "upsert" && event.status) finalStatus.set(event.id, event.status); + } + expect(Object.fromEntries(finalStatus)).toEqual({ + call_277173: "completed", + call_277176: "completed", + call_277179: "completed", + }); + }); test("declines foreign Agent results", () => { expect( createPiExtensionHost().mapToolCall({ 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 2b06779afa..9c6ef9917e 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 @@ -19,7 +19,7 @@ const Details = z description: z.string().optional(), }) .passthrough(); -const Notification = z +const NotificationEntry = z .object({ id: z.string().trim().min(1), status: z.string(), @@ -27,6 +27,11 @@ const Notification = z outputFile: z.string().trim().min(1).optional(), }) .passthrough(); +// Completions that land close together arrive as one notification: the first +// agent at the top level and the rest in `others`. +const Notification = NotificationEntry.extend({ + others: z.array(NotificationEntry).optional(), +}); const status = (value: string): "running" | "completed" | "failed" | "canceled" => { if (value === "completed") return "completed"; if (value === "error") return "failed"; @@ -127,22 +132,25 @@ export const tintinwebPiSubagents: PiExtension = { return undefined; const details = Notification.safeParse(message.details); if (!details.success) return undefined; - const id = callsByAgent.get(details.data.id); - if (!id) return undefined; - const file = details.data.outputFile; - const childSessions = file && !readSessions.has(file) ? [{ id, file }] : []; - if (childSessions.length && file) readSessions.add(file); - return { - subagents: [ - { - type: "upsert", - id, - description: details.data.description, - status: status(details.data.status), - }, - ], - childSessions, - }; + const subagents = []; + const childSessions = []; + for (const entry of [details.data, ...(details.data.others ?? [])]) { + const id = callsByAgent.get(entry.id); + if (!id) continue; + const file = entry.outputFile; + if (file && !readSessions.has(file)) { + readSessions.add(file); + childSessions.push({ id, file }); + } + subagents.push({ + type: "upsert" as const, + id, + description: entry.description, + status: status(entry.status), + }); + } + if (subagents.length === 0) return undefined; + return { subagents, childSessions }; }, }; },