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: {
142142 attachments: [],
143143 },
144144 ...(input.provider !== undefined ? { provider: input.provider } : {}),
145 approvalPolicy: "on-request",
146 sandboxMode: "workspace-write",
145 runtimeMode: "approval-required",
147146 createdAt: nowIso(),
148147 });
149148
......@@ -477,26 +476,33 @@ it.live("records failed turn runtime state and checkpoint status as error", () =
477476 turnId: FIXTURE_TURN_ID,
478477 },
479478 {
480 type: "message.delta",
479 type: "content.delta",
481480 ...runtimeBase("evt-failure-2", "2026-02-24T10:04:00.100Z"),
482481 threadId: FIXTURE_THREAD_ID,
483482 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 },
485487 },
486488 {
487489 type: "runtime.error",
488490 ...runtimeBase("evt-failure-3", "2026-02-24T10:04:00.200Z"),
489491 threadId: FIXTURE_THREAD_ID,
490492 turnId: FIXTURE_TURN_ID,
491 message: "Sandbox command failed.",
493 payload: {
494 message: "Sandbox command failed.",
495 },
492496 },
493497 {
494498 type: "turn.completed",
495499 ...runtimeBase("evt-failure-4", "2026-02-24T10:04:00.300Z"),
496500 threadId: FIXTURE_THREAD_ID,
497501 turnId: FIXTURE_TURN_ID,
498 status: "failed",
499 errorMessage: "Sandbox command failed.",
502 payload: {
503 state: "failed",
504 errorMessage: "Sandbox command failed.",
505 },
500506 },
501507 ],
502508 });
apps/server/src/codexAppServerManager.ts+18-2
......@@ -104,6 +104,23 @@ const RECOVERABLE_THREAD_RESUME_ERROR_SNIPPETS = [
104104 "does not exist",
105105];
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
107124/**
108125 * On Windows with `shell: true`, `child.kill()` only terminates the `cmd.exe`
109126 * wrapper, leaving the actual command running. Use `taskkill /T` to kill the
......@@ -239,8 +256,7 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
239256 const sessionOverrides = {
240257 model: normalizedModel ?? null,
241258 cwd: input.cwd ?? null,
242 approvalPolicy: input.approvalPolicy,
243 sandbox: input.sandboxMode,
259 ...mapCodexRuntimeMode(input.runtimeMode ?? "full-access"),
244260 };
245261
246262 const threadStartParams = {
apps/server/src/orchestration/Layers/CheckpointReactor.test.ts+11-22
......@@ -344,8 +344,7 @@ describe("CheckpointReactor", () => {
344344 providerName: "codex",
345345 providerSessionId: asSessionId("sess-1"),
346346 providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"),
347 approvalPolicy: "on-request",
348 sandboxMode: "workspace-write",
347 runtimeMode: "approval-required",
349348 activeTurnId: null,
350349 lastError: null,
351350 updatedAt: createdAt,
......@@ -423,8 +422,7 @@ describe("CheckpointReactor", () => {
423422 providerName: "codex",
424423 providerSessionId: asSessionId("sess-1"),
425424 providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"),
426 approvalPolicy: "on-request",
427 sandboxMode: "workspace-write",
425 runtimeMode: "approval-required",
428426 activeTurnId: asTurnId("turn-main"),
429427 lastError: null,
430428 updatedAt: createdAt,
......@@ -503,8 +501,7 @@ describe("CheckpointReactor", () => {
503501 providerName: "claudeCode",
504502 providerSessionId: asSessionId("sess-1"),
505503 providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"),
506 approvalPolicy: "on-request",
507 sandboxMode: "workspace-write",
504 runtimeMode: "approval-required",
508505 activeTurnId: null,
509506 lastError: null,
510507 updatedAt: createdAt,
......@@ -566,8 +563,7 @@ describe("CheckpointReactor", () => {
566563 providerName: "codex",
567564 providerSessionId: asSessionId("sess-1"),
568565 providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"),
569 approvalPolicy: "on-request",
570 sandboxMode: "workspace-write",
566 runtimeMode: "approval-required",
571567 activeTurnId: null,
572568 lastError: null,
573569 updatedAt: createdAt,
......@@ -619,8 +615,7 @@ describe("CheckpointReactor", () => {
619615 text: "start turn",
620616 attachments: [],
621617 },
622 approvalPolicy: "on-request",
623 sandboxMode: "workspace-write",
618 runtimeMode: "approval-required",
624619 createdAt: new Date().toISOString(),
625620 }),
626621 );
......@@ -657,8 +652,7 @@ describe("CheckpointReactor", () => {
657652 providerName: "codex",
658653 providerSessionId: asSessionId("sess-missing"),
659654 providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-missing"),
660 approvalPolicy: "on-request",
661 sandboxMode: "workspace-write",
655 runtimeMode: "approval-required",
662656 activeTurnId: asTurnId("turn-missing-cwd"),
663657 lastError: null,
664658 updatedAt: createdAt,
......@@ -707,8 +701,7 @@ describe("CheckpointReactor", () => {
707701 providerName: "codex",
708702 providerSessionId: asSessionId("sess-1"),
709703 providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"),
710 approvalPolicy: "on-request",
711 sandboxMode: "workspace-write",
704 runtimeMode: "approval-required",
712705 activeTurnId: null,
713706 lastError: null,
714707 updatedAt: createdAt,
......@@ -760,8 +753,7 @@ describe("CheckpointReactor", () => {
760753 providerName: "codex",
761754 providerSessionId: asSessionId("sess-1"),
762755 providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"),
763 approvalPolicy: "on-request",
764 sandboxMode: "workspace-write",
756 runtimeMode: "approval-required",
765757 activeTurnId: null,
766758 lastError: null,
767759 updatedAt: createdAt,
......@@ -815,8 +807,7 @@ describe("CheckpointReactor", () => {
815807 providerName: "codex",
816808 providerSessionId: asSessionId("sess-1"),
817809 providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"),
818 approvalPolicy: "on-request",
819 sandboxMode: "workspace-write",
810 runtimeMode: "approval-required",
820811 activeTurnId: null,
821812 lastError: null,
822813 updatedAt: createdAt,
......@@ -896,8 +887,7 @@ describe("CheckpointReactor", () => {
896887 providerName: "claudeCode",
897888 providerSessionId: asSessionId("sess-1"),
898889 providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"),
899 approvalPolicy: "on-request",
900 sandboxMode: "workspace-write",
890 runtimeMode: "approval-required",
901891 activeTurnId: null,
902892 lastError: null,
903893 updatedAt: createdAt,
......@@ -968,8 +958,7 @@ describe("CheckpointReactor", () => {
968958 providerName: "codex",
969959 providerSessionId: asSessionId("sess-1"),
970960 providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"),
971 approvalPolicy: "on-request",
972 sandboxMode: "workspace-write",
961 runtimeMode: "approval-required",
973962 activeTurnId: null,
974963 lastError: null,
975964 updatedAt: createdAt,
apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts+3-6
......@@ -96,8 +96,7 @@ describe("OrchestrationEngine", () => {
9696 text: "hello",
9797 attachments: [],
9898 },
99 approvalPolicy: "on-request",
100 sandboxMode: "workspace-write",
99 runtimeMode: "approval-required",
101100 createdAt,
102101 }),
103102 );
......@@ -436,8 +435,7 @@ describe("OrchestrationEngine", () => {
436435 text: "hello",
437436 attachments: [],
438437 },
439 approvalPolicy: "on-request" as const,
440 sandboxMode: "workspace-write" as const,
438 runtimeMode: "approval-required" as const,
441439 createdAt,
442440 };
443441
......@@ -596,8 +594,7 @@ describe("OrchestrationEngine", () => {
596594 text: "hello",
597595 attachments: [],
598596 },
599 approvalPolicy: "on-request",
600 sandboxMode: "workspace-write",
597 runtimeMode: "approval-required",
601598 createdAt: now(),
602599 }),
603600 ),
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
17491749 payload: {
17501750 threadId,
17511751 messageId,
1752 approvalPolicy: "on-request",
1753 sandboxMode: "workspace-write",
1752 runtimeMode: "approval-required",
17541753 createdAt: turnStartedAt,
17551754 },
17561755 });
......@@ -1784,8 +1783,7 @@ it.effect("restores pending turn-start metadata across projection pipeline resta
17841783 providerName: "codex",
17851784 providerSessionId: ProviderSessionId.makeUnsafe("provider-session-restart"),
17861785 providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-restart"),
1787 approvalPolicy: "on-request",
1788 sandboxMode: "workspace-write",
1786 runtimeMode: "approval-required",
17891787 activeTurnId: turnId,
17901788 lastError: null,
17911789 updatedAt: sessionSetAt,
apps/server/src/orchestration/Layers/ProjectionPipeline.ts+1-2
......@@ -651,8 +651,7 @@ const makeOrchestrationProjectionPipeline = Effect.gen(function* () {
651651 providerName: event.payload.session.providerName,
652652 providerSessionId: event.payload.session.providerSessionId,
653653 providerThreadId: event.payload.session.providerThreadId,
654 approvalPolicy: event.payload.session.approvalPolicy,
655 sandboxMode: event.payload.session.sandboxMode,
654 runtimeMode: event.payload.session.runtimeMode,
656655 activeTurnId: event.payload.session.activeTurnId,
657656 lastError: event.payload.session.lastError,
658657 updatedAt: event.payload.session.updatedAt,
apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts+3-6
......@@ -143,8 +143,7 @@ projectionSnapshotLayer("ProjectionSnapshotQuery", (it) => {
143143 provider_name,
144144 provider_session_id,
145145 provider_thread_id,
146 approval_policy,
147 sandbox_mode,
146 runtime_mode,
148147 active_turn_id,
149148 last_error,
150149 updated_at
......@@ -155,8 +154,7 @@ projectionSnapshotLayer("ProjectionSnapshotQuery", (it) => {
155154 'codex',
156155 'provider-session-1',
157156 'provider-thread-1',
158 'on-request',
159 'workspace-write',
157 'approval-required',
160158 'turn-1',
161159 NULL,
162160 '2026-02-24T00:00:07.000Z'
......@@ -293,8 +291,7 @@ projectionSnapshotLayer("ProjectionSnapshotQuery", (it) => {
293291 providerName: "codex",
294292 providerSessionId: asProviderSessionId("provider-session-1"),
295293 providerThreadId: asProviderThreadId("provider-thread-1"),
296 approvalPolicy: "on-request",
297 sandboxMode: "workspace-write",
294 runtimeMode: "approval-required",
298295 activeTurnId: asTurnId("turn-1"),
299296 lastError: null,
300297 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* () {
220220 provider_name AS "providerName",
221221 provider_session_id AS "providerSessionId",
222222 provider_thread_id AS "providerThreadId",
223 approval_policy AS "approvalPolicy",
224 sandbox_mode AS "sandboxMode",
223 runtime_mode AS "runtimeMode",
225224 active_turn_id AS "activeTurnId",
226225 last_error AS "lastError",
227226 updated_at AS "updatedAt"
......@@ -463,8 +462,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
463462 providerName: row.providerName,
464463 providerSessionId: row.providerSessionId,
465464 providerThreadId: row.providerThreadId,
466 approvalPolicy: row.approvalPolicy,
467 sandboxMode: row.sandboxMode,
465 runtimeMode: row.runtimeMode,
468466 activeTurnId: row.activeTurnId,
469467 lastError: row.lastError,
470468 updatedAt: row.updatedAt,
apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts+28-56
......@@ -260,8 +260,7 @@ describe("ProviderCommandReactor", () => {
260260 text: "hello reactor",
261261 attachments: [],
262262 },
263 approvalPolicy: "on-request",
264 sandboxMode: "workspace-write",
263 runtimeMode: "approval-required",
265264 createdAt: now,
266265 }),
267266 );
......@@ -272,15 +271,13 @@ describe("ProviderCommandReactor", () => {
272271 expect(harness.startSession.mock.calls[0]?.[1]).toMatchObject({
273272 cwd: "/tmp/provider-project",
274273 model: "gpt-5-codex",
275 approvalPolicy: "on-request",
276 sandboxMode: "workspace-write",
274 runtimeMode: "approval-required",
277275 });
278276
279277 const readModel = await Effect.runPromise(harness.engine.getReadModel());
280278 const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1"));
281279 expect(thread?.session?.providerSessionId).toBe("sess-1");
282 expect(thread?.session?.approvalPolicy).toBe("on-request");
283 expect(thread?.session?.sandboxMode).toBe("workspace-write");
280 expect(thread?.session?.runtimeMode).toBe("approval-required");
284281 });
285282
286283 it("starts first turn with requested provider when provider is specified", async () => {
......@@ -299,8 +296,7 @@ describe("ProviderCommandReactor", () => {
299296 attachments: [],
300297 },
301298 provider: "claudeCode",
302 approvalPolicy: "on-request",
303 sandboxMode: "workspace-write",
299 runtimeMode: "approval-required",
304300 createdAt: now,
305301 }),
306302 );
......@@ -311,8 +307,7 @@ describe("ProviderCommandReactor", () => {
311307 provider: "claudeCode",
312308 cwd: "/tmp/provider-project",
313309 model: "gpt-5-codex",
314 approvalPolicy: "on-request",
315 sandboxMode: "workspace-write",
310 runtimeMode: "approval-required",
316311 });
317312
318313 const readModel = await Effect.runPromise(harness.engine.getReadModel());
......@@ -337,8 +332,7 @@ describe("ProviderCommandReactor", () => {
337332 attachments: [],
338333 },
339334 provider: "cursor",
340 approvalPolicy: "on-request",
341 sandboxMode: "workspace-write",
335 runtimeMode: "approval-required",
342336 createdAt: now,
343337 }),
344338 );
......@@ -349,8 +343,7 @@ describe("ProviderCommandReactor", () => {
349343 provider: "cursor",
350344 cwd: "/tmp/provider-project",
351345 model: "gpt-5-codex",
352 approvalPolicy: "on-request",
353 sandboxMode: "workspace-write",
346 runtimeMode: "approval-required",
354347 });
355348
356349 const readModel = await Effect.runPromise(harness.engine.getReadModel());
......@@ -374,8 +367,7 @@ describe("ProviderCommandReactor", () => {
374367 text: "first",
375368 attachments: [],
376369 },
377 approvalPolicy: "on-request",
378 sandboxMode: "workspace-write",
370 runtimeMode: "approval-required",
379371 createdAt: now,
380372 }),
381373 );
......@@ -394,8 +386,7 @@ describe("ProviderCommandReactor", () => {
394386 text: "second",
395387 attachments: [],
396388 },
397 approvalPolicy: "on-request",
398 sandboxMode: "workspace-write",
389 runtimeMode: "approval-required",
399390 createdAt: now,
400391 }),
401392 );
......@@ -422,8 +413,7 @@ describe("ProviderCommandReactor", () => {
422413 },
423414 provider: "cursor",
424415 model: "composer-1.5",
425 approvalPolicy: "on-request",
426 sandboxMode: "workspace-write",
416 runtimeMode: "approval-required",
427417 createdAt: now,
428418 }),
429419 );
......@@ -444,8 +434,7 @@ describe("ProviderCommandReactor", () => {
444434 },
445435 provider: "cursor",
446436 model: "composer-1.5",
447 approvalPolicy: "on-request",
448 sandboxMode: "workspace-write",
437 runtimeMode: "approval-required",
449438 createdAt: now,
450439 }),
451440 );
......@@ -472,8 +461,7 @@ describe("ProviderCommandReactor", () => {
472461 },
473462 provider: "cursor",
474463 model: "gpt-5.3-codex",
475 approvalPolicy: "on-request",
476 sandboxMode: "workspace-write",
464 runtimeMode: "approval-required",
477465 createdAt: now,
478466 }),
479467 );
......@@ -494,8 +482,7 @@ describe("ProviderCommandReactor", () => {
494482 },
495483 provider: "cursor",
496484 model: "composer-1.5",
497 approvalPolicy: "on-request",
498 sandboxMode: "workspace-write",
485 runtimeMode: "approval-required",
499486 createdAt: now,
500487 }),
501488 );
......@@ -525,8 +512,7 @@ describe("ProviderCommandReactor", () => {
525512 text: "first",
526513 attachments: [],
527514 },
528 approvalPolicy: "never",
529 sandboxMode: "danger-full-access",
515 runtimeMode: "full-access",
530516 createdAt: now,
531517 }),
532518 );
......@@ -545,8 +531,7 @@ describe("ProviderCommandReactor", () => {
545531 text: "second",
546532 attachments: [],
547533 },
548 approvalPolicy: "on-request",
549 sandboxMode: "workspace-write",
534 runtimeMode: "approval-required",
550535 createdAt: now,
551536 }),
552537 );
......@@ -558,8 +543,7 @@ describe("ProviderCommandReactor", () => {
558543 expect(harness.stopSession.mock.calls[0]?.[0]).toEqual({ sessionId: asSessionId("sess-1") });
559544 expect(harness.startSession.mock.calls[1]?.[1]).toMatchObject({
560545 resumeCursor: { opaque: "cursor-1" },
561 approvalPolicy: "on-request",
562 sandboxMode: "workspace-write",
546 runtimeMode: "approval-required",
563547 });
564548 expect(harness.sendTurn.mock.calls[1]?.[0]).toMatchObject({
565549 sessionId: asSessionId("sess-2"),
......@@ -568,8 +552,7 @@ describe("ProviderCommandReactor", () => {
568552 const readModel = await Effect.runPromise(harness.engine.getReadModel());
569553 const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1"));
570554 expect(thread?.session?.providerSessionId).toBe("sess-2");
571 expect(thread?.session?.approvalPolicy).toBe("on-request");
572 expect(thread?.session?.sandboxMode).toBe("workspace-write");
555 expect(thread?.session?.runtimeMode).toBe("approval-required");
573556 });
574557
575558 it("switches provider by restarting the session when turn request provider changes", async () => {
......@@ -587,8 +570,7 @@ describe("ProviderCommandReactor", () => {
587570 text: "first",
588571 attachments: [],
589572 },
590 approvalPolicy: "on-request",
591 sandboxMode: "workspace-write",
573 runtimeMode: "approval-required",
592574 createdAt: now,
593575 }),
594576 );
......@@ -608,8 +590,7 @@ describe("ProviderCommandReactor", () => {
608590 attachments: [],
609591 },
610592 provider: "claudeCode",
611 approvalPolicy: "on-request",
612 sandboxMode: "workspace-write",
593 runtimeMode: "approval-required",
613594 createdAt: now,
614595 }),
615596 );
......@@ -621,8 +602,7 @@ describe("ProviderCommandReactor", () => {
621602 expect(harness.stopSession.mock.calls[0]?.[0]).toEqual({ sessionId: asSessionId("sess-1") });
622603 expect(harness.startSession.mock.calls[1]?.[1]).toMatchObject({
623604 provider: "claudeCode",
624 approvalPolicy: "on-request",
625 sandboxMode: "workspace-write",
605 runtimeMode: "approval-required",
626606 });
627607 expect(harness.startSession.mock.calls[1]?.[1]).not.toHaveProperty("resumeCursor");
628608
......@@ -630,8 +610,7 @@ describe("ProviderCommandReactor", () => {
630610 const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1"));
631611 expect(thread?.session?.providerSessionId).toBe("sess-2");
632612 expect(thread?.session?.providerName).toBe("claudeCode");
633 expect(thread?.session?.approvalPolicy).toBe("on-request");
634 expect(thread?.session?.sandboxMode).toBe("workspace-write");
613 expect(thread?.session?.runtimeMode).toBe("approval-required");
635614 });
636615
637616 it("does not stop the active session when restart fails before rebind", async () => {
......@@ -649,8 +628,7 @@ describe("ProviderCommandReactor", () => {
649628 text: "first",
650629 attachments: [],
651630 },
652 approvalPolicy: "never",
653 sandboxMode: "danger-full-access",
631 runtimeMode: "full-access",
654632 createdAt: now,
655633 }),
656634 );
......@@ -673,8 +651,7 @@ describe("ProviderCommandReactor", () => {
673651 text: "second",
674652 attachments: [],
675653 },
676 approvalPolicy: "on-request",
677 sandboxMode: "workspace-write",
654 runtimeMode: "approval-required",
678655 createdAt: now,
679656 }),
680657 );
......@@ -688,8 +665,7 @@ describe("ProviderCommandReactor", () => {
688665 const readModel = await Effect.runPromise(harness.engine.getReadModel());
689666 const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1"));
690667 expect(thread?.session?.providerSessionId).toBe("sess-1");
691 expect(thread?.session?.approvalPolicy).toBe("never");
692 expect(thread?.session?.sandboxMode).toBe("danger-full-access");
668 expect(thread?.session?.runtimeMode).toBe("full-access");
693669 });
694670
695671 it("reacts to thread.turn.interrupt-requested by calling provider interrupt", async () => {
......@@ -707,8 +683,7 @@ describe("ProviderCommandReactor", () => {
707683 providerName: "codex",
708684 providerSessionId: asSessionId("sess-1"),
709685 providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"),
710 approvalPolicy: "on-request",
711 sandboxMode: "workspace-write",
686 runtimeMode: "approval-required",
712687 activeTurnId: asTurnId("turn-1"),
713688 lastError: null,
714689 updatedAt: now,
......@@ -748,8 +723,7 @@ describe("ProviderCommandReactor", () => {
748723 providerName: "codex",
749724 providerSessionId: asSessionId("sess-1"),
750725 providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"),
751 approvalPolicy: "on-request",
752 sandboxMode: "workspace-write",
726 runtimeMode: "approval-required",
753727 activeTurnId: null,
754728 lastError: null,
755729 updatedAt: now,
......@@ -801,8 +775,7 @@ describe("ProviderCommandReactor", () => {
801775 providerName: "cursor",
802776 providerSessionId: asSessionId("sess-1"),
803777 providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"),
804 approvalPolicy: "on-request",
805 sandboxMode: "workspace-write",
778 runtimeMode: "approval-required",
806779 activeTurnId: null,
807780 lastError: null,
808781 updatedAt: now,
......@@ -887,8 +860,7 @@ describe("ProviderCommandReactor", () => {
887860 providerName: "codex",
888861 providerSessionId: asSessionId("sess-1"),
889862 providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"),
890 approvalPolicy: "on-request",
891 sandboxMode: "workspace-write",
863 runtimeMode: "approval-required",
892864 activeTurnId: null,
893865 lastError: null,
894866 updatedAt: now,
apps/server/src/orchestration/Layers/ProviderCommandReactor.ts+14-29
......@@ -3,13 +3,12 @@ import {
33 CommandId,
44 EventId,
55 type OrchestrationEvent,
6 type ProviderApprovalPolicy,
76 type ProviderKind,
8 type ProviderSandboxMode,
97 type ProviderSessionId,
108 type OrchestrationSession,
119 type ThreadId,
1210 type ProviderSession,
11 type RuntimeMode,
1312 type TurnId,
1413} from "@t3tools/contracts";
1514import { Cache, Cause, Duration, Effect, Layer, Option, Queue, Schema, Stream } from "effect";
......@@ -67,8 +66,7 @@ const serverCommandId = (tag: string): CommandId =>
6766
6867const HANDLED_TURN_START_KEY_MAX = 10_000;
6968const HANDLED_TURN_START_KEY_TTL = Duration.minutes(30);
70const DEFAULT_APPROVAL_POLICY: ProviderApprovalPolicy = "never";
71const DEFAULT_SANDBOX_MODE: ProviderSandboxMode = "workspace-write";
69const DEFAULT_RUNTIME_MODE: RuntimeMode = "full-access";
7270const WORKTREE_BRANCH_PREFIX = "t3code";
7371const TEMP_WORKTREE_BRANCH_PATTERN = new RegExp(`^${WORKTREE_BRANCH_PREFIX}\\/[0-9a-f]{8}$`);
7472
......@@ -199,8 +197,7 @@ const make = Effect.gen(function* () {
199197 options?: {
200198 readonly provider?: ProviderKind;
201199 readonly model?: string;
202 readonly approvalPolicy?: ProviderApprovalPolicy;
203 readonly sandboxMode?: ProviderSandboxMode;
200 readonly runtimeMode?: RuntimeMode;
204201 },
205202 ) {
206203 const readModel = yield* orchestrationEngine.getReadModel();
......@@ -209,10 +206,8 @@ const make = Effect.gen(function* () {
209206 return yield* Effect.die(new Error(`Thread '${threadId}' was not found in read model.`));
210207 }
211208
212 const desiredApprovalPolicy =
213 options?.approvalPolicy ?? thread.session?.approvalPolicy ?? DEFAULT_APPROVAL_POLICY;
214 const desiredSandboxMode =
215 options?.sandboxMode ?? thread.session?.sandboxMode ?? DEFAULT_SANDBOX_MODE;
209 const desiredRuntimeMode =
210 options?.runtimeMode ?? thread.session?.runtimeMode ?? DEFAULT_RUNTIME_MODE;
216211 const currentProvider: ProviderKind | undefined =
217212 thread.session?.providerName === "codex" ||
218213 thread.session?.providerName === "claudeCode" ||
......@@ -242,8 +237,7 @@ const make = Effect.gen(function* () {
242237 ...(effectiveCwd ? { cwd: effectiveCwd } : {}),
243238 ...(desiredModel ? { model: desiredModel } : {}),
244239 ...(input?.resumeCursor !== undefined ? { resumeCursor: input.resumeCursor } : {}),
245 approvalPolicy: desiredApprovalPolicy,
246 sandboxMode: desiredSandboxMode,
240 runtimeMode: desiredRuntimeMode,
247241 });
248242
249243 const bindSessionToThread = (session: ProviderSession) =>
......@@ -255,8 +249,7 @@ const make = Effect.gen(function* () {
255249 providerName: session.provider,
256250 providerSessionId: session.sessionId,
257251 providerThreadId: session.threadId ?? null,
258 approvalPolicy: desiredApprovalPolicy,
259 sandboxMode: desiredSandboxMode,
252 runtimeMode: desiredRuntimeMode,
260253 // Provider turn ids are not orchestration turn ids.
261254 activeTurnId: null,
262255 lastError: session.lastError ?? null,
......@@ -267,11 +260,8 @@ const make = Effect.gen(function* () {
267260
268261 const existingSessionId = thread.session?.providerSessionId;
269262 if (existingSessionId) {
270 const approvalPolicyChanged =
271 options?.approvalPolicy !== undefined &&
272 options.approvalPolicy !== thread.session?.approvalPolicy;
273 const sandboxModeChanged =
274 options?.sandboxMode !== undefined && options.sandboxMode !== thread.session?.sandboxMode;
263 const runtimeModeChanged =
264 options?.runtimeMode !== undefined && options.runtimeMode !== thread.session?.runtimeMode;
275265 const providerChanged = options?.provider !== undefined && options.provider !== currentProvider;
276266 const activeSession = yield* resolveActiveSession(existingSessionId);
277267 const sessionModelSwitch =
......@@ -284,8 +274,7 @@ const make = Effect.gen(function* () {
284274 modelChanged && sessionModelSwitch === "restart-session";
285275
286276 if (
287 !approvalPolicyChanged &&
288 !sandboxModeChanged &&
277 !runtimeModeChanged &&
289278 !providerChanged &&
290279 !shouldRestartForModelChange
291280 ) {
......@@ -327,8 +316,7 @@ const make = Effect.gen(function* () {
327316 readonly provider?: ProviderKind;
328317 readonly model?: string;
329318 readonly effort?: string;
330 readonly approvalPolicy: ProviderApprovalPolicy;
331 readonly sandboxMode: ProviderSandboxMode;
319 readonly runtimeMode: RuntimeMode;
332320 readonly createdAt: string;
333321 }) {
334322 const thread = yield* resolveThread(input.threadId);
......@@ -338,8 +326,7 @@ const make = Effect.gen(function* () {
338326 const sessionId = yield* ensureSessionForThread(input.threadId, input.createdAt, {
339327 ...(input.provider !== undefined ? { provider: input.provider } : {}),
340328 ...(input.model !== undefined ? { model: input.model } : {}),
341 approvalPolicy: input.approvalPolicy,
342 sandboxMode: input.sandboxMode,
329 runtimeMode: input.runtimeMode,
343330 });
344331 const normalizedInput = toNonEmptyProviderInput(input.messageText);
345332 const normalizedAttachments = input.attachments ?? [];
......@@ -472,8 +459,7 @@ const make = Effect.gen(function* () {
472459 ...(event.payload.provider !== undefined ? { provider: event.payload.provider } : {}),
473460 ...(event.payload.model !== undefined ? { model: event.payload.model } : {}),
474461 ...(event.payload.effort !== undefined ? { effort: event.payload.effort } : {}),
475 approvalPolicy: event.payload.approvalPolicy,
476 sandboxMode: event.payload.sandboxMode,
462 runtimeMode: event.payload.runtimeMode,
477463 createdAt: event.payload.createdAt,
478464 });
479465 });
......@@ -571,8 +557,7 @@ const make = Effect.gen(function* () {
571557 providerName: thread.session?.providerName ?? null,
572558 providerSessionId: null,
573559 providerThreadId: null,
574 approvalPolicy: thread.session?.approvalPolicy ?? DEFAULT_APPROVAL_POLICY,
575 sandboxMode: thread.session?.sandboxMode ?? DEFAULT_SANDBOX_MODE,
560 runtimeMode: thread.session?.runtimeMode ?? DEFAULT_RUNTIME_MODE,
576561 activeTurnId: null,
577562 lastError: thread.session?.lastError ?? null,
578563 updatedAt: now,
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts+270-64
......@@ -1,4 +1,4 @@
1import type { ProviderRuntimeEvent } from "@t3tools/contracts";
1import type { OrchestrationReadModel, ProviderRuntimeEvent } from "@t3tools/contracts";
22import {
33 ApprovalRequestId,
44 CommandId,
......@@ -83,19 +83,11 @@ function createProviderServiceHarness() {
8383
8484async function waitForThread(
8585 engine: OrchestrationEngineShape,
86 predicate: (thread: {
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,
86 predicate: (thread: ProviderRuntimeTestThread) => boolean,
9187 timeoutMs = 2000,
9288) {
9389 const deadline = Date.now() + timeoutMs;
94 const poll = async (): Promise<{
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 }> => {
90 const poll = async (): Promise<ProviderRuntimeTestThread> => {
9991 const readModel = await Effect.runPromise(engine.getReadModel());
10092 const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1"));
10193 if (thread && predicate(thread)) {
......@@ -110,6 +102,12 @@ async function waitForThread(
110102 return poll();
111103}
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
113111describe("ProviderRuntimeIngestion", () => {
114112 let runtime: ManagedRuntime.ManagedRuntime<
115113 OrchestrationEngineService | ProviderRuntimeIngestionService,
......@@ -185,8 +183,7 @@ describe("ProviderRuntimeIngestion", () => {
185183 providerName: "codex",
186184 providerSessionId: asSessionId("sess-1"),
187185 providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"),
188 approvalPolicy: "on-request",
189 sandboxMode: "workspace-write",
186 runtimeMode: "approval-required",
190187 activeTurnId: null,
191188 updatedAt: createdAt,
192189 lastError: null,
......@@ -226,8 +223,10 @@ describe("ProviderRuntimeIngestion", () => {
226223 sessionId: asSessionId("sess-1"),
227224 createdAt: new Date().toISOString(),
228225 turnId: asProviderTurnId("turn-1"),
229 status: "failed",
230 errorMessage: "turn failed",
226 payload: {
227 state: "failed",
228 errorMessage: "turn failed",
229 },
231230 });
232231
233232 const thread = await waitForThread(
......@@ -465,8 +464,7 @@ describe("ProviderRuntimeIngestion", () => {
465464 providerName: "claudeCode",
466465 providerSessionId: asSessionId("sess-1"),
467466 providerThreadId: asProviderThreadId("claude-thread-placeholder"),
468 approvalPolicy: "on-request",
469 sandboxMode: "workspace-write",
467 runtimeMode: "approval-required",
470468 activeTurnId: null,
471469 updatedAt: seededAt,
472470 lastError: null,
......@@ -608,9 +606,14 @@ describe("ProviderRuntimeIngestion", () => {
608606 });
609607
610608 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",
612616 );
613 const message = thread.messages.find((entry) => entry.id === "assistant:item-1");
614617 expect(message?.text).toBe("hello world");
615618 expect(message?.streaming).toBe(false);
616619 });
......@@ -635,9 +638,14 @@ describe("ProviderRuntimeIngestion", () => {
635638 });
636639
637640 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",
639648 );
640 const message = thread.messages.find((entry) => entry.id === "assistant:item-no-delta");
641649 expect(message?.text).toBe("assistant-only final text");
642650 expect(message?.streaming).toBe(false);
643651 });
......@@ -661,14 +669,17 @@ describe("ProviderRuntimeIngestion", () => {
661669 );
662670
663671 harness.emit({
664 type: "message.delta",
672 type: "content.delta",
665673 eventId: asEventId("evt-message-delta-buffered"),
666674 provider: "codex",
667675 sessionId: asSessionId("sess-1"),
668676 createdAt: now,
669677 turnId: asProviderTurnId("turn-buffered"),
670678 itemId: asItemId("item-buffered"),
671 delta: "buffer me",
679 payload: {
680 streamKind: "assistant_text",
681 delta: "buffer me",
682 },
672683 });
673684
674685 await Effect.runPromise(Effect.sleep("30 millis"));
......@@ -676,26 +687,35 @@ describe("ProviderRuntimeIngestion", () => {
676687 const midThread = midReadModel.threads.find(
677688 (entry) => entry.id === ThreadId.makeUnsafe("thread-1"),
678689 );
679 expect(midThread?.messages.some((message) => message.id === "assistant:item-buffered")).toBe(
680 false,
681 );
690 expect(
691 midThread?.messages.some(
692 (message: ProviderRuntimeTestMessage) => message.id === "assistant:item-buffered",
693 ),
694 ).toBe(false);
682695
683696 harness.emit({
684 type: "message.completed",
697 type: "item.completed",
685698 eventId: asEventId("evt-message-completed-buffered"),
686699 provider: "codex",
687700 sessionId: asSessionId("sess-1"),
688701 createdAt: now,
689702 turnId: asProviderTurnId("turn-buffered"),
690703 itemId: asItemId("item-buffered"),
704 payload: {
705 itemType: "assistant_message",
706 status: "completed",
707 },
691708 });
692709
693710 const thread = await waitForThread(harness.engine, (entry) =>
694711 entry.messages.some(
695 (message) => message.id === "assistant:item-buffered" && !message.streaming,
712 (message: ProviderRuntimeTestMessage) =>
713 message.id === "assistant:item-buffered" && !message.streaming,
696714 ),
697715 );
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 );
699719 expect(message?.text).toBe("buffer me");
700720 expect(message?.streaming).toBe(false);
701721 });
......@@ -716,8 +736,7 @@ describe("ProviderRuntimeIngestion", () => {
716736 attachments: [],
717737 },
718738 assistantDeliveryMode: "streaming",
719 approvalPolicy: "on-request",
720 sandboxMode: "workspace-write",
739 runtimeMode: "approval-required",
721740 createdAt: now,
722741 }),
723742 );
......@@ -739,46 +758,54 @@ describe("ProviderRuntimeIngestion", () => {
739758 );
740759
741760 harness.emit({
742 type: "message.delta",
761 type: "content.delta",
743762 eventId: asEventId("evt-message-delta-streaming-mode"),
744763 provider: "codex",
745764 sessionId: asSessionId("sess-1"),
746765 createdAt: now,
747766 turnId: asProviderTurnId("turn-streaming-mode"),
748767 itemId: asItemId("item-streaming-mode"),
749 delta: "hello live",
768 payload: {
769 streamKind: "assistant_text",
770 delta: "hello live",
771 },
750772 });
751773
752774 const liveThread = await waitForThread(harness.engine, (entry) =>
753775 entry.messages.some(
754 (message) =>
776 (message: ProviderRuntimeTestMessage) =>
755777 message.id === "assistant:item-streaming-mode" &&
756778 message.streaming &&
757779 message.text === "hello live",
758780 ),
759781 );
760782 const liveMessage = liveThread.messages.find(
761 (entry) => entry.id === "assistant:item-streaming-mode",
783 (entry: ProviderRuntimeTestMessage) => entry.id === "assistant:item-streaming-mode",
762784 );
763785 expect(liveMessage?.streaming).toBe(true);
764786
765787 harness.emit({
766 type: "message.completed",
788 type: "item.completed",
767789 eventId: asEventId("evt-message-completed-streaming-mode"),
768790 provider: "codex",
769791 sessionId: asSessionId("sess-1"),
770792 createdAt: now,
771793 turnId: asProviderTurnId("turn-streaming-mode"),
772794 itemId: asItemId("item-streaming-mode"),
795 payload: {
796 itemType: "assistant_message",
797 status: "completed",
798 },
773799 });
774800
775801 const finalThread = await waitForThread(harness.engine, (entry) =>
776802 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,
778805 ),
779806 );
780807 const finalMessage = finalThread.messages.find(
781 (entry) => entry.id === "assistant:item-streaming-mode",
808 (entry: ProviderRuntimeTestMessage) => entry.id === "assistant:item-streaming-mode",
782809 );
783810 expect(finalMessage?.text).toBe("hello live");
784811 expect(finalMessage?.streaming).toBe(false);
......@@ -805,37 +832,47 @@ describe("ProviderRuntimeIngestion", () => {
805832 );
806833
807834 harness.emit({
808 type: "message.delta",
835 type: "content.delta",
809836 eventId: asEventId("evt-message-delta-buffer-spill"),
810837 provider: "codex",
811838 sessionId: asSessionId("sess-1"),
812839 createdAt: now,
813840 turnId: asProviderTurnId("turn-buffer-spill"),
814841 itemId: asItemId("item-buffer-spill"),
815 delta: oversizedText,
842 payload: {
843 streamKind: "assistant_text",
844 delta: oversizedText,
845 },
816846 });
817847 harness.emit({
818 type: "message.completed",
848 type: "item.completed",
819849 eventId: asEventId("evt-message-completed-buffer-spill"),
820850 provider: "codex",
821851 sessionId: asSessionId("sess-1"),
822852 createdAt: now,
823853 turnId: asProviderTurnId("turn-buffer-spill"),
824854 itemId: asItemId("item-buffer-spill"),
855 payload: {
856 itemType: "assistant_message",
857 status: "completed",
858 },
825859 });
826860
827861 const thread = await waitForThread(harness.engine, (entry) =>
828862 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,
830865 ),
831866 );
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 );
833870 expect(message?.text.length).toBe(oversizedText.length);
834871 expect(message?.text).toBe(oversizedText);
835872 expect(message?.streaming).toBe(false);
836873 });
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 () => {
839876 const harness = await createHarness();
840877 const now = new Date().toISOString();
841878
......@@ -856,23 +893,30 @@ describe("ProviderRuntimeIngestion", () => {
856893 );
857894
858895 harness.emit({
859 type: "message.delta",
896 type: "content.delta",
860897 eventId: asEventId("evt-message-delta-for-complete-dedup"),
861898 provider: "codex",
862899 sessionId: asSessionId("sess-1"),
863900 createdAt: now,
864901 turnId: asProviderTurnId("turn-complete-dedup"),
865902 itemId: asItemId("item-complete-dedup"),
866 delta: "done",
903 payload: {
904 streamKind: "assistant_text",
905 delta: "done",
906 },
867907 });
868908 harness.emit({
869 type: "message.completed",
909 type: "item.completed",
870910 eventId: asEventId("evt-message-completed-for-complete-dedup"),
871911 provider: "codex",
872912 sessionId: asSessionId("sess-1"),
873913 createdAt: now,
874914 turnId: asProviderTurnId("turn-complete-dedup"),
875915 itemId: asItemId("item-complete-dedup"),
916 payload: {
917 itemType: "assistant_message",
918 status: "completed",
919 },
876920 });
877921 harness.emit({
878922 type: "turn.completed",
......@@ -881,7 +925,9 @@ describe("ProviderRuntimeIngestion", () => {
881925 sessionId: asSessionId("sess-1"),
882926 createdAt: now,
883927 turnId: asProviderTurnId("turn-complete-dedup"),
884 status: "completed",
928 payload: {
929 state: "completed",
930 },
885931 });
886932
887933 await waitForThread(
......@@ -890,7 +936,8 @@ describe("ProviderRuntimeIngestion", () => {
890936 thread.session?.status === "ready" &&
891937 thread.session?.activeTurnId === null &&
892938 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,
894941 ),
895942 );
896943
......@@ -944,15 +991,21 @@ describe("ProviderRuntimeIngestion", () => {
944991 await waitForThread(
945992 harness.engine,
946993 (entry) =>
947 entry.activities.some((activity) => activity.kind === "approval.requested") &&
948 entry.activities.some((activity) => activity.kind === "approval.resolved"),
994 entry.activities.some(
995 (activity: ProviderRuntimeTestActivity) => activity.kind === "approval.requested",
996 ) &&
997 entry.activities.some(
998 (activity: ProviderRuntimeTestActivity) => activity.kind === "approval.resolved",
999 ),
9491000 );
9501001
9511002 const readModel = await Effect.runPromise(harness.engine.getReadModel());
9521003 const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1"));
9531004 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 );
9561009 const requestedPayload =
9571010 requested?.payload && typeof requested.payload === "object"
9581011 ? (requested.payload as Record<string, unknown>)
......@@ -960,7 +1013,9 @@ describe("ProviderRuntimeIngestion", () => {
9601013 expect(requestedPayload?.requestKind).toBe("command");
9611014 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 );
9641019 const resolvedPayload =
9651020 resolved?.payload && typeof resolved.payload === "object"
9661021 ? (resolved.payload as Record<string, unknown>)
......@@ -980,7 +1035,9 @@ describe("ProviderRuntimeIngestion", () => {
9801035 sessionId: asSessionId("sess-1"),
9811036 createdAt: now,
9821037 turnId: asProviderTurnId("turn-3"),
983 message: "runtime exploded",
1038 payload: {
1039 message: "runtime exploded",
1040 },
9841041 });
9851042
9861043 const thread = await waitForThread(
......@@ -994,7 +1051,7 @@ describe("ProviderRuntimeIngestion", () => {
9941051 expect(thread.session?.lastError).toBe("runtime exploded");
9951052 });
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 () => {
9981055 const harness = await createHarness();
9991056 const now = new Date().toISOString();
10001057
......@@ -1016,15 +1073,18 @@ describe("ProviderRuntimeIngestion", () => {
10161073 threadId: ProviderThreadId.makeUnsafe("provider-thread-2"),
10171074 });
10181075 harness.emit({
1019 type: "tool.started",
1076 type: "item.started",
10201077 eventId: asEventId("evt-tool-started"),
10211078 provider: "codex",
10221079 sessionId: asSessionId("sess-1"),
10231080 createdAt: now,
10241081 turnId: asProviderTurnId("turn-9"),
1025 toolKind: "other",
1026 title: "Read file",
1027 detail: "/tmp/file.ts",
1082 payload: {
1083 itemType: "command_execution",
1084 status: "in_progress",
1085 title: "Read file",
1086 detail: "/tmp/file.ts",
1087 },
10281088 });
10291089
10301090 const thread = await waitForThread(
......@@ -1032,11 +1092,152 @@ describe("ProviderRuntimeIngestion", () => {
10321092 (entry) =>
10331093 entry.session?.status === "ready" &&
10341094 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 ),
10361098 );
10371099
10381100 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");
10401241 });
10411242
10421243 it("continues processing runtime events after a single event handler failure", async () => {
......@@ -1044,14 +1245,17 @@ describe("ProviderRuntimeIngestion", () => {
10441245 const now = new Date().toISOString();
10451246
10461247 harness.emit({
1047 type: "message.delta",
1248 type: "content.delta",
10481249 eventId: asEventId("evt-invalid-delta"),
10491250 provider: "codex",
10501251 sessionId: asSessionId("sess-1"),
10511252 createdAt: now,
10521253 turnId: asProviderTurnId("turn-invalid"),
10531254 itemId: asItemId("item-invalid"),
1054 delta: undefined,
1255 payload: {
1256 streamKind: "assistant_text",
1257 delta: undefined,
1258 },
10551259 } as unknown as ProviderRuntimeEvent);
10561260
10571261 harness.emit({
......@@ -1061,7 +1265,9 @@ describe("ProviderRuntimeIngestion", () => {
10611265 sessionId: asSessionId("sess-1"),
10621266 createdAt: new Date().toISOString(),
10631267 turnId: asProviderTurnId("turn-after-failure"),
1064 message: "runtime still processed",
1268 payload: {
1269 message: "runtime still processed",
1270 },
10651271 });
10661272
10671273 const thread = await waitForThread(
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts+82-92
......@@ -4,8 +4,7 @@ import {
44 CommandId,
55 MessageId,
66 type OrchestrationEvent,
7 type ProviderApprovalPolicy,
8 type ProviderSandboxMode,
7 CheckpointRef,
98 ProviderThreadId,
109 type ThreadId,
1110 TurnId,
......@@ -28,8 +27,6 @@ const providerCommandId = (event: ProviderRuntimeEvent, tag: string): CommandId
2827 CommandId.makeUnsafe(`provider:${event.eventId}:${tag}:${crypto.randomUUID()}`);
2928
3029const DEFAULT_ASSISTANT_DELIVERY_MODE: AssistantDeliveryMode = "buffered";
31const DEFAULT_APPROVAL_POLICY: ProviderApprovalPolicy = "on-request";
32const DEFAULT_SANDBOX_MODE: ProviderSandboxMode = "workspace-write";
3330const TURN_MESSAGE_IDS_BY_TURN_CACHE_CAPACITY = 10_000;
3431const TURN_MESSAGE_IDS_BY_TURN_TTL = Duration.minutes(120);
3532const BUFFERED_MESSAGE_TEXT_BY_MESSAGE_ID_CACHE_CAPACITY = 20_000;
......@@ -90,17 +87,6 @@ function asString(value: unknown): string | undefined {
9087 return typeof value === "string" ? value : undefined;
9188}
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
10490function runtimePayloadRecord(event: ProviderRuntimeEvent): Record<string, unknown> | undefined {
10591 const payload = (event as { payload?: unknown }).payload;
10692 if (!payload || typeof payload !== "object") {
......@@ -127,24 +113,21 @@ function runtimeTurnState(
127113 event: ProviderRuntimeEvent,
128114): "completed" | "failed" | "interrupted" | "cancelled" {
129115 const payloadState = asString(runtimePayloadRecord(event)?.state);
130 const legacyStatus = legacyRuntimeStringField(event, "status");
131 return normalizeRuntimeTurnState(payloadState ?? legacyStatus);
116 return normalizeRuntimeTurnState(payloadState);
132117}
133118
134119function runtimeTurnErrorMessage(event: ProviderRuntimeEvent): string | undefined {
135120 const payloadErrorMessage = asString(runtimePayloadRecord(event)?.errorMessage);
136 const legacyErrorMessage = legacyRuntimeStringField(event, "errorMessage");
137 return payloadErrorMessage ?? legacyErrorMessage;
121 return payloadErrorMessage;
138122}
139123
140124function runtimeErrorMessageFromEvent(event: ProviderRuntimeEvent): string | undefined {
141125 const payloadMessage = asString(runtimePayloadRecord(event)?.message);
142 const legacyMessage = legacyRuntimeStringField(event, "message");
143 return payloadMessage ?? legacyMessage;
126 return payloadMessage;
144127}
145128
146129function orchestrationSessionStatusFromRuntimeState(
147 state: ProviderRuntimeEvent extends { payload: { state: infer T } } ? T : never,
130 state: "starting" | "running" | "waiting" | "ready" | "interrupted" | "stopped" | "error",
148131): "starting" | "running" | "ready" | "interrupted" | "stopped" | "error" {
149132 switch (state) {
150133 case "starting":
......@@ -268,20 +251,17 @@ function runtimeEventToActivities(
268251 ];
269252 }
270253
271 case "item.completed": {
272 if (!isToolLifecycleItemType(event.payload.itemType)) {
273 return [];
274 }
254 case "runtime.warning": {
275255 return [
276256 {
277257 id: event.eventId,
278258 createdAt: event.createdAt,
279 tone: "tool",
280 kind: "tool.completed",
281 summary: `${event.payload.title ?? "Tool"} complete`,
259 tone: "info",
260 kind: "runtime.warning",
261 summary: "Runtime warning",
282262 payload: {
283 itemType: event.payload.itemType,
284 ...(event.payload.detail ? { detail: truncateDetail(event.payload.detail) } : {}),
263 message: truncateDetail(event.payload.message),
264 ...(event.payload.detail !== undefined ? { detail: event.payload.detail } : {}),
285265 },
286266 turnId: toTurnId(event.turnId) ?? null,
287267 ...maybeSequence,
......@@ -289,20 +269,17 @@ function runtimeEventToActivities(
289269 ];
290270 }
291271
292 case "item.started": {
293 if (!isToolLifecycleItemType(event.payload.itemType)) {
294 return [];
295 }
272 case "turn.plan.updated": {
296273 return [
297274 {
298275 id: event.eventId,
299276 createdAt: event.createdAt,
300 tone: "tool",
301 kind: "tool.started",
302 summary: `${event.payload.title ?? "Tool"} started`,
277 tone: "info",
278 kind: "turn.plan.updated",
279 summary: "Plan updated",
303280 payload: {
304 itemType: event.payload.itemType,
305 ...(event.payload.detail ? { detail: truncateDetail(event.payload.detail) } : {}),
281 plan: event.payload.plan,
282 ...(event.payload.explanation !== undefined ? { explanation: event.payload.explanation } : {}),
306283 },
307284 turnId: toTurnId(event.turnId) ?? null,
308285 ...maybeSequence,
......@@ -310,30 +287,22 @@ function runtimeEventToActivities(
310287 ];
311288 }
312289
313 default:
314 break;
315 }
316
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");
290 case "item.updated": {
291 if (!isToolLifecycleItemType(event.payload.itemType)) {
292 return [];
293 }
323294 return [
324295 {
325296 id: event.eventId,
326297 createdAt: event.createdAt,
327 tone: "approval",
328 kind: "approval.requested",
329 summary:
330 requestKind === "command"
331 ? "Command approval requested"
332 : "File-change approval requested",
298 tone: "tool",
299 kind: "tool.updated",
300 summary: event.payload.title ?? "Tool updated",
333301 payload: {
334 requestId: toApprovalRequestId(event.requestId),
335 ...(requestKind ? { requestKind } : {}),
336 ...(detail ? { detail: truncateDetail(detail) } : {}),
302 itemType: event.payload.itemType,
303 ...(event.payload.status ? { status: event.payload.status } : {}),
304 ...(event.payload.detail ? { detail: truncateDetail(event.payload.detail) } : {}),
305 ...(event.payload.data !== undefined ? { data: event.payload.data } : {}),
337306 },
338307 turnId: toTurnId(event.turnId) ?? null,
339308 ...maybeSequence,
......@@ -341,20 +310,20 @@ function runtimeEventToActivities(
341310 ];
342311 }
343312
344 case "approval.resolved": {
345 const decision = legacyRuntimeStringField(event, "decision");
346 const requestKind = legacyRuntimeStringField(event, "requestKind");
313 case "item.completed": {
314 if (!isToolLifecycleItemType(event.payload.itemType)) {
315 return [];
316 }
347317 return [
348318 {
349319 id: event.eventId,
350320 createdAt: event.createdAt,
351 tone: "approval",
352 kind: "approval.resolved",
353 summary: "Approval resolved",
321 tone: "tool",
322 kind: "tool.completed",
323 summary: `${event.payload.title ?? "Tool"} complete`,
354324 payload: {
355 requestId: toApprovalRequestId(event.requestId),
356 ...(requestKind ? { requestKind } : {}),
357 ...(decision ? { decision } : {}),
325 itemType: event.payload.itemType,
326 ...(event.payload.detail ? { detail: truncateDetail(event.payload.detail) } : {}),
358327 },
359328 turnId: toTurnId(event.turnId) ?? null,
360329 ...maybeSequence,
......@@ -362,22 +331,20 @@ function runtimeEventToActivities(
362331 ];
363332 }
364333
365 case "tool.completed":
366 case "tool.started": {
367 const title = legacyRuntimeStringField(event, "title") ?? "Tool";
368 const detail = legacyRuntimeStringField(event, "detail");
369 const toolKind = legacyRuntimeStringField(event, "toolKind");
370 const activityKind = legacyType;
334 case "item.started": {
335 if (!isToolLifecycleItemType(event.payload.itemType)) {
336 return [];
337 }
371338 return [
372339 {
373340 id: event.eventId,
374341 createdAt: event.createdAt,
375342 tone: "tool",
376 kind: activityKind,
377 summary: `${title} ${legacyType === "tool.started" ? "started" : "complete"}`,
343 kind: "tool.started",
344 summary: `${event.payload.title ?? "Tool"} started`,
378345 payload: {
379 ...(toolKind ? { toolKind } : {}),
380 ...(detail ? { detail: truncateDetail(detail) } : {}),
346 itemType: event.payload.itemType,
347 ...(event.payload.detail ? { detail: truncateDetail(event.payload.detail) } : {}),
381348 },
382349 turnId: toTurnId(event.turnId) ?? null,
383350 ...maybeSequence,
......@@ -386,8 +353,10 @@ function runtimeEventToActivities(
386353 }
387354
388355 default:
389 return [];
356 break;
390357 }
358
359 return [];
391360}
392361
393362const make = Effect.gen(function* () {
......@@ -570,7 +539,6 @@ const make = Effect.gen(function* () {
570539 );
571540 if (!thread) return;
572541
573 const legacyType = legacyRuntimeType(event);
574542 const now = event.createdAt;
575543 const sessionProviderThreadId = thread.session?.providerThreadId ?? null;
576544 const scopedSessionProviderThreadId = isSyntheticClaudeThreadId(
......@@ -692,8 +660,7 @@ const make = Effect.gen(function* () {
692660 providerName: event.provider,
693661 providerSessionId,
694662 providerThreadId,
695 approvalPolicy: thread.session?.approvalPolicy ?? DEFAULT_APPROVAL_POLICY,
696 sandboxMode: thread.session?.sandboxMode ?? DEFAULT_SANDBOX_MODE,
663 runtimeMode: thread.session?.runtimeMode ?? "full-access",
697664 activeTurnId: nextActiveTurnId,
698665 lastError,
699666 updatedAt: now,
......@@ -706,9 +673,7 @@ const make = Effect.gen(function* () {
706673 const assistantDelta =
707674 event.type === "content.delta" && event.payload.streamKind === "assistant_text"
708675 ? event.payload.delta
709 : legacyType === "message.delta"
710 ? legacyRuntimeStringField(event, "delta")
711 : undefined;
676 : undefined;
712677
713678 if (assistantDelta && assistantDelta.length > 0) {
714679 const assistantMessageId = MessageId.makeUnsafe(
......@@ -752,11 +717,6 @@ const make = Effect.gen(function* () {
752717 messageId: MessageId.makeUnsafe(`assistant:${event.itemId ?? event.turnId ?? event.sessionId}`),
753718 fallbackText: event.payload.detail,
754719 }
755 : legacyType === "message.completed"
756 ? {
757 messageId: MessageId.makeUnsafe(`assistant:${event.itemId ?? event.turnId ?? event.sessionId}`),
758 fallbackText: undefined,
759 }
760720 : undefined;
761721
762722 if (assistantCompletion) {
......@@ -836,8 +796,7 @@ const make = Effect.gen(function* () {
836796 providerName: event.provider,
837797 providerSessionId,
838798 providerThreadId,
839 approvalPolicy: thread.session?.approvalPolicy ?? DEFAULT_APPROVAL_POLICY,
840 sandboxMode: thread.session?.sandboxMode ?? DEFAULT_SANDBOX_MODE,
799 runtimeMode: thread.session?.runtimeMode ?? "full-access",
841800 activeTurnId: eventTurnId ?? null,
842801 lastError: runtimeErrorMessage,
843802 updatedAt: now,
......@@ -847,6 +806,37 @@ const make = Effect.gen(function* () {
847806 }
848807 }
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
850840 const activities = runtimeEventToActivities(event);
851841 yield* Effect.forEach(activities, (activity) =>
852842 orchestrationEngine.dispatch({
apps/server/src/orchestration/commandInvariants.test.ts+1-2
......@@ -90,8 +90,7 @@ const messageSendCommand: OrchestrationCommand = {
9090 text: "hello",
9191 attachments: [],
9292 },
93 approvalPolicy: "on-request",
94 sandboxMode: "workspace-write",
93 runtimeMode: "approval-required",
9594 createdAt: now,
9695};
9796
apps/server/src/orchestration/decider.projectScripts.test.ts+2-4
......@@ -153,8 +153,7 @@ describe("decider project scripts", () => {
153153 provider: "claudeCode",
154154 model: "gpt-5",
155155 effort: "high",
156 approvalPolicy: "on-request",
157 sandboxMode: "workspace-write",
156 runtimeMode: "approval-required",
158157 createdAt: now,
159158 },
160159 readModel,
......@@ -178,8 +177,7 @@ describe("decider project scripts", () => {
178177 provider: "claudeCode",
179178 model: "gpt-5",
180179 effort: "high",
181 approvalPolicy: "on-request",
182 sandboxMode: "workspace-write",
180 runtimeMode: "approval-required",
183181 });
184182 });
185183});
apps/server/src/orchestration/decider.ts+1-2
......@@ -255,8 +255,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand"
255255 ...(command.model !== undefined ? { model: command.model } : {}),
256256 ...(command.effort !== undefined ? { effort: command.effort } : {}),
257257 assistantDeliveryMode: command.assistantDeliveryMode ?? DEFAULT_ASSISTANT_DELIVERY_MODE,
258 approvalPolicy: command.approvalPolicy,
259 sandboxMode: command.sandboxMode,
258 runtimeMode: command.runtimeMode,
260259 createdAt: command.createdAt,
261260 },
262261 };
apps/server/src/orchestration/projector.test.ts+2-4
......@@ -135,8 +135,7 @@ describe("orchestration projector", () => {
135135 payload: {
136136 threadId: "thread-1",
137137 messageId: "message-1",
138 approvalPolicy: "on-request",
139 sandboxMode: "workspace-write",
138 runtimeMode: "approval-required",
140139 createdAt: "2026-01-01T00:00:00.000Z",
141140 },
142141 }),
......@@ -195,8 +194,7 @@ describe("orchestration projector", () => {
195194 providerName: "codex",
196195 providerSessionId: "session-1",
197196 providerThreadId: "provider-thread-1",
198 approvalPolicy: "on-request",
199 sandboxMode: "workspace-write",
197 runtimeMode: "approval-required",
200198 activeTurnId: "turn-1",
201199 lastError: null,
202200 updatedAt: startedAt,
apps/server/src/persistence/Layers/ProjectionThreadSessions.ts+4-8
......@@ -25,8 +25,7 @@ const makeProjectionThreadSessionRepository = Effect.gen(function* () {
2525 provider_name,
2626 provider_session_id,
2727 provider_thread_id,
28 approval_policy,
29 sandbox_mode,
28 runtime_mode,
3029 active_turn_id,
3130 last_error,
3231 updated_at
......@@ -37,8 +36,7 @@ const makeProjectionThreadSessionRepository = Effect.gen(function* () {
3736 ${row.providerName},
3837 ${row.providerSessionId},
3938 ${row.providerThreadId},
40 ${row.approvalPolicy},
41 ${row.sandboxMode},
39 ${row.runtimeMode},
4240 ${row.activeTurnId},
4341 ${row.lastError},
4442 ${row.updatedAt}
......@@ -49,8 +47,7 @@ const makeProjectionThreadSessionRepository = Effect.gen(function* () {
4947 provider_name = excluded.provider_name,
5048 provider_session_id = excluded.provider_session_id,
5149 provider_thread_id = excluded.provider_thread_id,
52 approval_policy = excluded.approval_policy,
53 sandbox_mode = excluded.sandbox_mode,
50 runtime_mode = excluded.runtime_mode,
5451 active_turn_id = excluded.active_turn_id,
5552 last_error = excluded.last_error,
5653 updated_at = excluded.updated_at
......@@ -68,8 +65,7 @@ const makeProjectionThreadSessionRepository = Effect.gen(function* () {
6865 provider_name AS "providerName",
6966 provider_session_id AS "providerSessionId",
7067 provider_thread_id AS "providerThreadId",
71 approval_policy AS "approvalPolicy",
72 sandbox_mode AS "sandboxMode",
68 runtime_mode AS "runtimeMode",
7369 active_turn_id AS "activeTurnId",
7470 last_error AS "lastError",
7571 updated_at AS "updatedAt"
apps/server/src/persistence/Migrations/006_ProjectionThreadSessionRuntimeModeColumns.ts+4-15
......@@ -1,30 +1,19 @@
11import * as SqlClient from "effect/unstable/sql/SqlClient";
22import * as Effect from "effect/Effect";
33
4const DEFAULT_APPROVAL_POLICY = "never";
5const DEFAULT_SANDBOX_MODE = "workspace-write";
4const DEFAULT_RUNTIME_MODE = "full-access";
65
76export default Effect.gen(function* () {
87 const sql = yield* SqlClient.SqlClient;
98
109 yield* sql`
1110 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'
1312 `;
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 `;
2514 yield* sql`
2615 UPDATE projection_thread_sessions
27 SET sandbox_mode = ${DEFAULT_SANDBOX_MODE}
28 WHERE sandbox_mode IS NULL
16 SET runtime_mode = ${DEFAULT_RUNTIME_MODE}
17 WHERE runtime_mode IS NULL
2918 `;
3019});
apps/server/src/persistence/Services/ProjectionThreadSessions.ts+2-4
......@@ -7,10 +7,9 @@
77 * @module ProjectionThreadSessionRepository
88 */
99import {
10 RuntimeMode,
1011 IsoDateTime,
1112 OrchestrationSessionStatus,
12 ProviderApprovalPolicy,
13 ProviderSandboxMode,
1413 ProviderSessionId,
1514 ProviderThreadId,
1615 ThreadId,
......@@ -27,8 +26,7 @@ export const ProjectionThreadSession = Schema.Struct({
2726 providerName: Schema.NullOr(Schema.String),
2827 providerSessionId: Schema.NullOr(ProviderSessionId),
2928 providerThreadId: Schema.NullOr(ProviderThreadId),
30 approvalPolicy: ProviderApprovalPolicy,
31 sandboxMode: ProviderSandboxMode,
29 runtimeMode: RuntimeMode,
3230 activeTurnId: Schema.NullOr(TurnId),
3331 lastError: Schema.NullOr(Schema.String),
3432 updatedAt: IsoDateTime,
apps/server/src/provider/Layers/ClaudeCodeAdapter.test.ts+3-4
......@@ -192,8 +192,7 @@ describe("ClaudeCodeAdapterLive", () => {
192192 const adapter = yield* ClaudeCodeAdapter;
193193 yield* adapter.startSession({
194194 provider: "claudeCode",
195 approvalPolicy: "never",
196 sandboxMode: "danger-full-access",
195 runtimeMode: "full-access",
197196 });
198197
199198 const createInput = harness.getLastCreateQueryInput();
......@@ -211,7 +210,7 @@ describe("ClaudeCodeAdapterLive", () => {
211210 const adapter = yield* ClaudeCodeAdapter;
212211 yield* adapter.startSession({
213212 provider: "claudeCode",
214 approvalPolicy: "never",
213 runtimeMode: "full-access",
215214 providerOptions: {
216215 claudeCode: {
217216 permissionMode: "plan",
......@@ -598,7 +597,7 @@ describe("ClaudeCodeAdapterLive", () => {
598597
599598 const session = yield* adapter.startSession({
600599 provider: "claudeCode",
601 approvalPolicy: "on-request",
600 runtimeMode: "approval-required",
602601 });
603602
604603 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) {
14611461 } satisfies PermissionResult;
14621462 }
14631463
1464 const approvalPolicy = input.approvalPolicy ?? "never";
1465 if (approvalPolicy === "never") {
1464 const runtimeMode = input.runtimeMode ?? "full-access";
1465 if (runtimeMode === "full-access") {
14661466 return {
14671467 behavior: "allow",
14681468 updatedInput: toolInput,
......@@ -1590,7 +1590,7 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) {
15901590 const providerOptions = input.providerOptions?.claudeCode;
15911591 const permissionMode =
15921592 toPermissionMode(providerOptions?.permissionMode) ??
1593 (input.approvalPolicy === "never" ? "bypassPermissions" : undefined);
1593 (input.runtimeMode === "full-access" ? "bypassPermissions" : undefined);
15941594
15951595 const queryOptions: ClaudeQueryOptions = {
15961596 ...(input.cwd ? { cwd: input.cwd } : {}),
apps/server/src/provider/Layers/CodexAdapter.test.ts+42
......@@ -320,6 +320,48 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {
320320 assert.equal(firstEvent.value.payload.requestType, "command_execution_approval");
321321 }),
322322 );
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 );
323365});
324366
325367afterAll(() => {
apps/server/src/provider/Layers/CodexAdapter.ts+33-13
......@@ -917,28 +917,37 @@ function mapToRuntimeEvents(event: ProviderEvent): ReadonlyArray<ProviderRuntime
917917 }
918918
919919 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
920925 return [
921926 {
922927 ...runtimeEventBase(event),
923 type: "runtime.warning",
928 type: "session.state.changed",
924929 payload: {
925 message: event.message ?? "Windows sandbox setup completed",
930 state: success === false ? "error" : "ready",
931 reason: success === false ? failureMessage : successMessage,
926932 ...(event.payload !== undefined ? { detail: event.payload } : {}),
927933 },
928934 },
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 : []),
929947 ];
930948 }
931949
932 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 ];
950 return [];
942951}
943952
944953const makeCodexAdapter = (options?: CodexAdapterLiveOptions) =>
......@@ -1121,7 +1130,18 @@ const makeCodexAdapter = (options?: CodexAdapterLiveOptions) =>
11211130 const listener = (event: ProviderEvent) =>
11221131 Effect.gen(function* () {
11231132 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);
11251145 }).pipe(Effect.runPromiseWith(services));
11261146 manager.on("event", listener);
11271147 return listener;
apps/server/src/provider/Layers/CursorAdapter.test.ts+2-1
......@@ -467,6 +467,7 @@ describe("CursorAdapterLive", () => {
467467
468468 const session = yield* adapter.startSession({
469469 provider: "cursor",
470 runtimeMode: "approval-required",
470471 });
471472
472473 // consume startup events
......@@ -520,7 +521,7 @@ describe("CursorAdapterLive", () => {
520521
521522 yield* adapter.startSession({
522523 provider: "cursor",
523 approvalPolicy: "never",
524 runtimeMode: "full-access",
524525 });
525526
526527 // consume startup events
apps/server/src/provider/Layers/CursorAdapter.ts+4-4
......@@ -15,7 +15,6 @@ import {
1515 EventId,
1616 type CanonicalItemType,
1717 type CanonicalRequestType,
18 type ProviderApprovalPolicy,
1918 ProviderItemId,
2019 ProviderSessionId,
2120 type ProviderRuntimeEvent,
......@@ -23,6 +22,7 @@ import {
2322 ProviderThreadId,
2423 ProviderTurnId,
2524 type ProviderTurnStartResult,
25 type RuntimeMode,
2626 RuntimeItemId,
2727 RuntimeRequestId,
2828 RuntimeSessionId,
......@@ -81,7 +81,7 @@ interface CursorTurnState {
8181
8282interface CursorSessionContext {
8383 session: ProviderSession;
84 approvalPolicy: ProviderApprovalPolicy;
84 runtimeMode: RuntimeMode;
8585 readonly child: ChildProcessWithoutNullStreams;
8686 readonly output: readline.Interface;
8787 readonly pending: Map<string, PendingRequest>;
......@@ -539,7 +539,7 @@ function makeCursorAdapter(options?: CursorAdapterLiveOptions) {
539539 const options = decoded.params.options.map((entry) => ({ optionId: entry.optionId }));
540540 const detail = asString(asObject(decoded.params.toolCall)?.title);
541541
542 if (context.approvalPolicy === "never") {
542 if (context.runtimeMode === "full-access") {
543543 const selection =
544544 selectCursorAutoApprovalOption(options) ??
545545 (options[0]
......@@ -1048,7 +1048,7 @@ function makeCursorAdapter(options?: CursorAdapterLiveOptions) {
10481048
10491049 const context: CursorSessionContext = {
10501050 session,
1051 approvalPolicy: input.approvalPolicy ?? "on-request",
1051 runtimeMode: input.runtimeMode ?? "full-access",
10521052 child,
10531053 output,
10541054 pending: new Map(),
apps/server/src/provider/Layers/ProviderService.ts+1-2
......@@ -415,8 +415,7 @@ const makeProviderService = (options?: ProviderServiceLiveOptions) =>
415415 const input = {
416416 ...parsed,
417417 provider: parsed.provider ?? "codex",
418 approvalPolicy: parsed.approvalPolicy ?? "never",
419 sandboxMode: parsed.sandboxMode ?? "workspace-write",
418 runtimeMode: parsed.runtimeMode ?? "full-access",
420419 };
421420 const adapter = yield* registry.getByProvider(input.provider);
422421 const session = yield* adapter.startSession(input);
apps/server/src/wsServer.test.ts+6-4
......@@ -1223,8 +1223,7 @@ describe("WebSocket Server", () => {
12231223 attachments: [],
12241224 },
12251225 assistantDeliveryMode: "streaming",
1226 approvalPolicy: "on-request",
1227 sandboxMode: "workspace-write",
1226 runtimeMode: "approval-required",
12281227 createdAt,
12291228 });
12301229 expect(startTurnResponse.error).toBeUndefined();
......@@ -1236,14 +1235,17 @@ describe("WebSocket Server", () => {
12361235
12371236 emitRuntimeEvent(
12381237 {
1239 type: "message.delta",
1238 type: "content.delta",
12401239 eventId: asEventId("evt-ws-runtime-message-delta"),
12411240 provider: "codex",
12421241 sessionId,
12431242 createdAt: new Date().toISOString(),
12441243 turnId: asProviderTurnId("turn-1"),
12451244 itemId: asProviderItemId("item-1"),
1246 delta: "hello from runtime",
1245 payload: {
1246 streamKind: "assistant_text",
1247 delta: "hello from runtime",
1248 },
12471249 } as unknown as ProviderRuntimeEvent,
12481250 );
12491251
apps/web/src/components/ChatView.tsx+1-4
......@@ -2231,8 +2231,6 @@ export default function ChatView({ threadId }: ChatViewProps) {
22312231
22322232 setSendPhase("sending-turn");
22332233 const turnAttachments = await turnAttachmentsPromise;
2234 const approvalPolicy = runtimeMode === "full-access" ? "never" : "on-request";
2235 const sandboxMode = runtimeMode === "full-access" ? "danger-full-access" : "workspace-write";
22362234 await api.orchestration.dispatchCommand({
22372235 type: "thread.turn.start",
22382236 commandId: newCommandId(),
......@@ -2247,8 +2245,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
22472245 provider: selectedProvider,
22482246 ...(supportsReasoningEffort && selectedEffort ? { effort: selectedEffort } : {}),
22492247 assistantDeliveryMode: settings.enableAssistantStreaming ? "streaming" : "buffered",
2250 approvalPolicy,
2251 sandboxMode,
2248 runtimeMode,
22522249 createdAt: messageCreatedAt,
22532250 });
22542251 turnStartSucceeded = true;
apps/web/src/session-logic.ts+9
......@@ -140,6 +140,15 @@ export function derivePendingApprovals(
140140 openByRequestId.delete(requestId);
141141 continue;
142142 }
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 }
143152 }
144153
145154 return [...openByRequestId.values()].toSorted((left, right) =>
apps/web/src/store.test.ts+2-4
......@@ -148,8 +148,7 @@ describe("store read model sync", () => {
148148 providerName: "claudeCode",
149149 providerSessionId: null,
150150 providerThreadId: null,
151 approvalPolicy: "on-request",
152 sandboxMode: "workspace-write",
151 runtimeMode: "approval-required",
153152 activeTurnId: null,
154153 lastError: null,
155154 updatedAt: "2026-02-27T00:00:00.000Z",
......@@ -173,8 +172,7 @@ describe("store read model sync", () => {
173172 providerName: "cursor",
174173 providerSessionId: null,
175174 providerThreadId: null,
176 approvalPolicy: "on-request",
177 sandboxMode: "workspace-write",
175 runtimeMode: "approval-required",
178176 activeTurnId: null,
179177 lastError: null,
180178 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";
22import { Effect, Schema } from "effect";
33
44import {
5 DEFAULT_PROVIDER_APPROVAL_POLICY,
6 DEFAULT_PROVIDER_SANDBOX_MODE,
5 DEFAULT_RUNTIME_MODE,
76 OrchestrationGetTurnDiffInput,
87 OrchestrationSession,
98 ProjectCreateCommand,
......@@ -110,8 +109,7 @@ it.effect("decodes thread.turn.start defaults for provider and runtime mode", ()
110109 createdAt: "2026-01-01T00:00:00.000Z",
111110 });
112111 assert.strictEqual(parsed.provider, undefined);
113 assert.strictEqual(parsed.approvalPolicy, DEFAULT_PROVIDER_APPROVAL_POLICY);
114 assert.strictEqual(parsed.sandboxMode, DEFAULT_PROVIDER_SANDBOX_MODE);
112 assert.strictEqual(parsed.runtimeMode, DEFAULT_RUNTIME_MODE);
115113 }),
116114);
117115
......@@ -128,13 +126,11 @@ it.effect("preserves explicit provider and runtime mode in thread.turn.start", (
128126 attachments: [],
129127 },
130128 provider: "claudeCode",
131 approvalPolicy: "never",
132 sandboxMode: "danger-full-access",
129 runtimeMode: "full-access",
133130 createdAt: "2026-01-01T00:00:00.000Z",
134131 });
135132 assert.strictEqual(parsed.provider, "claudeCode");
136 assert.strictEqual(parsed.approvalPolicy, "never");
137 assert.strictEqual(parsed.sandboxMode, "danger-full-access");
133 assert.strictEqual(parsed.runtimeMode, "full-access");
138134 }),
139135);
140136
......@@ -165,8 +161,7 @@ it.effect("decodes thread.turn-start-requested defaults for provider and runtime
165161 createdAt: "2026-01-01T00:00:00.000Z",
166162 });
167163 assert.strictEqual(parsed.provider, undefined);
168 assert.strictEqual(parsed.approvalPolicy, DEFAULT_PROVIDER_APPROVAL_POLICY);
169 assert.strictEqual(parsed.sandboxMode, DEFAULT_PROVIDER_SANDBOX_MODE);
164 assert.strictEqual(parsed.runtimeMode, DEFAULT_RUNTIME_MODE);
170165 }),
171166);
172167
......@@ -182,7 +177,6 @@ it.effect("decodes orchestration session runtime mode defaults", () =>
182177 lastError: null,
183178 updatedAt: "2026-01-01T00:00:00.000Z",
184179 });
185 assert.strictEqual(parsed.approvalPolicy, DEFAULT_PROVIDER_APPROVAL_POLICY);
186 assert.strictEqual(parsed.sandboxMode, DEFAULT_PROVIDER_SANDBOX_MODE);
180 assert.strictEqual(parsed.runtimeMode, DEFAULT_RUNTIME_MODE);
187181 }),
188182);
packages/contracts/src/orchestration.ts+7-35
......@@ -32,21 +32,9 @@ export const ORCHESTRATION_WS_CHANNELS = {
3232export const ProviderKind = Schema.Literals(["codex", "claudeCode", "cursor"]);
3333export type ProviderKind = typeof ProviderKind.Type;
3434export const DEFAULT_PROVIDER_KIND: ProviderKind = "codex";
35export const ProviderApprovalPolicy = Schema.Literals([
36 "untrusted",
37 "on-failure",
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";
35export const RuntimeMode = Schema.Literals(["approval-required", "full-access"]);
36export type RuntimeMode = typeof RuntimeMode.Type;
37export const DEFAULT_RUNTIME_MODE: RuntimeMode = "full-access";
5038export const ProviderRequestKind = Schema.Literals(["command", "file-change"]);
5139export type ProviderRequestKind = typeof ProviderRequestKind.Type;
5240export const AssistantDeliveryMode = Schema.Literals(["buffered", "streaming"]);
......@@ -163,12 +151,7 @@ export const OrchestrationSession = Schema.Struct({
163151 providerName: Schema.NullOr(TrimmedNonEmptyString),
164152 providerSessionId: Schema.NullOr(ProviderSessionId),
165153 providerThreadId: Schema.NullOr(ProviderThreadId),
166 approvalPolicy: ProviderApprovalPolicy.pipe(
167 Schema.withDecodingDefault(() => DEFAULT_PROVIDER_APPROVAL_POLICY),
168 ),
169 sandboxMode: ProviderSandboxMode.pipe(
170 Schema.withDecodingDefault(() => DEFAULT_PROVIDER_SANDBOX_MODE),
171 ),
154 runtimeMode: RuntimeMode.pipe(Schema.withDecodingDefault(() => DEFAULT_RUNTIME_MODE)),
172155 activeTurnId: Schema.NullOr(TurnId),
173156 lastError: Schema.NullOr(TrimmedNonEmptyString),
174157 updatedAt: IsoDateTime,
......@@ -329,12 +312,7 @@ export const ThreadTurnStartCommand = Schema.Struct({
329312 model: Schema.optional(TrimmedNonEmptyString),
330313 effort: Schema.optional(TrimmedNonEmptyString),
331314 assistantDeliveryMode: Schema.optional(AssistantDeliveryMode),
332 approvalPolicy: ProviderApprovalPolicy.pipe(
333 Schema.withDecodingDefault(() => DEFAULT_PROVIDER_APPROVAL_POLICY),
334 ),
335 sandboxMode: ProviderSandboxMode.pipe(
336 Schema.withDecodingDefault(() => DEFAULT_PROVIDER_SANDBOX_MODE),
337 ),
315 runtimeMode: RuntimeMode.pipe(Schema.withDecodingDefault(() => DEFAULT_RUNTIME_MODE)),
338316 createdAt: IsoDateTime,
339317});
340318
......@@ -352,8 +330,7 @@ export const ClientThreadTurnStartCommand = Schema.Struct({
352330 model: Schema.optional(TrimmedNonEmptyString),
353331 effort: Schema.optional(TrimmedNonEmptyString),
354332 assistantDeliveryMode: Schema.optional(AssistantDeliveryMode),
355 approvalPolicy: ProviderApprovalPolicy,
356 sandboxMode: ProviderSandboxMode,
333 runtimeMode: RuntimeMode,
357334 createdAt: IsoDateTime,
358335});
359336
......@@ -585,12 +562,7 @@ export const ThreadTurnStartRequestedPayload = Schema.Struct({
585562 model: Schema.optional(TrimmedNonEmptyString),
586563 effort: Schema.optional(TrimmedNonEmptyString),
587564 assistantDeliveryMode: Schema.optional(AssistantDeliveryMode),
588 approvalPolicy: ProviderApprovalPolicy.pipe(
589 Schema.withDecodingDefault(() => DEFAULT_PROVIDER_APPROVAL_POLICY),
590 ),
591 sandboxMode: ProviderSandboxMode.pipe(
592 Schema.withDecodingDefault(() => DEFAULT_PROVIDER_SANDBOX_MODE),
593 ),
565 runtimeMode: RuntimeMode.pipe(Schema.withDecodingDefault(() => DEFAULT_RUNTIME_MODE)),
594566 createdAt: IsoDateTime,
595567});
596568
packages/contracts/src/provider.test.ts+5-10
......@@ -18,8 +18,7 @@ describe("ProviderSessionStartInput", () => {
1818 },
1919 },
2020 });
21 expect(parsed.approvalPolicy).toBeUndefined();
22 expect(parsed.sandboxMode).toBeUndefined();
21 expect(parsed.runtimeMode).toBeUndefined();
2322 expect(parsed.providerOptions?.codex?.binaryPath).toBe("/usr/local/bin/codex");
2423 expect(parsed.providerOptions?.codex?.homePath).toBe("/tmp/.codex");
2524 });
......@@ -36,15 +35,13 @@ describe("ProviderSessionStartInput", () => {
3635 maxThinkingTokens: 12_000,
3736 },
3837 },
39 approvalPolicy: "never",
40 sandboxMode: "danger-full-access",
38 runtimeMode: "full-access",
4139 });
4240 expect(parsed.provider).toBe("claudeCode");
4341 expect(parsed.providerOptions?.claudeCode?.binaryPath).toBe("/usr/local/bin/claude");
4442 expect(parsed.providerOptions?.claudeCode?.permissionMode).toBe("plan");
4543 expect(parsed.providerOptions?.claudeCode?.maxThinkingTokens).toBe(12_000);
46 expect(parsed.approvalPolicy).toBe("never");
47 expect(parsed.sandboxMode).toBe("danger-full-access");
44 expect(parsed.runtimeMode).toBe("full-access");
4845 });
4946
5047 it("accepts cursor provider payloads", () => {
......@@ -57,13 +54,11 @@ describe("ProviderSessionStartInput", () => {
5754 binaryPath: "/usr/local/bin/agent",
5855 },
5956 },
60 approvalPolicy: "on-request",
61 sandboxMode: "workspace-write",
57 runtimeMode: "approval-required",
6258 });
6359 expect(parsed.provider).toBe("cursor");
6460 expect(parsed.model).toBe("composer-1.5");
6561 expect(parsed.providerOptions?.cursor?.binaryPath).toBe("/usr/local/bin/agent");
66 expect(parsed.approvalPolicy).toBe("on-request");
67 expect(parsed.sandboxMode).toBe("workspace-write");
62 expect(parsed.runtimeMode).toBe("approval-required");
6863 });
6964});
packages/contracts/src/provider.ts+2-4
......@@ -16,10 +16,9 @@ import {
1616 PROVIDER_SEND_TURN_MAX_ATTACHMENTS,
1717 PROVIDER_SEND_TURN_MAX_INPUT_CHARS,
1818 ProviderApprovalDecision,
19 ProviderApprovalPolicy,
2019 ProviderKind,
2120 ProviderRequestKind,
22 ProviderSandboxMode,
21 RuntimeMode,
2322 TurnCountRange,
2423} from "./orchestration";
2524
......@@ -80,8 +79,7 @@ export const ProviderSessionStartInput = Schema.Struct({
8079 model: Schema.optional(TrimmedNonEmptyStringSchema),
8180 resumeCursor: Schema.optional(Schema.Unknown),
8281 providerOptions: Schema.optional(ProviderStartOptions),
83 approvalPolicy: Schema.optional(ProviderApprovalPolicy),
84 sandboxMode: Schema.optional(ProviderSandboxMode),
82 runtimeMode: Schema.optional(RuntimeMode),
8583});
8684export type ProviderSessionStartInput = typeof ProviderSessionStartInput.Type;
8785