authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-04 00:03:37-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-05 23:00:41-08:00
log03053d9ee0db927881542c7075aa2fa239c3853d
tree1cb044b3c4cd49173e7450c87bc8f2646e50fcbe
parent9c4d7a83e22e4a64803552d3920686f4beb07470

Add runtime mode tracking across provider orchestration pipeline

- 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 behavior

39 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 @@
1Subproject commit 9345c75d6ef60e794a78df2a795ae07abc0a420f
.tmp/ext/codex created+1
...@@ -0,0 +1 @@
1Subproject 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 });
149148
...@@ -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];
106106
107function 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 the126 * 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 };
245261
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 };
443441
...@@ -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_at149 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 });
278276
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 });
285282
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 });
317312
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 });
355348
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 });
574557
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");
628608
...@@ -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 });
636615
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 });
694670
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";
15import { Cache, Cause, Duration, Effect, Layer, Option, Queue, Schema, Stream } from "effect";14import { 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 =>
6766
68const HANDLED_TURN_START_KEY_MAX = 10_000;67const HANDLED_TURN_START_KEY_MAX = 10_000;
69const HANDLED_TURN_START_KEY_TTL = Duration.minutes(30);68const HANDLED_TURN_START_KEY_TTL = Duration.minutes(30);
70const DEFAULT_APPROVAL_POLICY: ProviderApprovalPolicy = "never";69const DEFAULT_RUNTIME_MODE: RuntimeMode = "full-access";
71const DEFAULT_SANDBOX_MODE: ProviderSandboxMode = "workspace-write";
72const WORKTREE_BRANCH_PREFIX = "t3code";70const WORKTREE_BRANCH_PREFIX = "t3code";
73const TEMP_WORKTREE_BRANCH_PATTERN = new RegExp(`^${WORKTREE_BRANCH_PREFIX}\\/[0-9a-f]{8}$`);71const TEMP_WORKTREE_BRANCH_PATTERN = new RegExp(`^${WORKTREE_BRANCH_PREFIX}\\/[0-9a-f]{8}$`);
7472
...@@ -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 }
211208
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 });
248242
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* () {
267260
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";
285275
286 if (276 if (
287 !approvalPolicyChanged &&277 !runtimeModeChanged &&
288 !sandboxModeChanged &&
289 !providerChanged &&278 !providerChanged &&
290 !shouldRestartForModelChange279 !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 @@
1import type { ProviderRuntimeEvent } from "@t3tools/contracts";1import type { OrchestrationReadModel, ProviderRuntimeEvent } from "@t3tools/contracts";
2import {2import {
3 ApprovalRequestId,3 ApprovalRequestId,
4 CommandId,4 CommandId,
...@@ -83,19 +83,11 @@ function createProviderServiceHarness() {...@@ -83,19 +83,11 @@ function createProviderServiceHarness() {
8383
84async function waitForThread(84async 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}
112104
105type ProviderRuntimeTestReadModel = OrchestrationReadModel;
106type ProviderRuntimeTestThread = ProviderRuntimeTestReadModel["threads"][number];
107type ProviderRuntimeTestMessage = ProviderRuntimeTestThread["messages"][number];
108type ProviderRuntimeTestActivity = ProviderRuntimeTestThread["activities"][number];
109type ProviderRuntimeTestCheckpoint = ProviderRuntimeTestThread["checkpoints"][number];
110
113describe("ProviderRuntimeIngestion", () => {111describe("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 });
232231
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 });
609607
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 });
636639
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 );
662670
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 });
673684
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);
682695
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 });
692709
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 );
740759
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 });
751773
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);
764786
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 });
774800
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 );
806833
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 });
826860
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 });
837874
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();
841878
...@@ -856,23 +893,30 @@ describe("ProviderRuntimeIngestion", () => {...@@ -856,23 +893,30 @@ describe("ProviderRuntimeIngestion", () => {
856 );893 );
857894
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 });
886932
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 );
896943
...@@ -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 );
9501001
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();
9541005
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");
9621015
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 });
9851042
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 });
9961053
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();
10001057
...@@ -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 });
10291089
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 );
10371099
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 });
10411242
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();
10451246
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);
10561260
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 });
10661272
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()}`);
2928
30const DEFAULT_ASSISTANT_DELIVERY_MODE: AssistantDeliveryMode = "buffered";29const DEFAULT_ASSISTANT_DELIVERY_MODE: AssistantDeliveryMode = "buffered";
31const DEFAULT_APPROVAL_POLICY: ProviderApprovalPolicy = "on-request";
32const DEFAULT_SANDBOX_MODE: ProviderSandboxMode = "workspace-write";
33const TURN_MESSAGE_IDS_BY_TURN_CACHE_CAPACITY = 10_000;30const TURN_MESSAGE_IDS_BY_TURN_CACHE_CAPACITY = 10_000;
34const TURN_MESSAGE_IDS_BY_TURN_TTL = Duration.minutes(120);31const TURN_MESSAGE_IDS_BY_TURN_TTL = Duration.minutes(120);
35const BUFFERED_MESSAGE_TEXT_BY_MESSAGE_ID_CACHE_CAPACITY = 20_000;32const 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}
9289
93function legacyRuntimeType(event: ProviderRuntimeEvent): string | undefined {
94 return asString((event as Record<string, unknown>).type);
95}
96
97function legacyRuntimeStringField(
98 event: ProviderRuntimeEvent,
99 key: string,
100): string | undefined {
101 return asString((event as Record<string, unknown>)[key]);
102}
103
104function runtimePayloadRecord(event: ProviderRuntimeEvent): Record<string, unknown> | undefined {90function 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}
133118
134function runtimeTurnErrorMessage(event: ProviderRuntimeEvent): string | undefined {119function 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}
139123
140function runtimeErrorMessageFromEvent(event: ProviderRuntimeEvent): string | undefined {124function 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}
145128
146function orchestrationSessionStatusFromRuntimeState(129function 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 }
270253
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 }
291271
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 }
312289
313 default:290 case "item.updated": {
314 break;291 if (!isToolLifecycleItemType(event.payload.itemType)) {
315 }292 return [];
316293 }
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 }
343312
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 }
364333
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 }
387354
388 default:355 default:
389 return [];356 break;
390 }357 }
358
359 return [];
391}360}
392361
393const make = Effect.gen(function* () {362const 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;
572541
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.delta675 ? event.payload.delta
709 : legacyType === "message.delta"676 : undefined;
710 ? legacyRuntimeStringField(event, "delta")
711 : undefined;
712677
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;
761721
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 }
849808
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};
9796
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_at31 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_at53 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 @@
1import * as SqlClient from "effect/unstable/sql/SqlClient";1import * as SqlClient from "effect/unstable/sql/SqlClient";
2import * as Effect from "effect/Effect";2import * as Effect from "effect/Effect";
33
4const DEFAULT_APPROVAL_POLICY = "never";4const DEFAULT_RUNTIME_MODE = "full-access";
5const DEFAULT_SANDBOX_MODE = "workspace-write";
65
7export default Effect.gen(function* () {6export default Effect.gen(function* () {
8 const sql = yield* SqlClient.SqlClient;7 const sql = yield* SqlClient.SqlClient;
98
10 yield* sql`9 yield* sql`
11 ALTER TABLE projection_thread_sessions10 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 `;
1413
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_sessions15 UPDATE projection_thread_sessions
27 SET sandbox_mode = ${DEFAULT_SANDBOX_MODE}16 SET runtime_mode = ${DEFAULT_RUNTIME_MODE}
28 WHERE sandbox_mode IS NULL17 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 ProjectionThreadSessionRepository7 * @module ProjectionThreadSessionRepository
8 */8 */
9import {9import {
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 });
198197
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", () => {
598597
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 });
603602
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 }
14631463
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);
15941594
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});
324366
325afterAll(() => {367afterAll(() => {
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 }
918918
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 }
931949
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}
943952
944const makeCodexAdapter = (options?: CodexAdapterLiveOptions) =>953const 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", () => {
467467
468 const session = yield* adapter.startSession({468 const session = yield* adapter.startSession({
469 provider: "cursor",469 provider: "cursor",
470 runtimeMode: "approval-required",
470 });471 });
471472
472 // consume startup events473 // consume startup events
...@@ -520,7 +521,7 @@ describe("CursorAdapterLive", () => {...@@ -520,7 +521,7 @@ describe("CursorAdapterLive", () => {
520521
521 yield* adapter.startSession({522 yield* adapter.startSession({
522 provider: "cursor",523 provider: "cursor",
523 approvalPolicy: "never",524 runtimeMode: "full-access",
524 });525 });
525526
526 // consume startup events527 // 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 {
8181
82interface CursorSessionContext {82interface 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);
541541
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) {
10481048
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", () => {
12361235
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 );
12491251
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) {
22312231
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 }
144153
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
5Understand 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
12Providers covered:
13
14- Codex App Server
15- Claude Agent SDK / Claude Code
16- Cursor ACP
17
18---
19
20## Existing T3 Code groundwork
21
22Current 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
31Canonical 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
40This 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
52Codex has the richest first-class support for this feature set.
53
54### Real local session evidence
55
56The 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
61From 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
66That 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
69Native 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
78Evidence 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
89Codex plan mode is not just “tool execution denied”. It has explicit behavioral guidance and explicit planning outputs.
90
91Two distinct plan surfaces exist:
92
931. **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
1002. **Plan text stream**
101 - `item/plan/delta`
102 - useful for rendering streaming plan prose / bullet lists before or alongside structured state
103
104This 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
111Codex has a dedicated `request_user_input` capability.
112
113Server request:
114
115- `item/tool/requestUserInput`
116
117Shape 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
127Important 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
135Codex 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
144For 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
157Claude has an explicit plan permission mode:
158
159### Real local session evidence
160
161The sampled Claude session file provides concrete payloads for both key plan-mode tools:
162
1631. `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
1872. `ExitPlanMode` also appears as a `tool_use` block, with a long-form `plan` string payload containing the finalized implementation spec.
188
189That 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
196Recommended 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
202The `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
209The 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
214Confirmed 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
219Claude also exposes plan-related tools/types:
220
221- `ExitPlanMode`
222- `AskUserQuestion`
223
224These 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
228Claude 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
237The 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
244What 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
250Instead, 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
260Claude’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
267That means Claude plan mode is native, but **structured plan state is not guaranteed**.
268
269In practice, there are two likely UI strategies:
270
2711. **Best-effort structured extraction**
272 - detect plan-like assistant/tool outputs
273 - synthesize `turn.plan.updated` from them
2742. **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
280Claude’s `AskUserQuestion` appears to be a tool, not a dedicated transport-level question request primitive.
281
282That 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
289This is adapter work, not just schema plumbing.
290
291### Recommended stance for Claude
292
293Claude should support the same canonical user-facing contract, but the adapter will need to **manufacture structure from native tool activity**.
294
295Recommended 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
305Claude currently appears less deterministic than Codex for structured plans.
306
307Main 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
319Cursor ACP does have native session modes.
320
321Confirmed by local probe output in `.tmp/acp-plan-probe/summary.json`:
322
323- `agent`
324- `plan`
325- `ask`
326
327The 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
338The 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
344The current ACP surface therefore looks much thinner than Codex.
345
346### Semantics
347
348#### Plan mode
349
350Cursor `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
356So the plan is native as a mode, but not obviously as a structured protocol surface.
357
358#### Asking the user questions
359
360Cursor ACP currently gives us a nearby primitive only for **permission requests**:
361
362- `session/request_permission`
363
364That is not the same as product-level question asking.
365
366The 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
372But 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
376Treat 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
382Therefore 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
390If we want the same rich “multiple-choice question card” UX across all providers, Cursor may need one of these paths:
391
3921. **Provider-native path** if later ACP exploration finds structured user question primitives
3932. **Adapter-mediated path** where the provider is instructed to emit a machine-readable question block in text and adapter parses it
3943. **Capability downgrade** where Cursor only supports conversational follow-up, not full structured plan-question cards
395
396Today, 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
425We should separate three concerns that are currently easy to conflate.
426
427### 1. Session / turn mode
428
429A canonical mode flag:
430
431- `default`
432- `plan`
433- `ask`
434- `execute`
435- `unknown`
436
437This is about **agent operating mode**, not UI widgets.
438
439### 2. Structured plan state
440
441A canonical plan model:
442
443```ts
444interface 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
455Rules:
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
463Canonical prompt should be modeled independently of approvals:
464
465```ts
466interface 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
488And answers:
489
490```ts
491interface 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
503Because 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
508Codex already blurs these at transport level because `item/tool/requestUserInput` is a request.
509But product and UI should keep them separate.
510
511Recommended 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
522Use native primitives directly.
523
524Implementation 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
534Implement tool-aware adaptation.
535
536Implementation 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
546Implement conservative degraded support first.
547
548Implementation 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
559To avoid overpromising in the UI, add provider runtime capabilities such as:
560
561```ts
562interface 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
573Expected 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
579This 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
5891. **Formalize provider capability flags**
590 - make support explicit before UI work
5912. **Finish canonical user-input contracts**
592 - question schema, answer schema, source markers
5933. **Codex end-to-end implementation first**
594 - closest match to desired UX
5954. **Claude adapter translation second**
596 - parse `AskUserQuestion` and `ExitPlanMode`
5975. **Cursor degraded plan-mode support third**
598 - mode awareness first, richer structure later
5996. **UI renders by capability, not provider name**
600 - prevents provider-specific branching from leaking upward
601
602---
603
604## Open questions
605
6061. Claude runtime payloads:
607 - what exact `AskUserQuestion` input/output shapes arrive over the stream?
6082. Claude answer submission path:
609 - should answers be injected as tool results, control responses, or synthetic user turns?
6103. Cursor ACP:
611 - is there an undocumented or less common interactive prompt primitive beyond `session/request_permission`?
6124. Cursor mode switching:
613 - can ACP session mode change mid-session, and is that surfaced as `session/update` or request/response only?
6145. 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
621Build the product contract around **capabilities**, not around the assumption that every provider has Codex-style plan/question primitives.
622
623Concretely:
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
629That 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";
2import { Effect, Schema } from "effect";2import { Effect, Schema } from "effect";
33
4import {4import {
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);
117115
...@@ -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);
140136
...@@ -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);
172167
...@@ -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 = {
32export const ProviderKind = Schema.Literals(["codex", "claudeCode", "cursor"]);32export const ProviderKind = Schema.Literals(["codex", "claudeCode", "cursor"]);
33export type ProviderKind = typeof ProviderKind.Type;33export type ProviderKind = typeof ProviderKind.Type;
34export const DEFAULT_PROVIDER_KIND: ProviderKind = "codex";34export const DEFAULT_PROVIDER_KIND: ProviderKind = "codex";
35export const ProviderApprovalPolicy = Schema.Literals([35export const RuntimeMode = Schema.Literals(["approval-required", "full-access"]);
36 "untrusted",36export type RuntimeMode = typeof RuntimeMode.Type;
37 "on-failure",37export const DEFAULT_RUNTIME_MODE: RuntimeMode = "full-access";
38 "on-request",
39 "never",
40]);
41export type ProviderApprovalPolicy = typeof ProviderApprovalPolicy.Type;
42export const DEFAULT_PROVIDER_APPROVAL_POLICY: ProviderApprovalPolicy = "on-failure";
43export const ProviderSandboxMode = Schema.Literals([
44 "read-only",
45 "workspace-write",
46 "danger-full-access",
47]);
48export type ProviderSandboxMode = typeof ProviderSandboxMode.Type;
49export const DEFAULT_PROVIDER_SANDBOX_MODE: ProviderSandboxMode = "workspace-write";
50export const ProviderRequestKind = Schema.Literals(["command", "file-change"]);38export const ProviderRequestKind = Schema.Literals(["command", "file-change"]);
51export type ProviderRequestKind = typeof ProviderRequestKind.Type;39export type ProviderRequestKind = typeof ProviderRequestKind.Type;
52export const AssistantDeliveryMode = Schema.Literals(["buffered", "streaming"]);40export 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});
340318
...@@ -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});
359336
...@@ -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});
596568
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 });
4946
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";
2524
...@@ -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});
86export type ProviderSessionStartInput = typeof ProviderSessionStartInput.Type;84export type ProviderSessionStartInput = typeof ProviderSessionStartInput.Type;
8785