diff --git a/packages/server/src/server/agent/providers/pi/extensions/edxeth-pi-subagents/fixtures/background-child-session.jsonl b/packages/server/src/server/agent/providers/pi/extensions/edxeth-pi-subagents/fixtures/background-child-session.jsonl new file mode 100644 index 0000000000..ed668e807f --- /dev/null +++ b/packages/server/src/server/agent/providers/pi/extensions/edxeth-pi-subagents/fixtures/background-child-session.jsonl @@ -0,0 +1,8 @@ +{"type": "session", "version": 3, "id": "e56a0006-f421-45be-9aef-e4019db1cf24", "timestamp": "2026-10-21T13:05:57.384Z", "cwd": "/tmp/paseo-edxeth-fixture/lean/workspace", "name": "[marker] Fixture marker verification", "parentSession": "/tmp/paseo-edxeth-fixture/lean/sessions/2026-10-21T13-05-53-085Z_01a15e91-73bd-7282-82c6-d3974711a941.jsonl"} +{"type": "custom", "customType": "pi-subagents_launch_metadata", "data": {"version": 1, "timestamp": "2026-10-21T13:05:57.385Z", "name": "fixture-marker", "title": "Fixture marker verification", "sessionTitle": "[marker] Fixture marker verification", "agent": "marker", "mode": "background", "sessionMode": "lineage-only", "autoExit": true, "parentClosePolicy": "terminate", "reportContextUsage": true, "async": true, "model": "openai-codex/gpt-5.6-sol", "thinking": "high", "modelRef": "openai-codex/gpt-5.6-sol:high", "definitionModel": "openai-codex/gpt-5.6-sol", "definitionThinking": "high", "allowModelOverride": true, "modelSource": "agent", "tools": "read", "denyTools": ["subagent", "subagent_resume", "subagent_kill"], "spawnableAgents": [], "spawnBudget": null, "extensions": [], "noContextFiles": false, "inheritAppendSystem": false, "noSession": false, "trustProject": false, "agentConfigDir": "/tmp/paseo-edxeth-fixture/lean/agent", "cwd": "/tmp/paseo-edxeth-fixture/lean/workspace", "boundarySystemPrompt": false}, "id": "57355eeb", "parentId": null, "timestamp": "2026-10-21T13:05:57.386Z"} +{"type": "session_info", "id": "847415e4", "parentId": "57355eeb", "timestamp": "2026-10-21T13:05:57.566Z", "name": "[marker] Fixture marker verification"} +{"type": "model_change", "id": "d4ad59be", "parentId": "847415e4", "timestamp": "2026-10-21T13:05:57.672Z", "provider": "openai-codex", "modelId": "gpt-5.6-sol"} +{"type": "thinking_level_change", "id": "7f6bf9d2", "parentId": "d4ad59be", "timestamp": "2026-10-21T13:05:57.672Z", "thinkingLevel": "high"} +{"type": "message", "id": "737b762e", "parentId": "6b436446", "timestamp": "2026-10-21T13:05:57.679Z", "message": {"role": "user", "content": [{"type": "text", "text": "\n\n\nReturn only the exact marker supplied by the user. Do not use tools.\n\nComplete your task autonomously.\n\nReturn only PASEO_EDXETH_LEAN_OK\n\nYour FINAL assistant message should summarize what you accomplished.\n\n"}], "timestamp": 1792587957678}} +{"type": "message", "id": "100b110d", "parentId": "737b762e", "timestamp": "2026-10-21T13:06:02.050Z", "message": {"role": "assistant", "content": [{"type": "text", "text": "PASEO_EDXETH_LEAN_OK"}], "api": "openai-codex-responses", "provider": "openai-codex", "model": "gpt-5.6-sol", "usage": {"input": 1713, "output": 13, "cacheRead": 0, "cacheWrite": 0, "reasoning": 0, "totalTokens": 1726, "cost": {"input": 0.006852, "output": 0.00026000000000000003, "cacheRead": 0, "cacheWrite": 0, "total": 0.007111999999999999}}, "stopReason": "stop", "timestamp": 1792587957684, "responseId": "resp_0bc8bfaee079148a016ad8b8b7edb08191924676ed8dcfd447", "rawStopReason": "completed"}} +{"type": "custom", "customType": "pi-subagent-completion", "data": {"reason": "normal"}, "id": "80bbd3bb", "parentId": "100b110d", "timestamp": "2026-10-21T13:06:02.054Z"} diff --git a/packages/server/src/server/agent/providers/pi/extensions/edxeth-pi-subagents/fixtures/background.json b/packages/server/src/server/agent/providers/pi/extensions/edxeth-pi-subagents/fixtures/background.json new file mode 100644 index 0000000000..fe4913f6b6 --- /dev/null +++ b/packages/server/src/server/agent/providers/pi/extensions/edxeth-pi-subagents/fixtures/background.json @@ -0,0 +1,263 @@ +{ + "provenance": { + "package": "Zeno2019/pi-subagents-lean", + "version": "2.9.2-lean.1", + "sourceCommit": "e10fe855808abcae193a205ea98f8ccd70d25620", + "piVersion": "0.87.1", + "captureDate": "2026-10-21" + }, + "events": [ + { + "type": "tool_execution_start", + "toolCallId": "call_f28b261de3c6440b953f0f56c3fec7d0|fc_0d35cf51b3ed48b9016ad8b8b4a63c81918a55903d0a4f12cb", + "toolName": "subagent", + "args": { + "agent": "marker", + "name": "fixture-marker", + "title": "Fixture marker verification", + "task": "Return only PASEO_EDXETH_LEAN_OK" + } + }, + { + "type": "tool_execution_end", + "toolCallId": "call_f28b261de3c6440b953f0f56c3fec7d0|fc_0d35cf51b3ed48b9016ad8b8b4a63c81918a55903d0a4f12cb", + "toolName": "subagent", + "result": { + "content": [ + { + "type": "text", + "text": "Sub-agent \"fixture-marker\" launched async with id 1bfdf3b3. Results will be delivered automatically as a steer message when it finishes. Use this exact id if you need to resume or stop this child.\n\nActivity: /home/pi-user/.pi/history/workspace/artifacts/2026-10-21T13-05-57-380Z_db802a71-7bb32177-573971ee-535c/subagents/1bfdf3b3/activity.jsonl\nRun evidence: /home/pi-user/.pi/history/workspace/artifacts/2026-10-21T13-05-57-380Z_db802a71-7bb32177-573971ee-535c/subagents/1bfdf3b3/result.json\nRead these on demand for progress or failure diagnosis; activity is evidence, not a completion verdict." + } + ], + "details": { + "evidence": { + "runId": "1bfdf3b3", + "cwd": "/tmp/paseo-edxeth-fixture/lean/workspace", + "activityFile": "/home/pi-user/.pi/history/workspace/artifacts/2026-10-21T13-05-57-380Z_db802a71-7bb32177-573971ee-535c/subagents/1bfdf3b3/activity.jsonl", + "resultFile": "/home/pi-user/.pi/history/workspace/artifacts/2026-10-21T13-05-57-380Z_db802a71-7bb32177-573971ee-535c/subagents/1bfdf3b3/result.json" + }, + "id": "1bfdf3b3", + "name": "fixture-marker", + "title": "Fixture marker verification", + "task": "Return only PASEO_EDXETH_LEAN_OK", + "agent": "marker", + "sessionFile": "/tmp/paseo-edxeth-fixture/background/child-session.jsonl", + "noSession": false, + "status": "started", + "mode": "background", + "deliveryState": "detached", + "parentClosePolicy": "terminate", + "async": true, + "autoExit": true + } + }, + "isError": false + }, + { + "type": "message_end", + "message": { + "role": "custom", + "customType": "subagent_result", + "content": "Sub-agent \"fixture-marker\" completed (7s).\n\nPASEO_EDXETH_LEAN_OK\n\nSession: /tmp/paseo-edxeth-fixture/background/child-session.jsonl\nResume: pi --session /tmp/paseo-edxeth-fixture/background/child-session.jsonl\n\nSub-agent context: 2K/372K tokens (0%) used at finish.\n\nActivity: /home/pi-user/.pi/history/workspace/artifacts/2026-10-21T13-05-57-380Z_db802a71-7bb32177-573971ee-535c/subagents/1bfdf3b3/activity.jsonl\nRun evidence: /home/pi-user/.pi/history/workspace/artifacts/2026-10-21T13-05-57-380Z_db802a71-7bb32177-573971ee-535c/subagents/1bfdf3b3/result.json\nRead these on demand for progress or failure diagnosis; activity is evidence, not a completion verdict.", + "display": true, + "details": { + "evidence": { + "runId": "1bfdf3b3", + "cwd": "/tmp/paseo-edxeth-fixture/lean/workspace", + "activityFile": "/home/pi-user/.pi/history/workspace/artifacts/2026-10-21T13-05-57-380Z_db802a71-7bb32177-573971ee-535c/subagents/1bfdf3b3/activity.jsonl", + "resultFile": "/home/pi-user/.pi/history/workspace/artifacts/2026-10-21T13-05-57-380Z_db802a71-7bb32177-573971ee-535c/subagents/1bfdf3b3/result.json", + "finalized": true + }, + "id": "1bfdf3b3", + "name": "fixture-marker", + "task": "Return only PASEO_EDXETH_LEAN_OK", + "agent": "marker", + "mode": "background", + "status": "completed", + "deliveryState": "detached", + "parentClosePolicy": "terminate", + "async": true, + "exitCode": 0, + "elapsed": 7, + "outputTokens": 13, + "contextTokens": 1775, + "contextWindow": 372000, + "sessionFile": "/tmp/paseo-edxeth-fixture/background/child-session.jsonl" + }, + "timestamp": 1792587964414 + } + } + ], + "messages": [ + { + "role": "assistant", + "content": [ + { + "type": "toolCall", + "id": "call_f28b261de3c6440b953f0f56c3fec7d0|fc_0d35cf51b3ed48b9016ad8b8b4a63c81918a55903d0a4f12cb", + "name": "subagent", + "arguments": { + "agent": "marker", + "name": "fixture-marker", + "title": "Fixture marker verification", + "task": "Return only PASEO_EDXETH_LEAN_OK" + } + } + ], + "api": "openai-codex-responses", + "provider": "openai-codex", + "model": "gpt-5.6-sol", + "usage": { + "input": 2020, + "output": 43, + "cacheRead": 0, + "cacheWrite": 0, + "reasoning": 0, + "totalTokens": 2063, + "cost": { + "input": 0.00808, + "output": 0.0008600000000000001, + "cacheRead": 0, + "cacheWrite": 0, + "total": 0.00894 + } + }, + "stopReason": "toolUse", + "timestamp": 1792587953256, + "responseId": "resp_0d35cf51b3ed48b9016ad8b8b3698481918f82962b6eaf2788", + "rawStopReason": "completed" + }, + { + "role": "toolResult", + "toolCallId": "call_f28b261de3c6440b953f0f56c3fec7d0|fc_0d35cf51b3ed48b9016ad8b8b4a63c81918a55903d0a4f12cb", + "toolName": "subagent", + "content": [ + { + "type": "text", + "text": "Sub-agent \"fixture-marker\" launched async with id 1bfdf3b3. Results will be delivered automatically as a steer message when it finishes. Use this exact id if you need to resume or stop this child.\n\nActivity: /home/pi-user/.pi/history/workspace/artifacts/2026-10-21T13-05-57-380Z_db802a71-7bb32177-573971ee-535c/subagents/1bfdf3b3/activity.jsonl\nRun evidence: /home/pi-user/.pi/history/workspace/artifacts/2026-10-21T13-05-57-380Z_db802a71-7bb32177-573971ee-535c/subagents/1bfdf3b3/result.json\nRead these on demand for progress or failure diagnosis; activity is evidence, not a completion verdict." + } + ], + "details": { + "evidence": { + "runId": "1bfdf3b3", + "cwd": "/tmp/paseo-edxeth-fixture/lean/workspace", + "activityFile": "/home/pi-user/.pi/history/workspace/artifacts/2026-10-21T13-05-57-380Z_db802a71-7bb32177-573971ee-535c/subagents/1bfdf3b3/activity.jsonl", + "resultFile": "/home/pi-user/.pi/history/workspace/artifacts/2026-10-21T13-05-57-380Z_db802a71-7bb32177-573971ee-535c/subagents/1bfdf3b3/result.json", + "finalized": true + }, + "id": "1bfdf3b3", + "name": "fixture-marker", + "title": "Fixture marker verification", + "task": "Return only PASEO_EDXETH_LEAN_OK", + "agent": "marker", + "sessionFile": "/tmp/paseo-edxeth-fixture/background/child-session.jsonl", + "noSession": false, + "status": "started", + "mode": "background", + "deliveryState": "detached", + "parentClosePolicy": "terminate", + "async": true, + "autoExit": true + }, + "isError": false, + "timestamp": 1792587957399 + }, + { + "role": "assistant", + "content": [ + { + "type": "thinking", + "thinking": "**Identifying need for await keyword**" + }, + { + "type": "text", + "text": "DONE" + } + ], + "api": "openai-codex-responses", + "provider": "openai-codex", + "model": "gpt-5.6-sol", + "usage": { + "input": 2258, + "output": 18, + "cacheRead": 0, + "cacheWrite": 0, + "reasoning": 11, + "totalTokens": 2276, + "cost": { + "input": 0.009032, + "output": 0.00036, + "cacheRead": 0, + "cacheWrite": 0, + "total": 0.009392 + } + }, + "stopReason": "stop", + "timestamp": 1792587957402, + "responseId": "resp_0d35cf51b3ed48b9016ad8b8b602108191a8e8f7fd77fbe5b5", + "rawStopReason": "completed" + }, + { + "role": "custom", + "customType": "subagent_result", + "content": "Sub-agent \"fixture-marker\" completed (7s).\n\nPASEO_EDXETH_LEAN_OK\n\nSession: /tmp/paseo-edxeth-fixture/background/child-session.jsonl\nResume: pi --session /tmp/paseo-edxeth-fixture/background/child-session.jsonl\n\nSub-agent context: 2K/372K tokens (0%) used at finish.\n\nActivity: /home/pi-user/.pi/history/workspace/artifacts/2026-10-21T13-05-57-380Z_db802a71-7bb32177-573971ee-535c/subagents/1bfdf3b3/activity.jsonl\nRun evidence: /home/pi-user/.pi/history/workspace/artifacts/2026-10-21T13-05-57-380Z_db802a71-7bb32177-573971ee-535c/subagents/1bfdf3b3/result.json\nRead these on demand for progress or failure diagnosis; activity is evidence, not a completion verdict.", + "display": true, + "details": { + "evidence": { + "runId": "1bfdf3b3", + "cwd": "/tmp/paseo-edxeth-fixture/lean/workspace", + "activityFile": "/home/pi-user/.pi/history/workspace/artifacts/2026-10-21T13-05-57-380Z_db802a71-7bb32177-573971ee-535c/subagents/1bfdf3b3/activity.jsonl", + "resultFile": "/home/pi-user/.pi/history/workspace/artifacts/2026-10-21T13-05-57-380Z_db802a71-7bb32177-573971ee-535c/subagents/1bfdf3b3/result.json", + "finalized": true + }, + "id": "1bfdf3b3", + "name": "fixture-marker", + "task": "Return only PASEO_EDXETH_LEAN_OK", + "agent": "marker", + "mode": "background", + "status": "completed", + "deliveryState": "detached", + "parentClosePolicy": "terminate", + "async": true, + "exitCode": 0, + "elapsed": 7, + "outputTokens": 13, + "contextTokens": 1775, + "contextWindow": 372000, + "sessionFile": "/tmp/paseo-edxeth-fixture/background/child-session.jsonl" + }, + "timestamp": 1792587964414 + }, + { + "role": "assistant", + "content": [ + { + "type": "text", + "text": "DONE" + } + ], + "api": "openai-codex-responses", + "provider": "openai-codex", + "model": "gpt-5.6-sol", + "usage": { + "input": 389, + "output": 5, + "cacheRead": 2176, + "cacheWrite": 0, + "reasoning": 0, + "totalTokens": 2570, + "cost": { + "input": 0.0015559999999999999, + "output": 0.0001, + "cacheRead": 0.0008704000000000001, + "cacheWrite": 0, + "total": 0.0025264 + } + }, + "stopReason": "stop", + "timestamp": 1792587964417, + "responseId": "resp_0d35cf51b3ed48b9016ad8b8bd0c548191bddd1fe3410a1c82", + "rawStopReason": "completed" + } + ] +} diff --git a/packages/server/src/server/agent/providers/pi/extensions/edxeth-pi-subagents/fixtures/foreground-child-session.jsonl b/packages/server/src/server/agent/providers/pi/extensions/edxeth-pi-subagents/fixtures/foreground-child-session.jsonl new file mode 100644 index 0000000000..a607a1032d --- /dev/null +++ b/packages/server/src/server/agent/providers/pi/extensions/edxeth-pi-subagents/fixtures/foreground-child-session.jsonl @@ -0,0 +1,8 @@ +{"type": "session", "version": 3, "id": "afbea6e0-7858-4ecf-8ef1-e26af9b7d6e0", "timestamp": "2026-10-21T13:05:17.628Z", "cwd": "/tmp/paseo-edxeth-fixture/upstream/workspace", "name": "[marker] Fixture marker verification", "parentSession": "/tmp/paseo-edxeth-fixture/upstream/sessions/2026-10-21T13-05-12-212Z_01a15e90-d414-7243-a701-4eb4b0bec880.jsonl"} +{"type": "custom", "customType": "pi-subagents_launch_metadata", "data": {"version": 1, "timestamp": "2026-10-21T13:05:17.628Z", "name": "fixture-marker", "title": "Fixture marker verification", "sessionTitle": "[marker] Fixture marker verification", "agent": "marker", "mode": "background", "sessionMode": "lineage-only", "autoExit": true, "parentClosePolicy": "terminate", "reportContextUsage": true, "async": false, "model": "openai-codex/gpt-5.6-sol", "thinking": "high", "modelRef": "openai-codex/gpt-5.6-sol:high", "definitionModel": "openai-codex/gpt-5.6-sol", "definitionThinking": "high", "allowModelOverride": true, "modelSource": "agent", "tools": "read", "denyTools": ["subagent", "subagent_resume", "subagent_kill"], "spawnableAgents": [], "spawnBudget": null, "extensions": [], "noContextFiles": false, "inheritAppendSystem": false, "noSession": false, "trustProject": false, "agentConfigDir": "/tmp/paseo-edxeth-fixture/upstream/agent", "cwd": "/tmp/paseo-edxeth-fixture/upstream/workspace", "boundarySystemPrompt": false}, "id": "3f7d3c3f", "parentId": null, "timestamp": "2026-10-21T13:05:17.629Z"} +{"type": "session_info", "id": "6e198450", "parentId": "3f7d3c3f", "timestamp": "2026-10-21T13:05:17.825Z", "name": "[marker] Fixture marker verification"} +{"type": "model_change", "id": "94bf4c0b", "parentId": "6e198450", "timestamp": "2026-10-21T13:05:17.962Z", "provider": "openai-codex", "modelId": "gpt-5.6-sol"} +{"type": "thinking_level_change", "id": "57cc6428", "parentId": "94bf4c0b", "timestamp": "2026-10-21T13:05:17.962Z", "thinkingLevel": "high"} +{"type": "message", "id": "a9e47f08", "parentId": "8c186624", "timestamp": "2026-10-21T13:05:17.970Z", "message": {"role": "user", "content": [{"type": "text", "text": "\n\n\nReturn only the exact marker supplied by the user. Do not use tools.\n\nComplete your task autonomously.\n\nReturn only PASEO_EDXETH_UPSTREAM_OK\n\nYour FINAL assistant message should summarize what you accomplished.\n\n"}], "timestamp": 1792587917968}} +{"type": "message", "id": "bc221a13", "parentId": "a9e47f08", "timestamp": "2026-10-21T13:05:23.476Z", "message": {"role": "assistant", "content": [{"type": "thinking", "thinking": "**Following user file marker exactly**"}, {"type": "text", "text": "PASEO_EDXETH_UPSTREAM_OK"}], "api": "openai-codex-responses", "provider": "openai-codex", "model": "gpt-5.6-sol", "usage": {"input": 1673, "output": 49, "cacheRead": 0, "cacheWrite": 0, "reasoning": 34, "totalTokens": 1722, "cost": {"input": 0.006692, "output": 0.0009800000000000002, "cacheRead": 0, "cacheWrite": 0, "total": 0.007672}}, "stopReason": "stop", "timestamp": 1792587917974, "responseId": "resp_09445c1733d532e7016ad8b8909c908191ba803c059497945e", "rawStopReason": "completed"}} +{"type": "custom", "customType": "pi-subagent-completion", "data": {"reason": "normal"}, "id": "832fc8c9", "parentId": "bc221a13", "timestamp": "2026-10-21T13:05:23.477Z"} diff --git a/packages/server/src/server/agent/providers/pi/extensions/edxeth-pi-subagents/fixtures/foreground.json b/packages/server/src/server/agent/providers/pi/extensions/edxeth-pi-subagents/fixtures/foreground.json new file mode 100644 index 0000000000..c8df8eb5c2 --- /dev/null +++ b/packages/server/src/server/agent/providers/pi/extensions/edxeth-pi-subagents/fixtures/foreground.json @@ -0,0 +1,156 @@ +{ + "provenance": { + "package": "edxeth/pi-subagents", + "version": "2.9.2", + "sourceCommit": "cf6dbf41c17986f3882e6804a68e8fb8282d25e6", + "piVersion": "0.87.1", + "captureDate": "2026-10-21" + }, + "events": [ + { + "type": "tool_execution_start", + "toolCallId": "call_0bc6fc2213cd49aa92950947bde5dd8a|fc_0a0ca4aa78cd296a016ad8b88d018481919eadbf09eb191a1a", + "toolName": "subagent", + "args": { + "agent": "marker", + "name": "fixture-marker", + "title": "Fixture marker verification", + "task": "Return only PASEO_EDXETH_UPSTREAM_OK" + } + }, + { + "type": "tool_execution_end", + "toolCallId": "call_0bc6fc2213cd49aa92950947bde5dd8a|fc_0a0ca4aa78cd296a016ad8b88d018481919eadbf09eb191a1a", + "toolName": "subagent", + "result": { + "content": [ + { + "type": "text", + "text": "Sub-agent \"fixture-marker\" completed (7s).\n\nPASEO_EDXETH_UPSTREAM_OK\n\nSession: /tmp/paseo-edxeth-fixture/foreground/child-session.jsonl\nResume: pi --session /tmp/paseo-edxeth-fixture/foreground/child-session.jsonl\n\nSub-agent context: 2K/372K tokens (0%) used at finish." + } + ], + "details": { + "id": "dd7f94e8", + "name": "fixture-marker", + "agent": "marker", + "status": "completed", + "mode": "background", + "parentClosePolicy": "terminate", + "deliveryState": "awaited", + "async": false, + "autoExit": true, + "exitCode": 0, + "elapsed": 7, + "outputTokens": 49, + "contextTokens": 1777, + "contextWindow": 372000, + "summary": "PASEO_EDXETH_UPSTREAM_OK", + "sessionFile": "/tmp/paseo-edxeth-fixture/foreground/child-session.jsonl" + } + }, + "isError": false + } + ], + "messages": [ + { + "role": "assistant", + "content": [ + { + "type": "toolCall", + "id": "call_0bc6fc2213cd49aa92950947bde5dd8a|fc_0a0ca4aa78cd296a016ad8b88d018481919eadbf09eb191a1a", + "name": "subagent", + "arguments": { + "agent": "marker", + "name": "fixture-marker", + "title": "Fixture marker verification", + "task": "Return only PASEO_EDXETH_UPSTREAM_OK" + } + } + ], + "api": "openai-codex-responses", + "provider": "openai-codex", + "model": "gpt-5.6-sol", + "usage": { + "input": 3256, + "output": 43, + "cacheRead": 0, + "cacheWrite": 0, + "reasoning": 0, + "totalTokens": 3299, + "cost": { + "input": 0.013023999999999999, + "output": 0.0008600000000000001, + "cacheRead": 0, + "cacheWrite": 0, + "total": 0.013883999999999999 + } + }, + "stopReason": "toolUse", + "timestamp": 1792587912428, + "responseId": "resp_0a0ca4aa78cd296a016ad8b88bd52881918cbb9b8326b3060a", + "rawStopReason": "completed" + }, + { + "role": "toolResult", + "toolCallId": "call_0bc6fc2213cd49aa92950947bde5dd8a|fc_0a0ca4aa78cd296a016ad8b88d018481919eadbf09eb191a1a", + "toolName": "subagent", + "content": [ + { + "type": "text", + "text": "Sub-agent \"fixture-marker\" completed (7s).\n\nPASEO_EDXETH_UPSTREAM_OK\n\nSession: /tmp/paseo-edxeth-fixture/foreground/child-session.jsonl\nResume: pi --session /tmp/paseo-edxeth-fixture/foreground/child-session.jsonl\n\nSub-agent context: 2K/372K tokens (0%) used at finish." + } + ], + "details": { + "id": "dd7f94e8", + "name": "fixture-marker", + "agent": "marker", + "status": "completed", + "mode": "background", + "parentClosePolicy": "terminate", + "deliveryState": "awaited", + "async": false, + "autoExit": true, + "exitCode": 0, + "elapsed": 7, + "outputTokens": 49, + "contextTokens": 1777, + "contextWindow": 372000, + "summary": "PASEO_EDXETH_UPSTREAM_OK", + "sessionFile": "/tmp/paseo-edxeth-fixture/foreground/child-session.jsonl" + }, + "isError": false, + "timestamp": 1792587924671 + }, + { + "role": "assistant", + "content": [ + { + "type": "text", + "text": "DONE" + } + ], + "api": "openai-codex-responses", + "provider": "openai-codex", + "model": "gpt-5.6-sol", + "usage": { + "input": 388, + "output": 5, + "cacheRead": 3072, + "cacheWrite": 0, + "reasoning": 0, + "totalTokens": 3465, + "cost": { + "input": 0.001552, + "output": 0.0001, + "cacheRead": 0.0012288000000000002, + "cacheWrite": 0, + "total": 0.0028808 + } + }, + "stopReason": "stop", + "timestamp": 1792587924675, + "responseId": "resp_0a0ca4aa78cd296a016ad8b896af2c81919c0ef586dd50f2f0", + "rawStopReason": "completed" + } + ] +} diff --git a/packages/server/src/server/agent/providers/pi/extensions/edxeth-pi-subagents/index.test.ts b/packages/server/src/server/agent/providers/pi/extensions/edxeth-pi-subagents/index.test.ts new file mode 100644 index 0000000000..86692ab32e --- /dev/null +++ b/packages/server/src/server/agent/providers/pi/extensions/edxeth-pi-subagents/index.test.ts @@ -0,0 +1,336 @@ +import { describe, expect, test } from "vitest"; +import { fileURLToPath } from "node:url"; +import { createPiExtensionHost } from "../index.js"; +import { readSubagentFixture, verifySubagentFixture } from "../subagent-fixture-test.js"; +import { mkdtemp, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { edxethPiSubagents } from "./index.js"; +import { streamPiHistory } from "../../history-mapper.js"; +import { ProviderSubagentStore } from "../../../../provider-subagents/store.js"; + +const args = { + agent: "scout", + name: "status-scout", + title: "Check child status", + task: "Return the completion marker", +}; + +describe("edxeth/pi-subagents adapter", () => { + test.each(["tool", "notification"])("ends a child that requested help via %s", (delivery) => { + const host = createPiExtensionHost(); + const call = { callId: "spawn-1", toolName: "subagent", args, status: "completed" as const }; + host.mapToolCall({ + ...call, + result: { details: { id: "run-1", name: args.name, status: "started" } }, + }); + const details = { id: "run-1", name: args.name, message: "Please clarify the task" }; + const result = + delivery === "tool" + ? host.mapToolCall({ ...call, result: { details: { ...details, status: "pinged" } } }) + : host.mapCustomMessage({ + role: "custom", + customType: "subagent_ping", + content: "Child requested help and exited", + details, + }); + expect(result?.subagents).toEqual([ + expect.objectContaining({ + type: "upsert", + id: "spawn-1", + status: "canceled", + subtitle: "Needs help", + }), + ]); + }); + + test("leaves unrelated tools and other subagent protocols to their adapters", () => { + const host = createPiExtensionHost(undefined, [edxethPiSubagents]); + const call = { callId: "other", toolName: "subagent", args, status: "completed" as const }; + for (const details of [null, {}, { mode: "single", results: [] }, { id: "run-1" }]) { + expect(host.mapToolCall({ ...call, result: { details } })).toBeUndefined(); + } + expect( + host.mapToolCall({ + ...call, + toolName: "unrelated", + result: { details: { id: "run-1", name: args.name, status: "completed" } }, + }), + ).toBeUndefined(); + for (const details of [{ id: "run-1" }, { id: "run-1", status: "unknown" }]) { + expect( + host.mapCustomMessage({ + role: "custom", + customType: "subagent_result", + content: "Unrecognized result", + details, + }), + ).toBeUndefined(); + } + }); + + test("waits for cancellation after a kill acknowledgement reports stopping", () => { + const host = createPiExtensionHost(); + host.mapToolCall({ + callId: "spawn-1", + toolName: "subagent", + args, + status: "completed", + result: { details: { id: "run-1", name: args.name, status: "started" } }, + }); + const stopping = host.mapToolCall({ + callId: "kill-1", + toolName: "subagent_kill", + args: { id: "run-1" }, + status: "completed", + result: { details: { id: "run-1", name: args.name, status: "stopping" } }, + }); + expect(stopping?.subagents ?? []).toEqual([]); + expect( + host.mapCustomMessage({ + role: "custom", + customType: "subagent_result", + content: "Cancelled", + details: { id: "run-1", status: "cancelled" }, + })?.subagents, + ).toEqual([{ type: "upsert", id: "spawn-1", status: "canceled" }]); + }); + + test("preserves a completion that arrives before its launch acknowledgement", () => { + const host = createPiExtensionHost(); + const early = host.mapCustomMessage({ + role: "custom", + customType: "subagent_result", + content: "Already done", + details: { id: "fast-run", status: "completed" }, + }); + expect(early?.subagents ?? []).toEqual([]); + const started = host.mapToolCall({ + callId: "fast-call", + toolName: "subagent", + args, + status: "completed", + result: { + details: { id: "fast-run", name: args.name, agent: args.agent, status: "started" }, + }, + }); + expect(started?.subagents).toEqual([ + { + type: "upsert", + id: "fast-call", + title: "scout", + description: args.task, + toolCallId: "fast-call", + status: "completed", + }, + ]); + }); + + test.each([ + { details: { status: "completed" }, expected: "completed" }, + { details: { status: "failed" }, expected: "failed" }, + { details: { status: "cancelled" }, expected: "canceled" }, + { details: { error: "Child watcher exited unexpectedly" }, expected: "failed" }, + ])("maps completion $details to $expected", ({ details, expected }) => { + const host = createPiExtensionHost(); + host.mapToolCall({ + callId: "spawn-1", + toolName: "subagent", + args, + status: "completed", + result: { details: { id: "run-1", name: args.name, status: "started" } }, + }); + expect( + host.mapCustomMessage({ + role: "custom", + customType: "subagent_result", + content: "Child result", + details: { id: "run-1", ...details }, + })?.subagents, + ).toEqual([{ type: "upsert", id: "spawn-1", status: expected }]); + }); + + test("a resume gets its own run and reads the retained child session", async () => { + const dir = await mkdtemp(join(tmpdir(), "paseo-edxeth-resume-")); + const file = join(dir, "child.jsonl"); + const host = createPiExtensionHost(); + try { + await writeFile( + file, + JSON.stringify({ + type: "message", + message: { role: "assistant", content: [{ type: "text", text: "RESUMED" }] }, + }) + "\n", + ); + const original = host.mapToolCall({ + callId: "spawn-1", + toolName: "subagent", + args, + status: "completed", + result: { + details: { id: "run-1", name: args.name, status: "completed", sessionFile: file }, + }, + }); + await original?.hydration; + const resumed = host.mapToolCall({ + callId: "resume-1", + toolName: "subagent_resume", + args: { sessionFile: file, task: "Continue" }, + status: "completed", + result: { + details: { id: "run-2", name: args.name, status: "started", sessionFile: file }, + }, + }); + expect(resumed?.childSessions).toEqual([{ id: "resume-1", file }]); + expect(await resumed?.hydration).toEqual([ + { + type: "provider_subagent", + provider: "pi", + event: { + type: "timeline", + id: "resume-1", + item: expect.objectContaining({ type: "assistant_message", text: "RESUMED" }), + }, + }, + ]); + expect( + host.mapCustomMessage({ + role: "custom", + customType: "subagent_result", + content: "Resumed run complete", + details: { id: "run-2", status: "completed", sessionFile: file }, + })?.subagents, + ).toEqual([{ type: "upsert", id: "resume-1", status: "completed" }]); + } finally { + host.close(); + await rm(dir, { recursive: true, force: true }); + } + }); + + test("settles each batched child by run id without creating a parent-call card", () => { + const host = createPiExtensionHost(); + const result = host.mapToolCall({ + callId: "batch-1", + toolName: "subagent", + args: { children: [args, { ...args, agent: "reviewer", name: "status-reviewer" }] }, + status: "completed", + result: { + details: { + status: "batch", + children: [ + { id: "run-1", ...args, mode: "background", status: "completed" }, + { id: "run-2", name: "status-reviewer", agent: "reviewer", status: "started" }, + ], + }, + }, + }); + expect(result?.subagents).toEqual([ + { + type: "upsert", + id: "batch-1:0", + title: "scout", + description: args.task, + toolCallId: "batch-1", + status: "completed", + }, + { + type: "upsert", + id: "batch-1:1", + title: "reviewer", + description: undefined, + toolCallId: "batch-1", + status: "running", + }, + ]); + expect( + host.mapCustomMessage({ + role: "custom", + customType: "subagent_result", + content: "Done", + details: { id: "run-2", status: "completed" }, + })?.subagents, + ).toEqual([{ type: "upsert", id: "batch-1:1", status: "completed" }]); + }); + + test.each([ + { mode: "foreground", statuses: ["running", "completed"] }, + { mode: "background", statuses: ["running", "running", "completed"] }, + ])("settles the captured $mode run live and on replay", async ({ mode, statuses }) => { + const fixture = readSubagentFixture(new URL(`./fixtures/${mode}.json`, import.meta.url), { + from: `/tmp/paseo-edxeth-fixture/${mode}/child-session.jsonl`, + to: fileURLToPath(new URL(`./fixtures/${mode}-child-session.jsonl`, import.meta.url)), + }); + const events = await verifySubagentFixture(fixture); + const updates = events.flatMap(({ event }) => (event.type === "upsert" ? [event] : [])); + expect(updates.map((event) => event.status)).toEqual(statuses); + expect(new Set(updates.map((event) => event.id)).size).toBe(1); + expect(events.some(({ event }) => event.type === "timeline")).toBe(true); + }); + + test.each(["foreground", "background"])( + "restores a completed %s child and its transcript to the consumer store", + async (mode) => { + const fixture = readSubagentFixture(new URL(`./fixtures/${mode}.json`, import.meta.url), { + from: `/tmp/paseo-edxeth-fixture/${mode}/child-session.jsonl`, + to: fileURLToPath(new URL(`./fixtures/${mode}-child-session.jsonl`, import.meta.url)), + }); + const store = new ProviderSubagentStore(); + for await (const event of streamPiHistory("pi", fixture.messages)) { + if (event.type === "provider_subagent") store.apply("parent", "pi", event.event); + } + const children = store.list("parent"); + expect(children).toEqual([expect.objectContaining({ status: "completed", title: "marker" })]); + const transcript = store.fetchTimeline("parent", children[0]!.id); + expect(transcript.rows).toContainEqual( + expect.objectContaining({ + item: expect.objectContaining({ + type: "assistant_message", + text: mode === "foreground" ? "PASEO_EDXETH_UPSTREAM_OK" : "PASEO_EDXETH_LEAN_OK", + }), + }), + ); + }, + ); + + test("settles a synchronous result on the card created when the tool started", () => { + const host = createPiExtensionHost(); + const call = { callId: "spawn-1", toolName: "subagent", args }; + const start = host.mapToolCall({ ...call, status: "running", result: null }); + expect(start?.subagents).toEqual([ + { + type: "upsert", + id: "spawn-1", + title: "scout", + description: args.task, + toolCallId: "spawn-1", + status: "running", + }, + ]); + const end = host.mapToolCall({ + ...call, + status: "completed", + result: { + content: [{ type: "text", text: "DONE" }], + details: { + id: "run-1", + name: args.name, + agent: args.agent, + mode: "background", + status: "completed", + deliveryState: "awaited", + exitCode: 0, + }, + }, + }); + expect(end?.subagents).toEqual([ + { + type: "upsert", + id: "spawn-1", + title: "scout", + description: args.task, + toolCallId: "spawn-1", + status: "completed", + }, + ]); + }); +}); 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 new file mode 100644 index 0000000000..c00e39aaad --- /dev/null +++ b/packages/server/src/server/agent/providers/pi/extensions/edxeth-pi-subagents/index.ts @@ -0,0 +1,119 @@ +import { z } from "zod"; +import { extractTextFromToolResult } from "../../tool-call-mapper.js"; +import type { PiChildSessionFile, PiExtension } from "../contract.js"; +import type { ProviderSubagentInputEvent } from "../../../../provider-subagents/store.js"; + +const Run = z + .object({ + id: z.string().trim().min(1), + name: z.string().trim().min(1), + agent: z.string().trim().min(1).optional(), + task: z.string().optional(), + status: z.enum(["started", "completed", "failed", "cancelled", "pinged"]), + sessionFile: z.string().trim().min(1).optional(), + }) + .passthrough(); +const Args = z.object({ task: z.string().optional() }).passthrough(); +const Batch = z.object({ status: z.enum(["started", "batch"]), children: z.array(Run).min(1) }); +const Completion = z + .object({ + id: z.string().trim().min(1), + status: z.enum(["completed", "failed", "cancelled", "pinged"]).optional(), + error: z.string().optional(), + sessionFile: z.string().trim().min(1).optional(), + }) + .passthrough(); +const Ping = Completion.extend({ message: z.string() }); + +function childStatus(status: z.infer["status"]) { + if (status === "started") return "running"; + // A ping ends the child process and needs a new resume run. It is not success. + if (status === "cancelled" || status === "pinged") return "canceled"; + return status; +} + +export const edxethPiSubagents: PiExtension = { + id: "edxeth/pi-subagents", + createSession: () => { + const callsByRun = new Map(); + const completions = new Map>(); + const readSessions = new Map(); + const childSession = (id: string, file: string | undefined): PiChildSessionFile[] => { + if (!file || readSessions.get(id) === file) return []; + readSessions.set(id, file); + return [{ id, file }]; + }; + const completeRun = (result: z.infer) => { + const status = result.status ?? (result.error === undefined ? undefined : "failed"); + if (!status) return undefined; + // A fast child can finish before the tool returns its run id. Keep the + // terminal outcome so a later "started" acknowledgement cannot regress it. + completions.set(result.id, { ...result, status }); + const id = callsByRun.get(result.id); + if (!id) return undefined; + const subagents: ProviderSubagentInputEvent[] = [ + { + type: "upsert", + id, + status: childStatus(status), + ...(status === "pinged" ? { subtitle: "Needs help" } : {}), + }, + ]; + return { subagents, childSessions: childSession(id, result.sessionFile) }; + }; + return { + mapToolCall(call) { + const isLaunch = call.toolName === "subagent" || call.toolName === "subagent_resume"; + if (!isLaunch || call.status === "running") return undefined; + const details = typeof call.result === "object" ? call.result?.details : null; + const single = Run.safeParse(details); + let runs: z.infer[]; + if (single.success) { + runs = [single.data]; + } else { + const batch = Batch.safeParse(details); + if (!batch.success) return undefined; + runs = batch.data.children; + } + const args = Args.safeParse(call.args); + const description = args.success ? args.data.task : undefined; + const subagents: ProviderSubagentInputEvent[] = []; + const childSessions: PiChildSessionFile[] = []; + for (const [index, run] of runs.entries()) { + const id = runs.length === 1 ? call.callId : `${call.callId}:${index}`; + const completion = completions.get(run.id); + const status = completion?.status ?? run.status; + callsByRun.set(run.id, id); + subagents.push({ + type: "upsert", + id, + title: run.agent ?? run.name, + description: run.task ?? description, + toolCallId: call.callId, + status: childStatus(status), + ...(status === "pinged" ? { subtitle: "Needs help" } : {}), + }); + childSessions.push(...childSession(id, completion?.sessionFile ?? run.sessionFile)); + } + return { + detail: { + type: "sub_agent", + description, + log: extractTextFromToolResult(call.result)?.trim() ?? "", + }, + subagents, + childSessions, + }; + }, + mapCustomMessage(message) { + if (message.customType === "subagent_ping") { + const ping = Ping.safeParse(message.details); + return ping.success ? completeRun({ ...ping.data, status: "pinged" }) : undefined; + } + if (message.customType !== "subagent_result") return undefined; + const parsed = Completion.safeParse(message.details); + return parsed.success ? completeRun(parsed.data) : undefined; + }, + }; + }, +}; diff --git a/packages/server/src/server/agent/providers/pi/extensions/registry.ts b/packages/server/src/server/agent/providers/pi/extensions/registry.ts index c66fabad80..ca273b83bb 100644 --- a/packages/server/src/server/agent/providers/pi/extensions/registry.ts +++ b/packages/server/src/server/agent/providers/pi/extensions/registry.ts @@ -1,5 +1,6 @@ import { piAskUser } from "./pi-ask-user/index.js"; import { piSubagents } from "./pi-subagents/index.js"; +import { edxethPiSubagents } from "./edxeth-pi-subagents/index.js"; import { piMcpAdapter } from "./pi-mcp-adapter/index.js"; import { rpivTodo } from "./rpiv-todo/index.js"; import { piExampleTodo } from "./pi-example-todo/index.js"; @@ -10,6 +11,7 @@ import { rpivAskUserQuestion } from "./rpiv-ask-user-question/index.js"; export const piExtensions = [ piAskUser, // Dialog correlation + edxethPiSubagents, // Structured results before the generic subagent tool matcher piSubagents, // Delegation calls tintinwebPiSubagents, gotgenesPiSubagents,