diff --git a/.tmp/acp-plan-probe/summary.json b/.tmp/acp-plan-probe/summary.json new file mode 100644 index 0000000000000000000000000000000000000000..e317394ff362766f1f4af1e069a7b64605d91248 --- /dev/null +++ b/.tmp/acp-plan-probe/summary.json @@ -0,0 +1,219 @@ +{ + "counts": { + "notificationsByMethod": { + "session/update": 205 + }, + "sessionUpdateByType": { + "available_commands_update": 1, + "agent_thought_chunk": 4, + "agent_message_chunk": 192, + "tool_call": 4, + "tool_call_update": 4 + }, + "serverRequestsByMethod": { + "session/request_permission": 2 + }, + "permissionDecisions": { + "allow-once": 2 + } + }, + "samples": { + "initializeResult": { + "protocolVersion": 1, + "agentCapabilities": { + "loadSession": true, + "mcpCapabilities": { + "http": true, + "sse": true + }, + "promptCapabilities": { + "audio": false, + "embeddedContext": false, + "image": true + } + }, + "authMethods": [ + { + "id": "cursor_login", + "name": "Cursor Login", + "description": "Authenticate using existing Cursor login credentials. Run 'agent login' first if not logged in." + } + ] + }, + "authenticateResult": {}, + "sessionNewResult": { + "sessionId": "1ca3aa23-fe4b-4468-95af-544a523440d7", + "modes": { + "currentModeId": "agent", + "availableModes": [ + { + "id": "agent", + "name": "Agent", + "description": "Full agent capabilities with tool access" + }, + { + "id": "plan", + "name": "Plan", + "description": "Read-only mode for planning and designing before implementation" + }, + { + "id": "ask", + "name": "Ask", + "description": "Q&A mode - no edits or command execution" + } + ] + } + }, + "sessionPromptResultByScenario": { + "hello": { + "stopReason": "end_turn" + }, + "tooling": { + "stopReason": "end_turn" + }, + "cancel": { + "stopReason": "end_turn" + } + }, + "sessionUpdateByType": { + "available_commands_update": { + "jsonrpc": "2.0", + "method": "session/update", + "params": { + "sessionId": "1ca3aa23-fe4b-4468-95af-544a523440d7", + "update": { + "sessionUpdate": "available_commands_update", + "availableCommands": [ + { + "name": "copy-request-id", + "description": "Copy the last request ID to clipboard" + }, + { + "name": "create-rule", + "description": "Create Cursor rules for persistent AI guidance. Use when you want to create a rule, add coding standards, set up project conventions, configure file-specific patterns, create RULE.md files, or asks about .cursor/rules/ or AGENTS.md. (builtin skill)" + }, + { + "name": "create-skill", + "description": "Guides users through creating effective Agent Skills for Cursor. Use when you want to create, write, or author a new skill, or asks about skill structure, best practices, or SKILL.md format. (builtin skill)" + }, + { + "name": "create-subagent", + "description": "Create custom subagents for specialized AI tasks. Use when you want to create a new type of subagent, set up task-specific agents, configure code reviewers, debuggers, or domain-specific assistants with custom prompts. (builtin skill)" + }, + { + "name": "migrate-to-skills", + "description": "Convert 'Applied intelligently' Cursor rules (.cursor/rules/*.mdc) and slash commands (.cursor/commands/*.md) to Agent Skills format (.cursor/skills/). Use when you want to migrate rules or commands to skills, convert .mdc rules to SKILL.md format, or consolidate commands into the skills directory. (builtin skill)" + }, + { + "name": "update-cursor-settings", + "description": "Modify Cursor/VSCode user settings in settings.json. Use when you want to change editor settings, preferences, configuration, themes, font size, tab size, format on save, auto save, keybindings, or any settings.json values. (builtin skill)" + }, + { + "name": "react-doctor", + "description": "Run after making React changes to catch issues early. Use when reviewing code, finishing a feature, or fixing bugs in a React project. (user skill)" + } + ] + } + } + }, + "agent_thought_chunk": { + "jsonrpc": "2.0", + "method": "session/update", + "params": { + "sessionId": "1ca3aa23-fe4b-4468-95af-544a523440d7", + "update": { + "sessionUpdate": "agent_thought_chunk", + "content": { + "type": "text", + "text": "" + } + } + } + }, + "agent_message_chunk": { + "jsonrpc": "2.0", + "method": "session/update", + "params": { + "sessionId": "1ca3aa23-fe4b-4468-95af-544a523440d7", + "update": { + "sessionUpdate": "agent_message_chunk", + "content": { + "type": "text", + "text": "\nHi there. How can I help you today?" + } + } + } + }, + "tool_call": { + "jsonrpc": "2.0", + "method": "session/update", + "params": { + "sessionId": "1ca3aa23-fe4b-4468-95af-544a523440d7", + "update": { + "sessionUpdate": "tool_call", + "toolCallId": "tool_2cb78ed4-0835-4b1d-bb63-ddb8646fb71", + "title": "Terminal", + "kind": "execute", + "status": "pending", + "rawInput": {} + } + } + }, + "tool_call_update": { + "jsonrpc": "2.0", + "method": "session/update", + "params": { + "sessionId": "1ca3aa23-fe4b-4468-95af-544a523440d7", + "update": { + "sessionUpdate": "tool_call_update", + "toolCallId": "tool_2cb78ed4-0835-4b1d-bb63-ddb8646fb71", + "status": "in_progress" + } + } + } + }, + "serverRequestByMethod": { + "session/request_permission": { + "jsonrpc": "2.0", + "id": 0, + "method": "session/request_permission", + "params": { + "sessionId": "1ca3aa23-fe4b-4468-95af-544a523440d7", + "toolCall": { + "toolCallId": "tool_2cb78ed4-0835-4b1d-bb63-ddb8646fb71", + "title": "`pwd`", + "kind": "execute", + "status": "pending", + "content": [ + { + "type": "content", + "content": { + "type": "text", + "text": "Not in allowlist: pwd" + } + } + ] + }, + "options": [ + { + "optionId": "allow-once", + "name": "Allow once", + "kind": "allow_once" + }, + { + "optionId": "allow-always", + "name": "Allow always", + "kind": "allow_always" + }, + { + "optionId": "reject-once", + "name": "Reject", + "kind": "reject_once" + } + ] + } + } + } + }, + "stderr": [] +} diff --git a/.tmp/acp-plan-probe/transcript.ndjson b/.tmp/acp-plan-probe/transcript.ndjson new file mode 100644 index 0000000000000000000000000000000000000000..b1e02015995ab4e504b179dbf251651746c0f554 --- /dev/null +++ b/.tmp/acp-plan-probe/transcript.ndjson @@ -0,0 +1,233 @@ +{"ts":"2026-03-04T07:30:35.721Z","channel":"client->server","message":{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{"fs":{"readTextFile":false,"writeTextFile":false},"terminal":false},"clientInfo":{"name":"t3-cursor-acp-probe","version":"0.1.0"}}}} +{"ts":"2026-03-04T07:30:36.334Z","channel":"server->client","message":{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentCapabilities":{"loadSession":true,"mcpCapabilities":{"http":true,"sse":true},"promptCapabilities":{"audio":false,"embeddedContext":false,"image":true}},"authMethods":[{"id":"cursor_login","name":"Cursor Login","description":"Authenticate using existing Cursor login credentials. Run 'agent login' first if not logged in."}]}}} +{"ts":"2026-03-04T07:30:36.334Z","channel":"scenario-result","scenario":"initialize","result":{"protocolVersion":1,"agentCapabilities":{"loadSession":true,"mcpCapabilities":{"http":true,"sse":true},"promptCapabilities":{"audio":false,"embeddedContext":false,"image":true}},"authMethods":[{"id":"cursor_login","name":"Cursor Login","description":"Authenticate using existing Cursor login credentials. Run 'agent login' first if not logged in."}]}} +{"ts":"2026-03-04T07:30:36.334Z","channel":"client->server","message":{"jsonrpc":"2.0","id":2,"method":"authenticate","params":{"methodId":"cursor_login"}}} +{"ts":"2026-03-04T07:30:36.335Z","channel":"server->client","message":{"jsonrpc":"2.0","id":2,"result":{}}} +{"ts":"2026-03-04T07:30:36.335Z","channel":"scenario-result","scenario":"authenticate","result":{}} +{"ts":"2026-03-04T07:30:36.335Z","channel":"client->server","message":{"jsonrpc":"2.0","id":3,"method":"session/new","params":{"cwd":"/Users/julius/.t3/worktrees/codething-mvp/t3code-f7431d5b","mcpServers":[]}}} +{"ts":"2026-03-04T07:30:36.626Z","channel":"server->client","message":{"jsonrpc":"2.0","id":3,"result":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","modes":{"currentModeId":"agent","availableModes":[{"id":"agent","name":"Agent","description":"Full agent capabilities with tool access"},{"id":"plan","name":"Plan","description":"Read-only mode for planning and designing before implementation"},{"id":"ask","name":"Ask","description":"Q&A mode - no edits or command execution"}]}}}} +{"ts":"2026-03-04T07:30:36.626Z","channel":"scenario-result","scenario":"session/new","result":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","modes":{"currentModeId":"agent","availableModes":[{"id":"agent","name":"Agent","description":"Full agent capabilities with tool access"},{"id":"plan","name":"Plan","description":"Read-only mode for planning and designing before implementation"},{"id":"ask","name":"Ask","description":"Q&A mode - no edits or command execution"}]}}} +{"ts":"2026-03-04T07:30:36.626Z","channel":"client->server","message":{"jsonrpc":"2.0","id":4,"method":"session/prompt","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","prompt":[{"type":"text","text":"Say hello in one sentence."}]}}} +{"ts":"2026-03-04T07:30:36.878Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"available_commands_update","availableCommands":[{"name":"copy-request-id","description":"Copy the last request ID to clipboard"},{"name":"create-rule","description":"Create Cursor rules for persistent AI guidance. Use when you want to create a rule, add coding standards, set up project conventions, configure file-specific patterns, create RULE.md files, or asks about .cursor/rules/ or AGENTS.md. (builtin skill)"},{"name":"create-skill","description":"Guides users through creating effective Agent Skills for Cursor. Use when you want to create, write, or author a new skill, or asks about skill structure, best practices, or SKILL.md format. (builtin skill)"},{"name":"create-subagent","description":"Create custom subagents for specialized AI tasks. Use when you want to create a new type of subagent, set up task-specific agents, configure code reviewers, debuggers, or domain-specific assistants with custom prompts. (builtin skill)"},{"name":"migrate-to-skills","description":"Convert 'Applied intelligently' Cursor rules (.cursor/rules/*.mdc) and slash commands (.cursor/commands/*.md) to Agent Skills format (.cursor/skills/). Use when you want to migrate rules or commands to skills, convert .mdc rules to SKILL.md format, or consolidate commands into the skills directory. (builtin skill)"},{"name":"update-cursor-settings","description":"Modify Cursor/VSCode user settings in settings.json. Use when you want to change editor settings, preferences, configuration, themes, font size, tab size, format on save, auto save, keybindings, or any settings.json values. (builtin skill)"},{"name":"react-doctor","description":"Run after making React changes to catch issues early. Use when reviewing code, finishing a feature, or fixing bugs in a React project. (user skill)"}]}}},"scenarioName":"hello"} +{"ts":"2026-03-04T07:30:37.891Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":""}}}},"scenarioName":"hello"} +{"ts":"2026-03-04T07:30:38.060Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"\nHi there. How can I help you today?"}}}},"scenarioName":"hello"} +{"ts":"2026-03-04T07:30:38.181Z","channel":"server->client","message":{"jsonrpc":"2.0","id":4,"result":{"stopReason":"end_turn"}}} +{"ts":"2026-03-04T07:30:38.181Z","channel":"scenario-result","scenario":"session/prompt","scenarioName":"hello","result":{"stopReason":"end_turn"}} +{"ts":"2026-03-04T07:30:38.683Z","channel":"client->server","message":{"jsonrpc":"2.0","id":5,"method":"session/prompt","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","prompt":[{"type":"text","text":"Use tools to run `pwd` and then `ls -1 | head -n 8`, and summarize what you found in one paragraph."}]}}} +{"ts":"2026-03-04T07:30:39.497Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":""}}}},"scenarioName":"tooling"} +{"ts":"2026-03-04T07:30:39.530Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"\n\n"}}}},"scenarioName":"tooling"} +{"ts":"2026-03-04T07:30:39.555Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"tool_call","toolCallId":"tool_2cb78ed4-0835-4b1d-bb63-ddb8646fb71","title":"Terminal","kind":"execute","status":"pending","rawInput":{}}}},"scenarioName":"tooling"} +{"ts":"2026-03-04T07:30:39.614Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"tool_call","toolCallId":"tool_6448bf17-7bec-498b-93dc-aa20a884154","title":"Terminal","kind":"execute","status":"pending","rawInput":{}}}},"scenarioName":"tooling"} +{"ts":"2026-03-04T07:30:39.615Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"tool_call","toolCallId":"tool_2cb78ed4-0835-4b1d-bb63-ddb8646fb71","title":"`pwd`","kind":"execute","status":"pending","rawInput":{"command":"pwd"}}}},"scenarioName":"tooling"} +{"ts":"2026-03-04T07:30:39.615Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"tool_call_update","toolCallId":"tool_2cb78ed4-0835-4b1d-bb63-ddb8646fb71","status":"in_progress"}}},"scenarioName":"tooling"} +{"ts":"2026-03-04T07:30:39.617Z","channel":"server->client","message":{"jsonrpc":"2.0","id":0,"method":"session/request_permission","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","toolCall":{"toolCallId":"tool_2cb78ed4-0835-4b1d-bb63-ddb8646fb71","title":"`pwd`","kind":"execute","status":"pending","content":[{"type":"content","content":{"type":"text","text":"Not in allowlist: pwd"}}]},"options":[{"optionId":"allow-once","name":"Allow once","kind":"allow_once"},{"optionId":"allow-always","name":"Allow always","kind":"allow_always"},{"optionId":"reject-once","name":"Reject","kind":"reject_once"}]}}} +{"ts":"2026-03-04T07:30:39.617Z","channel":"permission-decision","requestId":0,"optionId":"allow-once","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","toolCall":{"toolCallId":"tool_2cb78ed4-0835-4b1d-bb63-ddb8646fb71","title":"`pwd`","kind":"execute","status":"pending","content":[{"type":"content","content":{"type":"text","text":"Not in allowlist: pwd"}}]},"options":[{"optionId":"allow-once","name":"Allow once","kind":"allow_once"},{"optionId":"allow-always","name":"Allow always","kind":"allow_always"},{"optionId":"reject-once","name":"Reject","kind":"reject_once"}]}} +{"ts":"2026-03-04T07:30:39.617Z","channel":"client->server","message":{"jsonrpc":"2.0","id":0,"result":{"outcome":{"outcome":"selected","optionId":"allow-once"}}}} +{"ts":"2026-03-04T07:30:39.740Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"tool_call","toolCallId":"tool_6448bf17-7bec-498b-93dc-aa20a884154","title":"`ls -1 | head -n 8`","kind":"execute","status":"pending","rawInput":{"command":"ls -1 | head -n 8"}}}},"scenarioName":"tooling"} +{"ts":"2026-03-04T07:30:39.740Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"tool_call_update","toolCallId":"tool_6448bf17-7bec-498b-93dc-aa20a884154","status":"in_progress"}}},"scenarioName":"tooling"} +{"ts":"2026-03-04T07:30:39.741Z","channel":"server->client","message":{"jsonrpc":"2.0","id":1,"method":"session/request_permission","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","toolCall":{"toolCallId":"tool_6448bf17-7bec-498b-93dc-aa20a884154","title":"`ls -1 | head -n 8`","kind":"execute","status":"pending","content":[{"type":"content","content":{"type":"text","text":"Not in allowlist: head -n 8"}}]},"options":[{"optionId":"allow-once","name":"Allow once","kind":"allow_once"},{"optionId":"allow-always","name":"Allow always","kind":"allow_always"},{"optionId":"reject-once","name":"Reject","kind":"reject_once"}]}}} +{"ts":"2026-03-04T07:30:39.741Z","channel":"permission-decision","requestId":1,"optionId":"allow-once","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","toolCall":{"toolCallId":"tool_6448bf17-7bec-498b-93dc-aa20a884154","title":"`ls -1 | head -n 8`","kind":"execute","status":"pending","content":[{"type":"content","content":{"type":"text","text":"Not in allowlist: head -n 8"}}]},"options":[{"optionId":"allow-once","name":"Allow once","kind":"allow_once"},{"optionId":"allow-always","name":"Allow always","kind":"allow_always"},{"optionId":"reject-once","name":"Reject","kind":"reject_once"}]}} +{"ts":"2026-03-04T07:30:39.741Z","channel":"client->server","message":{"jsonrpc":"2.0","id":1,"result":{"outcome":{"outcome":"selected","optionId":"allow-once"}}}} +{"ts":"2026-03-04T07:30:39.750Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"tool_call_update","toolCallId":"tool_2cb78ed4-0835-4b1d-bb63-ddb8646fb71","status":"completed","rawOutput":{"exitCode":0,"stdout":"/Users/julius/.t3/worktrees/codething-mvp/t3code-f7431d5b\n","stderr":""}}}},"scenarioName":"tooling"} +{"ts":"2026-03-04T07:30:39.851Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"tool_call_update","toolCallId":"tool_6448bf17-7bec-498b-93dc-aa20a884154","status":"completed","rawOutput":{"exitCode":0,"stdout":"AGENTS.md\napps\nassets\nattachments\nbun.lock\nCLAUDE.md\ndocs\nEVENTS.md\n","stderr":""}}}},"scenarioName":"tooling"} +{"ts":"2026-03-04T07:30:40.517Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":""}}}},"scenarioName":"tooling"} +{"ts":"2026-03-04T07:30:40.938Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"\nThe workspace root is `/Use"}}}},"scenarioName":"tooling"} +{"ts":"2026-03-04T07:30:40.938Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"rs/julius/.t3/worktre"}}}},"scenarioName":"tooling"} +{"ts":"2026-03-04T07:30:40.964Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"es/codething-mvp/t3co"}}}},"scenarioName":"tooling"} +{"ts":"2026-03-04T07:30:40.964Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"de-f7431d5b`, a git work"}}}},"scenarioName":"tooling"} +{"ts":"2026-03-04T07:30:40.992Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"tree for the T3 Code proje"}}}},"scenarioName":"tooling"} +{"ts":"2026-03-04T07:30:40.992Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ct. The top-l"}}}},"scenarioName":"tooling"} +{"ts":"2026-03-04T07:30:41.020Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"evel layout includes `"}}}},"scenarioName":"tooling"} +{"ts":"2026-03-04T07:30:41.020Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"AGENTS.md`, `CLAUDE.md`"}}}},"scenarioName":"tooling"} +{"ts":"2026-03-04T07:30:41.047Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":", and `EVENTS.md` f"}}}},"scenarioName":"tooling"} +{"ts":"2026-03-04T07:30:41.075Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"or docs; `app"}}}},"scenarioName":"tooling"} +{"ts":"2026-03-04T07:30:41.076Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"s` (likely t"}}}},"scenarioName":"tooling"} +{"ts":"2026-03-04T07:30:41.106Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"he main app p"}}}},"scenarioName":"tooling"} +{"ts":"2026-03-04T07:30:41.106Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ackages); `assets` a"}}}},"scenarioName":"tooling"} +{"ts":"2026-03-04T07:30:41.156Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"nd `attachments`; `docs`; and `bun.lock`, indicating a Bun-based monorepo."}}}},"scenarioName":"tooling"} +{"ts":"2026-03-04T07:30:41.185Z","channel":"server->client","message":{"jsonrpc":"2.0","id":5,"result":{"stopReason":"end_turn"}}} +{"ts":"2026-03-04T07:30:41.185Z","channel":"scenario-result","scenario":"session/prompt","scenarioName":"tooling","result":{"stopReason":"end_turn"}} +{"ts":"2026-03-04T07:30:41.686Z","channel":"client->server","message":{"jsonrpc":"2.0","id":6,"method":"session/prompt","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","prompt":[{"type":"text","text":"Think for a while and draft a long detailed migration plan with at least 20 bullet points before answering."}]}}} +{"ts":"2026-03-04T07:30:42.598Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":""}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:43.187Z","channel":"client->server","message":{"jsonrpc":"2.0","id":7,"method":"session/cancel","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7"}}} +{"ts":"2026-03-04T07:30:43.189Z","channel":"server->client","message":{"jsonrpc":"2.0","id":7,"error":{"code":-32601,"message":"Method not found","data":{"method":"session/cancel"}}}} +{"ts":"2026-03-04T07:30:43.189Z","channel":"scenario-error","scenario":"session/cancel","scenarioName":"cancel","error":"{\"code\":-32601,\"message\":\"Method not found\",\"data\":{\"method\":\"session/cancel\"}}"} +{"ts":"2026-03-04T07:30:44.324Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"\nYour message doesn’t specif"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:44.850Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"y what we’re "}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:46.555Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"migrating from or"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:47.707Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" to. Here’s a "}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:48.477Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"migration plan for a plausi"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:48.491Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ble, high-impact change in your "}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:48.525Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"T3 Code project: **add"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:48.561Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ing Claude Co"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:48.655Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"de as a second pr"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:48.655Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ovider alongside Co"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:48.655Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"dex**.\n\n---\n\n"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:48.656Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"## Migration Plan"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:48.673Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":": Adding Cla"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:48.707Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ude Code as "}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:48.740Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"a Second Provider\n\n1. "}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:48.773Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"**Audit the current pr"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:48.790Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ovider abstraction** –"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:48.823Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" Map how Codex is integrated (C"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:48.857Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"odexAdapter, ProviderR"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:48.890Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"untimeIngestion, Code"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:48.922Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"xAppServerManager) and iden"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:48.971Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"tify provider-specif"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:49.022Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ic vs. shared logic.\n\n2"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:49.055Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":". **Define a provider interfa"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:49.179Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ce** – Create a"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:49.179Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" common interface/con"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:49.180Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"tract for providers (s"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:49.204Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ession lifecycle, turn hand"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:49.239Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ling, event str"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:49.255Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"eaming) so Codex and"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:49.346Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" Claude Code can bo"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:49.346Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"th implement it."}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:49.353Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"\n\n3. **Extract Codex-"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:49.389Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"specific logic** "}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:49.422Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"– Move JSON-RPC-over-stdio han"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:49.454Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"dling, app-server lifecyc"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:49.487Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"le, and Codex"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:49.504Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" event schemas into a dedi"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:49.522Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"cated Codex p"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:49.538Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"rovider module.\n\n4"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:49.574Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":". **Design Claude Cod"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:49.898Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"e protocol integrati"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:49.921Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"on** – Resea"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:49.948Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"rch Claude Code’s API (HTTP"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:49.988Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":", WebSocket, or othe"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:50.029Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"r) and docum"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:50.071Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ent how it maps to your provider interface.\n"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:50.130Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"\n5. **Add prov"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:50.224Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ider selection**"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:50.248Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" – Extend session c"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:50.251Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"reation and configuration so "}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:50.310Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"the client can choo"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:50.331Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"se Codex vs. Claude Code "}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:50.371Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"(and future providers).\n\n6. **Creat"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:50.411Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"e a ClaudeCodeA"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:50.436Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"dapter** – Implement the p"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:50.473Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"rovider interface"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:50.493Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" for Claude Code, m"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:50.534Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"irroring the str"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:50.572Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ucture of CodexAdap"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:50.612Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ter but using Claud"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:50.749Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"e Code’s protoco"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:50.749Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"l.\n\n7. **Unify event sch"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:50.749Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"emas** – Ens"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:50.749Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ure provider events ("}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:50.774Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"turns, tool calls, err"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:50.815Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ors) are norm"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:50.857Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"alized into shared orchestrat"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:50.895Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ion events in `packag"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:50.920Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"es/contracts`.\n\n8. "}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:50.937Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"**Refactor CodexAppServerManag"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:50.975Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"er** – Generalize i"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.037Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"t into a provider p"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.055Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"rocess manager"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.096Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" that can spawn"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.116Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" either `codex"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.157Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" app-server` or"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.176Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" the Claude C"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.273Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ode equivalent.\n\n9. **U"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.275Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"pdate WebSocket routing** – Adjust `wsS"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.277Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"erver.ts` so NativeApi methods wo"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.281Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"rk with both pr"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.299Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"oviders and route "}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.319Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"requests to th"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.337Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"e correct pr"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.383Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ovider instanc"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.420Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"e.\n\n10. **Handle provider-s"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.459Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"pecific configurati"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.547Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"on** – Support d"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.547Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ifferent env va"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.579Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"rs, API keys, and config "}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.600Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"per provider (e.g. Codex vs Claude Cod"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.661Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"e).\n\n11. **Migrate sessio"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.682Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"n state** – En"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.704Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"sure session persistence an"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.801Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"d resume wor"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.801Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"k for both provi"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.802Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ders, including any p"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.812Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"rovider-specific metadata.\n\n12. **Ad"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.844Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"d provider health checks**"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.866Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" – Implement prov"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.904Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ider-specific health"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.943Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" checks and failure "}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:51.965Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"handling for both"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.004Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" Codex and Claude Code."}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.044Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"\n\n13. **Update clien"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.085Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"t-side session logic** – Modif"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.124Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"y `session-logic."}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.144Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ts` and relate"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.164Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"d UI so users"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.205Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" can create and switch between Co"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.323Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"dex and Claude"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.323Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" Code sessions.\n\n14."}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.330Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" **Test provider isolation"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.349Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"** – Add test"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.367Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"s to ensure on"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.408Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"e provider’s failures or restarts do not affect"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.449Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" the other.\n\n15. **Document provider"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.470Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" differences** – "}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.511Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"Document capabi"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.532Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"lities, limits,"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.551Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" and UX diffe"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.595Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"rences between Codex and Claude Co"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.612Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"de for users and d"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.651Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"evelopers.\n\n16. **P"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.696Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"lan backward compatibilit"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.714Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"y** – Decide how"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.754Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" existing Codex-o"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.850Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"nly sessions and co"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.850Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"nfigs behave af"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.850Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ter the migratio"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.895Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"n (e.g. default prov"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.936Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ider).\n\n17. **Imp"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:52.981Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"lement feature pa"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.021Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"rity checks** – List fe"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.057Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"atures (e.g. tool calls, stream"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.079Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ing) and ens"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.119Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ure both provid"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.140Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ers support them "}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.179Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"or degrade gracefully.\n\n18. "}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.242Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"**Add provider-specif"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.261Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ic error han"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.374Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"dling** – Map ea"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.374Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ch provider’s error "}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.375Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"types to shared error"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.375Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" schemas and user-facing message"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.383Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"s.\n\n19. **Performance and "}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.434Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"load testing** – Te"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.466Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"st concurrent sessions acro"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.504Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ss both providers a"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.524Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"nd ensure the se"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.565Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"rver handles mixed load corr"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.628Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ectly.\n\n20. "}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.667Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"**Rollout strate"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.727Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"gy** – Plan ph"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.757Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ased rollout (e"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.788Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":".g. feature fl"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.897Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ag, beta users) a"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.899Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"nd rollback if issu"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.899Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"es appear.\n\n21. **Upd"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.909Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ate AGENTS.md and docs** – "}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.974Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"Reflect the multi-pr"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:53.991Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ovider archit"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:54.038Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ecture and how to add new "}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:54.073Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"providers.\n\n22. **Create a pro"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:54.112Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"vider contribution"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:54.175Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" guide** – Document how "}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:54.194Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"to add a new provide"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:54.214Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"r (interface, adapters, t"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:54.233Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ests) for fut"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:54.273Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ure contributors.\n\n-"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:54.314Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"--\n\nIf you mean"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:54.420Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"t a different"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:54.420Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" migration (e.g. f"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:54.423Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ramework, infra"}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:54.454Z","channel":"server->client","message":{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"1ca3aa23-fe4b-4468-95af-544a523440d7","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":", or protocol), say what you’re migrating from and to and I can tailor the plan."}}}},"scenarioName":"cancel"} +{"ts":"2026-03-04T07:30:54.486Z","channel":"server->client","message":{"jsonrpc":"2.0","id":6,"result":{"stopReason":"end_turn"}}} +{"ts":"2026-03-04T07:30:54.486Z","channel":"scenario-result","scenario":"session/prompt","scenarioName":"cancel","result":{"stopReason":"end_turn"}} +{"ts":"2026-03-04T07:30:55.005Z","channel":"lifecycle","event":"exit","code":143,"signal":null} diff --git a/.tmp/ext/claude-agent-sdk-typescript b/.tmp/ext/claude-agent-sdk-typescript new file mode 160000 index 0000000000000000000000000000000000000000..9345c75d6ef60e794a78df2a795ae07abc0a420f --- /dev/null +++ b/.tmp/ext/claude-agent-sdk-typescript @@ -0,0 +1 @@ +Subproject commit 9345c75d6ef60e794a78df2a795ae07abc0a420f diff --git a/.tmp/ext/codex b/.tmp/ext/codex new file mode 160000 index 0000000000000000000000000000000000000000..fa2306b303a8c7b1ef2ebb41117051bede08cb09 --- /dev/null +++ b/.tmp/ext/codex @@ -0,0 +1 @@ +Subproject commit fa2306b303a8c7b1ef2ebb41117051bede08cb09 diff --git a/apps/server/integration/orchestrationEngine.integration.test.ts b/apps/server/integration/orchestrationEngine.integration.test.ts index 6dbf5a89656547621522d7de556a208e82a26967..559c26d8d659f904dbf79e5990e89fd5b1f14aa7 100644 --- a/apps/server/integration/orchestrationEngine.integration.test.ts +++ b/apps/server/integration/orchestrationEngine.integration.test.ts @@ -142,8 +142,7 @@ const startTurn = (input: { attachments: [], }, ...(input.provider !== undefined ? { provider: input.provider } : {}), - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", createdAt: nowIso(), }); @@ -477,26 +476,33 @@ it.live("records failed turn runtime state and checkpoint status as error", () = turnId: FIXTURE_TURN_ID, }, { - type: "message.delta", + type: "content.delta", ...runtimeBase("evt-failure-2", "2026-02-24T10:04:00.100Z"), threadId: FIXTURE_THREAD_ID, turnId: FIXTURE_TURN_ID, - delta: "Partial output before failure.\n", + payload: { + streamKind: "assistant_text", + delta: "Partial output before failure.\n", + }, }, { type: "runtime.error", ...runtimeBase("evt-failure-3", "2026-02-24T10:04:00.200Z"), threadId: FIXTURE_THREAD_ID, turnId: FIXTURE_TURN_ID, - message: "Sandbox command failed.", + payload: { + message: "Sandbox command failed.", + }, }, { type: "turn.completed", ...runtimeBase("evt-failure-4", "2026-02-24T10:04:00.300Z"), threadId: FIXTURE_THREAD_ID, turnId: FIXTURE_TURN_ID, - status: "failed", - errorMessage: "Sandbox command failed.", + payload: { + state: "failed", + errorMessage: "Sandbox command failed.", + }, }, ], }); diff --git a/apps/server/src/codexAppServerManager.ts b/apps/server/src/codexAppServerManager.ts index 71b93df99a6406ddaa008c4d6b89e5739bb472c6..3eea8a1cb866fb65e8c610705e9e5ae356c894b4 100644 --- a/apps/server/src/codexAppServerManager.ts +++ b/apps/server/src/codexAppServerManager.ts @@ -104,6 +104,23 @@ const RECOVERABLE_THREAD_RESUME_ERROR_SNIPPETS = [ "does not exist", ]; +function mapCodexRuntimeMode(runtimeMode: "approval-required" | "full-access"): { + readonly approvalPolicy: "on-request" | "never"; + readonly sandbox: "workspace-write" | "danger-full-access"; +} { + if (runtimeMode === "approval-required") { + return { + approvalPolicy: "on-request", + sandbox: "workspace-write", + }; + } + + return { + approvalPolicy: "never", + sandbox: "danger-full-access", + }; +} + /** * On Windows with `shell: true`, `child.kill()` only terminates the `cmd.exe` * wrapper, leaving the actual command running. Use `taskkill /T` to kill the @@ -239,8 +256,7 @@ export class CodexAppServerManager extends EventEmitter { providerName: "codex", providerSessionId: asSessionId("sess-1"), providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", activeTurnId: null, lastError: null, updatedAt: createdAt, @@ -423,8 +422,7 @@ describe("CheckpointReactor", () => { providerName: "codex", providerSessionId: asSessionId("sess-1"), providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", activeTurnId: asTurnId("turn-main"), lastError: null, updatedAt: createdAt, @@ -503,8 +501,7 @@ describe("CheckpointReactor", () => { providerName: "claudeCode", providerSessionId: asSessionId("sess-1"), providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", activeTurnId: null, lastError: null, updatedAt: createdAt, @@ -566,8 +563,7 @@ describe("CheckpointReactor", () => { providerName: "codex", providerSessionId: asSessionId("sess-1"), providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", activeTurnId: null, lastError: null, updatedAt: createdAt, @@ -619,8 +615,7 @@ describe("CheckpointReactor", () => { text: "start turn", attachments: [], }, - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", createdAt: new Date().toISOString(), }), ); @@ -657,8 +652,7 @@ describe("CheckpointReactor", () => { providerName: "codex", providerSessionId: asSessionId("sess-missing"), providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-missing"), - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", activeTurnId: asTurnId("turn-missing-cwd"), lastError: null, updatedAt: createdAt, @@ -707,8 +701,7 @@ describe("CheckpointReactor", () => { providerName: "codex", providerSessionId: asSessionId("sess-1"), providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", activeTurnId: null, lastError: null, updatedAt: createdAt, @@ -760,8 +753,7 @@ describe("CheckpointReactor", () => { providerName: "codex", providerSessionId: asSessionId("sess-1"), providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", activeTurnId: null, lastError: null, updatedAt: createdAt, @@ -815,8 +807,7 @@ describe("CheckpointReactor", () => { providerName: "codex", providerSessionId: asSessionId("sess-1"), providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", activeTurnId: null, lastError: null, updatedAt: createdAt, @@ -896,8 +887,7 @@ describe("CheckpointReactor", () => { providerName: "claudeCode", providerSessionId: asSessionId("sess-1"), providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", activeTurnId: null, lastError: null, updatedAt: createdAt, @@ -968,8 +958,7 @@ describe("CheckpointReactor", () => { providerName: "codex", providerSessionId: asSessionId("sess-1"), providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", activeTurnId: null, lastError: null, updatedAt: createdAt, diff --git a/apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts b/apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts index a22b8979f27935370797592611a45fb7606b8c31..8131a6ed2d4a3d21482c2a3c8af1b29fb48773bf 100644 --- a/apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts +++ b/apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts @@ -96,8 +96,7 @@ describe("OrchestrationEngine", () => { text: "hello", attachments: [], }, - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", createdAt, }), ); @@ -436,8 +435,7 @@ describe("OrchestrationEngine", () => { text: "hello", attachments: [], }, - approvalPolicy: "on-request" as const, - sandboxMode: "workspace-write" as const, + runtimeMode: "approval-required" as const, createdAt, }; @@ -596,8 +594,7 @@ describe("OrchestrationEngine", () => { text: "hello", attachments: [], }, - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", createdAt: now(), }), ), diff --git a/apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts b/apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts index 741aa16f160522da476536891733acc50bf399e6..063eee4e4ae7be9410775460e8054191391e1a50 100644 --- a/apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts +++ b/apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts @@ -1749,8 +1749,7 @@ it.effect("restores pending turn-start metadata across projection pipeline resta payload: { threadId, messageId, - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", createdAt: turnStartedAt, }, }); @@ -1784,8 +1783,7 @@ it.effect("restores pending turn-start metadata across projection pipeline resta providerName: "codex", providerSessionId: ProviderSessionId.makeUnsafe("provider-session-restart"), providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-restart"), - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", activeTurnId: turnId, lastError: null, updatedAt: sessionSetAt, diff --git a/apps/server/src/orchestration/Layers/ProjectionPipeline.ts b/apps/server/src/orchestration/Layers/ProjectionPipeline.ts index 6aba1b65b3b6bc8d04da93dd431599b4154dd168..0c3bff45636d0c88c8290e67fb7e6e52b92884df 100644 --- a/apps/server/src/orchestration/Layers/ProjectionPipeline.ts +++ b/apps/server/src/orchestration/Layers/ProjectionPipeline.ts @@ -651,8 +651,7 @@ const makeOrchestrationProjectionPipeline = Effect.gen(function* () { providerName: event.payload.session.providerName, providerSessionId: event.payload.session.providerSessionId, providerThreadId: event.payload.session.providerThreadId, - approvalPolicy: event.payload.session.approvalPolicy, - sandboxMode: event.payload.session.sandboxMode, + runtimeMode: event.payload.session.runtimeMode, activeTurnId: event.payload.session.activeTurnId, lastError: event.payload.session.lastError, updatedAt: event.payload.session.updatedAt, diff --git a/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts b/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts index ba99a796487d1bb2a1b7e6d3266db5019fb63b67..bf4f15330b2d58b2758f30179dd5f0bf9a2587dc 100644 --- a/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts +++ b/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts @@ -143,8 +143,7 @@ projectionSnapshotLayer("ProjectionSnapshotQuery", (it) => { provider_name, provider_session_id, provider_thread_id, - approval_policy, - sandbox_mode, + runtime_mode, active_turn_id, last_error, updated_at @@ -155,8 +154,7 @@ projectionSnapshotLayer("ProjectionSnapshotQuery", (it) => { 'codex', 'provider-session-1', 'provider-thread-1', - 'on-request', - 'workspace-write', + 'approval-required', 'turn-1', NULL, '2026-02-24T00:00:07.000Z' @@ -293,8 +291,7 @@ projectionSnapshotLayer("ProjectionSnapshotQuery", (it) => { providerName: "codex", providerSessionId: asProviderSessionId("provider-session-1"), providerThreadId: asProviderThreadId("provider-thread-1"), - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", activeTurnId: asTurnId("turn-1"), lastError: null, updatedAt: "2026-02-24T00:00:07.000Z", diff --git a/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts b/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts index 0c930a9b5a2075279713772358ef303f9900862e..cdb129aff8e1974445a62f46ddce94ed661ca628 100644 --- a/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts +++ b/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts @@ -220,8 +220,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { provider_name AS "providerName", provider_session_id AS "providerSessionId", provider_thread_id AS "providerThreadId", - approval_policy AS "approvalPolicy", - sandbox_mode AS "sandboxMode", + runtime_mode AS "runtimeMode", active_turn_id AS "activeTurnId", last_error AS "lastError", updated_at AS "updatedAt" @@ -463,8 +462,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { providerName: row.providerName, providerSessionId: row.providerSessionId, providerThreadId: row.providerThreadId, - approvalPolicy: row.approvalPolicy, - sandboxMode: row.sandboxMode, + runtimeMode: row.runtimeMode, activeTurnId: row.activeTurnId, lastError: row.lastError, updatedAt: row.updatedAt, diff --git a/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts b/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts index f94cd183c918aa839991836c4004e9c37195099a..6ef0bea5661586bbc45c11d7815fd4ea39e9dabb 100644 --- a/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts +++ b/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts @@ -260,8 +260,7 @@ describe("ProviderCommandReactor", () => { text: "hello reactor", attachments: [], }, - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", createdAt: now, }), ); @@ -272,15 +271,13 @@ describe("ProviderCommandReactor", () => { expect(harness.startSession.mock.calls[0]?.[1]).toMatchObject({ cwd: "/tmp/provider-project", model: "gpt-5-codex", - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", }); const readModel = await Effect.runPromise(harness.engine.getReadModel()); const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1")); expect(thread?.session?.providerSessionId).toBe("sess-1"); - expect(thread?.session?.approvalPolicy).toBe("on-request"); - expect(thread?.session?.sandboxMode).toBe("workspace-write"); + expect(thread?.session?.runtimeMode).toBe("approval-required"); }); it("starts first turn with requested provider when provider is specified", async () => { @@ -299,8 +296,7 @@ describe("ProviderCommandReactor", () => { attachments: [], }, provider: "claudeCode", - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", createdAt: now, }), ); @@ -311,8 +307,7 @@ describe("ProviderCommandReactor", () => { provider: "claudeCode", cwd: "/tmp/provider-project", model: "gpt-5-codex", - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", }); const readModel = await Effect.runPromise(harness.engine.getReadModel()); @@ -337,8 +332,7 @@ describe("ProviderCommandReactor", () => { attachments: [], }, provider: "cursor", - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", createdAt: now, }), ); @@ -349,8 +343,7 @@ describe("ProviderCommandReactor", () => { provider: "cursor", cwd: "/tmp/provider-project", model: "gpt-5-codex", - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", }); const readModel = await Effect.runPromise(harness.engine.getReadModel()); @@ -374,8 +367,7 @@ describe("ProviderCommandReactor", () => { text: "first", attachments: [], }, - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", createdAt: now, }), ); @@ -394,8 +386,7 @@ describe("ProviderCommandReactor", () => { text: "second", attachments: [], }, - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", createdAt: now, }), ); @@ -422,8 +413,7 @@ describe("ProviderCommandReactor", () => { }, provider: "cursor", model: "composer-1.5", - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", createdAt: now, }), ); @@ -444,8 +434,7 @@ describe("ProviderCommandReactor", () => { }, provider: "cursor", model: "composer-1.5", - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", createdAt: now, }), ); @@ -472,8 +461,7 @@ describe("ProviderCommandReactor", () => { }, provider: "cursor", model: "gpt-5.3-codex", - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", createdAt: now, }), ); @@ -494,8 +482,7 @@ describe("ProviderCommandReactor", () => { }, provider: "cursor", model: "composer-1.5", - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", createdAt: now, }), ); @@ -525,8 +512,7 @@ describe("ProviderCommandReactor", () => { text: "first", attachments: [], }, - approvalPolicy: "never", - sandboxMode: "danger-full-access", + runtimeMode: "full-access", createdAt: now, }), ); @@ -545,8 +531,7 @@ describe("ProviderCommandReactor", () => { text: "second", attachments: [], }, - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", createdAt: now, }), ); @@ -558,8 +543,7 @@ describe("ProviderCommandReactor", () => { expect(harness.stopSession.mock.calls[0]?.[0]).toEqual({ sessionId: asSessionId("sess-1") }); expect(harness.startSession.mock.calls[1]?.[1]).toMatchObject({ resumeCursor: { opaque: "cursor-1" }, - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", }); expect(harness.sendTurn.mock.calls[1]?.[0]).toMatchObject({ sessionId: asSessionId("sess-2"), @@ -568,8 +552,7 @@ describe("ProviderCommandReactor", () => { const readModel = await Effect.runPromise(harness.engine.getReadModel()); const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1")); expect(thread?.session?.providerSessionId).toBe("sess-2"); - expect(thread?.session?.approvalPolicy).toBe("on-request"); - expect(thread?.session?.sandboxMode).toBe("workspace-write"); + expect(thread?.session?.runtimeMode).toBe("approval-required"); }); it("switches provider by restarting the session when turn request provider changes", async () => { @@ -587,8 +570,7 @@ describe("ProviderCommandReactor", () => { text: "first", attachments: [], }, - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", createdAt: now, }), ); @@ -608,8 +590,7 @@ describe("ProviderCommandReactor", () => { attachments: [], }, provider: "claudeCode", - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", createdAt: now, }), ); @@ -621,8 +602,7 @@ describe("ProviderCommandReactor", () => { expect(harness.stopSession.mock.calls[0]?.[0]).toEqual({ sessionId: asSessionId("sess-1") }); expect(harness.startSession.mock.calls[1]?.[1]).toMatchObject({ provider: "claudeCode", - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", }); expect(harness.startSession.mock.calls[1]?.[1]).not.toHaveProperty("resumeCursor"); @@ -630,8 +610,7 @@ describe("ProviderCommandReactor", () => { const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1")); expect(thread?.session?.providerSessionId).toBe("sess-2"); expect(thread?.session?.providerName).toBe("claudeCode"); - expect(thread?.session?.approvalPolicy).toBe("on-request"); - expect(thread?.session?.sandboxMode).toBe("workspace-write"); + expect(thread?.session?.runtimeMode).toBe("approval-required"); }); it("does not stop the active session when restart fails before rebind", async () => { @@ -649,8 +628,7 @@ describe("ProviderCommandReactor", () => { text: "first", attachments: [], }, - approvalPolicy: "never", - sandboxMode: "danger-full-access", + runtimeMode: "full-access", createdAt: now, }), ); @@ -673,8 +651,7 @@ describe("ProviderCommandReactor", () => { text: "second", attachments: [], }, - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", createdAt: now, }), ); @@ -688,8 +665,7 @@ describe("ProviderCommandReactor", () => { const readModel = await Effect.runPromise(harness.engine.getReadModel()); const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1")); expect(thread?.session?.providerSessionId).toBe("sess-1"); - expect(thread?.session?.approvalPolicy).toBe("never"); - expect(thread?.session?.sandboxMode).toBe("danger-full-access"); + expect(thread?.session?.runtimeMode).toBe("full-access"); }); it("reacts to thread.turn.interrupt-requested by calling provider interrupt", async () => { @@ -707,8 +683,7 @@ describe("ProviderCommandReactor", () => { providerName: "codex", providerSessionId: asSessionId("sess-1"), providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", activeTurnId: asTurnId("turn-1"), lastError: null, updatedAt: now, @@ -748,8 +723,7 @@ describe("ProviderCommandReactor", () => { providerName: "codex", providerSessionId: asSessionId("sess-1"), providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", activeTurnId: null, lastError: null, updatedAt: now, @@ -801,8 +775,7 @@ describe("ProviderCommandReactor", () => { providerName: "cursor", providerSessionId: asSessionId("sess-1"), providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", activeTurnId: null, lastError: null, updatedAt: now, @@ -887,8 +860,7 @@ describe("ProviderCommandReactor", () => { providerName: "codex", providerSessionId: asSessionId("sess-1"), providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", activeTurnId: null, lastError: null, updatedAt: now, diff --git a/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts b/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts index 33f22e4a1b4667e0b457773413083e1589c03012..bff647a465cb322653f7e1278b315e9b25754f02 100644 --- a/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts +++ b/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts @@ -3,13 +3,12 @@ import { CommandId, EventId, type OrchestrationEvent, - type ProviderApprovalPolicy, type ProviderKind, - type ProviderSandboxMode, type ProviderSessionId, type OrchestrationSession, type ThreadId, type ProviderSession, + type RuntimeMode, type TurnId, } from "@t3tools/contracts"; import { Cache, Cause, Duration, Effect, Layer, Option, Queue, Schema, Stream } from "effect"; @@ -67,8 +66,7 @@ const serverCommandId = (tag: string): CommandId => const HANDLED_TURN_START_KEY_MAX = 10_000; const HANDLED_TURN_START_KEY_TTL = Duration.minutes(30); -const DEFAULT_APPROVAL_POLICY: ProviderApprovalPolicy = "never"; -const DEFAULT_SANDBOX_MODE: ProviderSandboxMode = "workspace-write"; +const DEFAULT_RUNTIME_MODE: RuntimeMode = "full-access"; const WORKTREE_BRANCH_PREFIX = "t3code"; const TEMP_WORKTREE_BRANCH_PATTERN = new RegExp(`^${WORKTREE_BRANCH_PREFIX}\\/[0-9a-f]{8}$`); @@ -199,8 +197,7 @@ const make = Effect.gen(function* () { options?: { readonly provider?: ProviderKind; readonly model?: string; - readonly approvalPolicy?: ProviderApprovalPolicy; - readonly sandboxMode?: ProviderSandboxMode; + readonly runtimeMode?: RuntimeMode; }, ) { const readModel = yield* orchestrationEngine.getReadModel(); @@ -209,10 +206,8 @@ const make = Effect.gen(function* () { return yield* Effect.die(new Error(`Thread '${threadId}' was not found in read model.`)); } - const desiredApprovalPolicy = - options?.approvalPolicy ?? thread.session?.approvalPolicy ?? DEFAULT_APPROVAL_POLICY; - const desiredSandboxMode = - options?.sandboxMode ?? thread.session?.sandboxMode ?? DEFAULT_SANDBOX_MODE; + const desiredRuntimeMode = + options?.runtimeMode ?? thread.session?.runtimeMode ?? DEFAULT_RUNTIME_MODE; const currentProvider: ProviderKind | undefined = thread.session?.providerName === "codex" || thread.session?.providerName === "claudeCode" || @@ -242,8 +237,7 @@ const make = Effect.gen(function* () { ...(effectiveCwd ? { cwd: effectiveCwd } : {}), ...(desiredModel ? { model: desiredModel } : {}), ...(input?.resumeCursor !== undefined ? { resumeCursor: input.resumeCursor } : {}), - approvalPolicy: desiredApprovalPolicy, - sandboxMode: desiredSandboxMode, + runtimeMode: desiredRuntimeMode, }); const bindSessionToThread = (session: ProviderSession) => @@ -255,8 +249,7 @@ const make = Effect.gen(function* () { providerName: session.provider, providerSessionId: session.sessionId, providerThreadId: session.threadId ?? null, - approvalPolicy: desiredApprovalPolicy, - sandboxMode: desiredSandboxMode, + runtimeMode: desiredRuntimeMode, // Provider turn ids are not orchestration turn ids. activeTurnId: null, lastError: session.lastError ?? null, @@ -267,11 +260,8 @@ const make = Effect.gen(function* () { const existingSessionId = thread.session?.providerSessionId; if (existingSessionId) { - const approvalPolicyChanged = - options?.approvalPolicy !== undefined && - options.approvalPolicy !== thread.session?.approvalPolicy; - const sandboxModeChanged = - options?.sandboxMode !== undefined && options.sandboxMode !== thread.session?.sandboxMode; + const runtimeModeChanged = + options?.runtimeMode !== undefined && options.runtimeMode !== thread.session?.runtimeMode; const providerChanged = options?.provider !== undefined && options.provider !== currentProvider; const activeSession = yield* resolveActiveSession(existingSessionId); const sessionModelSwitch = @@ -284,8 +274,7 @@ const make = Effect.gen(function* () { modelChanged && sessionModelSwitch === "restart-session"; if ( - !approvalPolicyChanged && - !sandboxModeChanged && + !runtimeModeChanged && !providerChanged && !shouldRestartForModelChange ) { @@ -327,8 +316,7 @@ const make = Effect.gen(function* () { readonly provider?: ProviderKind; readonly model?: string; readonly effort?: string; - readonly approvalPolicy: ProviderApprovalPolicy; - readonly sandboxMode: ProviderSandboxMode; + readonly runtimeMode: RuntimeMode; readonly createdAt: string; }) { const thread = yield* resolveThread(input.threadId); @@ -338,8 +326,7 @@ const make = Effect.gen(function* () { const sessionId = yield* ensureSessionForThread(input.threadId, input.createdAt, { ...(input.provider !== undefined ? { provider: input.provider } : {}), ...(input.model !== undefined ? { model: input.model } : {}), - approvalPolicy: input.approvalPolicy, - sandboxMode: input.sandboxMode, + runtimeMode: input.runtimeMode, }); const normalizedInput = toNonEmptyProviderInput(input.messageText); const normalizedAttachments = input.attachments ?? []; @@ -472,8 +459,7 @@ const make = Effect.gen(function* () { ...(event.payload.provider !== undefined ? { provider: event.payload.provider } : {}), ...(event.payload.model !== undefined ? { model: event.payload.model } : {}), ...(event.payload.effort !== undefined ? { effort: event.payload.effort } : {}), - approvalPolicy: event.payload.approvalPolicy, - sandboxMode: event.payload.sandboxMode, + runtimeMode: event.payload.runtimeMode, createdAt: event.payload.createdAt, }); }); @@ -571,8 +557,7 @@ const make = Effect.gen(function* () { providerName: thread.session?.providerName ?? null, providerSessionId: null, providerThreadId: null, - approvalPolicy: thread.session?.approvalPolicy ?? DEFAULT_APPROVAL_POLICY, - sandboxMode: thread.session?.sandboxMode ?? DEFAULT_SANDBOX_MODE, + runtimeMode: thread.session?.runtimeMode ?? DEFAULT_RUNTIME_MODE, activeTurnId: null, lastError: thread.session?.lastError ?? null, updatedAt: now, diff --git a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts index 57a88bd6a32df119a4e31253af3af87f4aad9a6a..eee39997a67552b7bdf0934bfe9763434cc75d97 100644 --- a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts +++ b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts @@ -1,4 +1,4 @@ -import type { ProviderRuntimeEvent } from "@t3tools/contracts"; +import type { OrchestrationReadModel, ProviderRuntimeEvent } from "@t3tools/contracts"; import { ApprovalRequestId, CommandId, @@ -83,19 +83,11 @@ function createProviderServiceHarness() { async function waitForThread( engine: OrchestrationEngineShape, - predicate: (thread: { - session: { status: string; activeTurnId: string | null; lastError: string | null } | null; - messages: ReadonlyArray<{ id: string; text: string; streaming: boolean }>; - activities: ReadonlyArray<{ kind: string }>; - }) => boolean, + predicate: (thread: ProviderRuntimeTestThread) => boolean, timeoutMs = 2000, ) { const deadline = Date.now() + timeoutMs; - const poll = async (): Promise<{ - session: { status: string; activeTurnId: string | null; lastError: string | null } | null; - messages: ReadonlyArray<{ id: string; text: string; streaming: boolean }>; - activities: ReadonlyArray<{ kind: string }>; - }> => { + const poll = async (): Promise => { const readModel = await Effect.runPromise(engine.getReadModel()); const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1")); if (thread && predicate(thread)) { @@ -110,6 +102,12 @@ async function waitForThread( return poll(); } +type ProviderRuntimeTestReadModel = OrchestrationReadModel; +type ProviderRuntimeTestThread = ProviderRuntimeTestReadModel["threads"][number]; +type ProviderRuntimeTestMessage = ProviderRuntimeTestThread["messages"][number]; +type ProviderRuntimeTestActivity = ProviderRuntimeTestThread["activities"][number]; +type ProviderRuntimeTestCheckpoint = ProviderRuntimeTestThread["checkpoints"][number]; + describe("ProviderRuntimeIngestion", () => { let runtime: ManagedRuntime.ManagedRuntime< OrchestrationEngineService | ProviderRuntimeIngestionService, @@ -185,8 +183,7 @@ describe("ProviderRuntimeIngestion", () => { providerName: "codex", providerSessionId: asSessionId("sess-1"), providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", activeTurnId: null, updatedAt: createdAt, lastError: null, @@ -226,8 +223,10 @@ describe("ProviderRuntimeIngestion", () => { sessionId: asSessionId("sess-1"), createdAt: new Date().toISOString(), turnId: asProviderTurnId("turn-1"), - status: "failed", - errorMessage: "turn failed", + payload: { + state: "failed", + errorMessage: "turn failed", + }, }); const thread = await waitForThread( @@ -465,8 +464,7 @@ describe("ProviderRuntimeIngestion", () => { providerName: "claudeCode", providerSessionId: asSessionId("sess-1"), providerThreadId: asProviderThreadId("claude-thread-placeholder"), - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", activeTurnId: null, updatedAt: seededAt, lastError: null, @@ -608,9 +606,14 @@ describe("ProviderRuntimeIngestion", () => { }); const thread = await waitForThread(harness.engine, (entry) => - entry.messages.some((message) => message.id === "assistant:item-1" && !message.streaming), + entry.messages.some( + (message: ProviderRuntimeTestMessage) => + message.id === "assistant:item-1" && !message.streaming, + ), + ); + const message = thread.messages.find( + (entry: ProviderRuntimeTestMessage) => entry.id === "assistant:item-1", ); - const message = thread.messages.find((entry) => entry.id === "assistant:item-1"); expect(message?.text).toBe("hello world"); expect(message?.streaming).toBe(false); }); @@ -635,9 +638,14 @@ describe("ProviderRuntimeIngestion", () => { }); const thread = await waitForThread(harness.engine, (entry) => - entry.messages.some((message) => message.id === "assistant:item-no-delta" && !message.streaming), + entry.messages.some( + (message: ProviderRuntimeTestMessage) => + message.id === "assistant:item-no-delta" && !message.streaming, + ), + ); + const message = thread.messages.find( + (entry: ProviderRuntimeTestMessage) => entry.id === "assistant:item-no-delta", ); - const message = thread.messages.find((entry) => entry.id === "assistant:item-no-delta"); expect(message?.text).toBe("assistant-only final text"); expect(message?.streaming).toBe(false); }); @@ -661,14 +669,17 @@ describe("ProviderRuntimeIngestion", () => { ); harness.emit({ - type: "message.delta", + type: "content.delta", eventId: asEventId("evt-message-delta-buffered"), provider: "codex", sessionId: asSessionId("sess-1"), createdAt: now, turnId: asProviderTurnId("turn-buffered"), itemId: asItemId("item-buffered"), - delta: "buffer me", + payload: { + streamKind: "assistant_text", + delta: "buffer me", + }, }); await Effect.runPromise(Effect.sleep("30 millis")); @@ -676,26 +687,35 @@ describe("ProviderRuntimeIngestion", () => { const midThread = midReadModel.threads.find( (entry) => entry.id === ThreadId.makeUnsafe("thread-1"), ); - expect(midThread?.messages.some((message) => message.id === "assistant:item-buffered")).toBe( - false, - ); + expect( + midThread?.messages.some( + (message: ProviderRuntimeTestMessage) => message.id === "assistant:item-buffered", + ), + ).toBe(false); harness.emit({ - type: "message.completed", + type: "item.completed", eventId: asEventId("evt-message-completed-buffered"), provider: "codex", sessionId: asSessionId("sess-1"), createdAt: now, turnId: asProviderTurnId("turn-buffered"), itemId: asItemId("item-buffered"), + payload: { + itemType: "assistant_message", + status: "completed", + }, }); const thread = await waitForThread(harness.engine, (entry) => entry.messages.some( - (message) => message.id === "assistant:item-buffered" && !message.streaming, + (message: ProviderRuntimeTestMessage) => + message.id === "assistant:item-buffered" && !message.streaming, ), ); - const message = thread.messages.find((entry) => entry.id === "assistant:item-buffered"); + const message = thread.messages.find( + (entry: ProviderRuntimeTestMessage) => entry.id === "assistant:item-buffered", + ); expect(message?.text).toBe("buffer me"); expect(message?.streaming).toBe(false); }); @@ -716,8 +736,7 @@ describe("ProviderRuntimeIngestion", () => { attachments: [], }, assistantDeliveryMode: "streaming", - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", createdAt: now, }), ); @@ -739,46 +758,54 @@ describe("ProviderRuntimeIngestion", () => { ); harness.emit({ - type: "message.delta", + type: "content.delta", eventId: asEventId("evt-message-delta-streaming-mode"), provider: "codex", sessionId: asSessionId("sess-1"), createdAt: now, turnId: asProviderTurnId("turn-streaming-mode"), itemId: asItemId("item-streaming-mode"), - delta: "hello live", + payload: { + streamKind: "assistant_text", + delta: "hello live", + }, }); const liveThread = await waitForThread(harness.engine, (entry) => entry.messages.some( - (message) => + (message: ProviderRuntimeTestMessage) => message.id === "assistant:item-streaming-mode" && message.streaming && message.text === "hello live", ), ); const liveMessage = liveThread.messages.find( - (entry) => entry.id === "assistant:item-streaming-mode", + (entry: ProviderRuntimeTestMessage) => entry.id === "assistant:item-streaming-mode", ); expect(liveMessage?.streaming).toBe(true); harness.emit({ - type: "message.completed", + type: "item.completed", eventId: asEventId("evt-message-completed-streaming-mode"), provider: "codex", sessionId: asSessionId("sess-1"), createdAt: now, turnId: asProviderTurnId("turn-streaming-mode"), itemId: asItemId("item-streaming-mode"), + payload: { + itemType: "assistant_message", + status: "completed", + }, }); const finalThread = await waitForThread(harness.engine, (entry) => entry.messages.some( - (message) => message.id === "assistant:item-streaming-mode" && !message.streaming, + (message: ProviderRuntimeTestMessage) => + message.id === "assistant:item-streaming-mode" && !message.streaming, ), ); const finalMessage = finalThread.messages.find( - (entry) => entry.id === "assistant:item-streaming-mode", + (entry: ProviderRuntimeTestMessage) => entry.id === "assistant:item-streaming-mode", ); expect(finalMessage?.text).toBe("hello live"); expect(finalMessage?.streaming).toBe(false); @@ -805,37 +832,47 @@ describe("ProviderRuntimeIngestion", () => { ); harness.emit({ - type: "message.delta", + type: "content.delta", eventId: asEventId("evt-message-delta-buffer-spill"), provider: "codex", sessionId: asSessionId("sess-1"), createdAt: now, turnId: asProviderTurnId("turn-buffer-spill"), itemId: asItemId("item-buffer-spill"), - delta: oversizedText, + payload: { + streamKind: "assistant_text", + delta: oversizedText, + }, }); harness.emit({ - type: "message.completed", + type: "item.completed", eventId: asEventId("evt-message-completed-buffer-spill"), provider: "codex", sessionId: asSessionId("sess-1"), createdAt: now, turnId: asProviderTurnId("turn-buffer-spill"), itemId: asItemId("item-buffer-spill"), + payload: { + itemType: "assistant_message", + status: "completed", + }, }); const thread = await waitForThread(harness.engine, (entry) => entry.messages.some( - (message) => message.id === "assistant:item-buffer-spill" && !message.streaming, + (message: ProviderRuntimeTestMessage) => + message.id === "assistant:item-buffer-spill" && !message.streaming, ), ); - const message = thread.messages.find((entry) => entry.id === "assistant:item-buffer-spill"); + const message = thread.messages.find( + (entry: ProviderRuntimeTestMessage) => entry.id === "assistant:item-buffer-spill", + ); expect(message?.text.length).toBe(oversizedText.length); expect(message?.text).toBe(oversizedText); expect(message?.streaming).toBe(false); }); - it("does not duplicate assistant completion when message.completed is followed by turn.completed", async () => { + it("does not duplicate assistant completion when item.completed is followed by turn.completed", async () => { const harness = await createHarness(); const now = new Date().toISOString(); @@ -856,23 +893,30 @@ describe("ProviderRuntimeIngestion", () => { ); harness.emit({ - type: "message.delta", + type: "content.delta", eventId: asEventId("evt-message-delta-for-complete-dedup"), provider: "codex", sessionId: asSessionId("sess-1"), createdAt: now, turnId: asProviderTurnId("turn-complete-dedup"), itemId: asItemId("item-complete-dedup"), - delta: "done", + payload: { + streamKind: "assistant_text", + delta: "done", + }, }); harness.emit({ - type: "message.completed", + type: "item.completed", eventId: asEventId("evt-message-completed-for-complete-dedup"), provider: "codex", sessionId: asSessionId("sess-1"), createdAt: now, turnId: asProviderTurnId("turn-complete-dedup"), itemId: asItemId("item-complete-dedup"), + payload: { + itemType: "assistant_message", + status: "completed", + }, }); harness.emit({ type: "turn.completed", @@ -881,7 +925,9 @@ describe("ProviderRuntimeIngestion", () => { sessionId: asSessionId("sess-1"), createdAt: now, turnId: asProviderTurnId("turn-complete-dedup"), - status: "completed", + payload: { + state: "completed", + }, }); await waitForThread( @@ -890,7 +936,8 @@ describe("ProviderRuntimeIngestion", () => { thread.session?.status === "ready" && thread.session?.activeTurnId === null && thread.messages.some( - (message) => message.id === "assistant:item-complete-dedup" && !message.streaming, + (message: ProviderRuntimeTestMessage) => + message.id === "assistant:item-complete-dedup" && !message.streaming, ), ); @@ -944,15 +991,21 @@ describe("ProviderRuntimeIngestion", () => { await waitForThread( harness.engine, (entry) => - entry.activities.some((activity) => activity.kind === "approval.requested") && - entry.activities.some((activity) => activity.kind === "approval.resolved"), + entry.activities.some( + (activity: ProviderRuntimeTestActivity) => activity.kind === "approval.requested", + ) && + entry.activities.some( + (activity: ProviderRuntimeTestActivity) => activity.kind === "approval.resolved", + ), ); const readModel = await Effect.runPromise(harness.engine.getReadModel()); const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1")); expect(thread).toBeDefined(); - const requested = thread?.activities.find((activity) => activity.id === "evt-request-opened"); + const requested = thread?.activities.find( + (activity: ProviderRuntimeTestActivity) => activity.id === "evt-request-opened", + ); const requestedPayload = requested?.payload && typeof requested.payload === "object" ? (requested.payload as Record) @@ -960,7 +1013,9 @@ describe("ProviderRuntimeIngestion", () => { expect(requestedPayload?.requestKind).toBe("command"); expect(requestedPayload?.requestType).toBe("command_execution_approval"); - const resolved = thread?.activities.find((activity) => activity.id === "evt-request-resolved"); + const resolved = thread?.activities.find( + (activity: ProviderRuntimeTestActivity) => activity.id === "evt-request-resolved", + ); const resolvedPayload = resolved?.payload && typeof resolved.payload === "object" ? (resolved.payload as Record) @@ -980,7 +1035,9 @@ describe("ProviderRuntimeIngestion", () => { sessionId: asSessionId("sess-1"), createdAt: now, turnId: asProviderTurnId("turn-3"), - message: "runtime exploded", + payload: { + message: "runtime exploded", + }, }); const thread = await waitForThread( @@ -994,7 +1051,7 @@ describe("ProviderRuntimeIngestion", () => { expect(thread.session?.lastError).toBe("runtime exploded"); }); - it("maps session/thread lifecycle and tool.started into session/activity projections", async () => { + it("maps session/thread lifecycle and item.started into session/activity projections", async () => { const harness = await createHarness(); const now = new Date().toISOString(); @@ -1016,15 +1073,18 @@ describe("ProviderRuntimeIngestion", () => { threadId: ProviderThreadId.makeUnsafe("provider-thread-2"), }); harness.emit({ - type: "tool.started", + type: "item.started", eventId: asEventId("evt-tool-started"), provider: "codex", sessionId: asSessionId("sess-1"), createdAt: now, turnId: asProviderTurnId("turn-9"), - toolKind: "other", - title: "Read file", - detail: "/tmp/file.ts", + payload: { + itemType: "command_execution", + status: "in_progress", + title: "Read file", + detail: "/tmp/file.ts", + }, }); const thread = await waitForThread( @@ -1032,11 +1092,152 @@ describe("ProviderRuntimeIngestion", () => { (entry) => entry.session?.status === "ready" && entry.session?.activeTurnId === null && - entry.activities.some((activity) => activity.kind === "tool.started"), + entry.activities.some( + (activity: ProviderRuntimeTestActivity) => activity.kind === "tool.started", + ), ); expect(thread.session?.status).toBe("ready"); - expect(thread.activities.some((activity) => activity.kind === "tool.started")).toBe(true); + expect( + thread.activities.some( + (activity: ProviderRuntimeTestActivity) => activity.kind === "tool.started", + ), + ).toBe(true); + }); + + it("consumes P1 runtime events into thread metadata, diff checkpoints, and activities", async () => { + const harness = await createHarness(); + const now = new Date().toISOString(); + + harness.emit({ + type: "thread.metadata.updated", + eventId: asEventId("evt-thread-metadata-updated"), + provider: "codex", + sessionId: asSessionId("sess-1"), + createdAt: now, + threadId: asProviderThreadId("provider-thread-1"), + payload: { + name: "Renamed by provider", + metadata: { source: "provider" }, + }, + }); + + harness.emit({ + type: "turn.plan.updated", + eventId: asEventId("evt-turn-plan-updated"), + provider: "codex", + sessionId: asSessionId("sess-1"), + createdAt: now, + turnId: asProviderTurnId("turn-p1"), + payload: { + explanation: "Working through the plan", + plan: [ + { step: "Inspect files", status: "completed" }, + { step: "Apply patch", status: "in_progress" }, + ], + }, + }); + + harness.emit({ + type: "item.updated", + eventId: asEventId("evt-item-updated"), + provider: "codex", + sessionId: asSessionId("sess-1"), + createdAt: now, + turnId: asProviderTurnId("turn-p1"), + itemId: asItemId("item-p1-tool"), + payload: { + itemType: "command_execution", + status: "in_progress", + title: "Run tests", + detail: "bun test", + data: { pid: 123 }, + }, + }); + + harness.emit({ + type: "runtime.warning", + eventId: asEventId("evt-runtime-warning"), + provider: "codex", + sessionId: asSessionId("sess-1"), + createdAt: now, + turnId: asProviderTurnId("turn-p1"), + payload: { + message: "Provider got slow", + detail: { latencyMs: 1500 }, + }, + }); + + harness.emit({ + type: "turn.diff.updated", + eventId: asEventId("evt-turn-diff-updated"), + provider: "codex", + sessionId: asSessionId("sess-1"), + createdAt: now, + turnId: asProviderTurnId("turn-p1"), + itemId: asItemId("item-p1-assistant"), + payload: { + unifiedDiff: "diff --git a/file.txt b/file.txt\n+hello\n", + }, + }); + + const thread = await waitForThread( + harness.engine, + (entry) => + entry.title === "Renamed by provider" && + entry.activities.some( + (activity: ProviderRuntimeTestActivity) => activity.kind === "turn.plan.updated", + ) && + entry.activities.some( + (activity: ProviderRuntimeTestActivity) => activity.kind === "tool.updated", + ) && + entry.activities.some( + (activity: ProviderRuntimeTestActivity) => activity.kind === "runtime.warning", + ) && + entry.checkpoints.some( + (checkpoint: ProviderRuntimeTestCheckpoint) => checkpoint.turnId === "turn-p1", + ), + ); + + expect(thread.title).toBe("Renamed by provider"); + + const planActivity = thread.activities.find( + (activity: ProviderRuntimeTestActivity) => activity.id === "evt-turn-plan-updated", + ); + const planPayload = + planActivity?.payload && typeof planActivity.payload === "object" + ? (planActivity.payload as Record) + : undefined; + expect(planActivity?.kind).toBe("turn.plan.updated"); + expect(Array.isArray(planPayload?.plan)).toBe(true); + + const toolUpdate = thread.activities.find( + (activity: ProviderRuntimeTestActivity) => activity.id === "evt-item-updated", + ); + const toolUpdatePayload = + toolUpdate?.payload && typeof toolUpdate.payload === "object" + ? (toolUpdate.payload as Record) + : undefined; + expect(toolUpdate?.kind).toBe("tool.updated"); + expect(toolUpdatePayload?.itemType).toBe("command_execution"); + expect(toolUpdatePayload?.status).toBe("in_progress"); + + const warning = thread.activities.find( + (activity: ProviderRuntimeTestActivity) => activity.id === "evt-runtime-warning", + ); + const warningPayload = + warning?.payload && typeof warning.payload === "object" + ? (warning.payload as Record) + : undefined; + expect(warning?.kind).toBe("runtime.warning"); + expect(warningPayload?.message).toBe("Provider got slow"); + + const checkpoint = thread.checkpoints.find( + (entry: ProviderRuntimeTestCheckpoint) => entry.turnId === "turn-p1", + ); + expect(checkpoint?.status).toBe("missing"); + expect(checkpoint?.assistantMessageId).toBe("assistant:item-p1-assistant"); + expect(checkpoint?.checkpointRef).toBe("provider-diff:evt-turn-diff-updated"); }); it("continues processing runtime events after a single event handler failure", async () => { @@ -1044,14 +1245,17 @@ describe("ProviderRuntimeIngestion", () => { const now = new Date().toISOString(); harness.emit({ - type: "message.delta", + type: "content.delta", eventId: asEventId("evt-invalid-delta"), provider: "codex", sessionId: asSessionId("sess-1"), createdAt: now, turnId: asProviderTurnId("turn-invalid"), itemId: asItemId("item-invalid"), - delta: undefined, + payload: { + streamKind: "assistant_text", + delta: undefined, + }, } as unknown as ProviderRuntimeEvent); harness.emit({ @@ -1061,7 +1265,9 @@ describe("ProviderRuntimeIngestion", () => { sessionId: asSessionId("sess-1"), createdAt: new Date().toISOString(), turnId: asProviderTurnId("turn-after-failure"), - message: "runtime still processed", + payload: { + message: "runtime still processed", + }, }); const thread = await waitForThread( diff --git a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts index 46e68d439813310e06c29c97b7f1e8db534e2c90..d2719189bf4f846b5f1de3b073e1dd5b0387208e 100644 --- a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts +++ b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts @@ -4,8 +4,7 @@ import { CommandId, MessageId, type OrchestrationEvent, - type ProviderApprovalPolicy, - type ProviderSandboxMode, + CheckpointRef, ProviderThreadId, type ThreadId, TurnId, @@ -28,8 +27,6 @@ const providerCommandId = (event: ProviderRuntimeEvent, tag: string): CommandId CommandId.makeUnsafe(`provider:${event.eventId}:${tag}:${crypto.randomUUID()}`); const DEFAULT_ASSISTANT_DELIVERY_MODE: AssistantDeliveryMode = "buffered"; -const DEFAULT_APPROVAL_POLICY: ProviderApprovalPolicy = "on-request"; -const DEFAULT_SANDBOX_MODE: ProviderSandboxMode = "workspace-write"; const TURN_MESSAGE_IDS_BY_TURN_CACHE_CAPACITY = 10_000; const TURN_MESSAGE_IDS_BY_TURN_TTL = Duration.minutes(120); const BUFFERED_MESSAGE_TEXT_BY_MESSAGE_ID_CACHE_CAPACITY = 20_000; @@ -90,17 +87,6 @@ function asString(value: unknown): string | undefined { return typeof value === "string" ? value : undefined; } -function legacyRuntimeType(event: ProviderRuntimeEvent): string | undefined { - return asString((event as Record).type); -} - -function legacyRuntimeStringField( - event: ProviderRuntimeEvent, - key: string, -): string | undefined { - return asString((event as Record)[key]); -} - function runtimePayloadRecord(event: ProviderRuntimeEvent): Record | undefined { const payload = (event as { payload?: unknown }).payload; if (!payload || typeof payload !== "object") { @@ -127,24 +113,21 @@ function runtimeTurnState( event: ProviderRuntimeEvent, ): "completed" | "failed" | "interrupted" | "cancelled" { const payloadState = asString(runtimePayloadRecord(event)?.state); - const legacyStatus = legacyRuntimeStringField(event, "status"); - return normalizeRuntimeTurnState(payloadState ?? legacyStatus); + return normalizeRuntimeTurnState(payloadState); } function runtimeTurnErrorMessage(event: ProviderRuntimeEvent): string | undefined { const payloadErrorMessage = asString(runtimePayloadRecord(event)?.errorMessage); - const legacyErrorMessage = legacyRuntimeStringField(event, "errorMessage"); - return payloadErrorMessage ?? legacyErrorMessage; + return payloadErrorMessage; } function runtimeErrorMessageFromEvent(event: ProviderRuntimeEvent): string | undefined { const payloadMessage = asString(runtimePayloadRecord(event)?.message); - const legacyMessage = legacyRuntimeStringField(event, "message"); - return payloadMessage ?? legacyMessage; + return payloadMessage; } function orchestrationSessionStatusFromRuntimeState( - state: ProviderRuntimeEvent extends { payload: { state: infer T } } ? T : never, + state: "starting" | "running" | "waiting" | "ready" | "interrupted" | "stopped" | "error", ): "starting" | "running" | "ready" | "interrupted" | "stopped" | "error" { switch (state) { case "starting": @@ -268,6 +251,65 @@ function runtimeEventToActivities( ]; } + case "runtime.warning": { + return [ + { + id: event.eventId, + createdAt: event.createdAt, + tone: "info", + kind: "runtime.warning", + summary: "Runtime warning", + payload: { + message: truncateDetail(event.payload.message), + ...(event.payload.detail !== undefined ? { detail: event.payload.detail } : {}), + }, + turnId: toTurnId(event.turnId) ?? null, + ...maybeSequence, + }, + ]; + } + + case "turn.plan.updated": { + return [ + { + id: event.eventId, + createdAt: event.createdAt, + tone: "info", + kind: "turn.plan.updated", + summary: "Plan updated", + payload: { + plan: event.payload.plan, + ...(event.payload.explanation !== undefined ? { explanation: event.payload.explanation } : {}), + }, + turnId: toTurnId(event.turnId) ?? null, + ...maybeSequence, + }, + ]; + } + + case "item.updated": { + if (!isToolLifecycleItemType(event.payload.itemType)) { + return []; + } + return [ + { + id: event.eventId, + createdAt: event.createdAt, + tone: "tool", + kind: "tool.updated", + summary: event.payload.title ?? "Tool updated", + payload: { + itemType: event.payload.itemType, + ...(event.payload.status ? { status: event.payload.status } : {}), + ...(event.payload.detail ? { detail: truncateDetail(event.payload.detail) } : {}), + ...(event.payload.data !== undefined ? { data: event.payload.data } : {}), + }, + turnId: toTurnId(event.turnId) ?? null, + ...maybeSequence, + }, + ]; + } + case "item.completed": { if (!isToolLifecycleItemType(event.payload.itemType)) { return []; @@ -314,80 +356,7 @@ function runtimeEventToActivities( break; } - // Backward-compatibility for legacy event shapes still emitted by older tests/adapters. - const legacyType = legacyRuntimeType(event); - switch (legacyType) { - case "approval.requested": { - const detail = legacyRuntimeStringField(event, "detail"); - const requestKind = legacyRuntimeStringField(event, "requestKind"); - return [ - { - id: event.eventId, - createdAt: event.createdAt, - tone: "approval", - kind: "approval.requested", - summary: - requestKind === "command" - ? "Command approval requested" - : "File-change approval requested", - payload: { - requestId: toApprovalRequestId(event.requestId), - ...(requestKind ? { requestKind } : {}), - ...(detail ? { detail: truncateDetail(detail) } : {}), - }, - turnId: toTurnId(event.turnId) ?? null, - ...maybeSequence, - }, - ]; - } - - case "approval.resolved": { - const decision = legacyRuntimeStringField(event, "decision"); - const requestKind = legacyRuntimeStringField(event, "requestKind"); - return [ - { - id: event.eventId, - createdAt: event.createdAt, - tone: "approval", - kind: "approval.resolved", - summary: "Approval resolved", - payload: { - requestId: toApprovalRequestId(event.requestId), - ...(requestKind ? { requestKind } : {}), - ...(decision ? { decision } : {}), - }, - turnId: toTurnId(event.turnId) ?? null, - ...maybeSequence, - }, - ]; - } - - case "tool.completed": - case "tool.started": { - const title = legacyRuntimeStringField(event, "title") ?? "Tool"; - const detail = legacyRuntimeStringField(event, "detail"); - const toolKind = legacyRuntimeStringField(event, "toolKind"); - const activityKind = legacyType; - return [ - { - id: event.eventId, - createdAt: event.createdAt, - tone: "tool", - kind: activityKind, - summary: `${title} ${legacyType === "tool.started" ? "started" : "complete"}`, - payload: { - ...(toolKind ? { toolKind } : {}), - ...(detail ? { detail: truncateDetail(detail) } : {}), - }, - turnId: toTurnId(event.turnId) ?? null, - ...maybeSequence, - }, - ]; - } - - default: - return []; - } + return []; } const make = Effect.gen(function* () { @@ -570,7 +539,6 @@ const make = Effect.gen(function* () { ); if (!thread) return; - const legacyType = legacyRuntimeType(event); const now = event.createdAt; const sessionProviderThreadId = thread.session?.providerThreadId ?? null; const scopedSessionProviderThreadId = isSyntheticClaudeThreadId( @@ -692,8 +660,7 @@ const make = Effect.gen(function* () { providerName: event.provider, providerSessionId, providerThreadId, - approvalPolicy: thread.session?.approvalPolicy ?? DEFAULT_APPROVAL_POLICY, - sandboxMode: thread.session?.sandboxMode ?? DEFAULT_SANDBOX_MODE, + runtimeMode: thread.session?.runtimeMode ?? "full-access", activeTurnId: nextActiveTurnId, lastError, updatedAt: now, @@ -706,9 +673,7 @@ const make = Effect.gen(function* () { const assistantDelta = event.type === "content.delta" && event.payload.streamKind === "assistant_text" ? event.payload.delta - : legacyType === "message.delta" - ? legacyRuntimeStringField(event, "delta") - : undefined; + : undefined; if (assistantDelta && assistantDelta.length > 0) { const assistantMessageId = MessageId.makeUnsafe( @@ -752,11 +717,6 @@ const make = Effect.gen(function* () { messageId: MessageId.makeUnsafe(`assistant:${event.itemId ?? event.turnId ?? event.sessionId}`), fallbackText: event.payload.detail, } - : legacyType === "message.completed" - ? { - messageId: MessageId.makeUnsafe(`assistant:${event.itemId ?? event.turnId ?? event.sessionId}`), - fallbackText: undefined, - } : undefined; if (assistantCompletion) { @@ -836,8 +796,7 @@ const make = Effect.gen(function* () { providerName: event.provider, providerSessionId, providerThreadId, - approvalPolicy: thread.session?.approvalPolicy ?? DEFAULT_APPROVAL_POLICY, - sandboxMode: thread.session?.sandboxMode ?? DEFAULT_SANDBOX_MODE, + runtimeMode: thread.session?.runtimeMode ?? "full-access", activeTurnId: eventTurnId ?? null, lastError: runtimeErrorMessage, updatedAt: now, @@ -847,6 +806,37 @@ const make = Effect.gen(function* () { } } + if (event.type === "thread.metadata.updated" && event.payload.name) { + yield* orchestrationEngine.dispatch({ + type: "thread.meta.update", + commandId: providerCommandId(event, "thread-meta-update"), + threadId: thread.id, + title: event.payload.name, + }); + } + + if (event.type === "turn.diff.updated") { + const turnId = toTurnId(event.turnId); + if (turnId) { + const assistantMessageId = MessageId.makeUnsafe( + `assistant:${event.itemId ?? event.turnId ?? event.sessionId}`, + ); + yield* orchestrationEngine.dispatch({ + type: "thread.turn.diff.complete", + commandId: providerCommandId(event, "thread-turn-diff-complete"), + threadId: thread.id, + turnId, + completedAt: now, + checkpointRef: CheckpointRef.makeUnsafe(`provider-diff:${event.eventId}`), + status: "missing", + files: [], + assistantMessageId, + checkpointTurnCount: thread.checkpoints.length + 1, + createdAt: now, + }); + } + } + const activities = runtimeEventToActivities(event); yield* Effect.forEach(activities, (activity) => orchestrationEngine.dispatch({ diff --git a/apps/server/src/orchestration/commandInvariants.test.ts b/apps/server/src/orchestration/commandInvariants.test.ts index af64eba008ff4c0ca2e10ef483c2273f3d44d084..1437bff296a0dda4058499e296769e500b5c2f2f 100644 --- a/apps/server/src/orchestration/commandInvariants.test.ts +++ b/apps/server/src/orchestration/commandInvariants.test.ts @@ -90,8 +90,7 @@ const messageSendCommand: OrchestrationCommand = { text: "hello", attachments: [], }, - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", createdAt: now, }; diff --git a/apps/server/src/orchestration/decider.projectScripts.test.ts b/apps/server/src/orchestration/decider.projectScripts.test.ts index 5842497dbfdd0f8e836481882ce17509f04db0ec..f26c104aa1e9abccd7f924d8378f56a24c5c406b 100644 --- a/apps/server/src/orchestration/decider.projectScripts.test.ts +++ b/apps/server/src/orchestration/decider.projectScripts.test.ts @@ -153,8 +153,7 @@ describe("decider project scripts", () => { provider: "claudeCode", model: "gpt-5", effort: "high", - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", createdAt: now, }, readModel, @@ -178,8 +177,7 @@ describe("decider project scripts", () => { provider: "claudeCode", model: "gpt-5", effort: "high", - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", }); }); }); diff --git a/apps/server/src/orchestration/decider.ts b/apps/server/src/orchestration/decider.ts index 2c5218d3aca6b229c89bf4c5f20960fbb9444450..2c6bdfee46887f0f14c52db6753138963bcab752 100644 --- a/apps/server/src/orchestration/decider.ts +++ b/apps/server/src/orchestration/decider.ts @@ -255,8 +255,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" ...(command.model !== undefined ? { model: command.model } : {}), ...(command.effort !== undefined ? { effort: command.effort } : {}), assistantDeliveryMode: command.assistantDeliveryMode ?? DEFAULT_ASSISTANT_DELIVERY_MODE, - approvalPolicy: command.approvalPolicy, - sandboxMode: command.sandboxMode, + runtimeMode: command.runtimeMode, createdAt: command.createdAt, }, }; diff --git a/apps/server/src/orchestration/projector.test.ts b/apps/server/src/orchestration/projector.test.ts index 094290d90f3cf30f77bb256f3557cf3dad15118d..d59fdf74ff2a4d90bf7ce37abb8b0f7df930ab64 100644 --- a/apps/server/src/orchestration/projector.test.ts +++ b/apps/server/src/orchestration/projector.test.ts @@ -135,8 +135,7 @@ describe("orchestration projector", () => { payload: { threadId: "thread-1", messageId: "message-1", - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", createdAt: "2026-01-01T00:00:00.000Z", }, }), @@ -195,8 +194,7 @@ describe("orchestration projector", () => { providerName: "codex", providerSessionId: "session-1", providerThreadId: "provider-thread-1", - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", activeTurnId: "turn-1", lastError: null, updatedAt: startedAt, diff --git a/apps/server/src/persistence/Layers/ProjectionThreadSessions.ts b/apps/server/src/persistence/Layers/ProjectionThreadSessions.ts index 0793ef388dba91098f37abec3ab2b25967bf2ce4..77bb6ad622e2759b887619465f58a0b396db5871 100644 --- a/apps/server/src/persistence/Layers/ProjectionThreadSessions.ts +++ b/apps/server/src/persistence/Layers/ProjectionThreadSessions.ts @@ -25,8 +25,7 @@ const makeProjectionThreadSessionRepository = Effect.gen(function* () { provider_name, provider_session_id, provider_thread_id, - approval_policy, - sandbox_mode, + runtime_mode, active_turn_id, last_error, updated_at @@ -37,8 +36,7 @@ const makeProjectionThreadSessionRepository = Effect.gen(function* () { ${row.providerName}, ${row.providerSessionId}, ${row.providerThreadId}, - ${row.approvalPolicy}, - ${row.sandboxMode}, + ${row.runtimeMode}, ${row.activeTurnId}, ${row.lastError}, ${row.updatedAt} @@ -49,8 +47,7 @@ const makeProjectionThreadSessionRepository = Effect.gen(function* () { provider_name = excluded.provider_name, provider_session_id = excluded.provider_session_id, provider_thread_id = excluded.provider_thread_id, - approval_policy = excluded.approval_policy, - sandbox_mode = excluded.sandbox_mode, + runtime_mode = excluded.runtime_mode, active_turn_id = excluded.active_turn_id, last_error = excluded.last_error, updated_at = excluded.updated_at @@ -68,8 +65,7 @@ const makeProjectionThreadSessionRepository = Effect.gen(function* () { provider_name AS "providerName", provider_session_id AS "providerSessionId", provider_thread_id AS "providerThreadId", - approval_policy AS "approvalPolicy", - sandbox_mode AS "sandboxMode", + runtime_mode AS "runtimeMode", active_turn_id AS "activeTurnId", last_error AS "lastError", updated_at AS "updatedAt" diff --git a/apps/server/src/persistence/Migrations/006_ProjectionThreadSessionRuntimeModeColumns.ts b/apps/server/src/persistence/Migrations/006_ProjectionThreadSessionRuntimeModeColumns.ts index bcdef280163336a70b725fc34270f6cc6cb54690..a988a509766f893a4f7864cd82f56974eac32359 100644 --- a/apps/server/src/persistence/Migrations/006_ProjectionThreadSessionRuntimeModeColumns.ts +++ b/apps/server/src/persistence/Migrations/006_ProjectionThreadSessionRuntimeModeColumns.ts @@ -1,30 +1,19 @@ import * as SqlClient from "effect/unstable/sql/SqlClient"; import * as Effect from "effect/Effect"; -const DEFAULT_APPROVAL_POLICY = "never"; -const DEFAULT_SANDBOX_MODE = "workspace-write"; +const DEFAULT_RUNTIME_MODE = "full-access"; export default Effect.gen(function* () { const sql = yield* SqlClient.SqlClient; yield* sql` ALTER TABLE projection_thread_sessions - ADD COLUMN approval_policy TEXT NOT NULL DEFAULT 'never' + ADD COLUMN runtime_mode TEXT NOT NULL DEFAULT 'full-access' `; - yield* sql` - ALTER TABLE projection_thread_sessions - ADD COLUMN sandbox_mode TEXT NOT NULL DEFAULT 'workspace-write' - `; - - yield* sql` - UPDATE projection_thread_sessions - SET approval_policy = ${DEFAULT_APPROVAL_POLICY} - WHERE approval_policy IS NULL - `; yield* sql` UPDATE projection_thread_sessions - SET sandbox_mode = ${DEFAULT_SANDBOX_MODE} - WHERE sandbox_mode IS NULL + SET runtime_mode = ${DEFAULT_RUNTIME_MODE} + WHERE runtime_mode IS NULL `; }); diff --git a/apps/server/src/persistence/Services/ProjectionThreadSessions.ts b/apps/server/src/persistence/Services/ProjectionThreadSessions.ts index 2789e2481e3b772d5b86eac5db1505495fd481b8..485ad42e54af054868d61acee1c10316788e7367 100644 --- a/apps/server/src/persistence/Services/ProjectionThreadSessions.ts +++ b/apps/server/src/persistence/Services/ProjectionThreadSessions.ts @@ -7,10 +7,9 @@ * @module ProjectionThreadSessionRepository */ import { + RuntimeMode, IsoDateTime, OrchestrationSessionStatus, - ProviderApprovalPolicy, - ProviderSandboxMode, ProviderSessionId, ProviderThreadId, ThreadId, @@ -27,8 +26,7 @@ export const ProjectionThreadSession = Schema.Struct({ providerName: Schema.NullOr(Schema.String), providerSessionId: Schema.NullOr(ProviderSessionId), providerThreadId: Schema.NullOr(ProviderThreadId), - approvalPolicy: ProviderApprovalPolicy, - sandboxMode: ProviderSandboxMode, + runtimeMode: RuntimeMode, activeTurnId: Schema.NullOr(TurnId), lastError: Schema.NullOr(Schema.String), updatedAt: IsoDateTime, diff --git a/apps/server/src/provider/Layers/ClaudeCodeAdapter.test.ts b/apps/server/src/provider/Layers/ClaudeCodeAdapter.test.ts index de80b05313491bd1f8de5404cf66fa2608c4376d..27c24cbe27175aa64e9310714a9dd44f56fc18c7 100644 --- a/apps/server/src/provider/Layers/ClaudeCodeAdapter.test.ts +++ b/apps/server/src/provider/Layers/ClaudeCodeAdapter.test.ts @@ -192,8 +192,7 @@ describe("ClaudeCodeAdapterLive", () => { const adapter = yield* ClaudeCodeAdapter; yield* adapter.startSession({ provider: "claudeCode", - approvalPolicy: "never", - sandboxMode: "danger-full-access", + runtimeMode: "full-access", }); const createInput = harness.getLastCreateQueryInput(); @@ -211,7 +210,7 @@ describe("ClaudeCodeAdapterLive", () => { const adapter = yield* ClaudeCodeAdapter; yield* adapter.startSession({ provider: "claudeCode", - approvalPolicy: "never", + runtimeMode: "full-access", providerOptions: { claudeCode: { permissionMode: "plan", @@ -598,7 +597,7 @@ describe("ClaudeCodeAdapterLive", () => { const session = yield* adapter.startSession({ provider: "claudeCode", - approvalPolicy: "on-request", + runtimeMode: "approval-required", }); yield* Stream.take(adapter.streamEvents, 3).pipe(Stream.runDrain); diff --git a/apps/server/src/provider/Layers/ClaudeCodeAdapter.ts b/apps/server/src/provider/Layers/ClaudeCodeAdapter.ts index c32ba62e54362e458114cb5f82b2757503aa5269..5772018a6aa5d093dccc3ac209de3d1062ba9b92 100644 --- a/apps/server/src/provider/Layers/ClaudeCodeAdapter.ts +++ b/apps/server/src/provider/Layers/ClaudeCodeAdapter.ts @@ -1461,8 +1461,8 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { } satisfies PermissionResult; } - const approvalPolicy = input.approvalPolicy ?? "never"; - if (approvalPolicy === "never") { + const runtimeMode = input.runtimeMode ?? "full-access"; + if (runtimeMode === "full-access") { return { behavior: "allow", updatedInput: toolInput, @@ -1590,7 +1590,7 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { const providerOptions = input.providerOptions?.claudeCode; const permissionMode = toPermissionMode(providerOptions?.permissionMode) ?? - (input.approvalPolicy === "never" ? "bypassPermissions" : undefined); + (input.runtimeMode === "full-access" ? "bypassPermissions" : undefined); const queryOptions: ClaudeQueryOptions = { ...(input.cwd ? { cwd: input.cwd } : {}), diff --git a/apps/server/src/provider/Layers/CodexAdapter.test.ts b/apps/server/src/provider/Layers/CodexAdapter.test.ts index 6dea5a097893f9c2c34f4c26b145475d60d85681..7ae319265eed3366ebc2ad2fccac5c9e95c90e3a 100644 --- a/apps/server/src/provider/Layers/CodexAdapter.test.ts +++ b/apps/server/src/provider/Layers/CodexAdapter.test.ts @@ -320,6 +320,48 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { assert.equal(firstEvent.value.payload.requestType, "command_execution_approval"); }), ); + + it.effect("maps windowsSandbox/setupCompleted to session state and warning on failure", () => + Effect.gen(function* () { + const adapter = yield* CodexAdapter; + const eventsFiber = yield* Stream.runCollect(Stream.take(adapter.streamEvents, 2)).pipe( + Effect.forkChild, + ); + + const event: ProviderEvent = { + id: asEventId("evt-windows-sandbox-failed"), + kind: "notification", + provider: "codex", + sessionId: asSessionId("sess-1"), + createdAt: new Date().toISOString(), + method: "windowsSandbox/setupCompleted", + message: "Sandbox setup failed", + payload: { + success: false, + detail: "unsupported environment", + }, + }; + + lifecycleManager.emit("event", event); + const events = Array.from(yield* Fiber.join(eventsFiber)); + + assert.equal(events.length, 2); + + const firstEvent = events[0]; + const secondEvent = events[1]; + + assert.equal(firstEvent?.type, "session.state.changed"); + if (firstEvent?.type === "session.state.changed") { + assert.equal(firstEvent.payload.state, "error"); + assert.equal(firstEvent.payload.reason, "Sandbox setup failed"); + } + + assert.equal(secondEvent?.type, "runtime.warning"); + if (secondEvent?.type === "runtime.warning") { + assert.equal(secondEvent.payload.message, "Sandbox setup failed"); + } + }), + ); }); afterAll(() => { diff --git a/apps/server/src/provider/Layers/CodexAdapter.ts b/apps/server/src/provider/Layers/CodexAdapter.ts index 9e70b884402cc9fdbcd797f05521e2d96fae1300..0aa91f8b18a8765ae2b256f200dc904fc934c1da 100644 --- a/apps/server/src/provider/Layers/CodexAdapter.ts +++ b/apps/server/src/provider/Layers/CodexAdapter.ts @@ -917,28 +917,37 @@ function mapToRuntimeEvents(event: ProviderEvent): ReadonlyArray @@ -1121,7 +1130,18 @@ const makeCodexAdapter = (options?: CodexAdapterLiveOptions) => const listener = (event: ProviderEvent) => Effect.gen(function* () { yield* writeNativeEvent(event); - yield* Queue.offerAll(runtimeEventQueue, mapToRuntimeEvents(event)); + const runtimeEvents = mapToRuntimeEvents(event); + if (runtimeEvents.length === 0) { + yield* Effect.logDebug("ignoring unhandled Codex provider event", { + method: event.method, + sessionId: event.sessionId, + threadId: event.threadId, + turnId: event.turnId, + itemId: event.itemId, + }); + return; + } + yield* Queue.offerAll(runtimeEventQueue, runtimeEvents); }).pipe(Effect.runPromiseWith(services)); manager.on("event", listener); return listener; diff --git a/apps/server/src/provider/Layers/CursorAdapter.test.ts b/apps/server/src/provider/Layers/CursorAdapter.test.ts index 6c75e9eb8d5a75797989e3c2795d0a63e0ee3d05..0e9ca6d6f7e64326e7f622e75b20af4054875c03 100644 --- a/apps/server/src/provider/Layers/CursorAdapter.test.ts +++ b/apps/server/src/provider/Layers/CursorAdapter.test.ts @@ -467,6 +467,7 @@ describe("CursorAdapterLive", () => { const session = yield* adapter.startSession({ provider: "cursor", + runtimeMode: "approval-required", }); // consume startup events @@ -520,7 +521,7 @@ describe("CursorAdapterLive", () => { yield* adapter.startSession({ provider: "cursor", - approvalPolicy: "never", + runtimeMode: "full-access", }); // consume startup events diff --git a/apps/server/src/provider/Layers/CursorAdapter.ts b/apps/server/src/provider/Layers/CursorAdapter.ts index 9484ff3d6010a8516edca101e2d352d5ae89d7af..13ac149101a563acfab2c89020b47876f03a3d83 100644 --- a/apps/server/src/provider/Layers/CursorAdapter.ts +++ b/apps/server/src/provider/Layers/CursorAdapter.ts @@ -15,7 +15,6 @@ import { EventId, type CanonicalItemType, type CanonicalRequestType, - type ProviderApprovalPolicy, ProviderItemId, ProviderSessionId, type ProviderRuntimeEvent, @@ -23,6 +22,7 @@ import { ProviderThreadId, ProviderTurnId, type ProviderTurnStartResult, + type RuntimeMode, RuntimeItemId, RuntimeRequestId, RuntimeSessionId, @@ -81,7 +81,7 @@ interface CursorTurnState { interface CursorSessionContext { session: ProviderSession; - approvalPolicy: ProviderApprovalPolicy; + runtimeMode: RuntimeMode; readonly child: ChildProcessWithoutNullStreams; readonly output: readline.Interface; readonly pending: Map; @@ -539,7 +539,7 @@ function makeCursorAdapter(options?: CursorAdapterLiveOptions) { const options = decoded.params.options.map((entry) => ({ optionId: entry.optionId })); const detail = asString(asObject(decoded.params.toolCall)?.title); - if (context.approvalPolicy === "never") { + if (context.runtimeMode === "full-access") { const selection = selectCursorAutoApprovalOption(options) ?? (options[0] @@ -1048,7 +1048,7 @@ function makeCursorAdapter(options?: CursorAdapterLiveOptions) { const context: CursorSessionContext = { session, - approvalPolicy: input.approvalPolicy ?? "on-request", + runtimeMode: input.runtimeMode ?? "full-access", child, output, pending: new Map(), diff --git a/apps/server/src/provider/Layers/ProviderService.ts b/apps/server/src/provider/Layers/ProviderService.ts index 1422667733ed926ab1368900792b1213ca1a5380..423a0e0405ade5b6df9be7edfc097b17449219c8 100644 --- a/apps/server/src/provider/Layers/ProviderService.ts +++ b/apps/server/src/provider/Layers/ProviderService.ts @@ -415,8 +415,7 @@ const makeProviderService = (options?: ProviderServiceLiveOptions) => const input = { ...parsed, provider: parsed.provider ?? "codex", - approvalPolicy: parsed.approvalPolicy ?? "never", - sandboxMode: parsed.sandboxMode ?? "workspace-write", + runtimeMode: parsed.runtimeMode ?? "full-access", }; const adapter = yield* registry.getByProvider(input.provider); const session = yield* adapter.startSession(input); diff --git a/apps/server/src/wsServer.test.ts b/apps/server/src/wsServer.test.ts index b0a4bc20d4a3ddb8b3731ca33221997257285928..b483284fb551b8e3aaaf26e3dd38bcd4a68b6e05 100644 --- a/apps/server/src/wsServer.test.ts +++ b/apps/server/src/wsServer.test.ts @@ -1223,8 +1223,7 @@ describe("WebSocket Server", () => { attachments: [], }, assistantDeliveryMode: "streaming", - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", createdAt, }); expect(startTurnResponse.error).toBeUndefined(); @@ -1236,14 +1235,17 @@ describe("WebSocket Server", () => { emitRuntimeEvent( { - type: "message.delta", + type: "content.delta", eventId: asEventId("evt-ws-runtime-message-delta"), provider: "codex", sessionId, createdAt: new Date().toISOString(), turnId: asProviderTurnId("turn-1"), itemId: asProviderItemId("item-1"), - delta: "hello from runtime", + payload: { + streamKind: "assistant_text", + delta: "hello from runtime", + }, } as unknown as ProviderRuntimeEvent, ); diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index cfe05574fda2ab7e865d9334e521beffbf4a0c48..7bc1f223758e0d78ab164577b30824c3e1fc4db2 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -2231,8 +2231,6 @@ export default function ChatView({ threadId }: ChatViewProps) { setSendPhase("sending-turn"); const turnAttachments = await turnAttachmentsPromise; - const approvalPolicy = runtimeMode === "full-access" ? "never" : "on-request"; - const sandboxMode = runtimeMode === "full-access" ? "danger-full-access" : "workspace-write"; await api.orchestration.dispatchCommand({ type: "thread.turn.start", commandId: newCommandId(), @@ -2247,8 +2245,7 @@ export default function ChatView({ threadId }: ChatViewProps) { provider: selectedProvider, ...(supportsReasoningEffort && selectedEffort ? { effort: selectedEffort } : {}), assistantDeliveryMode: settings.enableAssistantStreaming ? "streaming" : "buffered", - approvalPolicy, - sandboxMode, + runtimeMode, createdAt: messageCreatedAt, }); turnStartSucceeded = true; diff --git a/apps/web/src/session-logic.ts b/apps/web/src/session-logic.ts index 1ffbe6c637eca83ff94684a430c1cf38ce98fe32..27ffddd4088825d2146cc4dc7f41f22a7e429a01 100644 --- a/apps/web/src/session-logic.ts +++ b/apps/web/src/session-logic.ts @@ -140,6 +140,15 @@ export function derivePendingApprovals( openByRequestId.delete(requestId); continue; } + + if ( + activity.kind === "provider.approval.respond.failed" && + requestId && + detail?.includes("Unknown pending permission request") + ) { + openByRequestId.delete(requestId); + continue; + } } return [...openByRequestId.values()].toSorted((left, right) => diff --git a/apps/web/src/store.test.ts b/apps/web/src/store.test.ts index 298f8cac5bcfe7a875017ce47c1305a6b6bf6085..4c9fe09b3465d2ae87d24c81312bce778a9ffa83 100644 --- a/apps/web/src/store.test.ts +++ b/apps/web/src/store.test.ts @@ -148,8 +148,7 @@ describe("store read model sync", () => { providerName: "claudeCode", providerSessionId: null, providerThreadId: null, - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", activeTurnId: null, lastError: null, updatedAt: "2026-02-27T00:00:00.000Z", @@ -173,8 +172,7 @@ describe("store read model sync", () => { providerName: "cursor", providerSessionId: null, providerThreadId: null, - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", activeTurnId: null, lastError: null, updatedAt: "2026-02-27T00:00:00.000Z", diff --git a/docs/plan-mode-deep-dive.md b/docs/plan-mode-deep-dive.md new file mode 100644 index 0000000000000000000000000000000000000000..da374b3cc52c28b0159955281d0449ebc4540023 --- /dev/null +++ b/docs/plan-mode-deep-dive.md @@ -0,0 +1,629 @@ +# Plan Mode Deep Dive + +## Goal + +Understand how the three providers expose: + +- planning / read-only execution modes +- structured plan updates +- structured user-input / question prompts +- response plumbing needed for a provider-agnostic adapter layer + +Providers covered: + +- Codex App Server +- Claude Agent SDK / Claude Code +- Cursor ACP + +--- + +## Existing T3 Code groundwork + +Current repo groundwork already exists in these places: + +- `EVENTS.md` +- `packages/contracts/src/providerRuntime.ts` +- `apps/server/src/provider/Layers/CodexAdapter.ts` +- `apps/server/src/provider/Layers/ClaudeCodeAdapter.ts` +- `apps/server/src/provider/Layers/CursorAdapter.ts` +- `apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts` + +Canonical runtime events already modeled: + +- `turn.plan.updated` +- `content.delta` with `streamKind: "plan_text"` +- `user-input.requested` +- `user-input.resolved` +- `request.opened` +- `request.resolved` + +This is a good base, but provider coverage is uneven: + +- Codex: strong native support for both structured plans and structured user questions. +- Claude: native plan mode exists, but plan updates are mostly implicit via tool/message stream; user questioning appears tool-based rather than a dedicated runtime event. +- Cursor ACP: native plan mode exists, but current ACP surface appears to expose neither structured plan updates nor structured ask-user prompts in the same way as Codex. + +--- + +## Provider 1: Codex App Server + +### What exists natively + +Codex has the richest first-class support for this feature set. + +### Real local session evidence + +The local Codex rollout file confirms at least one important distinction: + +- session metadata explicitly records collaboration mode as plan via `turn_context.payload.collaboration_mode.mode = "plan"` +- final plan handoff is emitted as a normal assistant message containing a `` block + +From the sampled rollout file, I did **not** find raw `event_msg.plan_update` / `event_msg.plan_delta` records, which suggests either: + +- this particular session never emitted incremental structured plan updates, or +- the desktop rollout file persists a higher-level message projection rather than every low-level app-server event + +That means the app-server protocol still supports native structured plan events, but local rollout files should not be assumed to contain every transport-level plan artifact. + + +Native surfaces confirmed from upstream docs/source: + +- collaboration mode with plan behavior +- `turn/plan/updated` +- `item/plan/delta` +- `item/tool/requestUserInput` +- `serverRequest/resolved` cleanup / completion notification for user input +- `request_user_input` tool in core/runtime + +Evidence inspected: + +- upstream `codex-rs/app-server/README.md` +- upstream `codex-rs/app-server-protocol` schemas +- upstream `codex-rs/core/src/tools/handlers/request_user_input.rs` +- upstream `codex-rs/core/templates/collaboration_mode/plan.md` + +### Semantics + +#### Plan mode + +Codex plan mode is not just “tool execution denied”. It has explicit behavioral guidance and explicit planning outputs. + +Two distinct plan surfaces exist: + +1. **Structured plan state** + - `turn/plan/updated` + - payload contains: + - optional `explanation` + - `plan: Array<{ step, status }>` + - statuses are `pending | inProgress | completed` + +2. **Plan text stream** + - `item/plan/delta` + - useful for rendering streaming plan prose / bullet lists before or alongside structured state + +This maps very cleanly to our canonical events: + +- `turn/plan.updated` -> `turn.plan.updated` +- `item/plan/delta` -> `content.delta(plan_text)` + +#### Structured user questions + +Codex has a dedicated `request_user_input` capability. + +Server request: + +- `item/tool/requestUserInput` + +Shape characteristics from upstream protocol/docs: + +- 1–3 questions +- each question has: + - prompt text + - stable id + - required options +- response carries answers per question +- freeform notes are supported in UX and answer model + +Important nuance: + +- upstream handler currently requires non-empty options for every question +- Codex TUI also supports an optional freeform note / text alongside selected options +- pending question requests are turn-scoped and cleaned up on turn completion/interruption + +### Why Codex is the reference model + +Codex should be treated as the reference shape for our provider-agnostic abstraction because it already matches the desired UI: + +- explicit plan lifecycle +- explicit multi-question prompt flow +- explicit answer submission +- explicit pending/resolved bookkeeping + +### Adapter implications + +For Codex, the provider adapter layer should stay close to native protocol: + +- keep `turn.plan.updated` +- keep `content.delta(plan_text)` +- map `item/tool/requestUserInput` to `user-input.requested` and `request.opened` +- map answer completion / cleanup to `user-input.resolved` and `request.resolved` + +--- + +## Provider 2: Claude Agent SDK / Claude Code + +### What exists natively + +Claude has an explicit plan permission mode: + +### Real local session evidence + +The sampled Claude session file provides concrete payloads for both key plan-mode tools: + +1. `AskUserQuestion` appears as a normal assistant `tool_use` block with input like: + +```json +{ + "type": "tool_use", + "name": "AskUserQuestion", + "input": { + "questions": [ + { + "question": "What does 'adding profiles to canvas' mean to you? What should profiles enable in canvas?", + "header": "Scope", + "options": [ + { "label": "Organize by profile", "description": "..." }, + { "label": "Profile icon bar in canvas", "description": "..." }, + { "label": "Per-profile canvas settings", "description": "..." }, + { "label": "All of the above", "description": "..." } + ], + "multiSelect": false + } + ] + } +} +``` + +2. `ExitPlanMode` also appears as a `tool_use` block, with a long-form `plan` string payload containing the finalized implementation spec. + +That gives us two strong adapter conclusions: + +- Claude user-input prompting is definitely tool-based and structurally parseable. +- Claude final plan handoff is tool-based too, not necessarily a dedicated transport event like Codex `turn/plan/updated`. + +### Concrete adapter mapping from transcript evidence + +Recommended Claude mapping based on real session data: + +- `tool_use.name === "AskUserQuestion"` -> `user-input.requested` +- user answer submission / tool result -> `user-input.resolved` +- `tool_use.name === "ExitPlanMode"` with `input.plan` -> final plan handoff event or synthesized `turn.plan.updated` snapshot + +The `AskUserQuestion` payload shape is already close to the UI we want: + +- `questions[]` array +- each question has `header` and `question` +- each question has `options[]` with `label` + `description` +- `multiSelect` is explicit + +The main remaining unknown is the exact runtime shape of the **answer** payload that Claude expects back for this tool. + + +- `PermissionMode = 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan' | 'dontAsk'` + +Confirmed from installed SDK typings: + +- `/Users/julius/.bun/install/cache/@anthropic-ai/claude-agent-sdk@0.2.62@@@1/sdk.d.ts` +- `/Users/julius/.bun/install/cache/@anthropic-ai/claude-agent-sdk@0.2.62@@@1/sdk-tools.d.ts` + +Claude also exposes plan-related tools/types: + +- `ExitPlanMode` +- `AskUserQuestion` + +These are strong signals that Claude’s plan UX is built around tool usage within a `permissionMode: 'plan'` session rather than a dedicated standalone `turn.plan.updated` event. + +### What the wire stream exposes + +Claude adapter input today is built from SDK messages like: + +- `system:init` +- `system:status` +- `stream_event` +- `assistant` +- `result` +- hook/task/tool summary events + +The SDK stream exposes: + +- message deltas +- tool-use starts/stops via content blocks +- permission-mode metadata +- final result and usage + +What it does **not** appear to expose as a first-class event: + +- no direct equivalent to Codex `turn/plan/updated` +- no direct equivalent to Codex `item/plan/delta` +- no dedicated top-level `user-input.requested` transport event in the SDK stream + +Instead, plan-mode-related behavior appears to surface through tool uses: + +- `AskUserQuestion` tool +- `ExitPlanMode` tool +- regular content/tool stream events + +### Semantics + +#### Plan mode + +Claude’s plan mode is primarily a **permission / behavior mode**: + +- session or turn is created with `permissionMode: 'plan'` +- tool execution is prevented / constrained +- assistant can explore, reason, and propose a plan +- eventually it may call `ExitPlanMode` to transition toward implementation + +That means Claude plan mode is native, but **structured plan state is not guaranteed**. + +In practice, there are two likely UI strategies: + +1. **Best-effort structured extraction** + - detect plan-like assistant/tool outputs + - synthesize `turn.plan.updated` from them +2. **Text-first plan rendering** + - show reasoning / assistant output as plan narrative + - only emit `turn.plan.updated` when we can confidently extract steps + +#### Asking user questions + +Claude’s `AskUserQuestion` appears to be a tool, not a dedicated transport-level question request primitive. + +That means the adapter likely needs to: + +- identify `tool_use` for `AskUserQuestion` +- parse its input payload into canonical question schema +- expose `user-input.requested` +- accept a user answer and feed it back as the tool result / synthetic user response, depending SDK control path + +This is adapter work, not just schema plumbing. + +### Recommended stance for Claude + +Claude should support the same canonical user-facing contract, but the adapter will need to **manufacture structure from native tool activity**. + +Recommended mapping: + +- `permissionMode: 'plan'` => canonical session/turn metadata `mode = plan` +- `AskUserQuestion` tool use => canonical `user-input.requested` +- submitted answer => canonical `user-input.resolved` +- `ExitPlanMode` tool use => canonical state transition or request to continue with implementation +- assistant text / tool summaries in plan mode => optional synthesized `turn.plan.updated` + +### Risk / uncertainty + +Claude currently appears less deterministic than Codex for structured plans. + +Main unknowns to resolve during implementation: + +- exact `AskUserQuestion` tool input/output payload shapes at runtime +- whether freeform responses are supported natively or need synthetic augmentation +- whether `ExitPlanMode` should be exposed to UI explicitly or handled internally by the adapter/session controller + +--- + +## Provider 3: Cursor ACP + +### What exists natively + +Cursor ACP does have native session modes. + +Confirmed by local probe output in `.tmp/acp-plan-probe/summary.json`: + +- `agent` +- `plan` +- `ask` + +The probe also confirmed current primary ACP update surfaces: + +- `available_commands_update` +- `agent_thought_chunk` +- `agent_message_chunk` +- `tool_call` +- `tool_call_update` +- `session/request_permission` + +### What the probe did not find + +The existing probe did **not** surface: + +- structured plan update events +- a dedicated “ask user question” request +- a dedicated plan delta stream + +The current ACP surface therefore looks much thinner than Codex. + +### Semantics + +#### Plan mode + +Cursor `plan` mode clearly exists as a session mode, but as currently observed it seems to mean: + +- read-only / design-first behavior +- regular thought/message chunk streaming +- no dedicated structured plan payload + +So the plan is native as a mode, but not obviously as a structured protocol surface. + +#### Asking the user questions + +Cursor ACP currently gives us a nearby primitive only for **permission requests**: + +- `session/request_permission` + +That is not the same as product-level question asking. + +The existence of `ask` mode suggests Cursor may handle asking by: + +- normal assistant prose in ask mode +- possibly mode-switch semantics +- possibly skills/commands that we have not yet triggered + +But based on current probe evidence, we should **not assume** a Codex-style structured question API exists in ACP. + +### Recommended stance for Cursor + +Treat Cursor as: + +- native support for `mode = plan` +- no proven native support for structured plan steps +- no proven native support for structured user-input questions + +Therefore Cursor likely needs the most adapter synthesis: + +- derive plan-mode state from ACP session mode +- optionally synthesize `turn.plan.updated` from assistant text if we can extract steps safely +- for interactive questions, likely fall back to regular conversational turns unless a richer ACP mechanism is discovered + +### Important product implication + +If we want the same rich “multiple-choice question card” UX across all providers, Cursor may need one of these paths: + +1. **Provider-native path** if later ACP exploration finds structured user question primitives +2. **Adapter-mediated path** where the provider is instructed to emit a machine-readable question block in text and adapter parses it +3. **Capability downgrade** where Cursor only supports conversational follow-up, not full structured plan-question cards + +Today, evidence supports option 3 as the safe baseline. + +--- + +## Cross-provider comparison + +### Capability matrix + +| Capability | Codex App Server | Claude Agent SDK | Cursor ACP | +| --- | --- | --- | --- | +| Native plan mode | Yes | Yes | Yes | +| Structured plan update event | Yes | Not obvious / likely no | Not observed | +| Plan text delta stream | Yes | Not first-class | Not observed | +| Native structured user question API | Yes | Tool-based, likely yes-ish | Not observed | +| Native answer resolution lifecycle | Yes | Likely adapter-mediated | Not observed | +| Best provider for canonical contract | Strong | Medium | Weak | + +### Core insight + +“Plan mode” means **different things** across providers: + +- **Codex**: plan mode is a first-class runtime concept with structured plan and question surfaces. +- **Claude**: plan mode is a first-class permission mode, but structured plan/question UI likely needs adapter interpretation. +- **Cursor**: plan mode is a first-class session mode, but richer plan/question structure is not currently exposed. + +--- + +## Recommended provider-agnostic contract + +We should separate three concerns that are currently easy to conflate. + +### 1. Session / turn mode + +A canonical mode flag: + +- `default` +- `plan` +- `ask` +- `execute` +- `unknown` + +This is about **agent operating mode**, not UI widgets. + +### 2. Structured plan state + +A canonical plan model: + +```ts +interface CanonicalPlanState { + explanation?: string | null; + steps: Array<{ + id?: string; + text: string; + status: 'pending' | 'inProgress' | 'completed'; + source: 'native' | 'synthesized'; + }>; +} +``` + +Rules: + +- Codex can populate this natively. +- Claude/Cursor may populate it only when extraction is confident. +- UI should tolerate absence of structured plan state. + +### 3. Structured user-input prompt + +Canonical prompt should be modeled independently of approvals: + +```ts +interface CanonicalUserInputPrompt { + promptId: string; + title?: string; + description?: string; + questions: Array<{ + id: string; + label: string; + description?: string; + options: Array<{ + id: string; + label: string; + description?: string; + recommended?: boolean; + }>; + allowFreeform?: boolean; + freeformPlaceholder?: string; + required?: boolean; + }>; + source: 'native' | 'tool-derived' | 'synthesized'; +} +``` + +And answers: + +```ts +interface CanonicalUserInputAnswer { + promptId: string; + answers: Array<{ + questionId: string; + selectedOptionId?: string; + text?: string; + }>; +} +``` + +### Why separate `request.opened` from `user-input.requested` + +Because they are not the same abstraction: + +- approvals = “may I run/edit/do X?” +- user-input prompts = “which direction do you want?” + +Codex already blurs these at transport level because `item/tool/requestUserInput` is a request. +But product and UI should keep them separate. + +Recommended rule: + +- keep `request.opened/request.resolved` for transport-level correlation +- keep `user-input.requested/user-input.resolved` for product-level UX + +--- + +## Adapter strategy by provider + +### Codex adapter + +Use native primitives directly. + +Implementation target: + +- preserve `turn.plan.updated` +- preserve `content.delta(plan_text)` +- preserve `user-input.requested` +- preserve `user-input.resolved` +- expose raw/native metadata for future UI improvements + +### Claude adapter + +Implement tool-aware adaptation. + +Implementation target: + +- treat `permissionMode: 'plan'` as native plan-mode state +- detect `AskUserQuestion` tool uses and convert to canonical user-input prompt +- convert question answers back into Claude-native tool response path +- detect `ExitPlanMode` and emit a canonical mode-change / handoff event +- optionally synthesize plan steps from assistant output in plan mode + +### Cursor adapter + +Implement conservative degraded support first. + +Implementation target: + +- expose native session mode including `plan` and `ask` +- do not claim structured plan updates unless proven +- do not claim structured user-input prompts unless proven +- optionally add a provider capability flag so UI can fall back to conversational follow-up + +--- + +## Capability flags we should add + +To avoid overpromising in the UI, add provider runtime capabilities such as: + +```ts +interface ProviderInteractiveCapabilities { + supportsPlanMode: boolean; + supportsAskMode: boolean; + supportsStructuredPlanUpdates: boolean; + supportsPlanTextStreaming: boolean; + supportsStructuredUserInput: boolean; + supportsFreeformUserInput: boolean; + supportsExitPlanMode: boolean; +} +``` + +Expected initial values: + +- Codex: all or nearly all `true` +- Claude: `supportsPlanMode=true`, `supportsStructuredUserInput=likely true via adapter`, `supportsStructuredPlanUpdates=partial` +- Cursor: `supportsPlanMode=true`, `supportsAskMode=true`, most structured flags `false` + +This lets the UI: + +- render rich plan cards only when supported +- fall back to plain assistant text + reply box otherwise +- avoid coupling the design to Codex-specific assumptions + +--- + +## Suggested implementation order + +1. **Formalize provider capability flags** + - make support explicit before UI work +2. **Finish canonical user-input contracts** + - question schema, answer schema, source markers +3. **Codex end-to-end implementation first** + - closest match to desired UX +4. **Claude adapter translation second** + - parse `AskUserQuestion` and `ExitPlanMode` +5. **Cursor degraded plan-mode support third** + - mode awareness first, richer structure later +6. **UI renders by capability, not provider name** + - prevents provider-specific branching from leaking upward + +--- + +## Open questions + +1. Claude runtime payloads: + - what exact `AskUserQuestion` input/output shapes arrive over the stream? +2. Claude answer submission path: + - should answers be injected as tool results, control responses, or synthetic user turns? +3. Cursor ACP: + - is there an undocumented or less common interactive prompt primitive beyond `session/request_permission`? +4. Cursor mode switching: + - can ACP session mode change mid-session, and is that surfaced as `session/update` or request/response only? +5. Plan synthesis: + - do we want best-effort extraction of numbered/bulleted plans into `turn.plan.updated` for Claude/Cursor, or should we keep structured plans native-only at first? + +--- + +## Immediate recommendation + +Build the product contract around **capabilities**, not around the assumption that every provider has Codex-style plan/question primitives. + +Concretely: + +- Codex should drive the first full UX implementation. +- Claude should be supported with adapter-derived question handling and partial/synthesized plan state. +- Cursor should initially support mode-aware UX with graceful fallback to conversational replies rather than full structured question cards. + +That gives us a provider-agnostic architecture without pretending the underlying protocols are already equivalent. diff --git a/packages/contracts/src/orchestration.test.ts b/packages/contracts/src/orchestration.test.ts index a6868541d8c8553d64a9f37bdca8854670d394ef..a7b7c63538685e1fa203a12891362f4d27fec519 100644 --- a/packages/contracts/src/orchestration.test.ts +++ b/packages/contracts/src/orchestration.test.ts @@ -2,8 +2,7 @@ import { assert, it } from "@effect/vitest"; import { Effect, Schema } from "effect"; import { - DEFAULT_PROVIDER_APPROVAL_POLICY, - DEFAULT_PROVIDER_SANDBOX_MODE, + DEFAULT_RUNTIME_MODE, OrchestrationGetTurnDiffInput, OrchestrationSession, ProjectCreateCommand, @@ -110,8 +109,7 @@ it.effect("decodes thread.turn.start defaults for provider and runtime mode", () createdAt: "2026-01-01T00:00:00.000Z", }); assert.strictEqual(parsed.provider, undefined); - assert.strictEqual(parsed.approvalPolicy, DEFAULT_PROVIDER_APPROVAL_POLICY); - assert.strictEqual(parsed.sandboxMode, DEFAULT_PROVIDER_SANDBOX_MODE); + assert.strictEqual(parsed.runtimeMode, DEFAULT_RUNTIME_MODE); }), ); @@ -128,13 +126,11 @@ it.effect("preserves explicit provider and runtime mode in thread.turn.start", ( attachments: [], }, provider: "claudeCode", - approvalPolicy: "never", - sandboxMode: "danger-full-access", + runtimeMode: "full-access", createdAt: "2026-01-01T00:00:00.000Z", }); assert.strictEqual(parsed.provider, "claudeCode"); - assert.strictEqual(parsed.approvalPolicy, "never"); - assert.strictEqual(parsed.sandboxMode, "danger-full-access"); + assert.strictEqual(parsed.runtimeMode, "full-access"); }), ); @@ -165,8 +161,7 @@ it.effect("decodes thread.turn-start-requested defaults for provider and runtime createdAt: "2026-01-01T00:00:00.000Z", }); assert.strictEqual(parsed.provider, undefined); - assert.strictEqual(parsed.approvalPolicy, DEFAULT_PROVIDER_APPROVAL_POLICY); - assert.strictEqual(parsed.sandboxMode, DEFAULT_PROVIDER_SANDBOX_MODE); + assert.strictEqual(parsed.runtimeMode, DEFAULT_RUNTIME_MODE); }), ); @@ -182,7 +177,6 @@ it.effect("decodes orchestration session runtime mode defaults", () => lastError: null, updatedAt: "2026-01-01T00:00:00.000Z", }); - assert.strictEqual(parsed.approvalPolicy, DEFAULT_PROVIDER_APPROVAL_POLICY); - assert.strictEqual(parsed.sandboxMode, DEFAULT_PROVIDER_SANDBOX_MODE); + assert.strictEqual(parsed.runtimeMode, DEFAULT_RUNTIME_MODE); }), ); diff --git a/packages/contracts/src/orchestration.ts b/packages/contracts/src/orchestration.ts index a807bac86eea5d67250153dfe09b9b284a08ebe9..9a552a78f61690626f439ff44b5d6a8feaaf3a22 100644 --- a/packages/contracts/src/orchestration.ts +++ b/packages/contracts/src/orchestration.ts @@ -32,21 +32,9 @@ export const ORCHESTRATION_WS_CHANNELS = { export const ProviderKind = Schema.Literals(["codex", "claudeCode", "cursor"]); export type ProviderKind = typeof ProviderKind.Type; export const DEFAULT_PROVIDER_KIND: ProviderKind = "codex"; -export const ProviderApprovalPolicy = Schema.Literals([ - "untrusted", - "on-failure", - "on-request", - "never", -]); -export type ProviderApprovalPolicy = typeof ProviderApprovalPolicy.Type; -export const DEFAULT_PROVIDER_APPROVAL_POLICY: ProviderApprovalPolicy = "on-failure"; -export const ProviderSandboxMode = Schema.Literals([ - "read-only", - "workspace-write", - "danger-full-access", -]); -export type ProviderSandboxMode = typeof ProviderSandboxMode.Type; -export const DEFAULT_PROVIDER_SANDBOX_MODE: ProviderSandboxMode = "workspace-write"; +export const RuntimeMode = Schema.Literals(["approval-required", "full-access"]); +export type RuntimeMode = typeof RuntimeMode.Type; +export const DEFAULT_RUNTIME_MODE: RuntimeMode = "full-access"; export const ProviderRequestKind = Schema.Literals(["command", "file-change"]); export type ProviderRequestKind = typeof ProviderRequestKind.Type; export const AssistantDeliveryMode = Schema.Literals(["buffered", "streaming"]); @@ -163,12 +151,7 @@ export const OrchestrationSession = Schema.Struct({ providerName: Schema.NullOr(TrimmedNonEmptyString), providerSessionId: Schema.NullOr(ProviderSessionId), providerThreadId: Schema.NullOr(ProviderThreadId), - approvalPolicy: ProviderApprovalPolicy.pipe( - Schema.withDecodingDefault(() => DEFAULT_PROVIDER_APPROVAL_POLICY), - ), - sandboxMode: ProviderSandboxMode.pipe( - Schema.withDecodingDefault(() => DEFAULT_PROVIDER_SANDBOX_MODE), - ), + runtimeMode: RuntimeMode.pipe(Schema.withDecodingDefault(() => DEFAULT_RUNTIME_MODE)), activeTurnId: Schema.NullOr(TurnId), lastError: Schema.NullOr(TrimmedNonEmptyString), updatedAt: IsoDateTime, @@ -329,12 +312,7 @@ export const ThreadTurnStartCommand = Schema.Struct({ model: Schema.optional(TrimmedNonEmptyString), effort: Schema.optional(TrimmedNonEmptyString), assistantDeliveryMode: Schema.optional(AssistantDeliveryMode), - approvalPolicy: ProviderApprovalPolicy.pipe( - Schema.withDecodingDefault(() => DEFAULT_PROVIDER_APPROVAL_POLICY), - ), - sandboxMode: ProviderSandboxMode.pipe( - Schema.withDecodingDefault(() => DEFAULT_PROVIDER_SANDBOX_MODE), - ), + runtimeMode: RuntimeMode.pipe(Schema.withDecodingDefault(() => DEFAULT_RUNTIME_MODE)), createdAt: IsoDateTime, }); @@ -352,8 +330,7 @@ export const ClientThreadTurnStartCommand = Schema.Struct({ model: Schema.optional(TrimmedNonEmptyString), effort: Schema.optional(TrimmedNonEmptyString), assistantDeliveryMode: Schema.optional(AssistantDeliveryMode), - approvalPolicy: ProviderApprovalPolicy, - sandboxMode: ProviderSandboxMode, + runtimeMode: RuntimeMode, createdAt: IsoDateTime, }); @@ -585,12 +562,7 @@ export const ThreadTurnStartRequestedPayload = Schema.Struct({ model: Schema.optional(TrimmedNonEmptyString), effort: Schema.optional(TrimmedNonEmptyString), assistantDeliveryMode: Schema.optional(AssistantDeliveryMode), - approvalPolicy: ProviderApprovalPolicy.pipe( - Schema.withDecodingDefault(() => DEFAULT_PROVIDER_APPROVAL_POLICY), - ), - sandboxMode: ProviderSandboxMode.pipe( - Schema.withDecodingDefault(() => DEFAULT_PROVIDER_SANDBOX_MODE), - ), + runtimeMode: RuntimeMode.pipe(Schema.withDecodingDefault(() => DEFAULT_RUNTIME_MODE)), createdAt: IsoDateTime, }); diff --git a/packages/contracts/src/provider.test.ts b/packages/contracts/src/provider.test.ts index 640ea520442d756e4db01b736647741902b81b99..fadde48cd9f15ee85c4c30c3a1d550c58973999e 100644 --- a/packages/contracts/src/provider.test.ts +++ b/packages/contracts/src/provider.test.ts @@ -18,8 +18,7 @@ describe("ProviderSessionStartInput", () => { }, }, }); - expect(parsed.approvalPolicy).toBeUndefined(); - expect(parsed.sandboxMode).toBeUndefined(); + expect(parsed.runtimeMode).toBeUndefined(); expect(parsed.providerOptions?.codex?.binaryPath).toBe("/usr/local/bin/codex"); expect(parsed.providerOptions?.codex?.homePath).toBe("/tmp/.codex"); }); @@ -36,15 +35,13 @@ describe("ProviderSessionStartInput", () => { maxThinkingTokens: 12_000, }, }, - approvalPolicy: "never", - sandboxMode: "danger-full-access", + runtimeMode: "full-access", }); expect(parsed.provider).toBe("claudeCode"); expect(parsed.providerOptions?.claudeCode?.binaryPath).toBe("/usr/local/bin/claude"); expect(parsed.providerOptions?.claudeCode?.permissionMode).toBe("plan"); expect(parsed.providerOptions?.claudeCode?.maxThinkingTokens).toBe(12_000); - expect(parsed.approvalPolicy).toBe("never"); - expect(parsed.sandboxMode).toBe("danger-full-access"); + expect(parsed.runtimeMode).toBe("full-access"); }); it("accepts cursor provider payloads", () => { @@ -57,13 +54,11 @@ describe("ProviderSessionStartInput", () => { binaryPath: "/usr/local/bin/agent", }, }, - approvalPolicy: "on-request", - sandboxMode: "workspace-write", + runtimeMode: "approval-required", }); expect(parsed.provider).toBe("cursor"); expect(parsed.model).toBe("composer-1.5"); expect(parsed.providerOptions?.cursor?.binaryPath).toBe("/usr/local/bin/agent"); - expect(parsed.approvalPolicy).toBe("on-request"); - expect(parsed.sandboxMode).toBe("workspace-write"); + expect(parsed.runtimeMode).toBe("approval-required"); }); }); diff --git a/packages/contracts/src/provider.ts b/packages/contracts/src/provider.ts index e763e101ae4eed5d45a490c15c0d64aa9f398edd..d7c83192fc98f0fc087b32b42e16040e0af9e009 100644 --- a/packages/contracts/src/provider.ts +++ b/packages/contracts/src/provider.ts @@ -16,10 +16,9 @@ import { PROVIDER_SEND_TURN_MAX_ATTACHMENTS, PROVIDER_SEND_TURN_MAX_INPUT_CHARS, ProviderApprovalDecision, - ProviderApprovalPolicy, ProviderKind, ProviderRequestKind, - ProviderSandboxMode, + RuntimeMode, TurnCountRange, } from "./orchestration"; @@ -80,8 +79,7 @@ export const ProviderSessionStartInput = Schema.Struct({ model: Schema.optional(TrimmedNonEmptyStringSchema), resumeCursor: Schema.optional(Schema.Unknown), providerOptions: Schema.optional(ProviderStartOptions), - approvalPolicy: Schema.optional(ProviderApprovalPolicy), - sandboxMode: Schema.optional(ProviderSandboxMode), + runtimeMode: Schema.optional(RuntimeMode), }); export type ProviderSessionStartInput = typeof ProviderSessionStartInput.Type;