| author | |
| committer | |
| log | 03053d9ee0db927881542c7075aa2fa239c3853d |
| tree | 1cb044b3c4cd49173e7450c87bc8f2646e50fcbe |
| parent | 9c4d7a83e22e4a64803552d3920686f4beb07470 |
- extend provider and orchestration contracts for runtime mode-aware events
- persist runtime mode fields in projection thread session storage and migration 006
- update adapters, reactors, projections, websocket/web session logic, and tests
- add plan mode deep dive docs and probe artifacts for ACP behavior39 files changed, 1672 insertions(+), 433 deletions(-)
.tmp/acp-plan-probe/summary.json created+219| ... | @@ -0,0 +1,219 @@ | ||
| 1 | { | ||
| 2 | "counts": { | ||
| 3 | "notificationsByMethod": { | ||
| 4 | "session/update": 205 | ||
| 5 | }, | ||
| 6 | "sessionUpdateByType": { | ||
| 7 | "available_commands_update": 1, | ||
| 8 | "agent_thought_chunk": 4, | ||
| 9 | "agent_message_chunk": 192, | ||
| 10 | "tool_call": 4, | ||
| 11 | "tool_call_update": 4 | ||
| 12 | }, | ||
| 13 | "serverRequestsByMethod": { | ||
| 14 | "session/request_permission": 2 | ||
| 15 | }, | ||
| 16 | "permissionDecisions": { | ||
| 17 | "allow-once": 2 | ||
| 18 | } | ||
| 19 | }, | ||
| 20 | "samples": { | ||
| 21 | "initializeResult": { | ||
| 22 | "protocolVersion": 1, | ||
| 23 | "agentCapabilities": { | ||
| 24 | "loadSession": true, | ||
| 25 | "mcpCapabilities": { | ||
| 26 | "http": true, | ||
| 27 | "sse": true | ||
| 28 | }, | ||
| 29 | "promptCapabilities": { | ||
| 30 | "audio": false, | ||
| 31 | "embeddedContext": false, | ||
| 32 | "image": true | ||
| 33 | } | ||
| 34 | }, | ||
| 35 | "authMethods": [ | ||
| 36 | { | ||
| 37 | "id": "cursor_login", | ||
| 38 | "name": "Cursor Login", | ||
| 39 | "description": "Authenticate using existing Cursor login credentials. Run 'agent login' first if not logged in." | ||
| 40 | } | ||
| 41 | ] | ||
| 42 | }, | ||
| 43 | "authenticateResult": {}, | ||
| 44 | "sessionNewResult": { | ||
| 45 | "sessionId": "1ca3aa23-fe4b-4468-95af-544a523440d7", | ||
| 46 | "modes": { | ||
| 47 | "currentModeId": "agent", | ||
| 48 | "availableModes": [ | ||
| 49 | { | ||
| 50 | "id": "agent", | ||
| 51 | "name": "Agent", | ||
| 52 | "description": "Full agent capabilities with tool access" | ||
| 53 | }, | ||
| 54 | { | ||
| 55 | "id": "plan", | ||
| 56 | "name": "Plan", | ||
| 57 | "description": "Read-only mode for planning and designing before implementation" | ||
| 58 | }, | ||
| 59 | { | ||
| 60 | "id": "ask", | ||
| 61 | "name": "Ask", | ||
| 62 | "description": "Q&A mode - no edits or command execution" | ||
| 63 | } | ||
| 64 | ] | ||
| 65 | } | ||
| 66 | }, | ||
| 67 | "sessionPromptResultByScenario": { | ||
| 68 | "hello": { | ||
| 69 | "stopReason": "end_turn" | ||
| 70 | }, | ||
| 71 | "tooling": { | ||
| 72 | "stopReason": "end_turn" | ||
| 73 | }, | ||
| 74 | "cancel": { | ||
| 75 | "stopReason": "end_turn" | ||
| 76 | } | ||
| 77 | }, | ||
| 78 | "sessionUpdateByType": { | ||
| 79 | "available_commands_update": { | ||
| 80 | "jsonrpc": "2.0", | ||
| 81 | "method": "session/update", | ||
| 82 | "params": { | ||
| 83 | "sessionId": "1ca3aa23-fe4b-4468-95af-544a523440d7", | ||
| 84 | "update": { | ||
| 85 | "sessionUpdate": "available_commands_update", | ||
| 86 | "availableCommands": [ | ||
| 87 | { | ||
| 88 | "name": "copy-request-id", | ||
| 89 | "description": "Copy the last request ID to clipboard" | ||
| 90 | }, | ||
| 91 | { | ||
| 92 | "name": "create-rule", | ||
| 93 | "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)" | ||
| 94 | }, | ||
| 95 | { | ||
| 96 | "name": "create-skill", | ||
| 97 | "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)" | ||
| 98 | }, | ||
| 99 | { | ||
| 100 | "name": "create-subagent", | ||
| 101 | "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)" | ||
| 102 | }, | ||
| 103 | { | ||
| 104 | "name": "migrate-to-skills", | ||
| 105 | "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)" | ||
| 106 | }, | ||
| 107 | { | ||
| 108 | "name": "update-cursor-settings", | ||
| 109 | "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)" | ||
| 110 | }, | ||
| 111 | { | ||
| 112 | "name": "react-doctor", | ||
| 113 | "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)" | ||
| 114 | } | ||
| 115 | ] | ||
| 116 | } | ||
| 117 | } | ||
| 118 | }, | ||
| 119 | "agent_thought_chunk": { | ||
| 120 | "jsonrpc": "2.0", | ||
| 121 | "method": "session/update", | ||
| 122 | "params": { | ||
| 123 | "sessionId": "1ca3aa23-fe4b-4468-95af-544a523440d7", | ||
| 124 | "update": { | ||
| 125 | "sessionUpdate": "agent_thought_chunk", | ||
| 126 | "content": { | ||
| 127 | "type": "text", | ||
| 128 | "text": "" | ||
| 129 | } | ||
| 130 | } | ||
| 131 | } | ||
| 132 | }, | ||
| 133 | "agent_message_chunk": { | ||
| 134 | "jsonrpc": "2.0", | ||
| 135 | "method": "session/update", | ||
| 136 | "params": { | ||
| 137 | "sessionId": "1ca3aa23-fe4b-4468-95af-544a523440d7", | ||
| 138 | "update": { | ||
| 139 | "sessionUpdate": "agent_message_chunk", | ||
| 140 | "content": { | ||
| 141 | "type": "text", | ||
| 142 | "text": "\nHi there. How can I help you today?" | ||
| 143 | } | ||
| 144 | } | ||
| 145 | } | ||
| 146 | }, | ||
| 147 | "tool_call": { | ||
| 148 | "jsonrpc": "2.0", | ||
| 149 | "method": "session/update", | ||
| 150 | "params": { | ||
| 151 | "sessionId": "1ca3aa23-fe4b-4468-95af-544a523440d7", | ||
| 152 | "update": { | ||
| 153 | "sessionUpdate": "tool_call", | ||
| 154 | "toolCallId": "tool_2cb78ed4-0835-4b1d-bb63-ddb8646fb71", | ||
| 155 | "title": "Terminal", | ||
| 156 | "kind": "execute", | ||
| 157 | "status": "pending", | ||
| 158 | "rawInput": {} | ||
| 159 | } | ||
| 160 | } | ||
| 161 | }, | ||
| 162 | "tool_call_update": { | ||
| 163 | "jsonrpc": "2.0", | ||
| 164 | "method": "session/update", | ||
| 165 | "params": { | ||
| 166 | "sessionId": "1ca3aa23-fe4b-4468-95af-544a523440d7", | ||
| 167 | "update": { | ||
| 168 | "sessionUpdate": "tool_call_update", | ||
| 169 | "toolCallId": "tool_2cb78ed4-0835-4b1d-bb63-ddb8646fb71", | ||
| 170 | "status": "in_progress" | ||
| 171 | } | ||
| 172 | } | ||
| 173 | } | ||
| 174 | }, | ||
| 175 | "serverRequestByMethod": { | ||
| 176 | "session/request_permission": { | ||
| 177 | "jsonrpc": "2.0", | ||
| 178 | "id": 0, | ||
| 179 | "method": "session/request_permission", | ||
| 180 | "params": { | ||
| 181 | "sessionId": "1ca3aa23-fe4b-4468-95af-544a523440d7", | ||
| 182 | "toolCall": { | ||
| 183 | "toolCallId": "tool_2cb78ed4-0835-4b1d-bb63-ddb8646fb71", | ||
| 184 | "title": "`pwd`", | ||
| 185 | "kind": "execute", | ||
| 186 | "status": "pending", | ||
| 187 | "content": [ | ||
| 188 | { | ||
| 189 | "type": "content", | ||
| 190 | "content": { | ||
| 191 | "type": "text", | ||
| 192 | "text": "Not in allowlist: pwd" | ||
| 193 | } | ||
| 194 | } | ||
| 195 | ] | ||
| 196 | }, | ||
| 197 | "options": [ | ||
| 198 | { | ||
| 199 | "optionId": "allow-once", | ||
| 200 | "name": "Allow once", | ||
| 201 | "kind": "allow_once" | ||
| 202 | }, | ||
| 203 | { | ||
| 204 | "optionId": "allow-always", | ||
| 205 | "name": "Allow always", | ||
| 206 | "kind": "allow_always" | ||
| 207 | }, | ||
| 208 | { | ||
| 209 | "optionId": "reject-once", | ||
| 210 | "name": "Reject", | ||
| 211 | "kind": "reject_once" | ||
| 212 | } | ||
| 213 | ] | ||
| 214 | } | ||
| 215 | } | ||
| 216 | } | ||
| 217 | }, | ||
| 218 | "stderr": [] | ||
| 219 | } | ||
.tmp/acp-plan-probe/transcript.ndjson created+233| ... | @@ -0,0 +1,233 @@ | ||
| 1 | {"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"}}}} | ||
| 2 | {"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."}]}}} | ||
| 3 | {"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."}]}} | ||
| 4 | {"ts":"2026-03-04T07:30:36.334Z","channel":"client->server","message":{"jsonrpc":"2.0","id":2,"method":"authenticate","params":{"methodId":"cursor_login"}}} | ||
| 5 | {"ts":"2026-03-04T07:30:36.335Z","channel":"server->client","message":{"jsonrpc":"2.0","id":2,"result":{}}} | ||
| 6 | {"ts":"2026-03-04T07:30:36.335Z","channel":"scenario-result","scenario":"authenticate","result":{}} | ||
| 7 | {"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":[]}}} | ||
| 8 | {"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"}]}}}} | ||
| 9 | {"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"}]}}} | ||
| 10 | {"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."}]}}} | ||
| 11 | {"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"} | ||
| 12 | {"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"} | ||
| 13 | {"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"} | ||
| 14 | {"ts":"2026-03-04T07:30:38.181Z","channel":"server->client","message":{"jsonrpc":"2.0","id":4,"result":{"stopReason":"end_turn"}}} | ||
| 15 | {"ts":"2026-03-04T07:30:38.181Z","channel":"scenario-result","scenario":"session/prompt","scenarioName":"hello","result":{"stopReason":"end_turn"}} | ||
| 16 | {"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."}]}}} | ||
| 17 | {"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"} | ||
| 18 | {"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"} | ||
| 19 | {"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"} | ||
| 20 | {"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"} | ||
| 21 | {"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"} | ||
| 22 | {"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"} | ||
| 23 | {"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"}]}}} | ||
| 24 | {"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"}]}} | ||
| 25 | {"ts":"2026-03-04T07:30:39.617Z","channel":"client->server","message":{"jsonrpc":"2.0","id":0,"result":{"outcome":{"outcome":"selected","optionId":"allow-once"}}}} | ||
| 26 | {"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"} | ||
| 27 | {"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"} | ||
| 28 | {"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"}]}}} | ||
| 29 | {"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"}]}} | ||
| 30 | {"ts":"2026-03-04T07:30:39.741Z","channel":"client->server","message":{"jsonrpc":"2.0","id":1,"result":{"outcome":{"outcome":"selected","optionId":"allow-once"}}}} | ||
| 31 | {"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"} | ||
| 32 | {"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"} | ||
| 33 | {"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"} | ||
| 34 | {"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"} | ||
| 35 | {"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"} | ||
| 36 | {"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"} | ||
| 37 | {"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"} | ||
| 38 | {"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"} | ||
| 39 | {"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"} | ||
| 40 | {"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"} | ||
| 41 | {"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"} | ||
| 42 | {"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"} | ||
| 43 | {"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"} | ||
| 44 | {"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"} | ||
| 45 | {"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"} | ||
| 46 | {"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"} | ||
| 47 | {"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"} | ||
| 48 | {"ts":"2026-03-04T07:30:41.185Z","channel":"server->client","message":{"jsonrpc":"2.0","id":5,"result":{"stopReason":"end_turn"}}} | ||
| 49 | {"ts":"2026-03-04T07:30:41.185Z","channel":"scenario-result","scenario":"session/prompt","scenarioName":"tooling","result":{"stopReason":"end_turn"}} | ||
| 50 | {"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."}]}}} | ||
| 51 | {"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"} | ||
| 52 | {"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"}}} | ||
| 53 | {"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"}}}} | ||
| 54 | {"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\"}}"} | ||
| 55 | {"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"} | ||
| 56 | {"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"} | ||
| 57 | {"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"} | ||
| 58 | {"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"} | ||
| 59 | {"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"} | ||
| 60 | {"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"} | ||
| 61 | {"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"} | ||
| 62 | {"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"} | ||
| 63 | {"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"} | ||
| 64 | {"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"} | ||
| 65 | {"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"} | ||
| 66 | {"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"} | ||
| 67 | {"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"} | ||
| 68 | {"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"} | ||
| 69 | {"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"} | ||
| 70 | {"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"} | ||
| 71 | {"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"} | ||
| 72 | {"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"} | ||
| 73 | {"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"} | ||
| 74 | {"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"} | ||
| 75 | {"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"} | ||
| 76 | {"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"} | ||
| 77 | {"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"} | ||
| 78 | {"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"} | ||
| 79 | {"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"} | ||
| 80 | {"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"} | ||
| 81 | {"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"} | ||
| 82 | {"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"} | ||
| 83 | {"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"} | ||
| 84 | {"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"} | ||
| 85 | {"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"} | ||
| 86 | {"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"} | ||
| 87 | {"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"} | ||
| 88 | {"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"} | ||
| 89 | {"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"} | ||
| 90 | {"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"} | ||
| 91 | {"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"} | ||
| 92 | {"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"} | ||
| 93 | {"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"} | ||
| 94 | {"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"} | ||
| 95 | {"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"} | ||
| 96 | {"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"} | ||
| 97 | {"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"} | ||
| 98 | {"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"} | ||
| 99 | {"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"} | ||
| 100 | {"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"} | ||
| 101 | {"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"} | ||
| 102 | {"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"} | ||
| 103 | {"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"} | ||
| 104 | {"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"} | ||
| 105 | {"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"} | ||
| 106 | {"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"} | ||
| 107 | {"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"} | ||
| 108 | {"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"} | ||
| 109 | {"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"} | ||
| 110 | {"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"} | ||
| 111 | {"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"} | ||
| 112 | {"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"} | ||
| 113 | {"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"} | ||
| 114 | {"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"} | ||
| 115 | {"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"} | ||
| 116 | {"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"} | ||
| 117 | {"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"} | ||
| 118 | {"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"} | ||
| 119 | {"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"} | ||
| 120 | {"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"} | ||
| 121 | {"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"} | ||
| 122 | {"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"} | ||
| 123 | {"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"} | ||
| 124 | {"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"} | ||
| 125 | {"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"} | ||
| 126 | {"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"} | ||
| 127 | {"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"} | ||
| 128 | {"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"} | ||
| 129 | {"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"} | ||
| 130 | {"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"} | ||
| 131 | {"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"} | ||
| 132 | {"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"} | ||
| 133 | {"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"} | ||
| 134 | {"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"} | ||
| 135 | {"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"} | ||
| 136 | {"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"} | ||
| 137 | {"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"} | ||
| 138 | {"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"} | ||
| 139 | {"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"} | ||
| 140 | {"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"} | ||
| 141 | {"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"} | ||
| 142 | {"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"} | ||
| 143 | {"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"} | ||
| 144 | {"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"} | ||
| 145 | {"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"} | ||
| 146 | {"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"} | ||
| 147 | {"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"} | ||
| 148 | {"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"} | ||
| 149 | {"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"} | ||
| 150 | {"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"} | ||
| 151 | {"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"} | ||
| 152 | {"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"} | ||
| 153 | {"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"} | ||
| 154 | {"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"} | ||
| 155 | {"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"} | ||
| 156 | {"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"} | ||
| 157 | {"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"} | ||
| 158 | {"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"} | ||
| 159 | {"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"} | ||
| 160 | {"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"} | ||
| 161 | {"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"} | ||
| 162 | {"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"} | ||
| 163 | {"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"} | ||
| 164 | {"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"} | ||
| 165 | {"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"} | ||
| 166 | {"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"} | ||
| 167 | {"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"} | ||
| 168 | {"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"} | ||
| 169 | {"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"} | ||
| 170 | {"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"} | ||
| 171 | {"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"} | ||
| 172 | {"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"} | ||
| 173 | {"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"} | ||
| 174 | {"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"} | ||
| 175 | {"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"} | ||
| 176 | {"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"} | ||
| 177 | {"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"} | ||
| 178 | {"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"} | ||
| 179 | {"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"} | ||
| 180 | {"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"} | ||
| 181 | {"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"} | ||
| 182 | {"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"} | ||
| 183 | {"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"} | ||
| 184 | {"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"} | ||
| 185 | {"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"} | ||
| 186 | {"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"} | ||
| 187 | {"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"} | ||
| 188 | {"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"} | ||
| 189 | {"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"} | ||
| 190 | {"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"} | ||
| 191 | {"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"} | ||
| 192 | {"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"} | ||
| 193 | {"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"} | ||
| 194 | {"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"} | ||
| 195 | {"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"} | ||
| 196 | {"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"} | ||
| 197 | {"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"} | ||
| 198 | {"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"} | ||
| 199 | {"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"} | ||
| 200 | {"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"} | ||
| 201 | {"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"} | ||
| 202 | {"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"} | ||
| 203 | {"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"} | ||
| 204 | {"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"} | ||
| 205 | {"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"} | ||
| 206 | {"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"} | ||
| 207 | {"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"} | ||
| 208 | {"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"} | ||
| 209 | {"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"} | ||
| 210 | {"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"} | ||
| 211 | {"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"} | ||
| 212 | {"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"} | ||
| 213 | {"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"} | ||
| 214 | {"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"} | ||
| 215 | {"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"} | ||
| 216 | {"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"} | ||
| 217 | {"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"} | ||
| 218 | {"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"} | ||
| 219 | {"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"} | ||
| 220 | {"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"} | ||
| 221 | {"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"} | ||
| 222 | {"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"} | ||
| 223 | {"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"} | ||
| 224 | {"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"} | ||
| 225 | {"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"} | ||
| 226 | {"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"} | ||
| 227 | {"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"} | ||
| 228 | {"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"} | ||
| 229 | {"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"} | ||
| 230 | {"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"} | ||
| 231 | {"ts":"2026-03-04T07:30:54.486Z","channel":"server->client","message":{"jsonrpc":"2.0","id":6,"result":{"stopReason":"end_turn"}}} | ||
| 232 | {"ts":"2026-03-04T07:30:54.486Z","channel":"scenario-result","scenario":"session/prompt","scenarioName":"cancel","result":{"stopReason":"end_turn"}} | ||
| 233 | {"ts":"2026-03-04T07:30:55.005Z","channel":"lifecycle","event":"exit","code":143,"signal":null} | ||
.tmp/ext/claude-agent-sdk-typescript created+1| ... | @@ -0,0 +1 @@ | ||
| 1 | Subproject commit 9345c75d6ef60e794a78df2a795ae07abc0a420f | ||
.tmp/ext/codex created+1| ... | @@ -0,0 +1 @@ | ||
| 1 | Subproject commit fa2306b303a8c7b1ef2ebb41117051bede08cb09 | ||
apps/server/integration/orchestrationEngine.integration.test.ts+13-7| ... | @@ -142,8 +142,7 @@ const startTurn = (input: { | ... | @@ -142,8 +142,7 @@ const startTurn = (input: { |
| 142 | attachments: [], | 142 | attachments: [], |
| 143 | }, | 143 | }, |
| 144 | ...(input.provider !== undefined ? { provider: input.provider } : {}), | 144 | ...(input.provider !== undefined ? { provider: input.provider } : {}), |
| 145 | approvalPolicy: "on-request", | 145 | runtimeMode: "approval-required", |
| 146 | sandboxMode: "workspace-write", | ||
| 147 | createdAt: nowIso(), | 146 | createdAt: nowIso(), |
| 148 | }); | 147 | }); |
| 149 | 148 | ||
| ... | @@ -477,26 +476,33 @@ it.live("records failed turn runtime state and checkpoint status as error", () = | ... | @@ -477,26 +476,33 @@ it.live("records failed turn runtime state and checkpoint status as error", () = |
| 477 | turnId: FIXTURE_TURN_ID, | 476 | turnId: FIXTURE_TURN_ID, |
| 478 | }, | 477 | }, |
| 479 | { | 478 | { |
| 480 | type: "message.delta", | 479 | type: "content.delta", |
| 481 | ...runtimeBase("evt-failure-2", "2026-02-24T10:04:00.100Z"), | 480 | ...runtimeBase("evt-failure-2", "2026-02-24T10:04:00.100Z"), |
| 482 | threadId: FIXTURE_THREAD_ID, | 481 | threadId: FIXTURE_THREAD_ID, |
| 483 | turnId: FIXTURE_TURN_ID, | 482 | turnId: FIXTURE_TURN_ID, |
| 484 | delta: "Partial output before failure.\n", | 483 | payload: { |
| 484 | streamKind: "assistant_text", | ||
| 485 | delta: "Partial output before failure.\n", | ||
| 486 | }, | ||
| 485 | }, | 487 | }, |
| 486 | { | 488 | { |
| 487 | type: "runtime.error", | 489 | type: "runtime.error", |
| 488 | ...runtimeBase("evt-failure-3", "2026-02-24T10:04:00.200Z"), | 490 | ...runtimeBase("evt-failure-3", "2026-02-24T10:04:00.200Z"), |
| 489 | threadId: FIXTURE_THREAD_ID, | 491 | threadId: FIXTURE_THREAD_ID, |
| 490 | turnId: FIXTURE_TURN_ID, | 492 | turnId: FIXTURE_TURN_ID, |
| 491 | message: "Sandbox command failed.", | 493 | payload: { |
| 494 | message: "Sandbox command failed.", | ||
| 495 | }, | ||
| 492 | }, | 496 | }, |
| 493 | { | 497 | { |
| 494 | type: "turn.completed", | 498 | type: "turn.completed", |
| 495 | ...runtimeBase("evt-failure-4", "2026-02-24T10:04:00.300Z"), | 499 | ...runtimeBase("evt-failure-4", "2026-02-24T10:04:00.300Z"), |
| 496 | threadId: FIXTURE_THREAD_ID, | 500 | threadId: FIXTURE_THREAD_ID, |
| 497 | turnId: FIXTURE_TURN_ID, | 501 | turnId: FIXTURE_TURN_ID, |
| 498 | status: "failed", | 502 | payload: { |
| 499 | errorMessage: "Sandbox command failed.", | 503 | state: "failed", |
| 504 | errorMessage: "Sandbox command failed.", | ||
| 505 | }, | ||
| 500 | }, | 506 | }, |
| 501 | ], | 507 | ], |
| 502 | }); | 508 | }); |
apps/server/src/codexAppServerManager.ts+18-2| ... | @@ -104,6 +104,23 @@ const RECOVERABLE_THREAD_RESUME_ERROR_SNIPPETS = [ | ... | @@ -104,6 +104,23 @@ const RECOVERABLE_THREAD_RESUME_ERROR_SNIPPETS = [ |
| 104 | "does not exist", | 104 | "does not exist", |
| 105 | ]; | 105 | ]; |
| 106 | 106 | ||
| 107 | function mapCodexRuntimeMode(runtimeMode: "approval-required" | "full-access"): { | ||
| 108 | readonly approvalPolicy: "on-request" | "never"; | ||
| 109 | readonly sandbox: "workspace-write" | "danger-full-access"; | ||
| 110 | } { | ||
| 111 | if (runtimeMode === "approval-required") { | ||
| 112 | return { | ||
| 113 | approvalPolicy: "on-request", | ||
| 114 | sandbox: "workspace-write", | ||
| 115 | }; | ||
| 116 | } | ||
| 117 | |||
| 118 | return { | ||
| 119 | approvalPolicy: "never", | ||
| 120 | sandbox: "danger-full-access", | ||
| 121 | }; | ||
| 122 | } | ||
| 123 | |||
| 107 | /** | 124 | /** |
| 108 | * On Windows with `shell: true`, `child.kill()` only terminates the `cmd.exe` | 125 | * On Windows with `shell: true`, `child.kill()` only terminates the `cmd.exe` |
| 109 | * wrapper, leaving the actual command running. Use `taskkill /T` to kill the | 126 | * wrapper, leaving the actual command running. Use `taskkill /T` to kill the |
| ... | @@ -239,8 +256,7 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve | ... | @@ -239,8 +256,7 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve |
| 239 | const sessionOverrides = { | 256 | const sessionOverrides = { |
| 240 | model: normalizedModel ?? null, | 257 | model: normalizedModel ?? null, |
| 241 | cwd: input.cwd ?? null, | 258 | cwd: input.cwd ?? null, |
| 242 | approvalPolicy: input.approvalPolicy, | 259 | ...mapCodexRuntimeMode(input.runtimeMode ?? "full-access"), |
| 243 | sandbox: input.sandboxMode, | ||
| 244 | }; | 260 | }; |
| 245 | 261 | ||
| 246 | const threadStartParams = { | 262 | const threadStartParams = { |
apps/server/src/orchestration/Layers/CheckpointReactor.test.ts+11-22| ... | @@ -344,8 +344,7 @@ describe("CheckpointReactor", () => { | ... | @@ -344,8 +344,7 @@ describe("CheckpointReactor", () => { |
| 344 | providerName: "codex", | 344 | providerName: "codex", |
| 345 | providerSessionId: asSessionId("sess-1"), | 345 | providerSessionId: asSessionId("sess-1"), |
| 346 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), | 346 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), |
| 347 | approvalPolicy: "on-request", | 347 | runtimeMode: "approval-required", |
| 348 | sandboxMode: "workspace-write", | ||
| 349 | activeTurnId: null, | 348 | activeTurnId: null, |
| 350 | lastError: null, | 349 | lastError: null, |
| 351 | updatedAt: createdAt, | 350 | updatedAt: createdAt, |
| ... | @@ -423,8 +422,7 @@ describe("CheckpointReactor", () => { | ... | @@ -423,8 +422,7 @@ describe("CheckpointReactor", () => { |
| 423 | providerName: "codex", | 422 | providerName: "codex", |
| 424 | providerSessionId: asSessionId("sess-1"), | 423 | providerSessionId: asSessionId("sess-1"), |
| 425 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), | 424 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), |
| 426 | approvalPolicy: "on-request", | 425 | runtimeMode: "approval-required", |
| 427 | sandboxMode: "workspace-write", | ||
| 428 | activeTurnId: asTurnId("turn-main"), | 426 | activeTurnId: asTurnId("turn-main"), |
| 429 | lastError: null, | 427 | lastError: null, |
| 430 | updatedAt: createdAt, | 428 | updatedAt: createdAt, |
| ... | @@ -503,8 +501,7 @@ describe("CheckpointReactor", () => { | ... | @@ -503,8 +501,7 @@ describe("CheckpointReactor", () => { |
| 503 | providerName: "claudeCode", | 501 | providerName: "claudeCode", |
| 504 | providerSessionId: asSessionId("sess-1"), | 502 | providerSessionId: asSessionId("sess-1"), |
| 505 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), | 503 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), |
| 506 | approvalPolicy: "on-request", | 504 | runtimeMode: "approval-required", |
| 507 | sandboxMode: "workspace-write", | ||
| 508 | activeTurnId: null, | 505 | activeTurnId: null, |
| 509 | lastError: null, | 506 | lastError: null, |
| 510 | updatedAt: createdAt, | 507 | updatedAt: createdAt, |
| ... | @@ -566,8 +563,7 @@ describe("CheckpointReactor", () => { | ... | @@ -566,8 +563,7 @@ describe("CheckpointReactor", () => { |
| 566 | providerName: "codex", | 563 | providerName: "codex", |
| 567 | providerSessionId: asSessionId("sess-1"), | 564 | providerSessionId: asSessionId("sess-1"), |
| 568 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), | 565 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), |
| 569 | approvalPolicy: "on-request", | 566 | runtimeMode: "approval-required", |
| 570 | sandboxMode: "workspace-write", | ||
| 571 | activeTurnId: null, | 567 | activeTurnId: null, |
| 572 | lastError: null, | 568 | lastError: null, |
| 573 | updatedAt: createdAt, | 569 | updatedAt: createdAt, |
| ... | @@ -619,8 +615,7 @@ describe("CheckpointReactor", () => { | ... | @@ -619,8 +615,7 @@ describe("CheckpointReactor", () => { |
| 619 | text: "start turn", | 615 | text: "start turn", |
| 620 | attachments: [], | 616 | attachments: [], |
| 621 | }, | 617 | }, |
| 622 | approvalPolicy: "on-request", | 618 | runtimeMode: "approval-required", |
| 623 | sandboxMode: "workspace-write", | ||
| 624 | createdAt: new Date().toISOString(), | 619 | createdAt: new Date().toISOString(), |
| 625 | }), | 620 | }), |
| 626 | ); | 621 | ); |
| ... | @@ -657,8 +652,7 @@ describe("CheckpointReactor", () => { | ... | @@ -657,8 +652,7 @@ describe("CheckpointReactor", () => { |
| 657 | providerName: "codex", | 652 | providerName: "codex", |
| 658 | providerSessionId: asSessionId("sess-missing"), | 653 | providerSessionId: asSessionId("sess-missing"), |
| 659 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-missing"), | 654 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-missing"), |
| 660 | approvalPolicy: "on-request", | 655 | runtimeMode: "approval-required", |
| 661 | sandboxMode: "workspace-write", | ||
| 662 | activeTurnId: asTurnId("turn-missing-cwd"), | 656 | activeTurnId: asTurnId("turn-missing-cwd"), |
| 663 | lastError: null, | 657 | lastError: null, |
| 664 | updatedAt: createdAt, | 658 | updatedAt: createdAt, |
| ... | @@ -707,8 +701,7 @@ describe("CheckpointReactor", () => { | ... | @@ -707,8 +701,7 @@ describe("CheckpointReactor", () => { |
| 707 | providerName: "codex", | 701 | providerName: "codex", |
| 708 | providerSessionId: asSessionId("sess-1"), | 702 | providerSessionId: asSessionId("sess-1"), |
| 709 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), | 703 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), |
| 710 | approvalPolicy: "on-request", | 704 | runtimeMode: "approval-required", |
| 711 | sandboxMode: "workspace-write", | ||
| 712 | activeTurnId: null, | 705 | activeTurnId: null, |
| 713 | lastError: null, | 706 | lastError: null, |
| 714 | updatedAt: createdAt, | 707 | updatedAt: createdAt, |
| ... | @@ -760,8 +753,7 @@ describe("CheckpointReactor", () => { | ... | @@ -760,8 +753,7 @@ describe("CheckpointReactor", () => { |
| 760 | providerName: "codex", | 753 | providerName: "codex", |
| 761 | providerSessionId: asSessionId("sess-1"), | 754 | providerSessionId: asSessionId("sess-1"), |
| 762 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), | 755 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), |
| 763 | approvalPolicy: "on-request", | 756 | runtimeMode: "approval-required", |
| 764 | sandboxMode: "workspace-write", | ||
| 765 | activeTurnId: null, | 757 | activeTurnId: null, |
| 766 | lastError: null, | 758 | lastError: null, |
| 767 | updatedAt: createdAt, | 759 | updatedAt: createdAt, |
| ... | @@ -815,8 +807,7 @@ describe("CheckpointReactor", () => { | ... | @@ -815,8 +807,7 @@ describe("CheckpointReactor", () => { |
| 815 | providerName: "codex", | 807 | providerName: "codex", |
| 816 | providerSessionId: asSessionId("sess-1"), | 808 | providerSessionId: asSessionId("sess-1"), |
| 817 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), | 809 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), |
| 818 | approvalPolicy: "on-request", | 810 | runtimeMode: "approval-required", |
| 819 | sandboxMode: "workspace-write", | ||
| 820 | activeTurnId: null, | 811 | activeTurnId: null, |
| 821 | lastError: null, | 812 | lastError: null, |
| 822 | updatedAt: createdAt, | 813 | updatedAt: createdAt, |
| ... | @@ -896,8 +887,7 @@ describe("CheckpointReactor", () => { | ... | @@ -896,8 +887,7 @@ describe("CheckpointReactor", () => { |
| 896 | providerName: "claudeCode", | 887 | providerName: "claudeCode", |
| 897 | providerSessionId: asSessionId("sess-1"), | 888 | providerSessionId: asSessionId("sess-1"), |
| 898 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), | 889 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), |
| 899 | approvalPolicy: "on-request", | 890 | runtimeMode: "approval-required", |
| 900 | sandboxMode: "workspace-write", | ||
| 901 | activeTurnId: null, | 891 | activeTurnId: null, |
| 902 | lastError: null, | 892 | lastError: null, |
| 903 | updatedAt: createdAt, | 893 | updatedAt: createdAt, |
| ... | @@ -968,8 +958,7 @@ describe("CheckpointReactor", () => { | ... | @@ -968,8 +958,7 @@ describe("CheckpointReactor", () => { |
| 968 | providerName: "codex", | 958 | providerName: "codex", |
| 969 | providerSessionId: asSessionId("sess-1"), | 959 | providerSessionId: asSessionId("sess-1"), |
| 970 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), | 960 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), |
| 971 | approvalPolicy: "on-request", | 961 | runtimeMode: "approval-required", |
| 972 | sandboxMode: "workspace-write", | ||
| 973 | activeTurnId: null, | 962 | activeTurnId: null, |
| 974 | lastError: null, | 963 | lastError: null, |
| 975 | updatedAt: createdAt, | 964 | updatedAt: createdAt, |
apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts+3-6| ... | @@ -96,8 +96,7 @@ describe("OrchestrationEngine", () => { | ... | @@ -96,8 +96,7 @@ describe("OrchestrationEngine", () => { |
| 96 | text: "hello", | 96 | text: "hello", |
| 97 | attachments: [], | 97 | attachments: [], |
| 98 | }, | 98 | }, |
| 99 | approvalPolicy: "on-request", | 99 | runtimeMode: "approval-required", |
| 100 | sandboxMode: "workspace-write", | ||
| 101 | createdAt, | 100 | createdAt, |
| 102 | }), | 101 | }), |
| 103 | ); | 102 | ); |
| ... | @@ -436,8 +435,7 @@ describe("OrchestrationEngine", () => { | ... | @@ -436,8 +435,7 @@ describe("OrchestrationEngine", () => { |
| 436 | text: "hello", | 435 | text: "hello", |
| 437 | attachments: [], | 436 | attachments: [], |
| 438 | }, | 437 | }, |
| 439 | approvalPolicy: "on-request" as const, | 438 | runtimeMode: "approval-required" as const, |
| 440 | sandboxMode: "workspace-write" as const, | ||
| 441 | createdAt, | 439 | createdAt, |
| 442 | }; | 440 | }; |
| 443 | 441 | ||
| ... | @@ -596,8 +594,7 @@ describe("OrchestrationEngine", () => { | ... | @@ -596,8 +594,7 @@ describe("OrchestrationEngine", () => { |
| 596 | text: "hello", | 594 | text: "hello", |
| 597 | attachments: [], | 595 | attachments: [], |
| 598 | }, | 596 | }, |
| 599 | approvalPolicy: "on-request", | 597 | runtimeMode: "approval-required", |
| 600 | sandboxMode: "workspace-write", | ||
| 601 | createdAt: now(), | 598 | createdAt: now(), |
| 602 | }), | 599 | }), |
| 603 | ), | 600 | ), |
apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts+2-4| ... | @@ -1749,8 +1749,7 @@ it.effect("restores pending turn-start metadata across projection pipeline resta | ... | @@ -1749,8 +1749,7 @@ it.effect("restores pending turn-start metadata across projection pipeline resta |
| 1749 | payload: { | 1749 | payload: { |
| 1750 | threadId, | 1750 | threadId, |
| 1751 | messageId, | 1751 | messageId, |
| 1752 | approvalPolicy: "on-request", | 1752 | runtimeMode: "approval-required", |
| 1753 | sandboxMode: "workspace-write", | ||
| 1754 | createdAt: turnStartedAt, | 1753 | createdAt: turnStartedAt, |
| 1755 | }, | 1754 | }, |
| 1756 | }); | 1755 | }); |
| ... | @@ -1784,8 +1783,7 @@ it.effect("restores pending turn-start metadata across projection pipeline resta | ... | @@ -1784,8 +1783,7 @@ it.effect("restores pending turn-start metadata across projection pipeline resta |
| 1784 | providerName: "codex", | 1783 | providerName: "codex", |
| 1785 | providerSessionId: ProviderSessionId.makeUnsafe("provider-session-restart"), | 1784 | providerSessionId: ProviderSessionId.makeUnsafe("provider-session-restart"), |
| 1786 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-restart"), | 1785 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-restart"), |
| 1787 | approvalPolicy: "on-request", | 1786 | runtimeMode: "approval-required", |
| 1788 | sandboxMode: "workspace-write", | ||
| 1789 | activeTurnId: turnId, | 1787 | activeTurnId: turnId, |
| 1790 | lastError: null, | 1788 | lastError: null, |
| 1791 | updatedAt: sessionSetAt, | 1789 | updatedAt: sessionSetAt, |
apps/server/src/orchestration/Layers/ProjectionPipeline.ts+1-2| ... | @@ -651,8 +651,7 @@ const makeOrchestrationProjectionPipeline = Effect.gen(function* () { | ... | @@ -651,8 +651,7 @@ const makeOrchestrationProjectionPipeline = Effect.gen(function* () { |
| 651 | providerName: event.payload.session.providerName, | 651 | providerName: event.payload.session.providerName, |
| 652 | providerSessionId: event.payload.session.providerSessionId, | 652 | providerSessionId: event.payload.session.providerSessionId, |
| 653 | providerThreadId: event.payload.session.providerThreadId, | 653 | providerThreadId: event.payload.session.providerThreadId, |
| 654 | approvalPolicy: event.payload.session.approvalPolicy, | 654 | runtimeMode: event.payload.session.runtimeMode, |
| 655 | sandboxMode: event.payload.session.sandboxMode, | ||
| 656 | activeTurnId: event.payload.session.activeTurnId, | 655 | activeTurnId: event.payload.session.activeTurnId, |
| 657 | lastError: event.payload.session.lastError, | 656 | lastError: event.payload.session.lastError, |
| 658 | updatedAt: event.payload.session.updatedAt, | 657 | updatedAt: event.payload.session.updatedAt, |
apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts+3-6| ... | @@ -143,8 +143,7 @@ projectionSnapshotLayer("ProjectionSnapshotQuery", (it) => { | ... | @@ -143,8 +143,7 @@ projectionSnapshotLayer("ProjectionSnapshotQuery", (it) => { |
| 143 | provider_name, | 143 | provider_name, |
| 144 | provider_session_id, | 144 | provider_session_id, |
| 145 | provider_thread_id, | 145 | provider_thread_id, |
| 146 | approval_policy, | 146 | runtime_mode, |
| 147 | sandbox_mode, | ||
| 148 | active_turn_id, | 147 | active_turn_id, |
| 149 | last_error, | 148 | last_error, |
| 150 | updated_at | 149 | updated_at |
| ... | @@ -155,8 +154,7 @@ projectionSnapshotLayer("ProjectionSnapshotQuery", (it) => { | ... | @@ -155,8 +154,7 @@ projectionSnapshotLayer("ProjectionSnapshotQuery", (it) => { |
| 155 | 'codex', | 154 | 'codex', |
| 156 | 'provider-session-1', | 155 | 'provider-session-1', |
| 157 | 'provider-thread-1', | 156 | 'provider-thread-1', |
| 158 | 'on-request', | 157 | 'approval-required', |
| 159 | 'workspace-write', | ||
| 160 | 'turn-1', | 158 | 'turn-1', |
| 161 | NULL, | 159 | NULL, |
| 162 | '2026-02-24T00:00:07.000Z' | 160 | '2026-02-24T00:00:07.000Z' |
| ... | @@ -293,8 +291,7 @@ projectionSnapshotLayer("ProjectionSnapshotQuery", (it) => { | ... | @@ -293,8 +291,7 @@ projectionSnapshotLayer("ProjectionSnapshotQuery", (it) => { |
| 293 | providerName: "codex", | 291 | providerName: "codex", |
| 294 | providerSessionId: asProviderSessionId("provider-session-1"), | 292 | providerSessionId: asProviderSessionId("provider-session-1"), |
| 295 | providerThreadId: asProviderThreadId("provider-thread-1"), | 293 | providerThreadId: asProviderThreadId("provider-thread-1"), |
| 296 | approvalPolicy: "on-request", | 294 | runtimeMode: "approval-required", |
| 297 | sandboxMode: "workspace-write", | ||
| 298 | activeTurnId: asTurnId("turn-1"), | 295 | activeTurnId: asTurnId("turn-1"), |
| 299 | lastError: null, | 296 | lastError: null, |
| 300 | updatedAt: "2026-02-24T00:00:07.000Z", | 297 | updatedAt: "2026-02-24T00:00:07.000Z", |
apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts+2-4| ... | @@ -220,8 +220,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { | ... | @@ -220,8 +220,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { |
| 220 | provider_name AS "providerName", | 220 | provider_name AS "providerName", |
| 221 | provider_session_id AS "providerSessionId", | 221 | provider_session_id AS "providerSessionId", |
| 222 | provider_thread_id AS "providerThreadId", | 222 | provider_thread_id AS "providerThreadId", |
| 223 | approval_policy AS "approvalPolicy", | 223 | runtime_mode AS "runtimeMode", |
| 224 | sandbox_mode AS "sandboxMode", | ||
| 225 | active_turn_id AS "activeTurnId", | 224 | active_turn_id AS "activeTurnId", |
| 226 | last_error AS "lastError", | 225 | last_error AS "lastError", |
| 227 | updated_at AS "updatedAt" | 226 | updated_at AS "updatedAt" |
| ... | @@ -463,8 +462,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { | ... | @@ -463,8 +462,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { |
| 463 | providerName: row.providerName, | 462 | providerName: row.providerName, |
| 464 | providerSessionId: row.providerSessionId, | 463 | providerSessionId: row.providerSessionId, |
| 465 | providerThreadId: row.providerThreadId, | 464 | providerThreadId: row.providerThreadId, |
| 466 | approvalPolicy: row.approvalPolicy, | 465 | runtimeMode: row.runtimeMode, |
| 467 | sandboxMode: row.sandboxMode, | ||
| 468 | activeTurnId: row.activeTurnId, | 466 | activeTurnId: row.activeTurnId, |
| 469 | lastError: row.lastError, | 467 | lastError: row.lastError, |
| 470 | updatedAt: row.updatedAt, | 468 | updatedAt: row.updatedAt, |
apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts+28-56| ... | @@ -260,8 +260,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -260,8 +260,7 @@ describe("ProviderCommandReactor", () => { |
| 260 | text: "hello reactor", | 260 | text: "hello reactor", |
| 261 | attachments: [], | 261 | attachments: [], |
| 262 | }, | 262 | }, |
| 263 | approvalPolicy: "on-request", | 263 | runtimeMode: "approval-required", |
| 264 | sandboxMode: "workspace-write", | ||
| 265 | createdAt: now, | 264 | createdAt: now, |
| 266 | }), | 265 | }), |
| 267 | ); | 266 | ); |
| ... | @@ -272,15 +271,13 @@ describe("ProviderCommandReactor", () => { | ... | @@ -272,15 +271,13 @@ describe("ProviderCommandReactor", () => { |
| 272 | expect(harness.startSession.mock.calls[0]?.[1]).toMatchObject({ | 271 | expect(harness.startSession.mock.calls[0]?.[1]).toMatchObject({ |
| 273 | cwd: "/tmp/provider-project", | 272 | cwd: "/tmp/provider-project", |
| 274 | model: "gpt-5-codex", | 273 | model: "gpt-5-codex", |
| 275 | approvalPolicy: "on-request", | 274 | runtimeMode: "approval-required", |
| 276 | sandboxMode: "workspace-write", | ||
| 277 | }); | 275 | }); |
| 278 | 276 | ||
| 279 | const readModel = await Effect.runPromise(harness.engine.getReadModel()); | 277 | const readModel = await Effect.runPromise(harness.engine.getReadModel()); |
| 280 | const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1")); | 278 | const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1")); |
| 281 | expect(thread?.session?.providerSessionId).toBe("sess-1"); | 279 | expect(thread?.session?.providerSessionId).toBe("sess-1"); |
| 282 | expect(thread?.session?.approvalPolicy).toBe("on-request"); | 280 | expect(thread?.session?.runtimeMode).toBe("approval-required"); |
| 283 | expect(thread?.session?.sandboxMode).toBe("workspace-write"); | ||
| 284 | }); | 281 | }); |
| 285 | 282 | ||
| 286 | it("starts first turn with requested provider when provider is specified", async () => { | 283 | it("starts first turn with requested provider when provider is specified", async () => { |
| ... | @@ -299,8 +296,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -299,8 +296,7 @@ describe("ProviderCommandReactor", () => { |
| 299 | attachments: [], | 296 | attachments: [], |
| 300 | }, | 297 | }, |
| 301 | provider: "claudeCode", | 298 | provider: "claudeCode", |
| 302 | approvalPolicy: "on-request", | 299 | runtimeMode: "approval-required", |
| 303 | sandboxMode: "workspace-write", | ||
| 304 | createdAt: now, | 300 | createdAt: now, |
| 305 | }), | 301 | }), |
| 306 | ); | 302 | ); |
| ... | @@ -311,8 +307,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -311,8 +307,7 @@ describe("ProviderCommandReactor", () => { |
| 311 | provider: "claudeCode", | 307 | provider: "claudeCode", |
| 312 | cwd: "/tmp/provider-project", | 308 | cwd: "/tmp/provider-project", |
| 313 | model: "gpt-5-codex", | 309 | model: "gpt-5-codex", |
| 314 | approvalPolicy: "on-request", | 310 | runtimeMode: "approval-required", |
| 315 | sandboxMode: "workspace-write", | ||
| 316 | }); | 311 | }); |
| 317 | 312 | ||
| 318 | const readModel = await Effect.runPromise(harness.engine.getReadModel()); | 313 | const readModel = await Effect.runPromise(harness.engine.getReadModel()); |
| ... | @@ -337,8 +332,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -337,8 +332,7 @@ describe("ProviderCommandReactor", () => { |
| 337 | attachments: [], | 332 | attachments: [], |
| 338 | }, | 333 | }, |
| 339 | provider: "cursor", | 334 | provider: "cursor", |
| 340 | approvalPolicy: "on-request", | 335 | runtimeMode: "approval-required", |
| 341 | sandboxMode: "workspace-write", | ||
| 342 | createdAt: now, | 336 | createdAt: now, |
| 343 | }), | 337 | }), |
| 344 | ); | 338 | ); |
| ... | @@ -349,8 +343,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -349,8 +343,7 @@ describe("ProviderCommandReactor", () => { |
| 349 | provider: "cursor", | 343 | provider: "cursor", |
| 350 | cwd: "/tmp/provider-project", | 344 | cwd: "/tmp/provider-project", |
| 351 | model: "gpt-5-codex", | 345 | model: "gpt-5-codex", |
| 352 | approvalPolicy: "on-request", | 346 | runtimeMode: "approval-required", |
| 353 | sandboxMode: "workspace-write", | ||
| 354 | }); | 347 | }); |
| 355 | 348 | ||
| 356 | const readModel = await Effect.runPromise(harness.engine.getReadModel()); | 349 | const readModel = await Effect.runPromise(harness.engine.getReadModel()); |
| ... | @@ -374,8 +367,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -374,8 +367,7 @@ describe("ProviderCommandReactor", () => { |
| 374 | text: "first", | 367 | text: "first", |
| 375 | attachments: [], | 368 | attachments: [], |
| 376 | }, | 369 | }, |
| 377 | approvalPolicy: "on-request", | 370 | runtimeMode: "approval-required", |
| 378 | sandboxMode: "workspace-write", | ||
| 379 | createdAt: now, | 371 | createdAt: now, |
| 380 | }), | 372 | }), |
| 381 | ); | 373 | ); |
| ... | @@ -394,8 +386,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -394,8 +386,7 @@ describe("ProviderCommandReactor", () => { |
| 394 | text: "second", | 386 | text: "second", |
| 395 | attachments: [], | 387 | attachments: [], |
| 396 | }, | 388 | }, |
| 397 | approvalPolicy: "on-request", | 389 | runtimeMode: "approval-required", |
| 398 | sandboxMode: "workspace-write", | ||
| 399 | createdAt: now, | 390 | createdAt: now, |
| 400 | }), | 391 | }), |
| 401 | ); | 392 | ); |
| ... | @@ -422,8 +413,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -422,8 +413,7 @@ describe("ProviderCommandReactor", () => { |
| 422 | }, | 413 | }, |
| 423 | provider: "cursor", | 414 | provider: "cursor", |
| 424 | model: "composer-1.5", | 415 | model: "composer-1.5", |
| 425 | approvalPolicy: "on-request", | 416 | runtimeMode: "approval-required", |
| 426 | sandboxMode: "workspace-write", | ||
| 427 | createdAt: now, | 417 | createdAt: now, |
| 428 | }), | 418 | }), |
| 429 | ); | 419 | ); |
| ... | @@ -444,8 +434,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -444,8 +434,7 @@ describe("ProviderCommandReactor", () => { |
| 444 | }, | 434 | }, |
| 445 | provider: "cursor", | 435 | provider: "cursor", |
| 446 | model: "composer-1.5", | 436 | model: "composer-1.5", |
| 447 | approvalPolicy: "on-request", | 437 | runtimeMode: "approval-required", |
| 448 | sandboxMode: "workspace-write", | ||
| 449 | createdAt: now, | 438 | createdAt: now, |
| 450 | }), | 439 | }), |
| 451 | ); | 440 | ); |
| ... | @@ -472,8 +461,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -472,8 +461,7 @@ describe("ProviderCommandReactor", () => { |
| 472 | }, | 461 | }, |
| 473 | provider: "cursor", | 462 | provider: "cursor", |
| 474 | model: "gpt-5.3-codex", | 463 | model: "gpt-5.3-codex", |
| 475 | approvalPolicy: "on-request", | 464 | runtimeMode: "approval-required", |
| 476 | sandboxMode: "workspace-write", | ||
| 477 | createdAt: now, | 465 | createdAt: now, |
| 478 | }), | 466 | }), |
| 479 | ); | 467 | ); |
| ... | @@ -494,8 +482,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -494,8 +482,7 @@ describe("ProviderCommandReactor", () => { |
| 494 | }, | 482 | }, |
| 495 | provider: "cursor", | 483 | provider: "cursor", |
| 496 | model: "composer-1.5", | 484 | model: "composer-1.5", |
| 497 | approvalPolicy: "on-request", | 485 | runtimeMode: "approval-required", |
| 498 | sandboxMode: "workspace-write", | ||
| 499 | createdAt: now, | 486 | createdAt: now, |
| 500 | }), | 487 | }), |
| 501 | ); | 488 | ); |
| ... | @@ -525,8 +512,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -525,8 +512,7 @@ describe("ProviderCommandReactor", () => { |
| 525 | text: "first", | 512 | text: "first", |
| 526 | attachments: [], | 513 | attachments: [], |
| 527 | }, | 514 | }, |
| 528 | approvalPolicy: "never", | 515 | runtimeMode: "full-access", |
| 529 | sandboxMode: "danger-full-access", | ||
| 530 | createdAt: now, | 516 | createdAt: now, |
| 531 | }), | 517 | }), |
| 532 | ); | 518 | ); |
| ... | @@ -545,8 +531,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -545,8 +531,7 @@ describe("ProviderCommandReactor", () => { |
| 545 | text: "second", | 531 | text: "second", |
| 546 | attachments: [], | 532 | attachments: [], |
| 547 | }, | 533 | }, |
| 548 | approvalPolicy: "on-request", | 534 | runtimeMode: "approval-required", |
| 549 | sandboxMode: "workspace-write", | ||
| 550 | createdAt: now, | 535 | createdAt: now, |
| 551 | }), | 536 | }), |
| 552 | ); | 537 | ); |
| ... | @@ -558,8 +543,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -558,8 +543,7 @@ describe("ProviderCommandReactor", () => { |
| 558 | expect(harness.stopSession.mock.calls[0]?.[0]).toEqual({ sessionId: asSessionId("sess-1") }); | 543 | expect(harness.stopSession.mock.calls[0]?.[0]).toEqual({ sessionId: asSessionId("sess-1") }); |
| 559 | expect(harness.startSession.mock.calls[1]?.[1]).toMatchObject({ | 544 | expect(harness.startSession.mock.calls[1]?.[1]).toMatchObject({ |
| 560 | resumeCursor: { opaque: "cursor-1" }, | 545 | resumeCursor: { opaque: "cursor-1" }, |
| 561 | approvalPolicy: "on-request", | 546 | runtimeMode: "approval-required", |
| 562 | sandboxMode: "workspace-write", | ||
| 563 | }); | 547 | }); |
| 564 | expect(harness.sendTurn.mock.calls[1]?.[0]).toMatchObject({ | 548 | expect(harness.sendTurn.mock.calls[1]?.[0]).toMatchObject({ |
| 565 | sessionId: asSessionId("sess-2"), | 549 | sessionId: asSessionId("sess-2"), |
| ... | @@ -568,8 +552,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -568,8 +552,7 @@ describe("ProviderCommandReactor", () => { |
| 568 | const readModel = await Effect.runPromise(harness.engine.getReadModel()); | 552 | const readModel = await Effect.runPromise(harness.engine.getReadModel()); |
| 569 | const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1")); | 553 | const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1")); |
| 570 | expect(thread?.session?.providerSessionId).toBe("sess-2"); | 554 | expect(thread?.session?.providerSessionId).toBe("sess-2"); |
| 571 | expect(thread?.session?.approvalPolicy).toBe("on-request"); | 555 | expect(thread?.session?.runtimeMode).toBe("approval-required"); |
| 572 | expect(thread?.session?.sandboxMode).toBe("workspace-write"); | ||
| 573 | }); | 556 | }); |
| 574 | 557 | ||
| 575 | it("switches provider by restarting the session when turn request provider changes", async () => { | 558 | it("switches provider by restarting the session when turn request provider changes", async () => { |
| ... | @@ -587,8 +570,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -587,8 +570,7 @@ describe("ProviderCommandReactor", () => { |
| 587 | text: "first", | 570 | text: "first", |
| 588 | attachments: [], | 571 | attachments: [], |
| 589 | }, | 572 | }, |
| 590 | approvalPolicy: "on-request", | 573 | runtimeMode: "approval-required", |
| 591 | sandboxMode: "workspace-write", | ||
| 592 | createdAt: now, | 574 | createdAt: now, |
| 593 | }), | 575 | }), |
| 594 | ); | 576 | ); |
| ... | @@ -608,8 +590,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -608,8 +590,7 @@ describe("ProviderCommandReactor", () => { |
| 608 | attachments: [], | 590 | attachments: [], |
| 609 | }, | 591 | }, |
| 610 | provider: "claudeCode", | 592 | provider: "claudeCode", |
| 611 | approvalPolicy: "on-request", | 593 | runtimeMode: "approval-required", |
| 612 | sandboxMode: "workspace-write", | ||
| 613 | createdAt: now, | 594 | createdAt: now, |
| 614 | }), | 595 | }), |
| 615 | ); | 596 | ); |
| ... | @@ -621,8 +602,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -621,8 +602,7 @@ describe("ProviderCommandReactor", () => { |
| 621 | expect(harness.stopSession.mock.calls[0]?.[0]).toEqual({ sessionId: asSessionId("sess-1") }); | 602 | expect(harness.stopSession.mock.calls[0]?.[0]).toEqual({ sessionId: asSessionId("sess-1") }); |
| 622 | expect(harness.startSession.mock.calls[1]?.[1]).toMatchObject({ | 603 | expect(harness.startSession.mock.calls[1]?.[1]).toMatchObject({ |
| 623 | provider: "claudeCode", | 604 | provider: "claudeCode", |
| 624 | approvalPolicy: "on-request", | 605 | runtimeMode: "approval-required", |
| 625 | sandboxMode: "workspace-write", | ||
| 626 | }); | 606 | }); |
| 627 | expect(harness.startSession.mock.calls[1]?.[1]).not.toHaveProperty("resumeCursor"); | 607 | expect(harness.startSession.mock.calls[1]?.[1]).not.toHaveProperty("resumeCursor"); |
| 628 | 608 | ||
| ... | @@ -630,8 +610,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -630,8 +610,7 @@ describe("ProviderCommandReactor", () => { |
| 630 | const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1")); | 610 | const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1")); |
| 631 | expect(thread?.session?.providerSessionId).toBe("sess-2"); | 611 | expect(thread?.session?.providerSessionId).toBe("sess-2"); |
| 632 | expect(thread?.session?.providerName).toBe("claudeCode"); | 612 | expect(thread?.session?.providerName).toBe("claudeCode"); |
| 633 | expect(thread?.session?.approvalPolicy).toBe("on-request"); | 613 | expect(thread?.session?.runtimeMode).toBe("approval-required"); |
| 634 | expect(thread?.session?.sandboxMode).toBe("workspace-write"); | ||
| 635 | }); | 614 | }); |
| 636 | 615 | ||
| 637 | it("does not stop the active session when restart fails before rebind", async () => { | 616 | it("does not stop the active session when restart fails before rebind", async () => { |
| ... | @@ -649,8 +628,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -649,8 +628,7 @@ describe("ProviderCommandReactor", () => { |
| 649 | text: "first", | 628 | text: "first", |
| 650 | attachments: [], | 629 | attachments: [], |
| 651 | }, | 630 | }, |
| 652 | approvalPolicy: "never", | 631 | runtimeMode: "full-access", |
| 653 | sandboxMode: "danger-full-access", | ||
| 654 | createdAt: now, | 632 | createdAt: now, |
| 655 | }), | 633 | }), |
| 656 | ); | 634 | ); |
| ... | @@ -673,8 +651,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -673,8 +651,7 @@ describe("ProviderCommandReactor", () => { |
| 673 | text: "second", | 651 | text: "second", |
| 674 | attachments: [], | 652 | attachments: [], |
| 675 | }, | 653 | }, |
| 676 | approvalPolicy: "on-request", | 654 | runtimeMode: "approval-required", |
| 677 | sandboxMode: "workspace-write", | ||
| 678 | createdAt: now, | 655 | createdAt: now, |
| 679 | }), | 656 | }), |
| 680 | ); | 657 | ); |
| ... | @@ -688,8 +665,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -688,8 +665,7 @@ describe("ProviderCommandReactor", () => { |
| 688 | const readModel = await Effect.runPromise(harness.engine.getReadModel()); | 665 | const readModel = await Effect.runPromise(harness.engine.getReadModel()); |
| 689 | const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1")); | 666 | const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1")); |
| 690 | expect(thread?.session?.providerSessionId).toBe("sess-1"); | 667 | expect(thread?.session?.providerSessionId).toBe("sess-1"); |
| 691 | expect(thread?.session?.approvalPolicy).toBe("never"); | 668 | expect(thread?.session?.runtimeMode).toBe("full-access"); |
| 692 | expect(thread?.session?.sandboxMode).toBe("danger-full-access"); | ||
| 693 | }); | 669 | }); |
| 694 | 670 | ||
| 695 | it("reacts to thread.turn.interrupt-requested by calling provider interrupt", async () => { | 671 | it("reacts to thread.turn.interrupt-requested by calling provider interrupt", async () => { |
| ... | @@ -707,8 +683,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -707,8 +683,7 @@ describe("ProviderCommandReactor", () => { |
| 707 | providerName: "codex", | 683 | providerName: "codex", |
| 708 | providerSessionId: asSessionId("sess-1"), | 684 | providerSessionId: asSessionId("sess-1"), |
| 709 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), | 685 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), |
| 710 | approvalPolicy: "on-request", | 686 | runtimeMode: "approval-required", |
| 711 | sandboxMode: "workspace-write", | ||
| 712 | activeTurnId: asTurnId("turn-1"), | 687 | activeTurnId: asTurnId("turn-1"), |
| 713 | lastError: null, | 688 | lastError: null, |
| 714 | updatedAt: now, | 689 | updatedAt: now, |
| ... | @@ -748,8 +723,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -748,8 +723,7 @@ describe("ProviderCommandReactor", () => { |
| 748 | providerName: "codex", | 723 | providerName: "codex", |
| 749 | providerSessionId: asSessionId("sess-1"), | 724 | providerSessionId: asSessionId("sess-1"), |
| 750 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), | 725 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), |
| 751 | approvalPolicy: "on-request", | 726 | runtimeMode: "approval-required", |
| 752 | sandboxMode: "workspace-write", | ||
| 753 | activeTurnId: null, | 727 | activeTurnId: null, |
| 754 | lastError: null, | 728 | lastError: null, |
| 755 | updatedAt: now, | 729 | updatedAt: now, |
| ... | @@ -801,8 +775,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -801,8 +775,7 @@ describe("ProviderCommandReactor", () => { |
| 801 | providerName: "cursor", | 775 | providerName: "cursor", |
| 802 | providerSessionId: asSessionId("sess-1"), | 776 | providerSessionId: asSessionId("sess-1"), |
| 803 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), | 777 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), |
| 804 | approvalPolicy: "on-request", | 778 | runtimeMode: "approval-required", |
| 805 | sandboxMode: "workspace-write", | ||
| 806 | activeTurnId: null, | 779 | activeTurnId: null, |
| 807 | lastError: null, | 780 | lastError: null, |
| 808 | updatedAt: now, | 781 | updatedAt: now, |
| ... | @@ -887,8 +860,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -887,8 +860,7 @@ describe("ProviderCommandReactor", () => { |
| 887 | providerName: "codex", | 860 | providerName: "codex", |
| 888 | providerSessionId: asSessionId("sess-1"), | 861 | providerSessionId: asSessionId("sess-1"), |
| 889 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), | 862 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), |
| 890 | approvalPolicy: "on-request", | 863 | runtimeMode: "approval-required", |
| 891 | sandboxMode: "workspace-write", | ||
| 892 | activeTurnId: null, | 864 | activeTurnId: null, |
| 893 | lastError: null, | 865 | lastError: null, |
| 894 | updatedAt: now, | 866 | updatedAt: now, |
apps/server/src/orchestration/Layers/ProviderCommandReactor.ts+14-29| ... | @@ -3,13 +3,12 @@ import { | ... | @@ -3,13 +3,12 @@ import { |
| 3 | CommandId, | 3 | CommandId, |
| 4 | EventId, | 4 | EventId, |
| 5 | type OrchestrationEvent, | 5 | type OrchestrationEvent, |
| 6 | type ProviderApprovalPolicy, | ||
| 7 | type ProviderKind, | 6 | type ProviderKind, |
| 8 | type ProviderSandboxMode, | ||
| 9 | type ProviderSessionId, | 7 | type ProviderSessionId, |
| 10 | type OrchestrationSession, | 8 | type OrchestrationSession, |
| 11 | type ThreadId, | 9 | type ThreadId, |
| 12 | type ProviderSession, | 10 | type ProviderSession, |
| 11 | type RuntimeMode, | ||
| 13 | type TurnId, | 12 | type TurnId, |
| 14 | } from "@t3tools/contracts"; | 13 | } from "@t3tools/contracts"; |
| 15 | import { Cache, Cause, Duration, Effect, Layer, Option, Queue, Schema, Stream } from "effect"; | 14 | import { Cache, Cause, Duration, Effect, Layer, Option, Queue, Schema, Stream } from "effect"; |
| ... | @@ -67,8 +66,7 @@ const serverCommandId = (tag: string): CommandId => | ... | @@ -67,8 +66,7 @@ const serverCommandId = (tag: string): CommandId => |
| 67 | 66 | ||
| 68 | const HANDLED_TURN_START_KEY_MAX = 10_000; | 67 | const HANDLED_TURN_START_KEY_MAX = 10_000; |
| 69 | const HANDLED_TURN_START_KEY_TTL = Duration.minutes(30); | 68 | const HANDLED_TURN_START_KEY_TTL = Duration.minutes(30); |
| 70 | const DEFAULT_APPROVAL_POLICY: ProviderApprovalPolicy = "never"; | 69 | const DEFAULT_RUNTIME_MODE: RuntimeMode = "full-access"; |
| 71 | const DEFAULT_SANDBOX_MODE: ProviderSandboxMode = "workspace-write"; | ||
| 72 | const WORKTREE_BRANCH_PREFIX = "t3code"; | 70 | const WORKTREE_BRANCH_PREFIX = "t3code"; |
| 73 | const TEMP_WORKTREE_BRANCH_PATTERN = new RegExp(`^${WORKTREE_BRANCH_PREFIX}\\/[0-9a-f]{8}$`); | 71 | const TEMP_WORKTREE_BRANCH_PATTERN = new RegExp(`^${WORKTREE_BRANCH_PREFIX}\\/[0-9a-f]{8}$`); |
| 74 | 72 | ||
| ... | @@ -199,8 +197,7 @@ const make = Effect.gen(function* () { | ... | @@ -199,8 +197,7 @@ const make = Effect.gen(function* () { |
| 199 | options?: { | 197 | options?: { |
| 200 | readonly provider?: ProviderKind; | 198 | readonly provider?: ProviderKind; |
| 201 | readonly model?: string; | 199 | readonly model?: string; |
| 202 | readonly approvalPolicy?: ProviderApprovalPolicy; | 200 | readonly runtimeMode?: RuntimeMode; |
| 203 | readonly sandboxMode?: ProviderSandboxMode; | ||
| 204 | }, | 201 | }, |
| 205 | ) { | 202 | ) { |
| 206 | const readModel = yield* orchestrationEngine.getReadModel(); | 203 | const readModel = yield* orchestrationEngine.getReadModel(); |
| ... | @@ -209,10 +206,8 @@ const make = Effect.gen(function* () { | ... | @@ -209,10 +206,8 @@ const make = Effect.gen(function* () { |
| 209 | return yield* Effect.die(new Error(`Thread '${threadId}' was not found in read model.`)); | 206 | return yield* Effect.die(new Error(`Thread '${threadId}' was not found in read model.`)); |
| 210 | } | 207 | } |
| 211 | 208 | ||
| 212 | const desiredApprovalPolicy = | 209 | const desiredRuntimeMode = |
| 213 | options?.approvalPolicy ?? thread.session?.approvalPolicy ?? DEFAULT_APPROVAL_POLICY; | 210 | options?.runtimeMode ?? thread.session?.runtimeMode ?? DEFAULT_RUNTIME_MODE; |
| 214 | const desiredSandboxMode = | ||
| 215 | options?.sandboxMode ?? thread.session?.sandboxMode ?? DEFAULT_SANDBOX_MODE; | ||
| 216 | const currentProvider: ProviderKind | undefined = | 211 | const currentProvider: ProviderKind | undefined = |
| 217 | thread.session?.providerName === "codex" || | 212 | thread.session?.providerName === "codex" || |
| 218 | thread.session?.providerName === "claudeCode" || | 213 | thread.session?.providerName === "claudeCode" || |
| ... | @@ -242,8 +237,7 @@ const make = Effect.gen(function* () { | ... | @@ -242,8 +237,7 @@ const make = Effect.gen(function* () { |
| 242 | ...(effectiveCwd ? { cwd: effectiveCwd } : {}), | 237 | ...(effectiveCwd ? { cwd: effectiveCwd } : {}), |
| 243 | ...(desiredModel ? { model: desiredModel } : {}), | 238 | ...(desiredModel ? { model: desiredModel } : {}), |
| 244 | ...(input?.resumeCursor !== undefined ? { resumeCursor: input.resumeCursor } : {}), | 239 | ...(input?.resumeCursor !== undefined ? { resumeCursor: input.resumeCursor } : {}), |
| 245 | approvalPolicy: desiredApprovalPolicy, | 240 | runtimeMode: desiredRuntimeMode, |
| 246 | sandboxMode: desiredSandboxMode, | ||
| 247 | }); | 241 | }); |
| 248 | 242 | ||
| 249 | const bindSessionToThread = (session: ProviderSession) => | 243 | const bindSessionToThread = (session: ProviderSession) => |
| ... | @@ -255,8 +249,7 @@ const make = Effect.gen(function* () { | ... | @@ -255,8 +249,7 @@ const make = Effect.gen(function* () { |
| 255 | providerName: session.provider, | 249 | providerName: session.provider, |
| 256 | providerSessionId: session.sessionId, | 250 | providerSessionId: session.sessionId, |
| 257 | providerThreadId: session.threadId ?? null, | 251 | providerThreadId: session.threadId ?? null, |
| 258 | approvalPolicy: desiredApprovalPolicy, | 252 | runtimeMode: desiredRuntimeMode, |
| 259 | sandboxMode: desiredSandboxMode, | ||
| 260 | // Provider turn ids are not orchestration turn ids. | 253 | // Provider turn ids are not orchestration turn ids. |
| 261 | activeTurnId: null, | 254 | activeTurnId: null, |
| 262 | lastError: session.lastError ?? null, | 255 | lastError: session.lastError ?? null, |
| ... | @@ -267,11 +260,8 @@ const make = Effect.gen(function* () { | ... | @@ -267,11 +260,8 @@ const make = Effect.gen(function* () { |
| 267 | 260 | ||
| 268 | const existingSessionId = thread.session?.providerSessionId; | 261 | const existingSessionId = thread.session?.providerSessionId; |
| 269 | if (existingSessionId) { | 262 | if (existingSessionId) { |
| 270 | const approvalPolicyChanged = | 263 | const runtimeModeChanged = |
| 271 | options?.approvalPolicy !== undefined && | 264 | options?.runtimeMode !== undefined && options.runtimeMode !== thread.session?.runtimeMode; |
| 272 | options.approvalPolicy !== thread.session?.approvalPolicy; | ||
| 273 | const sandboxModeChanged = | ||
| 274 | options?.sandboxMode !== undefined && options.sandboxMode !== thread.session?.sandboxMode; | ||
| 275 | const providerChanged = options?.provider !== undefined && options.provider !== currentProvider; | 265 | const providerChanged = options?.provider !== undefined && options.provider !== currentProvider; |
| 276 | const activeSession = yield* resolveActiveSession(existingSessionId); | 266 | const activeSession = yield* resolveActiveSession(existingSessionId); |
| 277 | const sessionModelSwitch = | 267 | const sessionModelSwitch = |
| ... | @@ -284,8 +274,7 @@ const make = Effect.gen(function* () { | ... | @@ -284,8 +274,7 @@ const make = Effect.gen(function* () { |
| 284 | modelChanged && sessionModelSwitch === "restart-session"; | 274 | modelChanged && sessionModelSwitch === "restart-session"; |
| 285 | 275 | ||
| 286 | if ( | 276 | if ( |
| 287 | !approvalPolicyChanged && | 277 | !runtimeModeChanged && |
| 288 | !sandboxModeChanged && | ||
| 289 | !providerChanged && | 278 | !providerChanged && |
| 290 | !shouldRestartForModelChange | 279 | !shouldRestartForModelChange |
| 291 | ) { | 280 | ) { |
| ... | @@ -327,8 +316,7 @@ const make = Effect.gen(function* () { | ... | @@ -327,8 +316,7 @@ const make = Effect.gen(function* () { |
| 327 | readonly provider?: ProviderKind; | 316 | readonly provider?: ProviderKind; |
| 328 | readonly model?: string; | 317 | readonly model?: string; |
| 329 | readonly effort?: string; | 318 | readonly effort?: string; |
| 330 | readonly approvalPolicy: ProviderApprovalPolicy; | 319 | readonly runtimeMode: RuntimeMode; |
| 331 | readonly sandboxMode: ProviderSandboxMode; | ||
| 332 | readonly createdAt: string; | 320 | readonly createdAt: string; |
| 333 | }) { | 321 | }) { |
| 334 | const thread = yield* resolveThread(input.threadId); | 322 | const thread = yield* resolveThread(input.threadId); |
| ... | @@ -338,8 +326,7 @@ const make = Effect.gen(function* () { | ... | @@ -338,8 +326,7 @@ const make = Effect.gen(function* () { |
| 338 | const sessionId = yield* ensureSessionForThread(input.threadId, input.createdAt, { | 326 | const sessionId = yield* ensureSessionForThread(input.threadId, input.createdAt, { |
| 339 | ...(input.provider !== undefined ? { provider: input.provider } : {}), | 327 | ...(input.provider !== undefined ? { provider: input.provider } : {}), |
| 340 | ...(input.model !== undefined ? { model: input.model } : {}), | 328 | ...(input.model !== undefined ? { model: input.model } : {}), |
| 341 | approvalPolicy: input.approvalPolicy, | 329 | runtimeMode: input.runtimeMode, |
| 342 | sandboxMode: input.sandboxMode, | ||
| 343 | }); | 330 | }); |
| 344 | const normalizedInput = toNonEmptyProviderInput(input.messageText); | 331 | const normalizedInput = toNonEmptyProviderInput(input.messageText); |
| 345 | const normalizedAttachments = input.attachments ?? []; | 332 | const normalizedAttachments = input.attachments ?? []; |
| ... | @@ -472,8 +459,7 @@ const make = Effect.gen(function* () { | ... | @@ -472,8 +459,7 @@ const make = Effect.gen(function* () { |
| 472 | ...(event.payload.provider !== undefined ? { provider: event.payload.provider } : {}), | 459 | ...(event.payload.provider !== undefined ? { provider: event.payload.provider } : {}), |
| 473 | ...(event.payload.model !== undefined ? { model: event.payload.model } : {}), | 460 | ...(event.payload.model !== undefined ? { model: event.payload.model } : {}), |
| 474 | ...(event.payload.effort !== undefined ? { effort: event.payload.effort } : {}), | 461 | ...(event.payload.effort !== undefined ? { effort: event.payload.effort } : {}), |
| 475 | approvalPolicy: event.payload.approvalPolicy, | 462 | runtimeMode: event.payload.runtimeMode, |
| 476 | sandboxMode: event.payload.sandboxMode, | ||
| 477 | createdAt: event.payload.createdAt, | 463 | createdAt: event.payload.createdAt, |
| 478 | }); | 464 | }); |
| 479 | }); | 465 | }); |
| ... | @@ -571,8 +557,7 @@ const make = Effect.gen(function* () { | ... | @@ -571,8 +557,7 @@ const make = Effect.gen(function* () { |
| 571 | providerName: thread.session?.providerName ?? null, | 557 | providerName: thread.session?.providerName ?? null, |
| 572 | providerSessionId: null, | 558 | providerSessionId: null, |
| 573 | providerThreadId: null, | 559 | providerThreadId: null, |
| 574 | approvalPolicy: thread.session?.approvalPolicy ?? DEFAULT_APPROVAL_POLICY, | 560 | runtimeMode: thread.session?.runtimeMode ?? DEFAULT_RUNTIME_MODE, |
| 575 | sandboxMode: thread.session?.sandboxMode ?? DEFAULT_SANDBOX_MODE, | ||
| 576 | activeTurnId: null, | 561 | activeTurnId: null, |
| 577 | lastError: thread.session?.lastError ?? null, | 562 | lastError: thread.session?.lastError ?? null, |
| 578 | updatedAt: now, | 563 | updatedAt: now, |
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts+270-64| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | import type { ProviderRuntimeEvent } from "@t3tools/contracts"; | 1 | import type { OrchestrationReadModel, ProviderRuntimeEvent } from "@t3tools/contracts"; |
| 2 | import { | 2 | import { |
| 3 | ApprovalRequestId, | 3 | ApprovalRequestId, |
| 4 | CommandId, | 4 | CommandId, |
| ... | @@ -83,19 +83,11 @@ function createProviderServiceHarness() { | ... | @@ -83,19 +83,11 @@ function createProviderServiceHarness() { |
| 83 | 83 | ||
| 84 | async function waitForThread( | 84 | async function waitForThread( |
| 85 | engine: OrchestrationEngineShape, | 85 | engine: OrchestrationEngineShape, |
| 86 | predicate: (thread: { | 86 | predicate: (thread: ProviderRuntimeTestThread) => boolean, |
| 87 | session: { status: string; activeTurnId: string | null; lastError: string | null } | null; | ||
| 88 | messages: ReadonlyArray<{ id: string; text: string; streaming: boolean }>; | ||
| 89 | activities: ReadonlyArray<{ kind: string }>; | ||
| 90 | }) => boolean, | ||
| 91 | timeoutMs = 2000, | 87 | timeoutMs = 2000, |
| 92 | ) { | 88 | ) { |
| 93 | const deadline = Date.now() + timeoutMs; | 89 | const deadline = Date.now() + timeoutMs; |
| 94 | const poll = async (): Promise<{ | 90 | const poll = async (): Promise<ProviderRuntimeTestThread> => { |
| 95 | session: { status: string; activeTurnId: string | null; lastError: string | null } | null; | ||
| 96 | messages: ReadonlyArray<{ id: string; text: string; streaming: boolean }>; | ||
| 97 | activities: ReadonlyArray<{ kind: string }>; | ||
| 98 | }> => { | ||
| 99 | const readModel = await Effect.runPromise(engine.getReadModel()); | 91 | const readModel = await Effect.runPromise(engine.getReadModel()); |
| 100 | const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1")); | 92 | const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1")); |
| 101 | if (thread && predicate(thread)) { | 93 | if (thread && predicate(thread)) { |
| ... | @@ -110,6 +102,12 @@ async function waitForThread( | ... | @@ -110,6 +102,12 @@ async function waitForThread( |
| 110 | return poll(); | 102 | return poll(); |
| 111 | } | 103 | } |
| 112 | 104 | ||
| 105 | type ProviderRuntimeTestReadModel = OrchestrationReadModel; | ||
| 106 | type ProviderRuntimeTestThread = ProviderRuntimeTestReadModel["threads"][number]; | ||
| 107 | type ProviderRuntimeTestMessage = ProviderRuntimeTestThread["messages"][number]; | ||
| 108 | type ProviderRuntimeTestActivity = ProviderRuntimeTestThread["activities"][number]; | ||
| 109 | type ProviderRuntimeTestCheckpoint = ProviderRuntimeTestThread["checkpoints"][number]; | ||
| 110 | |||
| 113 | describe("ProviderRuntimeIngestion", () => { | 111 | describe("ProviderRuntimeIngestion", () => { |
| 114 | let runtime: ManagedRuntime.ManagedRuntime< | 112 | let runtime: ManagedRuntime.ManagedRuntime< |
| 115 | OrchestrationEngineService | ProviderRuntimeIngestionService, | 113 | OrchestrationEngineService | ProviderRuntimeIngestionService, |
| ... | @@ -185,8 +183,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -185,8 +183,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 185 | providerName: "codex", | 183 | providerName: "codex", |
| 186 | providerSessionId: asSessionId("sess-1"), | 184 | providerSessionId: asSessionId("sess-1"), |
| 187 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), | 185 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), |
| 188 | approvalPolicy: "on-request", | 186 | runtimeMode: "approval-required", |
| 189 | sandboxMode: "workspace-write", | ||
| 190 | activeTurnId: null, | 187 | activeTurnId: null, |
| 191 | updatedAt: createdAt, | 188 | updatedAt: createdAt, |
| 192 | lastError: null, | 189 | lastError: null, |
| ... | @@ -226,8 +223,10 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -226,8 +223,10 @@ describe("ProviderRuntimeIngestion", () => { |
| 226 | sessionId: asSessionId("sess-1"), | 223 | sessionId: asSessionId("sess-1"), |
| 227 | createdAt: new Date().toISOString(), | 224 | createdAt: new Date().toISOString(), |
| 228 | turnId: asProviderTurnId("turn-1"), | 225 | turnId: asProviderTurnId("turn-1"), |
| 229 | status: "failed", | 226 | payload: { |
| 230 | errorMessage: "turn failed", | 227 | state: "failed", |
| 228 | errorMessage: "turn failed", | ||
| 229 | }, | ||
| 231 | }); | 230 | }); |
| 232 | 231 | ||
| 233 | const thread = await waitForThread( | 232 | const thread = await waitForThread( |
| ... | @@ -465,8 +464,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -465,8 +464,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 465 | providerName: "claudeCode", | 464 | providerName: "claudeCode", |
| 466 | providerSessionId: asSessionId("sess-1"), | 465 | providerSessionId: asSessionId("sess-1"), |
| 467 | providerThreadId: asProviderThreadId("claude-thread-placeholder"), | 466 | providerThreadId: asProviderThreadId("claude-thread-placeholder"), |
| 468 | approvalPolicy: "on-request", | 467 | runtimeMode: "approval-required", |
| 469 | sandboxMode: "workspace-write", | ||
| 470 | activeTurnId: null, | 468 | activeTurnId: null, |
| 471 | updatedAt: seededAt, | 469 | updatedAt: seededAt, |
| 472 | lastError: null, | 470 | lastError: null, |
| ... | @@ -608,9 +606,14 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -608,9 +606,14 @@ describe("ProviderRuntimeIngestion", () => { |
| 608 | }); | 606 | }); |
| 609 | 607 | ||
| 610 | const thread = await waitForThread(harness.engine, (entry) => | 608 | const thread = await waitForThread(harness.engine, (entry) => |
| 611 | entry.messages.some((message) => message.id === "assistant:item-1" && !message.streaming), | 609 | entry.messages.some( |
| 610 | (message: ProviderRuntimeTestMessage) => | ||
| 611 | message.id === "assistant:item-1" && !message.streaming, | ||
| 612 | ), | ||
| 613 | ); | ||
| 614 | const message = thread.messages.find( | ||
| 615 | (entry: ProviderRuntimeTestMessage) => entry.id === "assistant:item-1", | ||
| 612 | ); | 616 | ); |
| 613 | const message = thread.messages.find((entry) => entry.id === "assistant:item-1"); | ||
| 614 | expect(message?.text).toBe("hello world"); | 617 | expect(message?.text).toBe("hello world"); |
| 615 | expect(message?.streaming).toBe(false); | 618 | expect(message?.streaming).toBe(false); |
| 616 | }); | 619 | }); |
| ... | @@ -635,9 +638,14 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -635,9 +638,14 @@ describe("ProviderRuntimeIngestion", () => { |
| 635 | }); | 638 | }); |
| 636 | 639 | ||
| 637 | const thread = await waitForThread(harness.engine, (entry) => | 640 | const thread = await waitForThread(harness.engine, (entry) => |
| 638 | entry.messages.some((message) => message.id === "assistant:item-no-delta" && !message.streaming), | 641 | entry.messages.some( |
| 642 | (message: ProviderRuntimeTestMessage) => | ||
| 643 | message.id === "assistant:item-no-delta" && !message.streaming, | ||
| 644 | ), | ||
| 645 | ); | ||
| 646 | const message = thread.messages.find( | ||
| 647 | (entry: ProviderRuntimeTestMessage) => entry.id === "assistant:item-no-delta", | ||
| 639 | ); | 648 | ); |
| 640 | const message = thread.messages.find((entry) => entry.id === "assistant:item-no-delta"); | ||
| 641 | expect(message?.text).toBe("assistant-only final text"); | 649 | expect(message?.text).toBe("assistant-only final text"); |
| 642 | expect(message?.streaming).toBe(false); | 650 | expect(message?.streaming).toBe(false); |
| 643 | }); | 651 | }); |
| ... | @@ -661,14 +669,17 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -661,14 +669,17 @@ describe("ProviderRuntimeIngestion", () => { |
| 661 | ); | 669 | ); |
| 662 | 670 | ||
| 663 | harness.emit({ | 671 | harness.emit({ |
| 664 | type: "message.delta", | 672 | type: "content.delta", |
| 665 | eventId: asEventId("evt-message-delta-buffered"), | 673 | eventId: asEventId("evt-message-delta-buffered"), |
| 666 | provider: "codex", | 674 | provider: "codex", |
| 667 | sessionId: asSessionId("sess-1"), | 675 | sessionId: asSessionId("sess-1"), |
| 668 | createdAt: now, | 676 | createdAt: now, |
| 669 | turnId: asProviderTurnId("turn-buffered"), | 677 | turnId: asProviderTurnId("turn-buffered"), |
| 670 | itemId: asItemId("item-buffered"), | 678 | itemId: asItemId("item-buffered"), |
| 671 | delta: "buffer me", | 679 | payload: { |
| 680 | streamKind: "assistant_text", | ||
| 681 | delta: "buffer me", | ||
| 682 | }, | ||
| 672 | }); | 683 | }); |
| 673 | 684 | ||
| 674 | await Effect.runPromise(Effect.sleep("30 millis")); | 685 | await Effect.runPromise(Effect.sleep("30 millis")); |
| ... | @@ -676,26 +687,35 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -676,26 +687,35 @@ describe("ProviderRuntimeIngestion", () => { |
| 676 | const midThread = midReadModel.threads.find( | 687 | const midThread = midReadModel.threads.find( |
| 677 | (entry) => entry.id === ThreadId.makeUnsafe("thread-1"), | 688 | (entry) => entry.id === ThreadId.makeUnsafe("thread-1"), |
| 678 | ); | 689 | ); |
| 679 | expect(midThread?.messages.some((message) => message.id === "assistant:item-buffered")).toBe( | 690 | expect( |
| 680 | false, | 691 | midThread?.messages.some( |
| 681 | ); | 692 | (message: ProviderRuntimeTestMessage) => message.id === "assistant:item-buffered", |
| 693 | ), | ||
| 694 | ).toBe(false); | ||
| 682 | 695 | ||
| 683 | harness.emit({ | 696 | harness.emit({ |
| 684 | type: "message.completed", | 697 | type: "item.completed", |
| 685 | eventId: asEventId("evt-message-completed-buffered"), | 698 | eventId: asEventId("evt-message-completed-buffered"), |
| 686 | provider: "codex", | 699 | provider: "codex", |
| 687 | sessionId: asSessionId("sess-1"), | 700 | sessionId: asSessionId("sess-1"), |
| 688 | createdAt: now, | 701 | createdAt: now, |
| 689 | turnId: asProviderTurnId("turn-buffered"), | 702 | turnId: asProviderTurnId("turn-buffered"), |
| 690 | itemId: asItemId("item-buffered"), | 703 | itemId: asItemId("item-buffered"), |
| 704 | payload: { | ||
| 705 | itemType: "assistant_message", | ||
| 706 | status: "completed", | ||
| 707 | }, | ||
| 691 | }); | 708 | }); |
| 692 | 709 | ||
| 693 | const thread = await waitForThread(harness.engine, (entry) => | 710 | const thread = await waitForThread(harness.engine, (entry) => |
| 694 | entry.messages.some( | 711 | entry.messages.some( |
| 695 | (message) => message.id === "assistant:item-buffered" && !message.streaming, | 712 | (message: ProviderRuntimeTestMessage) => |
| 713 | message.id === "assistant:item-buffered" && !message.streaming, | ||
| 696 | ), | 714 | ), |
| 697 | ); | 715 | ); |
| 698 | const message = thread.messages.find((entry) => entry.id === "assistant:item-buffered"); | 716 | const message = thread.messages.find( |
| 717 | (entry: ProviderRuntimeTestMessage) => entry.id === "assistant:item-buffered", | ||
| 718 | ); | ||
| 699 | expect(message?.text).toBe("buffer me"); | 719 | expect(message?.text).toBe("buffer me"); |
| 700 | expect(message?.streaming).toBe(false); | 720 | expect(message?.streaming).toBe(false); |
| 701 | }); | 721 | }); |
| ... | @@ -716,8 +736,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -716,8 +736,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 716 | attachments: [], | 736 | attachments: [], |
| 717 | }, | 737 | }, |
| 718 | assistantDeliveryMode: "streaming", | 738 | assistantDeliveryMode: "streaming", |
| 719 | approvalPolicy: "on-request", | 739 | runtimeMode: "approval-required", |
| 720 | sandboxMode: "workspace-write", | ||
| 721 | createdAt: now, | 740 | createdAt: now, |
| 722 | }), | 741 | }), |
| 723 | ); | 742 | ); |
| ... | @@ -739,46 +758,54 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -739,46 +758,54 @@ describe("ProviderRuntimeIngestion", () => { |
| 739 | ); | 758 | ); |
| 740 | 759 | ||
| 741 | harness.emit({ | 760 | harness.emit({ |
| 742 | type: "message.delta", | 761 | type: "content.delta", |
| 743 | eventId: asEventId("evt-message-delta-streaming-mode"), | 762 | eventId: asEventId("evt-message-delta-streaming-mode"), |
| 744 | provider: "codex", | 763 | provider: "codex", |
| 745 | sessionId: asSessionId("sess-1"), | 764 | sessionId: asSessionId("sess-1"), |
| 746 | createdAt: now, | 765 | createdAt: now, |
| 747 | turnId: asProviderTurnId("turn-streaming-mode"), | 766 | turnId: asProviderTurnId("turn-streaming-mode"), |
| 748 | itemId: asItemId("item-streaming-mode"), | 767 | itemId: asItemId("item-streaming-mode"), |
| 749 | delta: "hello live", | 768 | payload: { |
| 769 | streamKind: "assistant_text", | ||
| 770 | delta: "hello live", | ||
| 771 | }, | ||
| 750 | }); | 772 | }); |
| 751 | 773 | ||
| 752 | const liveThread = await waitForThread(harness.engine, (entry) => | 774 | const liveThread = await waitForThread(harness.engine, (entry) => |
| 753 | entry.messages.some( | 775 | entry.messages.some( |
| 754 | (message) => | 776 | (message: ProviderRuntimeTestMessage) => |
| 755 | message.id === "assistant:item-streaming-mode" && | 777 | message.id === "assistant:item-streaming-mode" && |
| 756 | message.streaming && | 778 | message.streaming && |
| 757 | message.text === "hello live", | 779 | message.text === "hello live", |
| 758 | ), | 780 | ), |
| 759 | ); | 781 | ); |
| 760 | const liveMessage = liveThread.messages.find( | 782 | const liveMessage = liveThread.messages.find( |
| 761 | (entry) => entry.id === "assistant:item-streaming-mode", | 783 | (entry: ProviderRuntimeTestMessage) => entry.id === "assistant:item-streaming-mode", |
| 762 | ); | 784 | ); |
| 763 | expect(liveMessage?.streaming).toBe(true); | 785 | expect(liveMessage?.streaming).toBe(true); |
| 764 | 786 | ||
| 765 | harness.emit({ | 787 | harness.emit({ |
| 766 | type: "message.completed", | 788 | type: "item.completed", |
| 767 | eventId: asEventId("evt-message-completed-streaming-mode"), | 789 | eventId: asEventId("evt-message-completed-streaming-mode"), |
| 768 | provider: "codex", | 790 | provider: "codex", |
| 769 | sessionId: asSessionId("sess-1"), | 791 | sessionId: asSessionId("sess-1"), |
| 770 | createdAt: now, | 792 | createdAt: now, |
| 771 | turnId: asProviderTurnId("turn-streaming-mode"), | 793 | turnId: asProviderTurnId("turn-streaming-mode"), |
| 772 | itemId: asItemId("item-streaming-mode"), | 794 | itemId: asItemId("item-streaming-mode"), |
| 795 | payload: { | ||
| 796 | itemType: "assistant_message", | ||
| 797 | status: "completed", | ||
| 798 | }, | ||
| 773 | }); | 799 | }); |
| 774 | 800 | ||
| 775 | const finalThread = await waitForThread(harness.engine, (entry) => | 801 | const finalThread = await waitForThread(harness.engine, (entry) => |
| 776 | entry.messages.some( | 802 | entry.messages.some( |
| 777 | (message) => message.id === "assistant:item-streaming-mode" && !message.streaming, | 803 | (message: ProviderRuntimeTestMessage) => |
| 804 | message.id === "assistant:item-streaming-mode" && !message.streaming, | ||
| 778 | ), | 805 | ), |
| 779 | ); | 806 | ); |
| 780 | const finalMessage = finalThread.messages.find( | 807 | const finalMessage = finalThread.messages.find( |
| 781 | (entry) => entry.id === "assistant:item-streaming-mode", | 808 | (entry: ProviderRuntimeTestMessage) => entry.id === "assistant:item-streaming-mode", |
| 782 | ); | 809 | ); |
| 783 | expect(finalMessage?.text).toBe("hello live"); | 810 | expect(finalMessage?.text).toBe("hello live"); |
| 784 | expect(finalMessage?.streaming).toBe(false); | 811 | expect(finalMessage?.streaming).toBe(false); |
| ... | @@ -805,37 +832,47 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -805,37 +832,47 @@ describe("ProviderRuntimeIngestion", () => { |
| 805 | ); | 832 | ); |
| 806 | 833 | ||
| 807 | harness.emit({ | 834 | harness.emit({ |
| 808 | type: "message.delta", | 835 | type: "content.delta", |
| 809 | eventId: asEventId("evt-message-delta-buffer-spill"), | 836 | eventId: asEventId("evt-message-delta-buffer-spill"), |
| 810 | provider: "codex", | 837 | provider: "codex", |
| 811 | sessionId: asSessionId("sess-1"), | 838 | sessionId: asSessionId("sess-1"), |
| 812 | createdAt: now, | 839 | createdAt: now, |
| 813 | turnId: asProviderTurnId("turn-buffer-spill"), | 840 | turnId: asProviderTurnId("turn-buffer-spill"), |
| 814 | itemId: asItemId("item-buffer-spill"), | 841 | itemId: asItemId("item-buffer-spill"), |
| 815 | delta: oversizedText, | 842 | payload: { |
| 843 | streamKind: "assistant_text", | ||
| 844 | delta: oversizedText, | ||
| 845 | }, | ||
| 816 | }); | 846 | }); |
| 817 | harness.emit({ | 847 | harness.emit({ |
| 818 | type: "message.completed", | 848 | type: "item.completed", |
| 819 | eventId: asEventId("evt-message-completed-buffer-spill"), | 849 | eventId: asEventId("evt-message-completed-buffer-spill"), |
| 820 | provider: "codex", | 850 | provider: "codex", |
| 821 | sessionId: asSessionId("sess-1"), | 851 | sessionId: asSessionId("sess-1"), |
| 822 | createdAt: now, | 852 | createdAt: now, |
| 823 | turnId: asProviderTurnId("turn-buffer-spill"), | 853 | turnId: asProviderTurnId("turn-buffer-spill"), |
| 824 | itemId: asItemId("item-buffer-spill"), | 854 | itemId: asItemId("item-buffer-spill"), |
| 855 | payload: { | ||
| 856 | itemType: "assistant_message", | ||
| 857 | status: "completed", | ||
| 858 | }, | ||
| 825 | }); | 859 | }); |
| 826 | 860 | ||
| 827 | const thread = await waitForThread(harness.engine, (entry) => | 861 | const thread = await waitForThread(harness.engine, (entry) => |
| 828 | entry.messages.some( | 862 | entry.messages.some( |
| 829 | (message) => message.id === "assistant:item-buffer-spill" && !message.streaming, | 863 | (message: ProviderRuntimeTestMessage) => |
| 864 | message.id === "assistant:item-buffer-spill" && !message.streaming, | ||
| 830 | ), | 865 | ), |
| 831 | ); | 866 | ); |
| 832 | const message = thread.messages.find((entry) => entry.id === "assistant:item-buffer-spill"); | 867 | const message = thread.messages.find( |
| 868 | (entry: ProviderRuntimeTestMessage) => entry.id === "assistant:item-buffer-spill", | ||
| 869 | ); | ||
| 833 | expect(message?.text.length).toBe(oversizedText.length); | 870 | expect(message?.text.length).toBe(oversizedText.length); |
| 834 | expect(message?.text).toBe(oversizedText); | 871 | expect(message?.text).toBe(oversizedText); |
| 835 | expect(message?.streaming).toBe(false); | 872 | expect(message?.streaming).toBe(false); |
| 836 | }); | 873 | }); |
| 837 | 874 | ||
| 838 | it("does not duplicate assistant completion when message.completed is followed by turn.completed", async () => { | 875 | it("does not duplicate assistant completion when item.completed is followed by turn.completed", async () => { |
| 839 | const harness = await createHarness(); | 876 | const harness = await createHarness(); |
| 840 | const now = new Date().toISOString(); | 877 | const now = new Date().toISOString(); |
| 841 | 878 | ||
| ... | @@ -856,23 +893,30 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -856,23 +893,30 @@ describe("ProviderRuntimeIngestion", () => { |
| 856 | ); | 893 | ); |
| 857 | 894 | ||
| 858 | harness.emit({ | 895 | harness.emit({ |
| 859 | type: "message.delta", | 896 | type: "content.delta", |
| 860 | eventId: asEventId("evt-message-delta-for-complete-dedup"), | 897 | eventId: asEventId("evt-message-delta-for-complete-dedup"), |
| 861 | provider: "codex", | 898 | provider: "codex", |
| 862 | sessionId: asSessionId("sess-1"), | 899 | sessionId: asSessionId("sess-1"), |
| 863 | createdAt: now, | 900 | createdAt: now, |
| 864 | turnId: asProviderTurnId("turn-complete-dedup"), | 901 | turnId: asProviderTurnId("turn-complete-dedup"), |
| 865 | itemId: asItemId("item-complete-dedup"), | 902 | itemId: asItemId("item-complete-dedup"), |
| 866 | delta: "done", | 903 | payload: { |
| 904 | streamKind: "assistant_text", | ||
| 905 | delta: "done", | ||
| 906 | }, | ||
| 867 | }); | 907 | }); |
| 868 | harness.emit({ | 908 | harness.emit({ |
| 869 | type: "message.completed", | 909 | type: "item.completed", |
| 870 | eventId: asEventId("evt-message-completed-for-complete-dedup"), | 910 | eventId: asEventId("evt-message-completed-for-complete-dedup"), |
| 871 | provider: "codex", | 911 | provider: "codex", |
| 872 | sessionId: asSessionId("sess-1"), | 912 | sessionId: asSessionId("sess-1"), |
| 873 | createdAt: now, | 913 | createdAt: now, |
| 874 | turnId: asProviderTurnId("turn-complete-dedup"), | 914 | turnId: asProviderTurnId("turn-complete-dedup"), |
| 875 | itemId: asItemId("item-complete-dedup"), | 915 | itemId: asItemId("item-complete-dedup"), |
| 916 | payload: { | ||
| 917 | itemType: "assistant_message", | ||
| 918 | status: "completed", | ||
| 919 | }, | ||
| 876 | }); | 920 | }); |
| 877 | harness.emit({ | 921 | harness.emit({ |
| 878 | type: "turn.completed", | 922 | type: "turn.completed", |
| ... | @@ -881,7 +925,9 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -881,7 +925,9 @@ describe("ProviderRuntimeIngestion", () => { |
| 881 | sessionId: asSessionId("sess-1"), | 925 | sessionId: asSessionId("sess-1"), |
| 882 | createdAt: now, | 926 | createdAt: now, |
| 883 | turnId: asProviderTurnId("turn-complete-dedup"), | 927 | turnId: asProviderTurnId("turn-complete-dedup"), |
| 884 | status: "completed", | 928 | payload: { |
| 929 | state: "completed", | ||
| 930 | }, | ||
| 885 | }); | 931 | }); |
| 886 | 932 | ||
| 887 | await waitForThread( | 933 | await waitForThread( |
| ... | @@ -890,7 +936,8 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -890,7 +936,8 @@ describe("ProviderRuntimeIngestion", () => { |
| 890 | thread.session?.status === "ready" && | 936 | thread.session?.status === "ready" && |
| 891 | thread.session?.activeTurnId === null && | 937 | thread.session?.activeTurnId === null && |
| 892 | thread.messages.some( | 938 | thread.messages.some( |
| 893 | (message) => message.id === "assistant:item-complete-dedup" && !message.streaming, | 939 | (message: ProviderRuntimeTestMessage) => |
| 940 | message.id === "assistant:item-complete-dedup" && !message.streaming, | ||
| 894 | ), | 941 | ), |
| 895 | ); | 942 | ); |
| 896 | 943 | ||
| ... | @@ -944,15 +991,21 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -944,15 +991,21 @@ describe("ProviderRuntimeIngestion", () => { |
| 944 | await waitForThread( | 991 | await waitForThread( |
| 945 | harness.engine, | 992 | harness.engine, |
| 946 | (entry) => | 993 | (entry) => |
| 947 | entry.activities.some((activity) => activity.kind === "approval.requested") && | 994 | entry.activities.some( |
| 948 | entry.activities.some((activity) => activity.kind === "approval.resolved"), | 995 | (activity: ProviderRuntimeTestActivity) => activity.kind === "approval.requested", |
| 996 | ) && | ||
| 997 | entry.activities.some( | ||
| 998 | (activity: ProviderRuntimeTestActivity) => activity.kind === "approval.resolved", | ||
| 999 | ), | ||
| 949 | ); | 1000 | ); |
| 950 | 1001 | ||
| 951 | const readModel = await Effect.runPromise(harness.engine.getReadModel()); | 1002 | const readModel = await Effect.runPromise(harness.engine.getReadModel()); |
| 952 | const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1")); | 1003 | const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1")); |
| 953 | expect(thread).toBeDefined(); | 1004 | expect(thread).toBeDefined(); |
| 954 | 1005 | ||
| 955 | const requested = thread?.activities.find((activity) => activity.id === "evt-request-opened"); | 1006 | const requested = thread?.activities.find( |
| 1007 | (activity: ProviderRuntimeTestActivity) => activity.id === "evt-request-opened", | ||
| 1008 | ); | ||
| 956 | const requestedPayload = | 1009 | const requestedPayload = |
| 957 | requested?.payload && typeof requested.payload === "object" | 1010 | requested?.payload && typeof requested.payload === "object" |
| 958 | ? (requested.payload as Record<string, unknown>) | 1011 | ? (requested.payload as Record<string, unknown>) |
| ... | @@ -960,7 +1013,9 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -960,7 +1013,9 @@ describe("ProviderRuntimeIngestion", () => { |
| 960 | expect(requestedPayload?.requestKind).toBe("command"); | 1013 | expect(requestedPayload?.requestKind).toBe("command"); |
| 961 | expect(requestedPayload?.requestType).toBe("command_execution_approval"); | 1014 | expect(requestedPayload?.requestType).toBe("command_execution_approval"); |
| 962 | 1015 | ||
| 963 | const resolved = thread?.activities.find((activity) => activity.id === "evt-request-resolved"); | 1016 | const resolved = thread?.activities.find( |
| 1017 | (activity: ProviderRuntimeTestActivity) => activity.id === "evt-request-resolved", | ||
| 1018 | ); | ||
| 964 | const resolvedPayload = | 1019 | const resolvedPayload = |
| 965 | resolved?.payload && typeof resolved.payload === "object" | 1020 | resolved?.payload && typeof resolved.payload === "object" |
| 966 | ? (resolved.payload as Record<string, unknown>) | 1021 | ? (resolved.payload as Record<string, unknown>) |
| ... | @@ -980,7 +1035,9 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -980,7 +1035,9 @@ describe("ProviderRuntimeIngestion", () => { |
| 980 | sessionId: asSessionId("sess-1"), | 1035 | sessionId: asSessionId("sess-1"), |
| 981 | createdAt: now, | 1036 | createdAt: now, |
| 982 | turnId: asProviderTurnId("turn-3"), | 1037 | turnId: asProviderTurnId("turn-3"), |
| 983 | message: "runtime exploded", | 1038 | payload: { |
| 1039 | message: "runtime exploded", | ||
| 1040 | }, | ||
| 984 | }); | 1041 | }); |
| 985 | 1042 | ||
| 986 | const thread = await waitForThread( | 1043 | const thread = await waitForThread( |
| ... | @@ -994,7 +1051,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -994,7 +1051,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 994 | expect(thread.session?.lastError).toBe("runtime exploded"); | 1051 | expect(thread.session?.lastError).toBe("runtime exploded"); |
| 995 | }); | 1052 | }); |
| 996 | 1053 | ||
| 997 | it("maps session/thread lifecycle and tool.started into session/activity projections", async () => { | 1054 | it("maps session/thread lifecycle and item.started into session/activity projections", async () => { |
| 998 | const harness = await createHarness(); | 1055 | const harness = await createHarness(); |
| 999 | const now = new Date().toISOString(); | 1056 | const now = new Date().toISOString(); |
| 1000 | 1057 | ||
| ... | @@ -1016,15 +1073,18 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -1016,15 +1073,18 @@ describe("ProviderRuntimeIngestion", () => { |
| 1016 | threadId: ProviderThreadId.makeUnsafe("provider-thread-2"), | 1073 | threadId: ProviderThreadId.makeUnsafe("provider-thread-2"), |
| 1017 | }); | 1074 | }); |
| 1018 | harness.emit({ | 1075 | harness.emit({ |
| 1019 | type: "tool.started", | 1076 | type: "item.started", |
| 1020 | eventId: asEventId("evt-tool-started"), | 1077 | eventId: asEventId("evt-tool-started"), |
| 1021 | provider: "codex", | 1078 | provider: "codex", |
| 1022 | sessionId: asSessionId("sess-1"), | 1079 | sessionId: asSessionId("sess-1"), |
| 1023 | createdAt: now, | 1080 | createdAt: now, |
| 1024 | turnId: asProviderTurnId("turn-9"), | 1081 | turnId: asProviderTurnId("turn-9"), |
| 1025 | toolKind: "other", | 1082 | payload: { |
| 1026 | title: "Read file", | 1083 | itemType: "command_execution", |
| 1027 | detail: "/tmp/file.ts", | 1084 | status: "in_progress", |
| 1085 | title: "Read file", | ||
| 1086 | detail: "/tmp/file.ts", | ||
| 1087 | }, | ||
| 1028 | }); | 1088 | }); |
| 1029 | 1089 | ||
| 1030 | const thread = await waitForThread( | 1090 | const thread = await waitForThread( |
| ... | @@ -1032,11 +1092,152 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -1032,11 +1092,152 @@ describe("ProviderRuntimeIngestion", () => { |
| 1032 | (entry) => | 1092 | (entry) => |
| 1033 | entry.session?.status === "ready" && | 1093 | entry.session?.status === "ready" && |
| 1034 | entry.session?.activeTurnId === null && | 1094 | entry.session?.activeTurnId === null && |
| 1035 | entry.activities.some((activity) => activity.kind === "tool.started"), | 1095 | entry.activities.some( |
| 1096 | (activity: ProviderRuntimeTestActivity) => activity.kind === "tool.started", | ||
| 1097 | ), | ||
| 1036 | ); | 1098 | ); |
| 1037 | 1099 | ||
| 1038 | expect(thread.session?.status).toBe("ready"); | 1100 | expect(thread.session?.status).toBe("ready"); |
| 1039 | expect(thread.activities.some((activity) => activity.kind === "tool.started")).toBe(true); | 1101 | expect( |
| 1102 | thread.activities.some( | ||
| 1103 | (activity: ProviderRuntimeTestActivity) => activity.kind === "tool.started", | ||
| 1104 | ), | ||
| 1105 | ).toBe(true); | ||
| 1106 | }); | ||
| 1107 | |||
| 1108 | it("consumes P1 runtime events into thread metadata, diff checkpoints, and activities", async () => { | ||
| 1109 | const harness = await createHarness(); | ||
| 1110 | const now = new Date().toISOString(); | ||
| 1111 | |||
| 1112 | harness.emit({ | ||
| 1113 | type: "thread.metadata.updated", | ||
| 1114 | eventId: asEventId("evt-thread-metadata-updated"), | ||
| 1115 | provider: "codex", | ||
| 1116 | sessionId: asSessionId("sess-1"), | ||
| 1117 | createdAt: now, | ||
| 1118 | threadId: asProviderThreadId("provider-thread-1"), | ||
| 1119 | payload: { | ||
| 1120 | name: "Renamed by provider", | ||
| 1121 | metadata: { source: "provider" }, | ||
| 1122 | }, | ||
| 1123 | }); | ||
| 1124 | |||
| 1125 | harness.emit({ | ||
| 1126 | type: "turn.plan.updated", | ||
| 1127 | eventId: asEventId("evt-turn-plan-updated"), | ||
| 1128 | provider: "codex", | ||
| 1129 | sessionId: asSessionId("sess-1"), | ||
| 1130 | createdAt: now, | ||
| 1131 | turnId: asProviderTurnId("turn-p1"), | ||
| 1132 | payload: { | ||
| 1133 | explanation: "Working through the plan", | ||
| 1134 | plan: [ | ||
| 1135 | { step: "Inspect files", status: "completed" }, | ||
| 1136 | { step: "Apply patch", status: "in_progress" }, | ||
| 1137 | ], | ||
| 1138 | }, | ||
| 1139 | }); | ||
| 1140 | |||
| 1141 | harness.emit({ | ||
| 1142 | type: "item.updated", | ||
| 1143 | eventId: asEventId("evt-item-updated"), | ||
| 1144 | provider: "codex", | ||
| 1145 | sessionId: asSessionId("sess-1"), | ||
| 1146 | createdAt: now, | ||
| 1147 | turnId: asProviderTurnId("turn-p1"), | ||
| 1148 | itemId: asItemId("item-p1-tool"), | ||
| 1149 | payload: { | ||
| 1150 | itemType: "command_execution", | ||
| 1151 | status: "in_progress", | ||
| 1152 | title: "Run tests", | ||
| 1153 | detail: "bun test", | ||
| 1154 | data: { pid: 123 }, | ||
| 1155 | }, | ||
| 1156 | }); | ||
| 1157 | |||
| 1158 | harness.emit({ | ||
| 1159 | type: "runtime.warning", | ||
| 1160 | eventId: asEventId("evt-runtime-warning"), | ||
| 1161 | provider: "codex", | ||
| 1162 | sessionId: asSessionId("sess-1"), | ||
| 1163 | createdAt: now, | ||
| 1164 | turnId: asProviderTurnId("turn-p1"), | ||
| 1165 | payload: { | ||
| 1166 | message: "Provider got slow", | ||
| 1167 | detail: { latencyMs: 1500 }, | ||
| 1168 | }, | ||
| 1169 | }); | ||
| 1170 | |||
| 1171 | harness.emit({ | ||
| 1172 | type: "turn.diff.updated", | ||
| 1173 | eventId: asEventId("evt-turn-diff-updated"), | ||
| 1174 | provider: "codex", | ||
| 1175 | sessionId: asSessionId("sess-1"), | ||
| 1176 | createdAt: now, | ||
| 1177 | turnId: asProviderTurnId("turn-p1"), | ||
| 1178 | itemId: asItemId("item-p1-assistant"), | ||
| 1179 | payload: { | ||
| 1180 | unifiedDiff: "diff --git a/file.txt b/file.txt\n+hello\n", | ||
| 1181 | }, | ||
| 1182 | }); | ||
| 1183 | |||
| 1184 | const thread = await waitForThread( | ||
| 1185 | harness.engine, | ||
| 1186 | (entry) => | ||
| 1187 | entry.title === "Renamed by provider" && | ||
| 1188 | entry.activities.some( | ||
| 1189 | (activity: ProviderRuntimeTestActivity) => activity.kind === "turn.plan.updated", | ||
| 1190 | ) && | ||
| 1191 | entry.activities.some( | ||
| 1192 | (activity: ProviderRuntimeTestActivity) => activity.kind === "tool.updated", | ||
| 1193 | ) && | ||
| 1194 | entry.activities.some( | ||
| 1195 | (activity: ProviderRuntimeTestActivity) => activity.kind === "runtime.warning", | ||
| 1196 | ) && | ||
| 1197 | entry.checkpoints.some( | ||
| 1198 | (checkpoint: ProviderRuntimeTestCheckpoint) => checkpoint.turnId === "turn-p1", | ||
| 1199 | ), | ||
| 1200 | ); | ||
| 1201 | |||
| 1202 | expect(thread.title).toBe("Renamed by provider"); | ||
| 1203 | |||
| 1204 | const planActivity = thread.activities.find( | ||
| 1205 | (activity: ProviderRuntimeTestActivity) => activity.id === "evt-turn-plan-updated", | ||
| 1206 | ); | ||
| 1207 | const planPayload = | ||
| 1208 | planActivity?.payload && typeof planActivity.payload === "object" | ||
| 1209 | ? (planActivity.payload as Record<string, unknown>) | ||
| 1210 | : undefined; | ||
| 1211 | expect(planActivity?.kind).toBe("turn.plan.updated"); | ||
| 1212 | expect(Array.isArray(planPayload?.plan)).toBe(true); | ||
| 1213 | |||
| 1214 | const toolUpdate = thread.activities.find( | ||
| 1215 | (activity: ProviderRuntimeTestActivity) => activity.id === "evt-item-updated", | ||
| 1216 | ); | ||
| 1217 | const toolUpdatePayload = | ||
| 1218 | toolUpdate?.payload && typeof toolUpdate.payload === "object" | ||
| 1219 | ? (toolUpdate.payload as Record<string, unknown>) | ||
| 1220 | : undefined; | ||
| 1221 | expect(toolUpdate?.kind).toBe("tool.updated"); | ||
| 1222 | expect(toolUpdatePayload?.itemType).toBe("command_execution"); | ||
| 1223 | expect(toolUpdatePayload?.status).toBe("in_progress"); | ||
| 1224 | |||
| 1225 | const warning = thread.activities.find( | ||
| 1226 | (activity: ProviderRuntimeTestActivity) => activity.id === "evt-runtime-warning", | ||
| 1227 | ); | ||
| 1228 | const warningPayload = | ||
| 1229 | warning?.payload && typeof warning.payload === "object" | ||
| 1230 | ? (warning.payload as Record<string, unknown>) | ||
| 1231 | : undefined; | ||
| 1232 | expect(warning?.kind).toBe("runtime.warning"); | ||
| 1233 | expect(warningPayload?.message).toBe("Provider got slow"); | ||
| 1234 | |||
| 1235 | const checkpoint = thread.checkpoints.find( | ||
| 1236 | (entry: ProviderRuntimeTestCheckpoint) => entry.turnId === "turn-p1", | ||
| 1237 | ); | ||
| 1238 | expect(checkpoint?.status).toBe("missing"); | ||
| 1239 | expect(checkpoint?.assistantMessageId).toBe("assistant:item-p1-assistant"); | ||
| 1240 | expect(checkpoint?.checkpointRef).toBe("provider-diff:evt-turn-diff-updated"); | ||
| 1040 | }); | 1241 | }); |
| 1041 | 1242 | ||
| 1042 | it("continues processing runtime events after a single event handler failure", async () => { | 1243 | it("continues processing runtime events after a single event handler failure", async () => { |
| ... | @@ -1044,14 +1245,17 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -1044,14 +1245,17 @@ describe("ProviderRuntimeIngestion", () => { |
| 1044 | const now = new Date().toISOString(); | 1245 | const now = new Date().toISOString(); |
| 1045 | 1246 | ||
| 1046 | harness.emit({ | 1247 | harness.emit({ |
| 1047 | type: "message.delta", | 1248 | type: "content.delta", |
| 1048 | eventId: asEventId("evt-invalid-delta"), | 1249 | eventId: asEventId("evt-invalid-delta"), |
| 1049 | provider: "codex", | 1250 | provider: "codex", |
| 1050 | sessionId: asSessionId("sess-1"), | 1251 | sessionId: asSessionId("sess-1"), |
| 1051 | createdAt: now, | 1252 | createdAt: now, |
| 1052 | turnId: asProviderTurnId("turn-invalid"), | 1253 | turnId: asProviderTurnId("turn-invalid"), |
| 1053 | itemId: asItemId("item-invalid"), | 1254 | itemId: asItemId("item-invalid"), |
| 1054 | delta: undefined, | 1255 | payload: { |
| 1256 | streamKind: "assistant_text", | ||
| 1257 | delta: undefined, | ||
| 1258 | }, | ||
| 1055 | } as unknown as ProviderRuntimeEvent); | 1259 | } as unknown as ProviderRuntimeEvent); |
| 1056 | 1260 | ||
| 1057 | harness.emit({ | 1261 | harness.emit({ |
| ... | @@ -1061,7 +1265,9 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -1061,7 +1265,9 @@ describe("ProviderRuntimeIngestion", () => { |
| 1061 | sessionId: asSessionId("sess-1"), | 1265 | sessionId: asSessionId("sess-1"), |
| 1062 | createdAt: new Date().toISOString(), | 1266 | createdAt: new Date().toISOString(), |
| 1063 | turnId: asProviderTurnId("turn-after-failure"), | 1267 | turnId: asProviderTurnId("turn-after-failure"), |
| 1064 | message: "runtime still processed", | 1268 | payload: { |
| 1269 | message: "runtime still processed", | ||
| 1270 | }, | ||
| 1065 | }); | 1271 | }); |
| 1066 | 1272 | ||
| 1067 | const thread = await waitForThread( | 1273 | const thread = await waitForThread( |
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts+82-92| ... | @@ -4,8 +4,7 @@ import { | ... | @@ -4,8 +4,7 @@ import { |
| 4 | CommandId, | 4 | CommandId, |
| 5 | MessageId, | 5 | MessageId, |
| 6 | type OrchestrationEvent, | 6 | type OrchestrationEvent, |
| 7 | type ProviderApprovalPolicy, | 7 | CheckpointRef, |
| 8 | type ProviderSandboxMode, | ||
| 9 | ProviderThreadId, | 8 | ProviderThreadId, |
| 10 | type ThreadId, | 9 | type ThreadId, |
| 11 | TurnId, | 10 | TurnId, |
| ... | @@ -28,8 +27,6 @@ const providerCommandId = (event: ProviderRuntimeEvent, tag: string): CommandId | ... | @@ -28,8 +27,6 @@ const providerCommandId = (event: ProviderRuntimeEvent, tag: string): CommandId |
| 28 | CommandId.makeUnsafe(`provider:${event.eventId}:${tag}:${crypto.randomUUID()}`); | 27 | CommandId.makeUnsafe(`provider:${event.eventId}:${tag}:${crypto.randomUUID()}`); |
| 29 | 28 | ||
| 30 | const DEFAULT_ASSISTANT_DELIVERY_MODE: AssistantDeliveryMode = "buffered"; | 29 | const DEFAULT_ASSISTANT_DELIVERY_MODE: AssistantDeliveryMode = "buffered"; |
| 31 | const DEFAULT_APPROVAL_POLICY: ProviderApprovalPolicy = "on-request"; | ||
| 32 | const DEFAULT_SANDBOX_MODE: ProviderSandboxMode = "workspace-write"; | ||
| 33 | const TURN_MESSAGE_IDS_BY_TURN_CACHE_CAPACITY = 10_000; | 30 | const TURN_MESSAGE_IDS_BY_TURN_CACHE_CAPACITY = 10_000; |
| 34 | const TURN_MESSAGE_IDS_BY_TURN_TTL = Duration.minutes(120); | 31 | const TURN_MESSAGE_IDS_BY_TURN_TTL = Duration.minutes(120); |
| 35 | const BUFFERED_MESSAGE_TEXT_BY_MESSAGE_ID_CACHE_CAPACITY = 20_000; | 32 | const BUFFERED_MESSAGE_TEXT_BY_MESSAGE_ID_CACHE_CAPACITY = 20_000; |
| ... | @@ -90,17 +87,6 @@ function asString(value: unknown): string | undefined { | ... | @@ -90,17 +87,6 @@ function asString(value: unknown): string | undefined { |
| 90 | return typeof value === "string" ? value : undefined; | 87 | return typeof value === "string" ? value : undefined; |
| 91 | } | 88 | } |
| 92 | 89 | ||
| 93 | function legacyRuntimeType(event: ProviderRuntimeEvent): string | undefined { | ||
| 94 | return asString((event as Record<string, unknown>).type); | ||
| 95 | } | ||
| 96 | |||
| 97 | function legacyRuntimeStringField( | ||
| 98 | event: ProviderRuntimeEvent, | ||
| 99 | key: string, | ||
| 100 | ): string | undefined { | ||
| 101 | return asString((event as Record<string, unknown>)[key]); | ||
| 102 | } | ||
| 103 | |||
| 104 | function runtimePayloadRecord(event: ProviderRuntimeEvent): Record<string, unknown> | undefined { | 90 | function runtimePayloadRecord(event: ProviderRuntimeEvent): Record<string, unknown> | undefined { |
| 105 | const payload = (event as { payload?: unknown }).payload; | 91 | const payload = (event as { payload?: unknown }).payload; |
| 106 | if (!payload || typeof payload !== "object") { | 92 | if (!payload || typeof payload !== "object") { |
| ... | @@ -127,24 +113,21 @@ function runtimeTurnState( | ... | @@ -127,24 +113,21 @@ function runtimeTurnState( |
| 127 | event: ProviderRuntimeEvent, | 113 | event: ProviderRuntimeEvent, |
| 128 | ): "completed" | "failed" | "interrupted" | "cancelled" { | 114 | ): "completed" | "failed" | "interrupted" | "cancelled" { |
| 129 | const payloadState = asString(runtimePayloadRecord(event)?.state); | 115 | const payloadState = asString(runtimePayloadRecord(event)?.state); |
| 130 | const legacyStatus = legacyRuntimeStringField(event, "status"); | 116 | return normalizeRuntimeTurnState(payloadState); |
| 131 | return normalizeRuntimeTurnState(payloadState ?? legacyStatus); | ||
| 132 | } | 117 | } |
| 133 | 118 | ||
| 134 | function runtimeTurnErrorMessage(event: ProviderRuntimeEvent): string | undefined { | 119 | function runtimeTurnErrorMessage(event: ProviderRuntimeEvent): string | undefined { |
| 135 | const payloadErrorMessage = asString(runtimePayloadRecord(event)?.errorMessage); | 120 | const payloadErrorMessage = asString(runtimePayloadRecord(event)?.errorMessage); |
| 136 | const legacyErrorMessage = legacyRuntimeStringField(event, "errorMessage"); | 121 | return payloadErrorMessage; |
| 137 | return payloadErrorMessage ?? legacyErrorMessage; | ||
| 138 | } | 122 | } |
| 139 | 123 | ||
| 140 | function runtimeErrorMessageFromEvent(event: ProviderRuntimeEvent): string | undefined { | 124 | function runtimeErrorMessageFromEvent(event: ProviderRuntimeEvent): string | undefined { |
| 141 | const payloadMessage = asString(runtimePayloadRecord(event)?.message); | 125 | const payloadMessage = asString(runtimePayloadRecord(event)?.message); |
| 142 | const legacyMessage = legacyRuntimeStringField(event, "message"); | 126 | return payloadMessage; |
| 143 | return payloadMessage ?? legacyMessage; | ||
| 144 | } | 127 | } |
| 145 | 128 | ||
| 146 | function orchestrationSessionStatusFromRuntimeState( | 129 | function orchestrationSessionStatusFromRuntimeState( |
| 147 | state: ProviderRuntimeEvent extends { payload: { state: infer T } } ? T : never, | 130 | state: "starting" | "running" | "waiting" | "ready" | "interrupted" | "stopped" | "error", |
| 148 | ): "starting" | "running" | "ready" | "interrupted" | "stopped" | "error" { | 131 | ): "starting" | "running" | "ready" | "interrupted" | "stopped" | "error" { |
| 149 | switch (state) { | 132 | switch (state) { |
| 150 | case "starting": | 133 | case "starting": |
| ... | @@ -268,20 +251,17 @@ function runtimeEventToActivities( | ... | @@ -268,20 +251,17 @@ function runtimeEventToActivities( |
| 268 | ]; | 251 | ]; |
| 269 | } | 252 | } |
| 270 | 253 | ||
| 271 | case "item.completed": { | 254 | case "runtime.warning": { |
| 272 | if (!isToolLifecycleItemType(event.payload.itemType)) { | ||
| 273 | return []; | ||
| 274 | } | ||
| 275 | return [ | 255 | return [ |
| 276 | { | 256 | { |
| 277 | id: event.eventId, | 257 | id: event.eventId, |
| 278 | createdAt: event.createdAt, | 258 | createdAt: event.createdAt, |
| 279 | tone: "tool", | 259 | tone: "info", |
| 280 | kind: "tool.completed", | 260 | kind: "runtime.warning", |
| 281 | summary: `${event.payload.title ?? "Tool"} complete`, | 261 | summary: "Runtime warning", |
| 282 | payload: { | 262 | payload: { |
| 283 | itemType: event.payload.itemType, | 263 | message: truncateDetail(event.payload.message), |
| 284 | ...(event.payload.detail ? { detail: truncateDetail(event.payload.detail) } : {}), | 264 | ...(event.payload.detail !== undefined ? { detail: event.payload.detail } : {}), |
| 285 | }, | 265 | }, |
| 286 | turnId: toTurnId(event.turnId) ?? null, | 266 | turnId: toTurnId(event.turnId) ?? null, |
| 287 | ...maybeSequence, | 267 | ...maybeSequence, |
| ... | @@ -289,20 +269,17 @@ function runtimeEventToActivities( | ... | @@ -289,20 +269,17 @@ function runtimeEventToActivities( |
| 289 | ]; | 269 | ]; |
| 290 | } | 270 | } |
| 291 | 271 | ||
| 292 | case "item.started": { | 272 | case "turn.plan.updated": { |
| 293 | if (!isToolLifecycleItemType(event.payload.itemType)) { | ||
| 294 | return []; | ||
| 295 | } | ||
| 296 | return [ | 273 | return [ |
| 297 | { | 274 | { |
| 298 | id: event.eventId, | 275 | id: event.eventId, |
| 299 | createdAt: event.createdAt, | 276 | createdAt: event.createdAt, |
| 300 | tone: "tool", | 277 | tone: "info", |
| 301 | kind: "tool.started", | 278 | kind: "turn.plan.updated", |
| 302 | summary: `${event.payload.title ?? "Tool"} started`, | 279 | summary: "Plan updated", |
| 303 | payload: { | 280 | payload: { |
| 304 | itemType: event.payload.itemType, | 281 | plan: event.payload.plan, |
| 305 | ...(event.payload.detail ? { detail: truncateDetail(event.payload.detail) } : {}), | 282 | ...(event.payload.explanation !== undefined ? { explanation: event.payload.explanation } : {}), |
| 306 | }, | 283 | }, |
| 307 | turnId: toTurnId(event.turnId) ?? null, | 284 | turnId: toTurnId(event.turnId) ?? null, |
| 308 | ...maybeSequence, | 285 | ...maybeSequence, |
| ... | @@ -310,30 +287,22 @@ function runtimeEventToActivities( | ... | @@ -310,30 +287,22 @@ function runtimeEventToActivities( |
| 310 | ]; | 287 | ]; |
| 311 | } | 288 | } |
| 312 | 289 | ||
| 313 | default: | 290 | case "item.updated": { |
| 314 | break; | 291 | if (!isToolLifecycleItemType(event.payload.itemType)) { |
| 315 | } | 292 | return []; |
| 316 | 293 | } | |
| 317 | // Backward-compatibility for legacy event shapes still emitted by older tests/adapters. | ||
| 318 | const legacyType = legacyRuntimeType(event); | ||
| 319 | switch (legacyType) { | ||
| 320 | case "approval.requested": { | ||
| 321 | const detail = legacyRuntimeStringField(event, "detail"); | ||
| 322 | const requestKind = legacyRuntimeStringField(event, "requestKind"); | ||
| 323 | return [ | 294 | return [ |
| 324 | { | 295 | { |
| 325 | id: event.eventId, | 296 | id: event.eventId, |
| 326 | createdAt: event.createdAt, | 297 | createdAt: event.createdAt, |
| 327 | tone: "approval", | 298 | tone: "tool", |
| 328 | kind: "approval.requested", | 299 | kind: "tool.updated", |
| 329 | summary: | 300 | summary: event.payload.title ?? "Tool updated", |
| 330 | requestKind === "command" | ||
| 331 | ? "Command approval requested" | ||
| 332 | : "File-change approval requested", | ||
| 333 | payload: { | 301 | payload: { |
| 334 | requestId: toApprovalRequestId(event.requestId), | 302 | itemType: event.payload.itemType, |
| 335 | ...(requestKind ? { requestKind } : {}), | 303 | ...(event.payload.status ? { status: event.payload.status } : {}), |
| 336 | ...(detail ? { detail: truncateDetail(detail) } : {}), | 304 | ...(event.payload.detail ? { detail: truncateDetail(event.payload.detail) } : {}), |
| 305 | ...(event.payload.data !== undefined ? { data: event.payload.data } : {}), | ||
| 337 | }, | 306 | }, |
| 338 | turnId: toTurnId(event.turnId) ?? null, | 307 | turnId: toTurnId(event.turnId) ?? null, |
| 339 | ...maybeSequence, | 308 | ...maybeSequence, |
| ... | @@ -341,20 +310,20 @@ function runtimeEventToActivities( | ... | @@ -341,20 +310,20 @@ function runtimeEventToActivities( |
| 341 | ]; | 310 | ]; |
| 342 | } | 311 | } |
| 343 | 312 | ||
| 344 | case "approval.resolved": { | 313 | case "item.completed": { |
| 345 | const decision = legacyRuntimeStringField(event, "decision"); | 314 | if (!isToolLifecycleItemType(event.payload.itemType)) { |
| 346 | const requestKind = legacyRuntimeStringField(event, "requestKind"); | 315 | return []; |
| 316 | } | ||
| 347 | return [ | 317 | return [ |
| 348 | { | 318 | { |
| 349 | id: event.eventId, | 319 | id: event.eventId, |
| 350 | createdAt: event.createdAt, | 320 | createdAt: event.createdAt, |
| 351 | tone: "approval", | 321 | tone: "tool", |
| 352 | kind: "approval.resolved", | 322 | kind: "tool.completed", |
| 353 | summary: "Approval resolved", | 323 | summary: `${event.payload.title ?? "Tool"} complete`, |
| 354 | payload: { | 324 | payload: { |
| 355 | requestId: toApprovalRequestId(event.requestId), | 325 | itemType: event.payload.itemType, |
| 356 | ...(requestKind ? { requestKind } : {}), | 326 | ...(event.payload.detail ? { detail: truncateDetail(event.payload.detail) } : {}), |
| 357 | ...(decision ? { decision } : {}), | ||
| 358 | }, | 327 | }, |
| 359 | turnId: toTurnId(event.turnId) ?? null, | 328 | turnId: toTurnId(event.turnId) ?? null, |
| 360 | ...maybeSequence, | 329 | ...maybeSequence, |
| ... | @@ -362,22 +331,20 @@ function runtimeEventToActivities( | ... | @@ -362,22 +331,20 @@ function runtimeEventToActivities( |
| 362 | ]; | 331 | ]; |
| 363 | } | 332 | } |
| 364 | 333 | ||
| 365 | case "tool.completed": | 334 | case "item.started": { |
| 366 | case "tool.started": { | 335 | if (!isToolLifecycleItemType(event.payload.itemType)) { |
| 367 | const title = legacyRuntimeStringField(event, "title") ?? "Tool"; | 336 | return []; |
| 368 | const detail = legacyRuntimeStringField(event, "detail"); | 337 | } |
| 369 | const toolKind = legacyRuntimeStringField(event, "toolKind"); | ||
| 370 | const activityKind = legacyType; | ||
| 371 | return [ | 338 | return [ |
| 372 | { | 339 | { |
| 373 | id: event.eventId, | 340 | id: event.eventId, |
| 374 | createdAt: event.createdAt, | 341 | createdAt: event.createdAt, |
| 375 | tone: "tool", | 342 | tone: "tool", |
| 376 | kind: activityKind, | 343 | kind: "tool.started", |
| 377 | summary: `${title} ${legacyType === "tool.started" ? "started" : "complete"}`, | 344 | summary: `${event.payload.title ?? "Tool"} started`, |
| 378 | payload: { | 345 | payload: { |
| 379 | ...(toolKind ? { toolKind } : {}), | 346 | itemType: event.payload.itemType, |
| 380 | ...(detail ? { detail: truncateDetail(detail) } : {}), | 347 | ...(event.payload.detail ? { detail: truncateDetail(event.payload.detail) } : {}), |
| 381 | }, | 348 | }, |
| 382 | turnId: toTurnId(event.turnId) ?? null, | 349 | turnId: toTurnId(event.turnId) ?? null, |
| 383 | ...maybeSequence, | 350 | ...maybeSequence, |
| ... | @@ -386,8 +353,10 @@ function runtimeEventToActivities( | ... | @@ -386,8 +353,10 @@ function runtimeEventToActivities( |
| 386 | } | 353 | } |
| 387 | 354 | ||
| 388 | default: | 355 | default: |
| 389 | return []; | 356 | break; |
| 390 | } | 357 | } |
| 358 | |||
| 359 | return []; | ||
| 391 | } | 360 | } |
| 392 | 361 | ||
| 393 | const make = Effect.gen(function* () { | 362 | const make = Effect.gen(function* () { |
| ... | @@ -570,7 +539,6 @@ const make = Effect.gen(function* () { | ... | @@ -570,7 +539,6 @@ const make = Effect.gen(function* () { |
| 570 | ); | 539 | ); |
| 571 | if (!thread) return; | 540 | if (!thread) return; |
| 572 | 541 | ||
| 573 | const legacyType = legacyRuntimeType(event); | ||
| 574 | const now = event.createdAt; | 542 | const now = event.createdAt; |
| 575 | const sessionProviderThreadId = thread.session?.providerThreadId ?? null; | 543 | const sessionProviderThreadId = thread.session?.providerThreadId ?? null; |
| 576 | const scopedSessionProviderThreadId = isSyntheticClaudeThreadId( | 544 | const scopedSessionProviderThreadId = isSyntheticClaudeThreadId( |
| ... | @@ -692,8 +660,7 @@ const make = Effect.gen(function* () { | ... | @@ -692,8 +660,7 @@ const make = Effect.gen(function* () { |
| 692 | providerName: event.provider, | 660 | providerName: event.provider, |
| 693 | providerSessionId, | 661 | providerSessionId, |
| 694 | providerThreadId, | 662 | providerThreadId, |
| 695 | approvalPolicy: thread.session?.approvalPolicy ?? DEFAULT_APPROVAL_POLICY, | 663 | runtimeMode: thread.session?.runtimeMode ?? "full-access", |
| 696 | sandboxMode: thread.session?.sandboxMode ?? DEFAULT_SANDBOX_MODE, | ||
| 697 | activeTurnId: nextActiveTurnId, | 664 | activeTurnId: nextActiveTurnId, |
| 698 | lastError, | 665 | lastError, |
| 699 | updatedAt: now, | 666 | updatedAt: now, |
| ... | @@ -706,9 +673,7 @@ const make = Effect.gen(function* () { | ... | @@ -706,9 +673,7 @@ const make = Effect.gen(function* () { |
| 706 | const assistantDelta = | 673 | const assistantDelta = |
| 707 | event.type === "content.delta" && event.payload.streamKind === "assistant_text" | 674 | event.type === "content.delta" && event.payload.streamKind === "assistant_text" |
| 708 | ? event.payload.delta | 675 | ? event.payload.delta |
| 709 | : legacyType === "message.delta" | 676 | : undefined; |
| 710 | ? legacyRuntimeStringField(event, "delta") | ||
| 711 | : undefined; | ||
| 712 | 677 | ||
| 713 | if (assistantDelta && assistantDelta.length > 0) { | 678 | if (assistantDelta && assistantDelta.length > 0) { |
| 714 | const assistantMessageId = MessageId.makeUnsafe( | 679 | const assistantMessageId = MessageId.makeUnsafe( |
| ... | @@ -752,11 +717,6 @@ const make = Effect.gen(function* () { | ... | @@ -752,11 +717,6 @@ const make = Effect.gen(function* () { |
| 752 | messageId: MessageId.makeUnsafe(`assistant:${event.itemId ?? event.turnId ?? event.sessionId}`), | 717 | messageId: MessageId.makeUnsafe(`assistant:${event.itemId ?? event.turnId ?? event.sessionId}`), |
| 753 | fallbackText: event.payload.detail, | 718 | fallbackText: event.payload.detail, |
| 754 | } | 719 | } |
| 755 | : legacyType === "message.completed" | ||
| 756 | ? { | ||
| 757 | messageId: MessageId.makeUnsafe(`assistant:${event.itemId ?? event.turnId ?? event.sessionId}`), | ||
| 758 | fallbackText: undefined, | ||
| 759 | } | ||
| 760 | : undefined; | 720 | : undefined; |
| 761 | 721 | ||
| 762 | if (assistantCompletion) { | 722 | if (assistantCompletion) { |
| ... | @@ -836,8 +796,7 @@ const make = Effect.gen(function* () { | ... | @@ -836,8 +796,7 @@ const make = Effect.gen(function* () { |
| 836 | providerName: event.provider, | 796 | providerName: event.provider, |
| 837 | providerSessionId, | 797 | providerSessionId, |
| 838 | providerThreadId, | 798 | providerThreadId, |
| 839 | approvalPolicy: thread.session?.approvalPolicy ?? DEFAULT_APPROVAL_POLICY, | 799 | runtimeMode: thread.session?.runtimeMode ?? "full-access", |
| 840 | sandboxMode: thread.session?.sandboxMode ?? DEFAULT_SANDBOX_MODE, | ||
| 841 | activeTurnId: eventTurnId ?? null, | 800 | activeTurnId: eventTurnId ?? null, |
| 842 | lastError: runtimeErrorMessage, | 801 | lastError: runtimeErrorMessage, |
| 843 | updatedAt: now, | 802 | updatedAt: now, |
| ... | @@ -847,6 +806,37 @@ const make = Effect.gen(function* () { | ... | @@ -847,6 +806,37 @@ const make = Effect.gen(function* () { |
| 847 | } | 806 | } |
| 848 | } | 807 | } |
| 849 | 808 | ||
| 809 | if (event.type === "thread.metadata.updated" && event.payload.name) { | ||
| 810 | yield* orchestrationEngine.dispatch({ | ||
| 811 | type: "thread.meta.update", | ||
| 812 | commandId: providerCommandId(event, "thread-meta-update"), | ||
| 813 | threadId: thread.id, | ||
| 814 | title: event.payload.name, | ||
| 815 | }); | ||
| 816 | } | ||
| 817 | |||
| 818 | if (event.type === "turn.diff.updated") { | ||
| 819 | const turnId = toTurnId(event.turnId); | ||
| 820 | if (turnId) { | ||
| 821 | const assistantMessageId = MessageId.makeUnsafe( | ||
| 822 | `assistant:${event.itemId ?? event.turnId ?? event.sessionId}`, | ||
| 823 | ); | ||
| 824 | yield* orchestrationEngine.dispatch({ | ||
| 825 | type: "thread.turn.diff.complete", | ||
| 826 | commandId: providerCommandId(event, "thread-turn-diff-complete"), | ||
| 827 | threadId: thread.id, | ||
| 828 | turnId, | ||
| 829 | completedAt: now, | ||
| 830 | checkpointRef: CheckpointRef.makeUnsafe(`provider-diff:${event.eventId}`), | ||
| 831 | status: "missing", | ||
| 832 | files: [], | ||
| 833 | assistantMessageId, | ||
| 834 | checkpointTurnCount: thread.checkpoints.length + 1, | ||
| 835 | createdAt: now, | ||
| 836 | }); | ||
| 837 | } | ||
| 838 | } | ||
| 839 | |||
| 850 | const activities = runtimeEventToActivities(event); | 840 | const activities = runtimeEventToActivities(event); |
| 851 | yield* Effect.forEach(activities, (activity) => | 841 | yield* Effect.forEach(activities, (activity) => |
| 852 | orchestrationEngine.dispatch({ | 842 | orchestrationEngine.dispatch({ |
apps/server/src/orchestration/commandInvariants.test.ts+1-2| ... | @@ -90,8 +90,7 @@ const messageSendCommand: OrchestrationCommand = { | ... | @@ -90,8 +90,7 @@ const messageSendCommand: OrchestrationCommand = { |
| 90 | text: "hello", | 90 | text: "hello", |
| 91 | attachments: [], | 91 | attachments: [], |
| 92 | }, | 92 | }, |
| 93 | approvalPolicy: "on-request", | 93 | runtimeMode: "approval-required", |
| 94 | sandboxMode: "workspace-write", | ||
| 95 | createdAt: now, | 94 | createdAt: now, |
| 96 | }; | 95 | }; |
| 97 | 96 |
apps/server/src/orchestration/decider.projectScripts.test.ts+2-4| ... | @@ -153,8 +153,7 @@ describe("decider project scripts", () => { | ... | @@ -153,8 +153,7 @@ describe("decider project scripts", () => { |
| 153 | provider: "claudeCode", | 153 | provider: "claudeCode", |
| 154 | model: "gpt-5", | 154 | model: "gpt-5", |
| 155 | effort: "high", | 155 | effort: "high", |
| 156 | approvalPolicy: "on-request", | 156 | runtimeMode: "approval-required", |
| 157 | sandboxMode: "workspace-write", | ||
| 158 | createdAt: now, | 157 | createdAt: now, |
| 159 | }, | 158 | }, |
| 160 | readModel, | 159 | readModel, |
| ... | @@ -178,8 +177,7 @@ describe("decider project scripts", () => { | ... | @@ -178,8 +177,7 @@ describe("decider project scripts", () => { |
| 178 | provider: "claudeCode", | 177 | provider: "claudeCode", |
| 179 | model: "gpt-5", | 178 | model: "gpt-5", |
| 180 | effort: "high", | 179 | effort: "high", |
| 181 | approvalPolicy: "on-request", | 180 | runtimeMode: "approval-required", |
| 182 | sandboxMode: "workspace-write", | ||
| 183 | }); | 181 | }); |
| 184 | }); | 182 | }); |
| 185 | }); | 183 | }); |
apps/server/src/orchestration/decider.ts+1-2| ... | @@ -255,8 +255,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" | ... | @@ -255,8 +255,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" |
| 255 | ...(command.model !== undefined ? { model: command.model } : {}), | 255 | ...(command.model !== undefined ? { model: command.model } : {}), |
| 256 | ...(command.effort !== undefined ? { effort: command.effort } : {}), | 256 | ...(command.effort !== undefined ? { effort: command.effort } : {}), |
| 257 | assistantDeliveryMode: command.assistantDeliveryMode ?? DEFAULT_ASSISTANT_DELIVERY_MODE, | 257 | assistantDeliveryMode: command.assistantDeliveryMode ?? DEFAULT_ASSISTANT_DELIVERY_MODE, |
| 258 | approvalPolicy: command.approvalPolicy, | 258 | runtimeMode: command.runtimeMode, |
| 259 | sandboxMode: command.sandboxMode, | ||
| 260 | createdAt: command.createdAt, | 259 | createdAt: command.createdAt, |
| 261 | }, | 260 | }, |
| 262 | }; | 261 | }; |
apps/server/src/orchestration/projector.test.ts+2-4| ... | @@ -135,8 +135,7 @@ describe("orchestration projector", () => { | ... | @@ -135,8 +135,7 @@ describe("orchestration projector", () => { |
| 135 | payload: { | 135 | payload: { |
| 136 | threadId: "thread-1", | 136 | threadId: "thread-1", |
| 137 | messageId: "message-1", | 137 | messageId: "message-1", |
| 138 | approvalPolicy: "on-request", | 138 | runtimeMode: "approval-required", |
| 139 | sandboxMode: "workspace-write", | ||
| 140 | createdAt: "2026-01-01T00:00:00.000Z", | 139 | createdAt: "2026-01-01T00:00:00.000Z", |
| 141 | }, | 140 | }, |
| 142 | }), | 141 | }), |
| ... | @@ -195,8 +194,7 @@ describe("orchestration projector", () => { | ... | @@ -195,8 +194,7 @@ describe("orchestration projector", () => { |
| 195 | providerName: "codex", | 194 | providerName: "codex", |
| 196 | providerSessionId: "session-1", | 195 | providerSessionId: "session-1", |
| 197 | providerThreadId: "provider-thread-1", | 196 | providerThreadId: "provider-thread-1", |
| 198 | approvalPolicy: "on-request", | 197 | runtimeMode: "approval-required", |
| 199 | sandboxMode: "workspace-write", | ||
| 200 | activeTurnId: "turn-1", | 198 | activeTurnId: "turn-1", |
| 201 | lastError: null, | 199 | lastError: null, |
| 202 | updatedAt: startedAt, | 200 | updatedAt: startedAt, |
apps/server/src/persistence/Layers/ProjectionThreadSessions.ts+4-8| ... | @@ -25,8 +25,7 @@ const makeProjectionThreadSessionRepository = Effect.gen(function* () { | ... | @@ -25,8 +25,7 @@ const makeProjectionThreadSessionRepository = Effect.gen(function* () { |
| 25 | provider_name, | 25 | provider_name, |
| 26 | provider_session_id, | 26 | provider_session_id, |
| 27 | provider_thread_id, | 27 | provider_thread_id, |
| 28 | approval_policy, | 28 | runtime_mode, |
| 29 | sandbox_mode, | ||
| 30 | active_turn_id, | 29 | active_turn_id, |
| 31 | last_error, | 30 | last_error, |
| 32 | updated_at | 31 | updated_at |
| ... | @@ -37,8 +36,7 @@ const makeProjectionThreadSessionRepository = Effect.gen(function* () { | ... | @@ -37,8 +36,7 @@ const makeProjectionThreadSessionRepository = Effect.gen(function* () { |
| 37 | ${row.providerName}, | 36 | ${row.providerName}, |
| 38 | ${row.providerSessionId}, | 37 | ${row.providerSessionId}, |
| 39 | ${row.providerThreadId}, | 38 | ${row.providerThreadId}, |
| 40 | ${row.approvalPolicy}, | 39 | ${row.runtimeMode}, |
| 41 | ${row.sandboxMode}, | ||
| 42 | ${row.activeTurnId}, | 40 | ${row.activeTurnId}, |
| 43 | ${row.lastError}, | 41 | ${row.lastError}, |
| 44 | ${row.updatedAt} | 42 | ${row.updatedAt} |
| ... | @@ -49,8 +47,7 @@ const makeProjectionThreadSessionRepository = Effect.gen(function* () { | ... | @@ -49,8 +47,7 @@ const makeProjectionThreadSessionRepository = Effect.gen(function* () { |
| 49 | provider_name = excluded.provider_name, | 47 | provider_name = excluded.provider_name, |
| 50 | provider_session_id = excluded.provider_session_id, | 48 | provider_session_id = excluded.provider_session_id, |
| 51 | provider_thread_id = excluded.provider_thread_id, | 49 | provider_thread_id = excluded.provider_thread_id, |
| 52 | approval_policy = excluded.approval_policy, | 50 | runtime_mode = excluded.runtime_mode, |
| 53 | sandbox_mode = excluded.sandbox_mode, | ||
| 54 | active_turn_id = excluded.active_turn_id, | 51 | active_turn_id = excluded.active_turn_id, |
| 55 | last_error = excluded.last_error, | 52 | last_error = excluded.last_error, |
| 56 | updated_at = excluded.updated_at | 53 | updated_at = excluded.updated_at |
| ... | @@ -68,8 +65,7 @@ const makeProjectionThreadSessionRepository = Effect.gen(function* () { | ... | @@ -68,8 +65,7 @@ const makeProjectionThreadSessionRepository = Effect.gen(function* () { |
| 68 | provider_name AS "providerName", | 65 | provider_name AS "providerName", |
| 69 | provider_session_id AS "providerSessionId", | 66 | provider_session_id AS "providerSessionId", |
| 70 | provider_thread_id AS "providerThreadId", | 67 | provider_thread_id AS "providerThreadId", |
| 71 | approval_policy AS "approvalPolicy", | 68 | runtime_mode AS "runtimeMode", |
| 72 | sandbox_mode AS "sandboxMode", | ||
| 73 | active_turn_id AS "activeTurnId", | 69 | active_turn_id AS "activeTurnId", |
| 74 | last_error AS "lastError", | 70 | last_error AS "lastError", |
| 75 | updated_at AS "updatedAt" | 71 | updated_at AS "updatedAt" |
apps/server/src/persistence/Migrations/006_ProjectionThreadSessionRuntimeModeColumns.ts+4-15| ... | @@ -1,30 +1,19 @@ | ... | @@ -1,30 +1,19 @@ |
| 1 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 1 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 2 | import * as Effect from "effect/Effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | 3 | ||
| 4 | const DEFAULT_APPROVAL_POLICY = "never"; | 4 | const DEFAULT_RUNTIME_MODE = "full-access"; |
| 5 | const DEFAULT_SANDBOX_MODE = "workspace-write"; | ||
| 6 | 5 | ||
| 7 | export default Effect.gen(function* () { | 6 | export default Effect.gen(function* () { |
| 8 | const sql = yield* SqlClient.SqlClient; | 7 | const sql = yield* SqlClient.SqlClient; |
| 9 | 8 | ||
| 10 | yield* sql` | 9 | yield* sql` |
| 11 | ALTER TABLE projection_thread_sessions | 10 | ALTER TABLE projection_thread_sessions |
| 12 | ADD COLUMN approval_policy TEXT NOT NULL DEFAULT 'never' | 11 | ADD COLUMN runtime_mode TEXT NOT NULL DEFAULT 'full-access' |
| 13 | `; | 12 | `; |
| 14 | 13 | ||
| 15 | yield* sql` | ||
| 16 | ALTER TABLE projection_thread_sessions | ||
| 17 | ADD COLUMN sandbox_mode TEXT NOT NULL DEFAULT 'workspace-write' | ||
| 18 | `; | ||
| 19 | |||
| 20 | yield* sql` | ||
| 21 | UPDATE projection_thread_sessions | ||
| 22 | SET approval_policy = ${DEFAULT_APPROVAL_POLICY} | ||
| 23 | WHERE approval_policy IS NULL | ||
| 24 | `; | ||
| 25 | yield* sql` | 14 | yield* sql` |
| 26 | UPDATE projection_thread_sessions | 15 | UPDATE projection_thread_sessions |
| 27 | SET sandbox_mode = ${DEFAULT_SANDBOX_MODE} | 16 | SET runtime_mode = ${DEFAULT_RUNTIME_MODE} |
| 28 | WHERE sandbox_mode IS NULL | 17 | WHERE runtime_mode IS NULL |
| 29 | `; | 18 | `; |
| 30 | }); | 19 | }); |
apps/server/src/persistence/Services/ProjectionThreadSessions.ts+2-4| ... | @@ -7,10 +7,9 @@ | ... | @@ -7,10 +7,9 @@ |
| 7 | * @module ProjectionThreadSessionRepository | 7 | * @module ProjectionThreadSessionRepository |
| 8 | */ | 8 | */ |
| 9 | import { | 9 | import { |
| 10 | RuntimeMode, | ||
| 10 | IsoDateTime, | 11 | IsoDateTime, |
| 11 | OrchestrationSessionStatus, | 12 | OrchestrationSessionStatus, |
| 12 | ProviderApprovalPolicy, | ||
| 13 | ProviderSandboxMode, | ||
| 14 | ProviderSessionId, | 13 | ProviderSessionId, |
| 15 | ProviderThreadId, | 14 | ProviderThreadId, |
| 16 | ThreadId, | 15 | ThreadId, |
| ... | @@ -27,8 +26,7 @@ export const ProjectionThreadSession = Schema.Struct({ | ... | @@ -27,8 +26,7 @@ export const ProjectionThreadSession = Schema.Struct({ |
| 27 | providerName: Schema.NullOr(Schema.String), | 26 | providerName: Schema.NullOr(Schema.String), |
| 28 | providerSessionId: Schema.NullOr(ProviderSessionId), | 27 | providerSessionId: Schema.NullOr(ProviderSessionId), |
| 29 | providerThreadId: Schema.NullOr(ProviderThreadId), | 28 | providerThreadId: Schema.NullOr(ProviderThreadId), |
| 30 | approvalPolicy: ProviderApprovalPolicy, | 29 | runtimeMode: RuntimeMode, |
| 31 | sandboxMode: ProviderSandboxMode, | ||
| 32 | activeTurnId: Schema.NullOr(TurnId), | 30 | activeTurnId: Schema.NullOr(TurnId), |
| 33 | lastError: Schema.NullOr(Schema.String), | 31 | lastError: Schema.NullOr(Schema.String), |
| 34 | updatedAt: IsoDateTime, | 32 | updatedAt: IsoDateTime, |
apps/server/src/provider/Layers/ClaudeCodeAdapter.test.ts+3-4| ... | @@ -192,8 +192,7 @@ describe("ClaudeCodeAdapterLive", () => { | ... | @@ -192,8 +192,7 @@ describe("ClaudeCodeAdapterLive", () => { |
| 192 | const adapter = yield* ClaudeCodeAdapter; | 192 | const adapter = yield* ClaudeCodeAdapter; |
| 193 | yield* adapter.startSession({ | 193 | yield* adapter.startSession({ |
| 194 | provider: "claudeCode", | 194 | provider: "claudeCode", |
| 195 | approvalPolicy: "never", | 195 | runtimeMode: "full-access", |
| 196 | sandboxMode: "danger-full-access", | ||
| 197 | }); | 196 | }); |
| 198 | 197 | ||
| 199 | const createInput = harness.getLastCreateQueryInput(); | 198 | const createInput = harness.getLastCreateQueryInput(); |
| ... | @@ -211,7 +210,7 @@ describe("ClaudeCodeAdapterLive", () => { | ... | @@ -211,7 +210,7 @@ describe("ClaudeCodeAdapterLive", () => { |
| 211 | const adapter = yield* ClaudeCodeAdapter; | 210 | const adapter = yield* ClaudeCodeAdapter; |
| 212 | yield* adapter.startSession({ | 211 | yield* adapter.startSession({ |
| 213 | provider: "claudeCode", | 212 | provider: "claudeCode", |
| 214 | approvalPolicy: "never", | 213 | runtimeMode: "full-access", |
| 215 | providerOptions: { | 214 | providerOptions: { |
| 216 | claudeCode: { | 215 | claudeCode: { |
| 217 | permissionMode: "plan", | 216 | permissionMode: "plan", |
| ... | @@ -598,7 +597,7 @@ describe("ClaudeCodeAdapterLive", () => { | ... | @@ -598,7 +597,7 @@ describe("ClaudeCodeAdapterLive", () => { |
| 598 | 597 | ||
| 599 | const session = yield* adapter.startSession({ | 598 | const session = yield* adapter.startSession({ |
| 600 | provider: "claudeCode", | 599 | provider: "claudeCode", |
| 601 | approvalPolicy: "on-request", | 600 | runtimeMode: "approval-required", |
| 602 | }); | 601 | }); |
| 603 | 602 | ||
| 604 | yield* Stream.take(adapter.streamEvents, 3).pipe(Stream.runDrain); | 603 | yield* Stream.take(adapter.streamEvents, 3).pipe(Stream.runDrain); |
apps/server/src/provider/Layers/ClaudeCodeAdapter.ts+3-3| ... | @@ -1461,8 +1461,8 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { | ... | @@ -1461,8 +1461,8 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { |
| 1461 | } satisfies PermissionResult; | 1461 | } satisfies PermissionResult; |
| 1462 | } | 1462 | } |
| 1463 | 1463 | ||
| 1464 | const approvalPolicy = input.approvalPolicy ?? "never"; | 1464 | const runtimeMode = input.runtimeMode ?? "full-access"; |
| 1465 | if (approvalPolicy === "never") { | 1465 | if (runtimeMode === "full-access") { |
| 1466 | return { | 1466 | return { |
| 1467 | behavior: "allow", | 1467 | behavior: "allow", |
| 1468 | updatedInput: toolInput, | 1468 | updatedInput: toolInput, |
| ... | @@ -1590,7 +1590,7 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { | ... | @@ -1590,7 +1590,7 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { |
| 1590 | const providerOptions = input.providerOptions?.claudeCode; | 1590 | const providerOptions = input.providerOptions?.claudeCode; |
| 1591 | const permissionMode = | 1591 | const permissionMode = |
| 1592 | toPermissionMode(providerOptions?.permissionMode) ?? | 1592 | toPermissionMode(providerOptions?.permissionMode) ?? |
| 1593 | (input.approvalPolicy === "never" ? "bypassPermissions" : undefined); | 1593 | (input.runtimeMode === "full-access" ? "bypassPermissions" : undefined); |
| 1594 | 1594 | ||
| 1595 | const queryOptions: ClaudeQueryOptions = { | 1595 | const queryOptions: ClaudeQueryOptions = { |
| 1596 | ...(input.cwd ? { cwd: input.cwd } : {}), | 1596 | ...(input.cwd ? { cwd: input.cwd } : {}), |
apps/server/src/provider/Layers/CodexAdapter.test.ts+42| ... | @@ -320,6 +320,48 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { | ... | @@ -320,6 +320,48 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { |
| 320 | assert.equal(firstEvent.value.payload.requestType, "command_execution_approval"); | 320 | assert.equal(firstEvent.value.payload.requestType, "command_execution_approval"); |
| 321 | }), | 321 | }), |
| 322 | ); | 322 | ); |
| 323 | |||
| 324 | it.effect("maps windowsSandbox/setupCompleted to session state and warning on failure", () => | ||
| 325 | Effect.gen(function* () { | ||
| 326 | const adapter = yield* CodexAdapter; | ||
| 327 | const eventsFiber = yield* Stream.runCollect(Stream.take(adapter.streamEvents, 2)).pipe( | ||
| 328 | Effect.forkChild, | ||
| 329 | ); | ||
| 330 | |||
| 331 | const event: ProviderEvent = { | ||
| 332 | id: asEventId("evt-windows-sandbox-failed"), | ||
| 333 | kind: "notification", | ||
| 334 | provider: "codex", | ||
| 335 | sessionId: asSessionId("sess-1"), | ||
| 336 | createdAt: new Date().toISOString(), | ||
| 337 | method: "windowsSandbox/setupCompleted", | ||
| 338 | message: "Sandbox setup failed", | ||
| 339 | payload: { | ||
| 340 | success: false, | ||
| 341 | detail: "unsupported environment", | ||
| 342 | }, | ||
| 343 | }; | ||
| 344 | |||
| 345 | lifecycleManager.emit("event", event); | ||
| 346 | const events = Array.from(yield* Fiber.join(eventsFiber)); | ||
| 347 | |||
| 348 | assert.equal(events.length, 2); | ||
| 349 | |||
| 350 | const firstEvent = events[0]; | ||
| 351 | const secondEvent = events[1]; | ||
| 352 | |||
| 353 | assert.equal(firstEvent?.type, "session.state.changed"); | ||
| 354 | if (firstEvent?.type === "session.state.changed") { | ||
| 355 | assert.equal(firstEvent.payload.state, "error"); | ||
| 356 | assert.equal(firstEvent.payload.reason, "Sandbox setup failed"); | ||
| 357 | } | ||
| 358 | |||
| 359 | assert.equal(secondEvent?.type, "runtime.warning"); | ||
| 360 | if (secondEvent?.type === "runtime.warning") { | ||
| 361 | assert.equal(secondEvent.payload.message, "Sandbox setup failed"); | ||
| 362 | } | ||
| 363 | }), | ||
| 364 | ); | ||
| 323 | }); | 365 | }); |
| 324 | 366 | ||
| 325 | afterAll(() => { | 367 | afterAll(() => { |
apps/server/src/provider/Layers/CodexAdapter.ts+33-13| ... | @@ -917,28 +917,37 @@ function mapToRuntimeEvents(event: ProviderEvent): ReadonlyArray<ProviderRuntime | ... | @@ -917,28 +917,37 @@ function mapToRuntimeEvents(event: ProviderEvent): ReadonlyArray<ProviderRuntime |
| 917 | } | 917 | } |
| 918 | 918 | ||
| 919 | if (event.method === "windowsSandbox/setupCompleted") { | 919 | if (event.method === "windowsSandbox/setupCompleted") { |
| 920 | const payloadRecord = asObject(event.payload); | ||
| 921 | const success = payloadRecord?.success; | ||
| 922 | const successMessage = event.message ?? "Windows sandbox setup completed"; | ||
| 923 | const failureMessage = event.message ?? "Windows sandbox setup failed"; | ||
| 924 | |||
| 920 | return [ | 925 | return [ |
| 921 | { | 926 | { |
| 922 | ...runtimeEventBase(event), | 927 | ...runtimeEventBase(event), |
| 923 | type: "runtime.warning", | 928 | type: "session.state.changed", |
| 924 | payload: { | 929 | payload: { |
| 925 | message: event.message ?? "Windows sandbox setup completed", | 930 | state: success === false ? "error" : "ready", |
| 931 | reason: success === false ? failureMessage : successMessage, | ||
| 926 | ...(event.payload !== undefined ? { detail: event.payload } : {}), | 932 | ...(event.payload !== undefined ? { detail: event.payload } : {}), |
| 927 | }, | 933 | }, |
| 928 | }, | 934 | }, |
| 935 | ...(success === false | ||
| 936 | ? [ | ||
| 937 | { | ||
| 938 | ...runtimeEventBase(event), | ||
| 939 | type: "runtime.warning" as const, | ||
| 940 | payload: { | ||
| 941 | message: failureMessage, | ||
| 942 | ...(event.payload !== undefined ? { detail: event.payload } : {}), | ||
| 943 | }, | ||
| 944 | }, | ||
| 945 | ] | ||
| 946 | : []), | ||
| 929 | ]; | 947 | ]; |
| 930 | } | 948 | } |
| 931 | 949 | ||
| 932 | return [ | 950 | return []; |
| 933 | { | ||
| 934 | ...runtimeEventBase(event), | ||
| 935 | type: "runtime.warning", | ||
| 936 | payload: { | ||
| 937 | message: `Unhandled Codex provider event method: ${event.method}`, | ||
| 938 | ...(event.payload !== undefined ? { detail: event.payload } : {}), | ||
| 939 | }, | ||
| 940 | }, | ||
| 941 | ]; | ||
| 942 | } | 951 | } |
| 943 | 952 | ||
| 944 | const makeCodexAdapter = (options?: CodexAdapterLiveOptions) => | 953 | const makeCodexAdapter = (options?: CodexAdapterLiveOptions) => |
| ... | @@ -1121,7 +1130,18 @@ const makeCodexAdapter = (options?: CodexAdapterLiveOptions) => | ... | @@ -1121,7 +1130,18 @@ const makeCodexAdapter = (options?: CodexAdapterLiveOptions) => |
| 1121 | const listener = (event: ProviderEvent) => | 1130 | const listener = (event: ProviderEvent) => |
| 1122 | Effect.gen(function* () { | 1131 | Effect.gen(function* () { |
| 1123 | yield* writeNativeEvent(event); | 1132 | yield* writeNativeEvent(event); |
| 1124 | yield* Queue.offerAll(runtimeEventQueue, mapToRuntimeEvents(event)); | 1133 | const runtimeEvents = mapToRuntimeEvents(event); |
| 1134 | if (runtimeEvents.length === 0) { | ||
| 1135 | yield* Effect.logDebug("ignoring unhandled Codex provider event", { | ||
| 1136 | method: event.method, | ||
| 1137 | sessionId: event.sessionId, | ||
| 1138 | threadId: event.threadId, | ||
| 1139 | turnId: event.turnId, | ||
| 1140 | itemId: event.itemId, | ||
| 1141 | }); | ||
| 1142 | return; | ||
| 1143 | } | ||
| 1144 | yield* Queue.offerAll(runtimeEventQueue, runtimeEvents); | ||
| 1125 | }).pipe(Effect.runPromiseWith(services)); | 1145 | }).pipe(Effect.runPromiseWith(services)); |
| 1126 | manager.on("event", listener); | 1146 | manager.on("event", listener); |
| 1127 | return listener; | 1147 | return listener; |
apps/server/src/provider/Layers/CursorAdapter.test.ts+2-1| ... | @@ -467,6 +467,7 @@ describe("CursorAdapterLive", () => { | ... | @@ -467,6 +467,7 @@ describe("CursorAdapterLive", () => { |
| 467 | 467 | ||
| 468 | const session = yield* adapter.startSession({ | 468 | const session = yield* adapter.startSession({ |
| 469 | provider: "cursor", | 469 | provider: "cursor", |
| 470 | runtimeMode: "approval-required", | ||
| 470 | }); | 471 | }); |
| 471 | 472 | ||
| 472 | // consume startup events | 473 | // consume startup events |
| ... | @@ -520,7 +521,7 @@ describe("CursorAdapterLive", () => { | ... | @@ -520,7 +521,7 @@ describe("CursorAdapterLive", () => { |
| 520 | 521 | ||
| 521 | yield* adapter.startSession({ | 522 | yield* adapter.startSession({ |
| 522 | provider: "cursor", | 523 | provider: "cursor", |
| 523 | approvalPolicy: "never", | 524 | runtimeMode: "full-access", |
| 524 | }); | 525 | }); |
| 525 | 526 | ||
| 526 | // consume startup events | 527 | // consume startup events |
apps/server/src/provider/Layers/CursorAdapter.ts+4-4| ... | @@ -15,7 +15,6 @@ import { | ... | @@ -15,7 +15,6 @@ import { |
| 15 | EventId, | 15 | EventId, |
| 16 | type CanonicalItemType, | 16 | type CanonicalItemType, |
| 17 | type CanonicalRequestType, | 17 | type CanonicalRequestType, |
| 18 | type ProviderApprovalPolicy, | ||
| 19 | ProviderItemId, | 18 | ProviderItemId, |
| 20 | ProviderSessionId, | 19 | ProviderSessionId, |
| 21 | type ProviderRuntimeEvent, | 20 | type ProviderRuntimeEvent, |
| ... | @@ -23,6 +22,7 @@ import { | ... | @@ -23,6 +22,7 @@ import { |
| 23 | ProviderThreadId, | 22 | ProviderThreadId, |
| 24 | ProviderTurnId, | 23 | ProviderTurnId, |
| 25 | type ProviderTurnStartResult, | 24 | type ProviderTurnStartResult, |
| 25 | type RuntimeMode, | ||
| 26 | RuntimeItemId, | 26 | RuntimeItemId, |
| 27 | RuntimeRequestId, | 27 | RuntimeRequestId, |
| 28 | RuntimeSessionId, | 28 | RuntimeSessionId, |
| ... | @@ -81,7 +81,7 @@ interface CursorTurnState { | ... | @@ -81,7 +81,7 @@ interface CursorTurnState { |
| 81 | 81 | ||
| 82 | interface CursorSessionContext { | 82 | interface CursorSessionContext { |
| 83 | session: ProviderSession; | 83 | session: ProviderSession; |
| 84 | approvalPolicy: ProviderApprovalPolicy; | 84 | runtimeMode: RuntimeMode; |
| 85 | readonly child: ChildProcessWithoutNullStreams; | 85 | readonly child: ChildProcessWithoutNullStreams; |
| 86 | readonly output: readline.Interface; | 86 | readonly output: readline.Interface; |
| 87 | readonly pending: Map<string, PendingRequest>; | 87 | readonly pending: Map<string, PendingRequest>; |
| ... | @@ -539,7 +539,7 @@ function makeCursorAdapter(options?: CursorAdapterLiveOptions) { | ... | @@ -539,7 +539,7 @@ function makeCursorAdapter(options?: CursorAdapterLiveOptions) { |
| 539 | const options = decoded.params.options.map((entry) => ({ optionId: entry.optionId })); | 539 | const options = decoded.params.options.map((entry) => ({ optionId: entry.optionId })); |
| 540 | const detail = asString(asObject(decoded.params.toolCall)?.title); | 540 | const detail = asString(asObject(decoded.params.toolCall)?.title); |
| 541 | 541 | ||
| 542 | if (context.approvalPolicy === "never") { | 542 | if (context.runtimeMode === "full-access") { |
| 543 | const selection = | 543 | const selection = |
| 544 | selectCursorAutoApprovalOption(options) ?? | 544 | selectCursorAutoApprovalOption(options) ?? |
| 545 | (options[0] | 545 | (options[0] |
| ... | @@ -1048,7 +1048,7 @@ function makeCursorAdapter(options?: CursorAdapterLiveOptions) { | ... | @@ -1048,7 +1048,7 @@ function makeCursorAdapter(options?: CursorAdapterLiveOptions) { |
| 1048 | 1048 | ||
| 1049 | const context: CursorSessionContext = { | 1049 | const context: CursorSessionContext = { |
| 1050 | session, | 1050 | session, |
| 1051 | approvalPolicy: input.approvalPolicy ?? "on-request", | 1051 | runtimeMode: input.runtimeMode ?? "full-access", |
| 1052 | child, | 1052 | child, |
| 1053 | output, | 1053 | output, |
| 1054 | pending: new Map(), | 1054 | pending: new Map(), |
apps/server/src/provider/Layers/ProviderService.ts+1-2| ... | @@ -415,8 +415,7 @@ const makeProviderService = (options?: ProviderServiceLiveOptions) => | ... | @@ -415,8 +415,7 @@ const makeProviderService = (options?: ProviderServiceLiveOptions) => |
| 415 | const input = { | 415 | const input = { |
| 416 | ...parsed, | 416 | ...parsed, |
| 417 | provider: parsed.provider ?? "codex", | 417 | provider: parsed.provider ?? "codex", |
| 418 | approvalPolicy: parsed.approvalPolicy ?? "never", | 418 | runtimeMode: parsed.runtimeMode ?? "full-access", |
| 419 | sandboxMode: parsed.sandboxMode ?? "workspace-write", | ||
| 420 | }; | 419 | }; |
| 421 | const adapter = yield* registry.getByProvider(input.provider); | 420 | const adapter = yield* registry.getByProvider(input.provider); |
| 422 | const session = yield* adapter.startSession(input); | 421 | const session = yield* adapter.startSession(input); |
apps/server/src/wsServer.test.ts+6-4| ... | @@ -1223,8 +1223,7 @@ describe("WebSocket Server", () => { | ... | @@ -1223,8 +1223,7 @@ describe("WebSocket Server", () => { |
| 1223 | attachments: [], | 1223 | attachments: [], |
| 1224 | }, | 1224 | }, |
| 1225 | assistantDeliveryMode: "streaming", | 1225 | assistantDeliveryMode: "streaming", |
| 1226 | approvalPolicy: "on-request", | 1226 | runtimeMode: "approval-required", |
| 1227 | sandboxMode: "workspace-write", | ||
| 1228 | createdAt, | 1227 | createdAt, |
| 1229 | }); | 1228 | }); |
| 1230 | expect(startTurnResponse.error).toBeUndefined(); | 1229 | expect(startTurnResponse.error).toBeUndefined(); |
| ... | @@ -1236,14 +1235,17 @@ describe("WebSocket Server", () => { | ... | @@ -1236,14 +1235,17 @@ describe("WebSocket Server", () => { |
| 1236 | 1235 | ||
| 1237 | emitRuntimeEvent( | 1236 | emitRuntimeEvent( |
| 1238 | { | 1237 | { |
| 1239 | type: "message.delta", | 1238 | type: "content.delta", |
| 1240 | eventId: asEventId("evt-ws-runtime-message-delta"), | 1239 | eventId: asEventId("evt-ws-runtime-message-delta"), |
| 1241 | provider: "codex", | 1240 | provider: "codex", |
| 1242 | sessionId, | 1241 | sessionId, |
| 1243 | createdAt: new Date().toISOString(), | 1242 | createdAt: new Date().toISOString(), |
| 1244 | turnId: asProviderTurnId("turn-1"), | 1243 | turnId: asProviderTurnId("turn-1"), |
| 1245 | itemId: asProviderItemId("item-1"), | 1244 | itemId: asProviderItemId("item-1"), |
| 1246 | delta: "hello from runtime", | 1245 | payload: { |
| 1246 | streamKind: "assistant_text", | ||
| 1247 | delta: "hello from runtime", | ||
| 1248 | }, | ||
| 1247 | } as unknown as ProviderRuntimeEvent, | 1249 | } as unknown as ProviderRuntimeEvent, |
| 1248 | ); | 1250 | ); |
| 1249 | 1251 |
apps/web/src/components/ChatView.tsx+1-4| ... | @@ -2231,8 +2231,6 @@ export default function ChatView({ threadId }: ChatViewProps) { | ... | @@ -2231,8 +2231,6 @@ export default function ChatView({ threadId }: ChatViewProps) { |
| 2231 | 2231 | ||
| 2232 | setSendPhase("sending-turn"); | 2232 | setSendPhase("sending-turn"); |
| 2233 | const turnAttachments = await turnAttachmentsPromise; | 2233 | const turnAttachments = await turnAttachmentsPromise; |
| 2234 | const approvalPolicy = runtimeMode === "full-access" ? "never" : "on-request"; | ||
| 2235 | const sandboxMode = runtimeMode === "full-access" ? "danger-full-access" : "workspace-write"; | ||
| 2236 | await api.orchestration.dispatchCommand({ | 2234 | await api.orchestration.dispatchCommand({ |
| 2237 | type: "thread.turn.start", | 2235 | type: "thread.turn.start", |
| 2238 | commandId: newCommandId(), | 2236 | commandId: newCommandId(), |
| ... | @@ -2247,8 +2245,7 @@ export default function ChatView({ threadId }: ChatViewProps) { | ... | @@ -2247,8 +2245,7 @@ export default function ChatView({ threadId }: ChatViewProps) { |
| 2247 | provider: selectedProvider, | 2245 | provider: selectedProvider, |
| 2248 | ...(supportsReasoningEffort && selectedEffort ? { effort: selectedEffort } : {}), | 2246 | ...(supportsReasoningEffort && selectedEffort ? { effort: selectedEffort } : {}), |
| 2249 | assistantDeliveryMode: settings.enableAssistantStreaming ? "streaming" : "buffered", | 2247 | assistantDeliveryMode: settings.enableAssistantStreaming ? "streaming" : "buffered", |
| 2250 | approvalPolicy, | 2248 | runtimeMode, |
| 2251 | sandboxMode, | ||
| 2252 | createdAt: messageCreatedAt, | 2249 | createdAt: messageCreatedAt, |
| 2253 | }); | 2250 | }); |
| 2254 | turnStartSucceeded = true; | 2251 | turnStartSucceeded = true; |
apps/web/src/session-logic.ts+9| ... | @@ -140,6 +140,15 @@ export function derivePendingApprovals( | ... | @@ -140,6 +140,15 @@ export function derivePendingApprovals( |
| 140 | openByRequestId.delete(requestId); | 140 | openByRequestId.delete(requestId); |
| 141 | continue; | 141 | continue; |
| 142 | } | 142 | } |
| 143 | |||
| 144 | if ( | ||
| 145 | activity.kind === "provider.approval.respond.failed" && | ||
| 146 | requestId && | ||
| 147 | detail?.includes("Unknown pending permission request") | ||
| 148 | ) { | ||
| 149 | openByRequestId.delete(requestId); | ||
| 150 | continue; | ||
| 151 | } | ||
| 143 | } | 152 | } |
| 144 | 153 | ||
| 145 | return [...openByRequestId.values()].toSorted((left, right) => | 154 | return [...openByRequestId.values()].toSorted((left, right) => |
apps/web/src/store.test.ts+2-4| ... | @@ -148,8 +148,7 @@ describe("store read model sync", () => { | ... | @@ -148,8 +148,7 @@ describe("store read model sync", () => { |
| 148 | providerName: "claudeCode", | 148 | providerName: "claudeCode", |
| 149 | providerSessionId: null, | 149 | providerSessionId: null, |
| 150 | providerThreadId: null, | 150 | providerThreadId: null, |
| 151 | approvalPolicy: "on-request", | 151 | runtimeMode: "approval-required", |
| 152 | sandboxMode: "workspace-write", | ||
| 153 | activeTurnId: null, | 152 | activeTurnId: null, |
| 154 | lastError: null, | 153 | lastError: null, |
| 155 | updatedAt: "2026-02-27T00:00:00.000Z", | 154 | updatedAt: "2026-02-27T00:00:00.000Z", |
| ... | @@ -173,8 +172,7 @@ describe("store read model sync", () => { | ... | @@ -173,8 +172,7 @@ describe("store read model sync", () => { |
| 173 | providerName: "cursor", | 172 | providerName: "cursor", |
| 174 | providerSessionId: null, | 173 | providerSessionId: null, |
| 175 | providerThreadId: null, | 174 | providerThreadId: null, |
| 176 | approvalPolicy: "on-request", | 175 | runtimeMode: "approval-required", |
| 177 | sandboxMode: "workspace-write", | ||
| 178 | activeTurnId: null, | 176 | activeTurnId: null, |
| 179 | lastError: null, | 177 | lastError: null, |
| 180 | updatedAt: "2026-02-27T00:00:00.000Z", | 178 | updatedAt: "2026-02-27T00:00:00.000Z", |
docs/plan-mode-deep-dive.md created+629| ... | @@ -0,0 +1,629 @@ | ||
| 1 | # Plan Mode Deep Dive | ||
| 2 | |||
| 3 | ## Goal | ||
| 4 | |||
| 5 | Understand how the three providers expose: | ||
| 6 | |||
| 7 | - planning / read-only execution modes | ||
| 8 | - structured plan updates | ||
| 9 | - structured user-input / question prompts | ||
| 10 | - response plumbing needed for a provider-agnostic adapter layer | ||
| 11 | |||
| 12 | Providers covered: | ||
| 13 | |||
| 14 | - Codex App Server | ||
| 15 | - Claude Agent SDK / Claude Code | ||
| 16 | - Cursor ACP | ||
| 17 | |||
| 18 | --- | ||
| 19 | |||
| 20 | ## Existing T3 Code groundwork | ||
| 21 | |||
| 22 | Current repo groundwork already exists in these places: | ||
| 23 | |||
| 24 | - `EVENTS.md` | ||
| 25 | - `packages/contracts/src/providerRuntime.ts` | ||
| 26 | - `apps/server/src/provider/Layers/CodexAdapter.ts` | ||
| 27 | - `apps/server/src/provider/Layers/ClaudeCodeAdapter.ts` | ||
| 28 | - `apps/server/src/provider/Layers/CursorAdapter.ts` | ||
| 29 | - `apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts` | ||
| 30 | |||
| 31 | Canonical runtime events already modeled: | ||
| 32 | |||
| 33 | - `turn.plan.updated` | ||
| 34 | - `content.delta` with `streamKind: "plan_text"` | ||
| 35 | - `user-input.requested` | ||
| 36 | - `user-input.resolved` | ||
| 37 | - `request.opened` | ||
| 38 | - `request.resolved` | ||
| 39 | |||
| 40 | This is a good base, but provider coverage is uneven: | ||
| 41 | |||
| 42 | - Codex: strong native support for both structured plans and structured user questions. | ||
| 43 | - 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. | ||
| 44 | - 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. | ||
| 45 | |||
| 46 | --- | ||
| 47 | |||
| 48 | ## Provider 1: Codex App Server | ||
| 49 | |||
| 50 | ### What exists natively | ||
| 51 | |||
| 52 | Codex has the richest first-class support for this feature set. | ||
| 53 | |||
| 54 | ### Real local session evidence | ||
| 55 | |||
| 56 | The local Codex rollout file confirms at least one important distinction: | ||
| 57 | |||
| 58 | - session metadata explicitly records collaboration mode as plan via `turn_context.payload.collaboration_mode.mode = "plan"` | ||
| 59 | - final plan handoff is emitted as a normal assistant message containing a `<proposed_plan>` block | ||
| 60 | |||
| 61 | From the sampled rollout file, I did **not** find raw `event_msg.plan_update` / `event_msg.plan_delta` records, which suggests either: | ||
| 62 | |||
| 63 | - this particular session never emitted incremental structured plan updates, or | ||
| 64 | - the desktop rollout file persists a higher-level message projection rather than every low-level app-server event | ||
| 65 | |||
| 66 | 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. | ||
| 67 | |||
| 68 | |||
| 69 | Native surfaces confirmed from upstream docs/source: | ||
| 70 | |||
| 71 | - collaboration mode with plan behavior | ||
| 72 | - `turn/plan/updated` | ||
| 73 | - `item/plan/delta` | ||
| 74 | - `item/tool/requestUserInput` | ||
| 75 | - `serverRequest/resolved` cleanup / completion notification for user input | ||
| 76 | - `request_user_input` tool in core/runtime | ||
| 77 | |||
| 78 | Evidence inspected: | ||
| 79 | |||
| 80 | - upstream `codex-rs/app-server/README.md` | ||
| 81 | - upstream `codex-rs/app-server-protocol` schemas | ||
| 82 | - upstream `codex-rs/core/src/tools/handlers/request_user_input.rs` | ||
| 83 | - upstream `codex-rs/core/templates/collaboration_mode/plan.md` | ||
| 84 | |||
| 85 | ### Semantics | ||
| 86 | |||
| 87 | #### Plan mode | ||
| 88 | |||
| 89 | Codex plan mode is not just “tool execution denied”. It has explicit behavioral guidance and explicit planning outputs. | ||
| 90 | |||
| 91 | Two distinct plan surfaces exist: | ||
| 92 | |||
| 93 | 1. **Structured plan state** | ||
| 94 | - `turn/plan/updated` | ||
| 95 | - payload contains: | ||
| 96 | - optional `explanation` | ||
| 97 | - `plan: Array<{ step, status }>` | ||
| 98 | - statuses are `pending | inProgress | completed` | ||
| 99 | |||
| 100 | 2. **Plan text stream** | ||
| 101 | - `item/plan/delta` | ||
| 102 | - useful for rendering streaming plan prose / bullet lists before or alongside structured state | ||
| 103 | |||
| 104 | This maps very cleanly to our canonical events: | ||
| 105 | |||
| 106 | - `turn/plan.updated` -> `turn.plan.updated` | ||
| 107 | - `item/plan/delta` -> `content.delta(plan_text)` | ||
| 108 | |||
| 109 | #### Structured user questions | ||
| 110 | |||
| 111 | Codex has a dedicated `request_user_input` capability. | ||
| 112 | |||
| 113 | Server request: | ||
| 114 | |||
| 115 | - `item/tool/requestUserInput` | ||
| 116 | |||
| 117 | Shape characteristics from upstream protocol/docs: | ||
| 118 | |||
| 119 | - 1–3 questions | ||
| 120 | - each question has: | ||
| 121 | - prompt text | ||
| 122 | - stable id | ||
| 123 | - required options | ||
| 124 | - response carries answers per question | ||
| 125 | - freeform notes are supported in UX and answer model | ||
| 126 | |||
| 127 | Important nuance: | ||
| 128 | |||
| 129 | - upstream handler currently requires non-empty options for every question | ||
| 130 | - Codex TUI also supports an optional freeform note / text alongside selected options | ||
| 131 | - pending question requests are turn-scoped and cleaned up on turn completion/interruption | ||
| 132 | |||
| 133 | ### Why Codex is the reference model | ||
| 134 | |||
| 135 | Codex should be treated as the reference shape for our provider-agnostic abstraction because it already matches the desired UI: | ||
| 136 | |||
| 137 | - explicit plan lifecycle | ||
| 138 | - explicit multi-question prompt flow | ||
| 139 | - explicit answer submission | ||
| 140 | - explicit pending/resolved bookkeeping | ||
| 141 | |||
| 142 | ### Adapter implications | ||
| 143 | |||
| 144 | For Codex, the provider adapter layer should stay close to native protocol: | ||
| 145 | |||
| 146 | - keep `turn.plan.updated` | ||
| 147 | - keep `content.delta(plan_text)` | ||
| 148 | - map `item/tool/requestUserInput` to `user-input.requested` and `request.opened` | ||
| 149 | - map answer completion / cleanup to `user-input.resolved` and `request.resolved` | ||
| 150 | |||
| 151 | --- | ||
| 152 | |||
| 153 | ## Provider 2: Claude Agent SDK / Claude Code | ||
| 154 | |||
| 155 | ### What exists natively | ||
| 156 | |||
| 157 | Claude has an explicit plan permission mode: | ||
| 158 | |||
| 159 | ### Real local session evidence | ||
| 160 | |||
| 161 | The sampled Claude session file provides concrete payloads for both key plan-mode tools: | ||
| 162 | |||
| 163 | 1. `AskUserQuestion` appears as a normal assistant `tool_use` block with input like: | ||
| 164 | |||
| 165 | ```json | ||
| 166 | { | ||
| 167 | "type": "tool_use", | ||
| 168 | "name": "AskUserQuestion", | ||
| 169 | "input": { | ||
| 170 | "questions": [ | ||
| 171 | { | ||
| 172 | "question": "What does 'adding profiles to canvas' mean to you? What should profiles enable in canvas?", | ||
| 173 | "header": "Scope", | ||
| 174 | "options": [ | ||
| 175 | { "label": "Organize by profile", "description": "..." }, | ||
| 176 | { "label": "Profile icon bar in canvas", "description": "..." }, | ||
| 177 | { "label": "Per-profile canvas settings", "description": "..." }, | ||
| 178 | { "label": "All of the above", "description": "..." } | ||
| 179 | ], | ||
| 180 | "multiSelect": false | ||
| 181 | } | ||
| 182 | ] | ||
| 183 | } | ||
| 184 | } | ||
| 185 | ``` | ||
| 186 | |||
| 187 | 2. `ExitPlanMode` also appears as a `tool_use` block, with a long-form `plan` string payload containing the finalized implementation spec. | ||
| 188 | |||
| 189 | That gives us two strong adapter conclusions: | ||
| 190 | |||
| 191 | - Claude user-input prompting is definitely tool-based and structurally parseable. | ||
| 192 | - Claude final plan handoff is tool-based too, not necessarily a dedicated transport event like Codex `turn/plan/updated`. | ||
| 193 | |||
| 194 | ### Concrete adapter mapping from transcript evidence | ||
| 195 | |||
| 196 | Recommended Claude mapping based on real session data: | ||
| 197 | |||
| 198 | - `tool_use.name === "AskUserQuestion"` -> `user-input.requested` | ||
| 199 | - user answer submission / tool result -> `user-input.resolved` | ||
| 200 | - `tool_use.name === "ExitPlanMode"` with `input.plan` -> final plan handoff event or synthesized `turn.plan.updated` snapshot | ||
| 201 | |||
| 202 | The `AskUserQuestion` payload shape is already close to the UI we want: | ||
| 203 | |||
| 204 | - `questions[]` array | ||
| 205 | - each question has `header` and `question` | ||
| 206 | - each question has `options[]` with `label` + `description` | ||
| 207 | - `multiSelect` is explicit | ||
| 208 | |||
| 209 | The main remaining unknown is the exact runtime shape of the **answer** payload that Claude expects back for this tool. | ||
| 210 | |||
| 211 | |||
| 212 | - `PermissionMode = 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan' | 'dontAsk'` | ||
| 213 | |||
| 214 | Confirmed from installed SDK typings: | ||
| 215 | |||
| 216 | - `/Users/julius/.bun/install/cache/@anthropic-ai/claude-agent-sdk@0.2.62@@@1/sdk.d.ts` | ||
| 217 | - `/Users/julius/.bun/install/cache/@anthropic-ai/claude-agent-sdk@0.2.62@@@1/sdk-tools.d.ts` | ||
| 218 | |||
| 219 | Claude also exposes plan-related tools/types: | ||
| 220 | |||
| 221 | - `ExitPlanMode` | ||
| 222 | - `AskUserQuestion` | ||
| 223 | |||
| 224 | 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. | ||
| 225 | |||
| 226 | ### What the wire stream exposes | ||
| 227 | |||
| 228 | Claude adapter input today is built from SDK messages like: | ||
| 229 | |||
| 230 | - `system:init` | ||
| 231 | - `system:status` | ||
| 232 | - `stream_event` | ||
| 233 | - `assistant` | ||
| 234 | - `result` | ||
| 235 | - hook/task/tool summary events | ||
| 236 | |||
| 237 | The SDK stream exposes: | ||
| 238 | |||
| 239 | - message deltas | ||
| 240 | - tool-use starts/stops via content blocks | ||
| 241 | - permission-mode metadata | ||
| 242 | - final result and usage | ||
| 243 | |||
| 244 | What it does **not** appear to expose as a first-class event: | ||
| 245 | |||
| 246 | - no direct equivalent to Codex `turn/plan/updated` | ||
| 247 | - no direct equivalent to Codex `item/plan/delta` | ||
| 248 | - no dedicated top-level `user-input.requested` transport event in the SDK stream | ||
| 249 | |||
| 250 | Instead, plan-mode-related behavior appears to surface through tool uses: | ||
| 251 | |||
| 252 | - `AskUserQuestion` tool | ||
| 253 | - `ExitPlanMode` tool | ||
| 254 | - regular content/tool stream events | ||
| 255 | |||
| 256 | ### Semantics | ||
| 257 | |||
| 258 | #### Plan mode | ||
| 259 | |||
| 260 | Claude’s plan mode is primarily a **permission / behavior mode**: | ||
| 261 | |||
| 262 | - session or turn is created with `permissionMode: 'plan'` | ||
| 263 | - tool execution is prevented / constrained | ||
| 264 | - assistant can explore, reason, and propose a plan | ||
| 265 | - eventually it may call `ExitPlanMode` to transition toward implementation | ||
| 266 | |||
| 267 | That means Claude plan mode is native, but **structured plan state is not guaranteed**. | ||
| 268 | |||
| 269 | In practice, there are two likely UI strategies: | ||
| 270 | |||
| 271 | 1. **Best-effort structured extraction** | ||
| 272 | - detect plan-like assistant/tool outputs | ||
| 273 | - synthesize `turn.plan.updated` from them | ||
| 274 | 2. **Text-first plan rendering** | ||
| 275 | - show reasoning / assistant output as plan narrative | ||
| 276 | - only emit `turn.plan.updated` when we can confidently extract steps | ||
| 277 | |||
| 278 | #### Asking user questions | ||
| 279 | |||
| 280 | Claude’s `AskUserQuestion` appears to be a tool, not a dedicated transport-level question request primitive. | ||
| 281 | |||
| 282 | That means the adapter likely needs to: | ||
| 283 | |||
| 284 | - identify `tool_use` for `AskUserQuestion` | ||
| 285 | - parse its input payload into canonical question schema | ||
| 286 | - expose `user-input.requested` | ||
| 287 | - accept a user answer and feed it back as the tool result / synthetic user response, depending SDK control path | ||
| 288 | |||
| 289 | This is adapter work, not just schema plumbing. | ||
| 290 | |||
| 291 | ### Recommended stance for Claude | ||
| 292 | |||
| 293 | Claude should support the same canonical user-facing contract, but the adapter will need to **manufacture structure from native tool activity**. | ||
| 294 | |||
| 295 | Recommended mapping: | ||
| 296 | |||
| 297 | - `permissionMode: 'plan'` => canonical session/turn metadata `mode = plan` | ||
| 298 | - `AskUserQuestion` tool use => canonical `user-input.requested` | ||
| 299 | - submitted answer => canonical `user-input.resolved` | ||
| 300 | - `ExitPlanMode` tool use => canonical state transition or request to continue with implementation | ||
| 301 | - assistant text / tool summaries in plan mode => optional synthesized `turn.plan.updated` | ||
| 302 | |||
| 303 | ### Risk / uncertainty | ||
| 304 | |||
| 305 | Claude currently appears less deterministic than Codex for structured plans. | ||
| 306 | |||
| 307 | Main unknowns to resolve during implementation: | ||
| 308 | |||
| 309 | - exact `AskUserQuestion` tool input/output payload shapes at runtime | ||
| 310 | - whether freeform responses are supported natively or need synthetic augmentation | ||
| 311 | - whether `ExitPlanMode` should be exposed to UI explicitly or handled internally by the adapter/session controller | ||
| 312 | |||
| 313 | --- | ||
| 314 | |||
| 315 | ## Provider 3: Cursor ACP | ||
| 316 | |||
| 317 | ### What exists natively | ||
| 318 | |||
| 319 | Cursor ACP does have native session modes. | ||
| 320 | |||
| 321 | Confirmed by local probe output in `.tmp/acp-plan-probe/summary.json`: | ||
| 322 | |||
| 323 | - `agent` | ||
| 324 | - `plan` | ||
| 325 | - `ask` | ||
| 326 | |||
| 327 | The probe also confirmed current primary ACP update surfaces: | ||
| 328 | |||
| 329 | - `available_commands_update` | ||
| 330 | - `agent_thought_chunk` | ||
| 331 | - `agent_message_chunk` | ||
| 332 | - `tool_call` | ||
| 333 | - `tool_call_update` | ||
| 334 | - `session/request_permission` | ||
| 335 | |||
| 336 | ### What the probe did not find | ||
| 337 | |||
| 338 | The existing probe did **not** surface: | ||
| 339 | |||
| 340 | - structured plan update events | ||
| 341 | - a dedicated “ask user question” request | ||
| 342 | - a dedicated plan delta stream | ||
| 343 | |||
| 344 | The current ACP surface therefore looks much thinner than Codex. | ||
| 345 | |||
| 346 | ### Semantics | ||
| 347 | |||
| 348 | #### Plan mode | ||
| 349 | |||
| 350 | Cursor `plan` mode clearly exists as a session mode, but as currently observed it seems to mean: | ||
| 351 | |||
| 352 | - read-only / design-first behavior | ||
| 353 | - regular thought/message chunk streaming | ||
| 354 | - no dedicated structured plan payload | ||
| 355 | |||
| 356 | So the plan is native as a mode, but not obviously as a structured protocol surface. | ||
| 357 | |||
| 358 | #### Asking the user questions | ||
| 359 | |||
| 360 | Cursor ACP currently gives us a nearby primitive only for **permission requests**: | ||
| 361 | |||
| 362 | - `session/request_permission` | ||
| 363 | |||
| 364 | That is not the same as product-level question asking. | ||
| 365 | |||
| 366 | The existence of `ask` mode suggests Cursor may handle asking by: | ||
| 367 | |||
| 368 | - normal assistant prose in ask mode | ||
| 369 | - possibly mode-switch semantics | ||
| 370 | - possibly skills/commands that we have not yet triggered | ||
| 371 | |||
| 372 | But based on current probe evidence, we should **not assume** a Codex-style structured question API exists in ACP. | ||
| 373 | |||
| 374 | ### Recommended stance for Cursor | ||
| 375 | |||
| 376 | Treat Cursor as: | ||
| 377 | |||
| 378 | - native support for `mode = plan` | ||
| 379 | - no proven native support for structured plan steps | ||
| 380 | - no proven native support for structured user-input questions | ||
| 381 | |||
| 382 | Therefore Cursor likely needs the most adapter synthesis: | ||
| 383 | |||
| 384 | - derive plan-mode state from ACP session mode | ||
| 385 | - optionally synthesize `turn.plan.updated` from assistant text if we can extract steps safely | ||
| 386 | - for interactive questions, likely fall back to regular conversational turns unless a richer ACP mechanism is discovered | ||
| 387 | |||
| 388 | ### Important product implication | ||
| 389 | |||
| 390 | If we want the same rich “multiple-choice question card” UX across all providers, Cursor may need one of these paths: | ||
| 391 | |||
| 392 | 1. **Provider-native path** if later ACP exploration finds structured user question primitives | ||
| 393 | 2. **Adapter-mediated path** where the provider is instructed to emit a machine-readable question block in text and adapter parses it | ||
| 394 | 3. **Capability downgrade** where Cursor only supports conversational follow-up, not full structured plan-question cards | ||
| 395 | |||
| 396 | Today, evidence supports option 3 as the safe baseline. | ||
| 397 | |||
| 398 | --- | ||
| 399 | |||
| 400 | ## Cross-provider comparison | ||
| 401 | |||
| 402 | ### Capability matrix | ||
| 403 | |||
| 404 | | Capability | Codex App Server | Claude Agent SDK | Cursor ACP | | ||
| 405 | | --- | --- | --- | --- | | ||
| 406 | | Native plan mode | Yes | Yes | Yes | | ||
| 407 | | Structured plan update event | Yes | Not obvious / likely no | Not observed | | ||
| 408 | | Plan text delta stream | Yes | Not first-class | Not observed | | ||
| 409 | | Native structured user question API | Yes | Tool-based, likely yes-ish | Not observed | | ||
| 410 | | Native answer resolution lifecycle | Yes | Likely adapter-mediated | Not observed | | ||
| 411 | | Best provider for canonical contract | Strong | Medium | Weak | | ||
| 412 | |||
| 413 | ### Core insight | ||
| 414 | |||
| 415 | “Plan mode” means **different things** across providers: | ||
| 416 | |||
| 417 | - **Codex**: plan mode is a first-class runtime concept with structured plan and question surfaces. | ||
| 418 | - **Claude**: plan mode is a first-class permission mode, but structured plan/question UI likely needs adapter interpretation. | ||
| 419 | - **Cursor**: plan mode is a first-class session mode, but richer plan/question structure is not currently exposed. | ||
| 420 | |||
| 421 | --- | ||
| 422 | |||
| 423 | ## Recommended provider-agnostic contract | ||
| 424 | |||
| 425 | We should separate three concerns that are currently easy to conflate. | ||
| 426 | |||
| 427 | ### 1. Session / turn mode | ||
| 428 | |||
| 429 | A canonical mode flag: | ||
| 430 | |||
| 431 | - `default` | ||
| 432 | - `plan` | ||
| 433 | - `ask` | ||
| 434 | - `execute` | ||
| 435 | - `unknown` | ||
| 436 | |||
| 437 | This is about **agent operating mode**, not UI widgets. | ||
| 438 | |||
| 439 | ### 2. Structured plan state | ||
| 440 | |||
| 441 | A canonical plan model: | ||
| 442 | |||
| 443 | ```ts | ||
| 444 | interface CanonicalPlanState { | ||
| 445 | explanation?: string | null; | ||
| 446 | steps: Array<{ | ||
| 447 | id?: string; | ||
| 448 | text: string; | ||
| 449 | status: 'pending' | 'inProgress' | 'completed'; | ||
| 450 | source: 'native' | 'synthesized'; | ||
| 451 | }>; | ||
| 452 | } | ||
| 453 | ``` | ||
| 454 | |||
| 455 | Rules: | ||
| 456 | |||
| 457 | - Codex can populate this natively. | ||
| 458 | - Claude/Cursor may populate it only when extraction is confident. | ||
| 459 | - UI should tolerate absence of structured plan state. | ||
| 460 | |||
| 461 | ### 3. Structured user-input prompt | ||
| 462 | |||
| 463 | Canonical prompt should be modeled independently of approvals: | ||
| 464 | |||
| 465 | ```ts | ||
| 466 | interface CanonicalUserInputPrompt { | ||
| 467 | promptId: string; | ||
| 468 | title?: string; | ||
| 469 | description?: string; | ||
| 470 | questions: Array<{ | ||
| 471 | id: string; | ||
| 472 | label: string; | ||
| 473 | description?: string; | ||
| 474 | options: Array<{ | ||
| 475 | id: string; | ||
| 476 | label: string; | ||
| 477 | description?: string; | ||
| 478 | recommended?: boolean; | ||
| 479 | }>; | ||
| 480 | allowFreeform?: boolean; | ||
| 481 | freeformPlaceholder?: string; | ||
| 482 | required?: boolean; | ||
| 483 | }>; | ||
| 484 | source: 'native' | 'tool-derived' | 'synthesized'; | ||
| 485 | } | ||
| 486 | ``` | ||
| 487 | |||
| 488 | And answers: | ||
| 489 | |||
| 490 | ```ts | ||
| 491 | interface CanonicalUserInputAnswer { | ||
| 492 | promptId: string; | ||
| 493 | answers: Array<{ | ||
| 494 | questionId: string; | ||
| 495 | selectedOptionId?: string; | ||
| 496 | text?: string; | ||
| 497 | }>; | ||
| 498 | } | ||
| 499 | ``` | ||
| 500 | |||
| 501 | ### Why separate `request.opened` from `user-input.requested` | ||
| 502 | |||
| 503 | Because they are not the same abstraction: | ||
| 504 | |||
| 505 | - approvals = “may I run/edit/do X?” | ||
| 506 | - user-input prompts = “which direction do you want?” | ||
| 507 | |||
| 508 | Codex already blurs these at transport level because `item/tool/requestUserInput` is a request. | ||
| 509 | But product and UI should keep them separate. | ||
| 510 | |||
| 511 | Recommended rule: | ||
| 512 | |||
| 513 | - keep `request.opened/request.resolved` for transport-level correlation | ||
| 514 | - keep `user-input.requested/user-input.resolved` for product-level UX | ||
| 515 | |||
| 516 | --- | ||
| 517 | |||
| 518 | ## Adapter strategy by provider | ||
| 519 | |||
| 520 | ### Codex adapter | ||
| 521 | |||
| 522 | Use native primitives directly. | ||
| 523 | |||
| 524 | Implementation target: | ||
| 525 | |||
| 526 | - preserve `turn.plan.updated` | ||
| 527 | - preserve `content.delta(plan_text)` | ||
| 528 | - preserve `user-input.requested` | ||
| 529 | - preserve `user-input.resolved` | ||
| 530 | - expose raw/native metadata for future UI improvements | ||
| 531 | |||
| 532 | ### Claude adapter | ||
| 533 | |||
| 534 | Implement tool-aware adaptation. | ||
| 535 | |||
| 536 | Implementation target: | ||
| 537 | |||
| 538 | - treat `permissionMode: 'plan'` as native plan-mode state | ||
| 539 | - detect `AskUserQuestion` tool uses and convert to canonical user-input prompt | ||
| 540 | - convert question answers back into Claude-native tool response path | ||
| 541 | - detect `ExitPlanMode` and emit a canonical mode-change / handoff event | ||
| 542 | - optionally synthesize plan steps from assistant output in plan mode | ||
| 543 | |||
| 544 | ### Cursor adapter | ||
| 545 | |||
| 546 | Implement conservative degraded support first. | ||
| 547 | |||
| 548 | Implementation target: | ||
| 549 | |||
| 550 | - expose native session mode including `plan` and `ask` | ||
| 551 | - do not claim structured plan updates unless proven | ||
| 552 | - do not claim structured user-input prompts unless proven | ||
| 553 | - optionally add a provider capability flag so UI can fall back to conversational follow-up | ||
| 554 | |||
| 555 | --- | ||
| 556 | |||
| 557 | ## Capability flags we should add | ||
| 558 | |||
| 559 | To avoid overpromising in the UI, add provider runtime capabilities such as: | ||
| 560 | |||
| 561 | ```ts | ||
| 562 | interface ProviderInteractiveCapabilities { | ||
| 563 | supportsPlanMode: boolean; | ||
| 564 | supportsAskMode: boolean; | ||
| 565 | supportsStructuredPlanUpdates: boolean; | ||
| 566 | supportsPlanTextStreaming: boolean; | ||
| 567 | supportsStructuredUserInput: boolean; | ||
| 568 | supportsFreeformUserInput: boolean; | ||
| 569 | supportsExitPlanMode: boolean; | ||
| 570 | } | ||
| 571 | ``` | ||
| 572 | |||
| 573 | Expected initial values: | ||
| 574 | |||
| 575 | - Codex: all or nearly all `true` | ||
| 576 | - Claude: `supportsPlanMode=true`, `supportsStructuredUserInput=likely true via adapter`, `supportsStructuredPlanUpdates=partial` | ||
| 577 | - Cursor: `supportsPlanMode=true`, `supportsAskMode=true`, most structured flags `false` | ||
| 578 | |||
| 579 | This lets the UI: | ||
| 580 | |||
| 581 | - render rich plan cards only when supported | ||
| 582 | - fall back to plain assistant text + reply box otherwise | ||
| 583 | - avoid coupling the design to Codex-specific assumptions | ||
| 584 | |||
| 585 | --- | ||
| 586 | |||
| 587 | ## Suggested implementation order | ||
| 588 | |||
| 589 | 1. **Formalize provider capability flags** | ||
| 590 | - make support explicit before UI work | ||
| 591 | 2. **Finish canonical user-input contracts** | ||
| 592 | - question schema, answer schema, source markers | ||
| 593 | 3. **Codex end-to-end implementation first** | ||
| 594 | - closest match to desired UX | ||
| 595 | 4. **Claude adapter translation second** | ||
| 596 | - parse `AskUserQuestion` and `ExitPlanMode` | ||
| 597 | 5. **Cursor degraded plan-mode support third** | ||
| 598 | - mode awareness first, richer structure later | ||
| 599 | 6. **UI renders by capability, not provider name** | ||
| 600 | - prevents provider-specific branching from leaking upward | ||
| 601 | |||
| 602 | --- | ||
| 603 | |||
| 604 | ## Open questions | ||
| 605 | |||
| 606 | 1. Claude runtime payloads: | ||
| 607 | - what exact `AskUserQuestion` input/output shapes arrive over the stream? | ||
| 608 | 2. Claude answer submission path: | ||
| 609 | - should answers be injected as tool results, control responses, or synthetic user turns? | ||
| 610 | 3. Cursor ACP: | ||
| 611 | - is there an undocumented or less common interactive prompt primitive beyond `session/request_permission`? | ||
| 612 | 4. Cursor mode switching: | ||
| 613 | - can ACP session mode change mid-session, and is that surfaced as `session/update` or request/response only? | ||
| 614 | 5. Plan synthesis: | ||
| 615 | - 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? | ||
| 616 | |||
| 617 | --- | ||
| 618 | |||
| 619 | ## Immediate recommendation | ||
| 620 | |||
| 621 | Build the product contract around **capabilities**, not around the assumption that every provider has Codex-style plan/question primitives. | ||
| 622 | |||
| 623 | Concretely: | ||
| 624 | |||
| 625 | - Codex should drive the first full UX implementation. | ||
| 626 | - Claude should be supported with adapter-derived question handling and partial/synthesized plan state. | ||
| 627 | - Cursor should initially support mode-aware UX with graceful fallback to conversational replies rather than full structured question cards. | ||
| 628 | |||
| 629 | That gives us a provider-agnostic architecture without pretending the underlying protocols are already equivalent. | ||
packages/contracts/src/orchestration.test.ts+6-12| ... | @@ -2,8 +2,7 @@ import { assert, it } from "@effect/vitest"; | ... | @@ -2,8 +2,7 @@ import { assert, it } from "@effect/vitest"; |
| 2 | import { Effect, Schema } from "effect"; | 2 | import { Effect, Schema } from "effect"; |
| 3 | 3 | ||
| 4 | import { | 4 | import { |
| 5 | DEFAULT_PROVIDER_APPROVAL_POLICY, | 5 | DEFAULT_RUNTIME_MODE, |
| 6 | DEFAULT_PROVIDER_SANDBOX_MODE, | ||
| 7 | OrchestrationGetTurnDiffInput, | 6 | OrchestrationGetTurnDiffInput, |
| 8 | OrchestrationSession, | 7 | OrchestrationSession, |
| 9 | ProjectCreateCommand, | 8 | ProjectCreateCommand, |
| ... | @@ -110,8 +109,7 @@ it.effect("decodes thread.turn.start defaults for provider and runtime mode", () | ... | @@ -110,8 +109,7 @@ it.effect("decodes thread.turn.start defaults for provider and runtime mode", () |
| 110 | createdAt: "2026-01-01T00:00:00.000Z", | 109 | createdAt: "2026-01-01T00:00:00.000Z", |
| 111 | }); | 110 | }); |
| 112 | assert.strictEqual(parsed.provider, undefined); | 111 | assert.strictEqual(parsed.provider, undefined); |
| 113 | assert.strictEqual(parsed.approvalPolicy, DEFAULT_PROVIDER_APPROVAL_POLICY); | 112 | assert.strictEqual(parsed.runtimeMode, DEFAULT_RUNTIME_MODE); |
| 114 | assert.strictEqual(parsed.sandboxMode, DEFAULT_PROVIDER_SANDBOX_MODE); | ||
| 115 | }), | 113 | }), |
| 116 | ); | 114 | ); |
| 117 | 115 | ||
| ... | @@ -128,13 +126,11 @@ it.effect("preserves explicit provider and runtime mode in thread.turn.start", ( | ... | @@ -128,13 +126,11 @@ it.effect("preserves explicit provider and runtime mode in thread.turn.start", ( |
| 128 | attachments: [], | 126 | attachments: [], |
| 129 | }, | 127 | }, |
| 130 | provider: "claudeCode", | 128 | provider: "claudeCode", |
| 131 | approvalPolicy: "never", | 129 | runtimeMode: "full-access", |
| 132 | sandboxMode: "danger-full-access", | ||
| 133 | createdAt: "2026-01-01T00:00:00.000Z", | 130 | createdAt: "2026-01-01T00:00:00.000Z", |
| 134 | }); | 131 | }); |
| 135 | assert.strictEqual(parsed.provider, "claudeCode"); | 132 | assert.strictEqual(parsed.provider, "claudeCode"); |
| 136 | assert.strictEqual(parsed.approvalPolicy, "never"); | 133 | assert.strictEqual(parsed.runtimeMode, "full-access"); |
| 137 | assert.strictEqual(parsed.sandboxMode, "danger-full-access"); | ||
| 138 | }), | 134 | }), |
| 139 | ); | 135 | ); |
| 140 | 136 | ||
| ... | @@ -165,8 +161,7 @@ it.effect("decodes thread.turn-start-requested defaults for provider and runtime | ... | @@ -165,8 +161,7 @@ it.effect("decodes thread.turn-start-requested defaults for provider and runtime |
| 165 | createdAt: "2026-01-01T00:00:00.000Z", | 161 | createdAt: "2026-01-01T00:00:00.000Z", |
| 166 | }); | 162 | }); |
| 167 | assert.strictEqual(parsed.provider, undefined); | 163 | assert.strictEqual(parsed.provider, undefined); |
| 168 | assert.strictEqual(parsed.approvalPolicy, DEFAULT_PROVIDER_APPROVAL_POLICY); | 164 | assert.strictEqual(parsed.runtimeMode, DEFAULT_RUNTIME_MODE); |
| 169 | assert.strictEqual(parsed.sandboxMode, DEFAULT_PROVIDER_SANDBOX_MODE); | ||
| 170 | }), | 165 | }), |
| 171 | ); | 166 | ); |
| 172 | 167 | ||
| ... | @@ -182,7 +177,6 @@ it.effect("decodes orchestration session runtime mode defaults", () => | ... | @@ -182,7 +177,6 @@ it.effect("decodes orchestration session runtime mode defaults", () => |
| 182 | lastError: null, | 177 | lastError: null, |
| 183 | updatedAt: "2026-01-01T00:00:00.000Z", | 178 | updatedAt: "2026-01-01T00:00:00.000Z", |
| 184 | }); | 179 | }); |
| 185 | assert.strictEqual(parsed.approvalPolicy, DEFAULT_PROVIDER_APPROVAL_POLICY); | 180 | assert.strictEqual(parsed.runtimeMode, DEFAULT_RUNTIME_MODE); |
| 186 | assert.strictEqual(parsed.sandboxMode, DEFAULT_PROVIDER_SANDBOX_MODE); | ||
| 187 | }), | 181 | }), |
| 188 | ); | 182 | ); |
packages/contracts/src/orchestration.ts+7-35| ... | @@ -32,21 +32,9 @@ export const ORCHESTRATION_WS_CHANNELS = { | ... | @@ -32,21 +32,9 @@ export const ORCHESTRATION_WS_CHANNELS = { |
| 32 | export const ProviderKind = Schema.Literals(["codex", "claudeCode", "cursor"]); | 32 | export const ProviderKind = Schema.Literals(["codex", "claudeCode", "cursor"]); |
| 33 | export type ProviderKind = typeof ProviderKind.Type; | 33 | export type ProviderKind = typeof ProviderKind.Type; |
| 34 | export const DEFAULT_PROVIDER_KIND: ProviderKind = "codex"; | 34 | export const DEFAULT_PROVIDER_KIND: ProviderKind = "codex"; |
| 35 | export const ProviderApprovalPolicy = Schema.Literals([ | 35 | export const RuntimeMode = Schema.Literals(["approval-required", "full-access"]); |
| 36 | "untrusted", | 36 | export type RuntimeMode = typeof RuntimeMode.Type; |
| 37 | "on-failure", | 37 | export const DEFAULT_RUNTIME_MODE: RuntimeMode = "full-access"; |
| 38 | "on-request", | ||
| 39 | "never", | ||
| 40 | ]); | ||
| 41 | export type ProviderApprovalPolicy = typeof ProviderApprovalPolicy.Type; | ||
| 42 | export const DEFAULT_PROVIDER_APPROVAL_POLICY: ProviderApprovalPolicy = "on-failure"; | ||
| 43 | export const ProviderSandboxMode = Schema.Literals([ | ||
| 44 | "read-only", | ||
| 45 | "workspace-write", | ||
| 46 | "danger-full-access", | ||
| 47 | ]); | ||
| 48 | export type ProviderSandboxMode = typeof ProviderSandboxMode.Type; | ||
| 49 | export const DEFAULT_PROVIDER_SANDBOX_MODE: ProviderSandboxMode = "workspace-write"; | ||
| 50 | export const ProviderRequestKind = Schema.Literals(["command", "file-change"]); | 38 | export const ProviderRequestKind = Schema.Literals(["command", "file-change"]); |
| 51 | export type ProviderRequestKind = typeof ProviderRequestKind.Type; | 39 | export type ProviderRequestKind = typeof ProviderRequestKind.Type; |
| 52 | export const AssistantDeliveryMode = Schema.Literals(["buffered", "streaming"]); | 40 | export const AssistantDeliveryMode = Schema.Literals(["buffered", "streaming"]); |
| ... | @@ -163,12 +151,7 @@ export const OrchestrationSession = Schema.Struct({ | ... | @@ -163,12 +151,7 @@ export const OrchestrationSession = Schema.Struct({ |
| 163 | providerName: Schema.NullOr(TrimmedNonEmptyString), | 151 | providerName: Schema.NullOr(TrimmedNonEmptyString), |
| 164 | providerSessionId: Schema.NullOr(ProviderSessionId), | 152 | providerSessionId: Schema.NullOr(ProviderSessionId), |
| 165 | providerThreadId: Schema.NullOr(ProviderThreadId), | 153 | providerThreadId: Schema.NullOr(ProviderThreadId), |
| 166 | approvalPolicy: ProviderApprovalPolicy.pipe( | 154 | runtimeMode: RuntimeMode.pipe(Schema.withDecodingDefault(() => DEFAULT_RUNTIME_MODE)), |
| 167 | Schema.withDecodingDefault(() => DEFAULT_PROVIDER_APPROVAL_POLICY), | ||
| 168 | ), | ||
| 169 | sandboxMode: ProviderSandboxMode.pipe( | ||
| 170 | Schema.withDecodingDefault(() => DEFAULT_PROVIDER_SANDBOX_MODE), | ||
| 171 | ), | ||
| 172 | activeTurnId: Schema.NullOr(TurnId), | 155 | activeTurnId: Schema.NullOr(TurnId), |
| 173 | lastError: Schema.NullOr(TrimmedNonEmptyString), | 156 | lastError: Schema.NullOr(TrimmedNonEmptyString), |
| 174 | updatedAt: IsoDateTime, | 157 | updatedAt: IsoDateTime, |
| ... | @@ -329,12 +312,7 @@ export const ThreadTurnStartCommand = Schema.Struct({ | ... | @@ -329,12 +312,7 @@ export const ThreadTurnStartCommand = Schema.Struct({ |
| 329 | model: Schema.optional(TrimmedNonEmptyString), | 312 | model: Schema.optional(TrimmedNonEmptyString), |
| 330 | effort: Schema.optional(TrimmedNonEmptyString), | 313 | effort: Schema.optional(TrimmedNonEmptyString), |
| 331 | assistantDeliveryMode: Schema.optional(AssistantDeliveryMode), | 314 | assistantDeliveryMode: Schema.optional(AssistantDeliveryMode), |
| 332 | approvalPolicy: ProviderApprovalPolicy.pipe( | 315 | runtimeMode: RuntimeMode.pipe(Schema.withDecodingDefault(() => DEFAULT_RUNTIME_MODE)), |
| 333 | Schema.withDecodingDefault(() => DEFAULT_PROVIDER_APPROVAL_POLICY), | ||
| 334 | ), | ||
| 335 | sandboxMode: ProviderSandboxMode.pipe( | ||
| 336 | Schema.withDecodingDefault(() => DEFAULT_PROVIDER_SANDBOX_MODE), | ||
| 337 | ), | ||
| 338 | createdAt: IsoDateTime, | 316 | createdAt: IsoDateTime, |
| 339 | }); | 317 | }); |
| 340 | 318 | ||
| ... | @@ -352,8 +330,7 @@ export const ClientThreadTurnStartCommand = Schema.Struct({ | ... | @@ -352,8 +330,7 @@ export const ClientThreadTurnStartCommand = Schema.Struct({ |
| 352 | model: Schema.optional(TrimmedNonEmptyString), | 330 | model: Schema.optional(TrimmedNonEmptyString), |
| 353 | effort: Schema.optional(TrimmedNonEmptyString), | 331 | effort: Schema.optional(TrimmedNonEmptyString), |
| 354 | assistantDeliveryMode: Schema.optional(AssistantDeliveryMode), | 332 | assistantDeliveryMode: Schema.optional(AssistantDeliveryMode), |
| 355 | approvalPolicy: ProviderApprovalPolicy, | 333 | runtimeMode: RuntimeMode, |
| 356 | sandboxMode: ProviderSandboxMode, | ||
| 357 | createdAt: IsoDateTime, | 334 | createdAt: IsoDateTime, |
| 358 | }); | 335 | }); |
| 359 | 336 | ||
| ... | @@ -585,12 +562,7 @@ export const ThreadTurnStartRequestedPayload = Schema.Struct({ | ... | @@ -585,12 +562,7 @@ export const ThreadTurnStartRequestedPayload = Schema.Struct({ |
| 585 | model: Schema.optional(TrimmedNonEmptyString), | 562 | model: Schema.optional(TrimmedNonEmptyString), |
| 586 | effort: Schema.optional(TrimmedNonEmptyString), | 563 | effort: Schema.optional(TrimmedNonEmptyString), |
| 587 | assistantDeliveryMode: Schema.optional(AssistantDeliveryMode), | 564 | assistantDeliveryMode: Schema.optional(AssistantDeliveryMode), |
| 588 | approvalPolicy: ProviderApprovalPolicy.pipe( | 565 | runtimeMode: RuntimeMode.pipe(Schema.withDecodingDefault(() => DEFAULT_RUNTIME_MODE)), |
| 589 | Schema.withDecodingDefault(() => DEFAULT_PROVIDER_APPROVAL_POLICY), | ||
| 590 | ), | ||
| 591 | sandboxMode: ProviderSandboxMode.pipe( | ||
| 592 | Schema.withDecodingDefault(() => DEFAULT_PROVIDER_SANDBOX_MODE), | ||
| 593 | ), | ||
| 594 | createdAt: IsoDateTime, | 566 | createdAt: IsoDateTime, |
| 595 | }); | 567 | }); |
| 596 | 568 |
packages/contracts/src/provider.test.ts+5-10| ... | @@ -18,8 +18,7 @@ describe("ProviderSessionStartInput", () => { | ... | @@ -18,8 +18,7 @@ describe("ProviderSessionStartInput", () => { |
| 18 | }, | 18 | }, |
| 19 | }, | 19 | }, |
| 20 | }); | 20 | }); |
| 21 | expect(parsed.approvalPolicy).toBeUndefined(); | 21 | expect(parsed.runtimeMode).toBeUndefined(); |
| 22 | expect(parsed.sandboxMode).toBeUndefined(); | ||
| 23 | expect(parsed.providerOptions?.codex?.binaryPath).toBe("/usr/local/bin/codex"); | 22 | expect(parsed.providerOptions?.codex?.binaryPath).toBe("/usr/local/bin/codex"); |
| 24 | expect(parsed.providerOptions?.codex?.homePath).toBe("/tmp/.codex"); | 23 | expect(parsed.providerOptions?.codex?.homePath).toBe("/tmp/.codex"); |
| 25 | }); | 24 | }); |
| ... | @@ -36,15 +35,13 @@ describe("ProviderSessionStartInput", () => { | ... | @@ -36,15 +35,13 @@ describe("ProviderSessionStartInput", () => { |
| 36 | maxThinkingTokens: 12_000, | 35 | maxThinkingTokens: 12_000, |
| 37 | }, | 36 | }, |
| 38 | }, | 37 | }, |
| 39 | approvalPolicy: "never", | 38 | runtimeMode: "full-access", |
| 40 | sandboxMode: "danger-full-access", | ||
| 41 | }); | 39 | }); |
| 42 | expect(parsed.provider).toBe("claudeCode"); | 40 | expect(parsed.provider).toBe("claudeCode"); |
| 43 | expect(parsed.providerOptions?.claudeCode?.binaryPath).toBe("/usr/local/bin/claude"); | 41 | expect(parsed.providerOptions?.claudeCode?.binaryPath).toBe("/usr/local/bin/claude"); |
| 44 | expect(parsed.providerOptions?.claudeCode?.permissionMode).toBe("plan"); | 42 | expect(parsed.providerOptions?.claudeCode?.permissionMode).toBe("plan"); |
| 45 | expect(parsed.providerOptions?.claudeCode?.maxThinkingTokens).toBe(12_000); | 43 | expect(parsed.providerOptions?.claudeCode?.maxThinkingTokens).toBe(12_000); |
| 46 | expect(parsed.approvalPolicy).toBe("never"); | 44 | expect(parsed.runtimeMode).toBe("full-access"); |
| 47 | expect(parsed.sandboxMode).toBe("danger-full-access"); | ||
| 48 | }); | 45 | }); |
| 49 | 46 | ||
| 50 | it("accepts cursor provider payloads", () => { | 47 | it("accepts cursor provider payloads", () => { |
| ... | @@ -57,13 +54,11 @@ describe("ProviderSessionStartInput", () => { | ... | @@ -57,13 +54,11 @@ describe("ProviderSessionStartInput", () => { |
| 57 | binaryPath: "/usr/local/bin/agent", | 54 | binaryPath: "/usr/local/bin/agent", |
| 58 | }, | 55 | }, |
| 59 | }, | 56 | }, |
| 60 | approvalPolicy: "on-request", | 57 | runtimeMode: "approval-required", |
| 61 | sandboxMode: "workspace-write", | ||
| 62 | }); | 58 | }); |
| 63 | expect(parsed.provider).toBe("cursor"); | 59 | expect(parsed.provider).toBe("cursor"); |
| 64 | expect(parsed.model).toBe("composer-1.5"); | 60 | expect(parsed.model).toBe("composer-1.5"); |
| 65 | expect(parsed.providerOptions?.cursor?.binaryPath).toBe("/usr/local/bin/agent"); | 61 | expect(parsed.providerOptions?.cursor?.binaryPath).toBe("/usr/local/bin/agent"); |
| 66 | expect(parsed.approvalPolicy).toBe("on-request"); | 62 | expect(parsed.runtimeMode).toBe("approval-required"); |
| 67 | expect(parsed.sandboxMode).toBe("workspace-write"); | ||
| 68 | }); | 63 | }); |
| 69 | }); | 64 | }); |
packages/contracts/src/provider.ts+2-4| ... | @@ -16,10 +16,9 @@ import { | ... | @@ -16,10 +16,9 @@ import { |
| 16 | PROVIDER_SEND_TURN_MAX_ATTACHMENTS, | 16 | PROVIDER_SEND_TURN_MAX_ATTACHMENTS, |
| 17 | PROVIDER_SEND_TURN_MAX_INPUT_CHARS, | 17 | PROVIDER_SEND_TURN_MAX_INPUT_CHARS, |
| 18 | ProviderApprovalDecision, | 18 | ProviderApprovalDecision, |
| 19 | ProviderApprovalPolicy, | ||
| 20 | ProviderKind, | 19 | ProviderKind, |
| 21 | ProviderRequestKind, | 20 | ProviderRequestKind, |
| 22 | ProviderSandboxMode, | 21 | RuntimeMode, |
| 23 | TurnCountRange, | 22 | TurnCountRange, |
| 24 | } from "./orchestration"; | 23 | } from "./orchestration"; |
| 25 | 24 | ||
| ... | @@ -80,8 +79,7 @@ export const ProviderSessionStartInput = Schema.Struct({ | ... | @@ -80,8 +79,7 @@ export const ProviderSessionStartInput = Schema.Struct({ |
| 80 | model: Schema.optional(TrimmedNonEmptyStringSchema), | 79 | model: Schema.optional(TrimmedNonEmptyStringSchema), |
| 81 | resumeCursor: Schema.optional(Schema.Unknown), | 80 | resumeCursor: Schema.optional(Schema.Unknown), |
| 82 | providerOptions: Schema.optional(ProviderStartOptions), | 81 | providerOptions: Schema.optional(ProviderStartOptions), |
| 83 | approvalPolicy: Schema.optional(ProviderApprovalPolicy), | 82 | runtimeMode: Schema.optional(RuntimeMode), |
| 84 | sandboxMode: Schema.optional(ProviderSandboxMode), | ||
| 85 | }); | 83 | }); |
| 86 | export type ProviderSessionStartInput = typeof ProviderSessionStartInput.Type; | 84 | export type ProviderSessionStartInput = typeof ProviderSessionStartInput.Type; |
| 87 | 85 |