@zhanguym/chatroom 1.0.0 → 1.1.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/dist/app/application.d.ts +0 -1
  2. package/dist/app/application.js +8 -7
  3. package/dist/app/event-bus.d.ts +13 -10
  4. package/dist/app/git-access.d.ts +50 -0
  5. package/dist/app/git-access.js +2 -0
  6. package/dist/app/service-registry.d.ts +11 -0
  7. package/dist/app/service-registry.js +17 -0
  8. package/dist/app/workspace-access.d.ts +24 -0
  9. package/dist/app/workspace-access.js +2 -0
  10. package/dist/auth/ingress-policy.d.ts +2 -0
  11. package/dist/auth/ingress-policy.js +14 -2
  12. package/dist/auth/passkey-service.js +1 -1
  13. package/dist/cli/run-cli.js +1 -1
  14. package/dist/core/fs/safe-directory.d.ts +9 -0
  15. package/dist/core/fs/safe-directory.js +129 -0
  16. package/dist/core/operations/repository.d.ts +2 -0
  17. package/dist/core/runtime/line-process.d.ts +19 -0
  18. package/dist/core/runtime/line-process.js +51 -0
  19. package/dist/infrastructure/database/operation-repository.js +8 -2
  20. package/dist/infrastructure/database/schema.d.ts +1 -1
  21. package/dist/infrastructure/database/schema.js +62 -0
  22. package/dist/infrastructure/http/http-server.js +1 -1
  23. package/dist/native/linux/chatroom-computer-helper +0 -0
  24. package/dist/native/macos/ChatRoomComputerHelper.app/Contents/MacOS/chatroom-computer-helper +0 -0
  25. package/dist/native/windows/chatroom-computer-helper.exe +0 -0
  26. package/dist/operations/operation-log.js +16 -4
  27. package/dist/plugins/agent/async-queue.d.ts +10 -0
  28. package/dist/plugins/agent/async-queue.js +49 -0
  29. package/dist/plugins/agent/mcp.d.ts +3 -0
  30. package/dist/plugins/agent/mcp.js +502 -0
  31. package/dist/plugins/agent/plugin.d.ts +4 -0
  32. package/dist/plugins/agent/plugin.js +35 -0
  33. package/dist/plugins/agent/provider.d.ts +132 -0
  34. package/dist/plugins/agent/providers/codex/mapping.d.ts +56 -0
  35. package/dist/plugins/agent/providers/codex/mapping.js +427 -0
  36. package/dist/plugins/agent/providers/codex/provider.d.ts +84 -0
  37. package/dist/plugins/agent/providers/codex/provider.js +828 -0
  38. package/dist/plugins/agent/providers/codex/rpc.d.ts +40 -0
  39. package/dist/plugins/agent/providers/codex/rpc.js +227 -0
  40. package/dist/plugins/agent/providers/common.d.ts +11 -0
  41. package/dist/plugins/agent/providers/common.js +75 -0
  42. package/dist/plugins/agent/providers/index.d.ts +3 -0
  43. package/dist/plugins/agent/providers/index.js +4 -0
  44. package/dist/plugins/agent/repository.d.ts +21 -0
  45. package/dist/plugins/agent/repository.js +226 -0
  46. package/dist/plugins/agent/runtime-config.d.ts +5 -0
  47. package/dist/plugins/agent/runtime-config.js +60 -0
  48. package/dist/plugins/agent/runtime-files.d.ts +8 -0
  49. package/dist/plugins/agent/runtime-files.js +170 -0
  50. package/dist/plugins/agent/runtime-state.d.ts +17 -0
  51. package/dist/plugins/agent/runtime-state.js +133 -0
  52. package/dist/plugins/agent/runtime.d.ts +60 -0
  53. package/dist/plugins/agent/runtime.js +928 -0
  54. package/dist/plugins/agent/store.d.ts +17 -0
  55. package/dist/plugins/agent/store.js +1 -0
  56. package/dist/plugins/agent/types.d.ts +320 -0
  57. package/dist/plugins/agent/types.js +13 -0
  58. package/dist/plugins/cloud/plugin.d.ts +1 -1
  59. package/dist/plugins/cloud/plugin.js +3 -3
  60. package/dist/plugins/cloud/tunnel-client.js +6 -7
  61. package/dist/plugins/computer/computer-schemas.d.ts +7 -7
  62. package/dist/plugins/computer/computer-service.d.ts +3 -0
  63. package/dist/plugins/computer/computer-service.js +33 -1
  64. package/dist/plugins/computer/plugin.d.ts +1 -1
  65. package/dist/plugins/computer/plugin.js +1 -1
  66. package/dist/plugins/git/git-service.d.ts +3 -2
  67. package/dist/plugins/git/git-service.js +37 -1
  68. package/dist/plugins/git/plugin.d.ts +0 -2
  69. package/dist/plugins/git/plugin.js +3 -3
  70. package/dist/plugins/process/plugin.d.ts +1 -1
  71. package/dist/plugins/process/plugin.js +3 -3
  72. package/dist/plugins/process/process-supervisor.d.ts +1 -0
  73. package/dist/plugins/process/process-supervisor.js +15 -4
  74. package/dist/plugins/types.d.ts +1 -11
  75. package/dist/plugins/types.js +1 -17
  76. package/dist/plugins/web/api-types.d.ts +20 -5
  77. package/dist/plugins/web/http/agent-api-router.d.ts +3 -0
  78. package/dist/plugins/web/http/agent-api-router.js +254 -0
  79. package/dist/plugins/web/http/api-router.js +9 -0
  80. package/dist/plugins/web/http/computer-api-router.js +21 -6
  81. package/dist/plugins/web/http/process-api-router.js +3 -0
  82. package/dist/plugins/web/http/workspace-api-router.js +79 -6
  83. package/dist/plugins/web/plugin.d.ts +1 -1
  84. package/dist/plugins/web/plugin.js +5 -4
  85. package/dist/plugins/web/runtime.d.ts +6 -3
  86. package/dist/plugins/web/runtime.js +6 -1
  87. package/dist/plugins/workspace/mcp.js +16 -0
  88. package/dist/plugins/workspace/metadata.d.ts +1 -1
  89. package/dist/plugins/workspace/plugin.d.ts +1 -1
  90. package/dist/plugins/workspace/plugin.js +5 -2
  91. package/dist/plugins/workspace/types.d.ts +1 -18
  92. package/dist/plugins/workspace/workspace-fs.d.ts +9 -3
  93. package/dist/plugins/workspace/workspace-fs.js +78 -125
  94. package/dist/plugins/workspace/workspace-root-policy.d.ts +9 -0
  95. package/dist/plugins/workspace/workspace-root-policy.js +56 -0
  96. package/dist/plugins/workspace/workspace-service.d.ts +11 -6
  97. package/dist/plugins/workspace/workspace-service.js +39 -49
  98. package/dist/web/assets/AgentView-DKuspiki.js +4 -0
  99. package/dist/web/assets/AgentView-DYJeMJLX.css +1 -0
  100. package/dist/web/assets/{CloudView-CHjVbp-5.css → CloudView-DVVvwuWW.css} +1 -1
  101. package/dist/web/assets/CloudView-PbREbJgz.js +1 -0
  102. package/dist/web/assets/CodeViewer.vue_vue_type_script_setup_true_lang-CiFNEpWo.js +3 -0
  103. package/dist/web/assets/ComputerView-BM0u3wSF.css +1 -0
  104. package/dist/web/assets/ComputerView-BRb2sE0Z.js +1 -0
  105. package/dist/web/assets/McpToolsView-B8NSFdf4.js +1 -0
  106. package/dist/web/assets/McpToolsView-C22ZbSuV.css +1 -0
  107. package/dist/web/assets/OperationsView-BjTY2lIh.css +1 -0
  108. package/dist/web/assets/OperationsView-Cavi-6SZ.js +4 -0
  109. package/dist/web/assets/ProcessesView-Ce6iVwjA.css +1 -0
  110. package/dist/web/assets/ProcessesView-UO48Nh2q.js +4 -0
  111. package/dist/web/assets/StateChip.vue_vue_type_script_setup_true_lang-Bvc9wRyA.js +1 -0
  112. package/dist/web/assets/SystemLogsView-BXmOrmu5.js +1 -0
  113. package/dist/web/assets/SystemLogsView-CofP4Jvi.css +1 -0
  114. package/dist/web/assets/VSelect-BPRXx8KM.js +1 -0
  115. package/dist/web/assets/VSpacer-Cl-QiwaM.js +1 -0
  116. package/dist/web/assets/VSwitch-C0XNj1lR.js +1 -0
  117. package/dist/web/assets/{VTabs-qBcuuXh3.js → VTabs-CHj39Ket.js} +1 -1
  118. package/dist/web/assets/VTextarea-BIcUWOU6.js +16 -0
  119. package/dist/web/assets/VTextarea-DG1zE1as.css +1 -0
  120. package/dist/web/assets/WorkspacesView-DPCt6kYy.css +1 -0
  121. package/dist/web/assets/WorkspacesView-rY6RXeiR.js +2 -0
  122. package/dist/web/assets/entity-refresh-queue-wQii8Dl5.js +1 -0
  123. package/dist/web/assets/{index-CkjqXRSj.css → index-BPpl3-sP.css} +1 -1
  124. package/dist/web/assets/index-Dt-pWiW1.js +19 -0
  125. package/dist/web/assets/{syntax-highlight-B5ArnyrC.js → syntax-highlight-C_fuMcwC.js} +1 -1
  126. package/dist/web/assets/useMasterDetailFocus-D0j-KcXv.js +1 -0
  127. package/dist/web/assets/useOperationLabels-DR8r6Vrl.js +1 -0
  128. package/dist/web/index.html +2 -2
  129. package/package.json +4 -2
  130. package/dist/plugins/git/types.d.ts +0 -32
  131. package/dist/web/assets/CloudView-CvX0dkgu.js +0 -1
  132. package/dist/web/assets/CodeViewer.vue_vue_type_script_setup_true_lang-B5TxGkUW.js +0 -3
  133. package/dist/web/assets/ComputerView-91Z9Cefw.css +0 -1
  134. package/dist/web/assets/ComputerView-OOyEqWml.js +0 -1
  135. package/dist/web/assets/McpToolsView-BTnolyS6.css +0 -1
  136. package/dist/web/assets/McpToolsView-DjXWTKrZ.js +0 -1
  137. package/dist/web/assets/OperationsView-DqTY51Cr.js +0 -4
  138. package/dist/web/assets/ProcessesView-C13BykIa.js +0 -4
  139. package/dist/web/assets/StateChip.vue_vue_type_script_setup_true_lang-DyOhJmR6.js +0 -1
  140. package/dist/web/assets/SystemLogsView-Ci0u4BTb.js +0 -1
  141. package/dist/web/assets/VSelect-DrRpKyYI.js +0 -1
  142. package/dist/web/assets/VSpacer-C3wAY3F0.js +0 -1
  143. package/dist/web/assets/VSwitch-CxusR4iO.js +0 -1
  144. package/dist/web/assets/WorkspacesView-BzQ0aH74.css +0 -1
  145. package/dist/web/assets/WorkspacesView-CijIvfsp.js +0 -2
  146. package/dist/web/assets/index-BegKQ3Yd.js +0 -19
  147. package/dist/web/assets/useOperationLabels-BDjzGDkT.js +0 -1
  148. /package/dist/{plugins/process → core/runtime}/head-tail-buffer.d.ts +0 -0
  149. /package/dist/{plugins/process → core/runtime}/head-tail-buffer.js +0 -0
  150. /package/dist/plugins/{git/types.js → agent/provider.js} +0 -0
@@ -0,0 +1,828 @@
1
+ import { AsyncQueue } from "#plugins/agent/async-queue";
2
+ import { codexAccountUsage, codexInput, codexFileChanges, codexItem, codexItemFailed, codexQuestions, codexVersion, codexWarning, grantedPermissions, injectContinuity, injectWorkspacePrompt, reviewTarget, sessionConfig, standardApprovalChoices, stringOrNull, tokenUsage, turnError, } from "./mapping.js";
3
+ import { CodexRpcClient, } from "./rpc.js";
4
+ import { array, record, stringifyOptional, errorMessage, providerError, isMissingExecutable, } from "../common.js";
5
+ function codexSandbox(value) {
6
+ if (value === null)
7
+ return "workspace-write";
8
+ if (value === "read-only" ||
9
+ value === "workspace-write" ||
10
+ value === "danger-full-access")
11
+ return value;
12
+ throw new Error("Unsupported Codex sandbox: " + value);
13
+ }
14
+ export class CodexProvider {
15
+ logs;
16
+ id = "codex";
17
+ displayName = "Codex";
18
+ icon = "openai";
19
+ features = { nativeReview: true, steering: true };
20
+ nativeSettings = [
21
+ {
22
+ id: "approvalPolicy",
23
+ defaultValue: "on-request",
24
+ options: [
25
+ {
26
+ id: "untrusted",
27
+ description: "Ask for approval before running commands outside the trusted set.",
28
+ },
29
+ {
30
+ id: "on-request",
31
+ description: "Let Codex request approval when it decides escalation is needed.",
32
+ },
33
+ {
34
+ id: "granular",
35
+ description: "Control individual Codex approval categories.",
36
+ },
37
+ {
38
+ id: "never",
39
+ description: "Never ask for approval; disallowed operations fail instead.",
40
+ },
41
+ ],
42
+ granularFields: [
43
+ { id: "sandbox_approval", description: "Sandbox approval requests." },
44
+ { id: "rules", description: "Rule-based approval requests." },
45
+ { id: "skill_approval", description: "Skill approval requests." },
46
+ {
47
+ id: "request_permissions",
48
+ description: "Additional permission requests.",
49
+ },
50
+ { id: "mcp_elicitations", description: "MCP elicitation requests." },
51
+ ],
52
+ },
53
+ {
54
+ id: "permissionMode",
55
+ defaultValue: "workspace-write",
56
+ options: [
57
+ {
58
+ id: "read-only",
59
+ description: "Read-only sandbox; changes require approval.",
60
+ },
61
+ {
62
+ id: "workspace-write",
63
+ description: "Allow workspace writes within the sandbox.",
64
+ },
65
+ {
66
+ id: "danger-full-access",
67
+ description: "Disable the sandbox; commands can access files and the network outside the workspace.",
68
+ },
69
+ ],
70
+ granularFields: [],
71
+ },
72
+ {
73
+ id: "approvalsReviewer",
74
+ defaultValue: "user",
75
+ options: [
76
+ { id: "user", description: "Route approval requests to the user." },
77
+ {
78
+ id: "auto_review",
79
+ description: "Use Codex auto review to approve or deny requests with a risk-based subagent.",
80
+ },
81
+ {
82
+ id: "guardian_subagent",
83
+ description: "Route approval requests to the Codex guardian subagent.",
84
+ },
85
+ ],
86
+ granularFields: [],
87
+ },
88
+ ];
89
+ client;
90
+ modelNames = new Map();
91
+ constructor(logs) {
92
+ this.logs = logs;
93
+ this.client = new CodexRpcClient(logs);
94
+ }
95
+ async probe() {
96
+ try {
97
+ const initialized = await this.client.ensureStarted();
98
+ const account = await this.client.request("account/read", {
99
+ refreshToken: false,
100
+ });
101
+ const capabilities = await this.client
102
+ .request("modelProvider/capabilities/read", {})
103
+ .catch(() => null);
104
+ return {
105
+ capabilities,
106
+ installed: true,
107
+ authenticated: Boolean(account.account) || !account.requiresOpenaiAuth,
108
+ version: codexVersion(initialized.userAgent),
109
+ error: null,
110
+ };
111
+ }
112
+ catch (error) {
113
+ return {
114
+ capabilities: null,
115
+ installed: !isMissingExecutable(error),
116
+ authenticated: false,
117
+ version: null,
118
+ error: errorMessage(error),
119
+ };
120
+ }
121
+ }
122
+ async details() {
123
+ const [requirements, rateLimits] = await Promise.allSettled([
124
+ this.client.request("configRequirements/read"),
125
+ this.client.request("account/rateLimits/read"),
126
+ ]);
127
+ const rawRateLimits = rateLimits.status === "fulfilled" ? rateLimits.value : null;
128
+ return {
129
+ configRequirements: requirements.status === "fulfilled"
130
+ ? requirements.value.requirements
131
+ : null,
132
+ rateLimits: rawRateLimits,
133
+ accountUsage: codexAccountUsage(rawRateLimits),
134
+ };
135
+ }
136
+ async listModels() {
137
+ const output = [];
138
+ let cursor = null;
139
+ do {
140
+ const response = await this.client.request("model/list", {
141
+ cursor,
142
+ limit: 100,
143
+ includeHidden: false,
144
+ });
145
+ for (const model of response.data) {
146
+ if (model.hidden)
147
+ continue;
148
+ this.modelNames.set(model.id, model.model);
149
+ output.push({
150
+ id: model.id,
151
+ model: model.model,
152
+ modelSpecialty: model.modelSpecialty,
153
+ upgrade: model.upgrade,
154
+ upgradeInfo: model.upgradeInfo,
155
+ availabilityMessage: model.availabilityNux?.message ?? null,
156
+ reasoningEfforts: model.supportedReasoningEfforts.map((effort) => ({
157
+ id: effort.reasoningEffort,
158
+ description: effort.description,
159
+ })),
160
+ defaultReasoningEffort: model.defaultReasoningEffort,
161
+ reasoningSummaries: ["auto", "concise", "detailed", "none"],
162
+ defaultReasoningSummary: null,
163
+ inputModalities: model.inputModalities,
164
+ multiAgentVersion: model.multiAgentVersion,
165
+ serviceTiers: model.serviceTiers,
166
+ defaultServiceTier: model.defaultServiceTier,
167
+ availableAccessPrograms: model.availableAccessPrograms,
168
+ displayName: model.displayName,
169
+ description: model.description,
170
+ isDefault: model.isDefault,
171
+ });
172
+ }
173
+ cursor = response.nextCursor;
174
+ } while (cursor);
175
+ return output;
176
+ }
177
+ async createSession(context) {
178
+ const model = await this.resolveModel(context.model);
179
+ const response = await this.client.request("thread/start", {
180
+ cwd: context.workspaceRoot,
181
+ model,
182
+ serviceTier: context.serviceTier,
183
+ approvalPolicy: context.approvalPolicy ?? "on-request",
184
+ approvalsReviewer: context.approvalsReviewer ?? "user",
185
+ sandbox: codexSandbox(context.permissionMode),
186
+ });
187
+ await injectWorkspacePrompt(this.client, response.thread.id, context.workspacePrompt);
188
+ await injectContinuity(this.client, response.thread.id, context.continuityDelta);
189
+ const session = new CodexSession(this.client, response.thread.id, context.workspaceRoot, sessionConfig(context), (modelId) => this.resolveModel(modelId), this.logs);
190
+ await session.configure(sessionConfig(context));
191
+ return session;
192
+ }
193
+ async resumeSession(context, providerSessionId) {
194
+ const model = await this.resolveModel(context.model);
195
+ await this.client.request("thread/resume", {
196
+ threadId: providerSessionId,
197
+ cwd: context.workspaceRoot,
198
+ model,
199
+ serviceTier: context.serviceTier,
200
+ approvalPolicy: context.approvalPolicy ?? "on-request",
201
+ approvalsReviewer: context.approvalsReviewer ?? "user",
202
+ sandbox: codexSandbox(context.permissionMode),
203
+ excludeTurns: true,
204
+ });
205
+ await injectContinuity(this.client, providerSessionId, context.continuityDelta);
206
+ const session = new CodexSession(this.client, providerSessionId, context.workspaceRoot, sessionConfig(context), (modelId) => this.resolveModel(modelId), this.logs);
207
+ await session.configure(sessionConfig(context));
208
+ return session;
209
+ }
210
+ async resolveModel(modelId) {
211
+ if (modelId === null)
212
+ return null;
213
+ const cached = this.modelNames.get(modelId);
214
+ if (cached)
215
+ return cached;
216
+ const models = await this.listModels();
217
+ const model = models.find((candidate) => candidate.id === modelId);
218
+ if (!model)
219
+ throw new Error("Unknown Codex model: " + modelId);
220
+ return model.model;
221
+ }
222
+ close() {
223
+ return this.client.close();
224
+ }
225
+ }
226
+ class CodexSession {
227
+ client;
228
+ providerSessionId;
229
+ workspaceRoot;
230
+ config;
231
+ resolveModel;
232
+ logs;
233
+ pendingInteractions = new Map();
234
+ unsubscribeNotification;
235
+ unsubscribeRequest;
236
+ unsubscribeTransportClose;
237
+ queue = null;
238
+ activeTurnId = null;
239
+ needsResume = false;
240
+ noticeSequence = 0;
241
+ toolItems = new Map();
242
+ turnDiff = null;
243
+ turnPlan = null;
244
+ constructor(client, providerSessionId, workspaceRoot, config, resolveModel, logs) {
245
+ this.client = client;
246
+ this.providerSessionId = providerSessionId;
247
+ this.workspaceRoot = workspaceRoot;
248
+ this.config = config;
249
+ this.resolveModel = resolveModel;
250
+ this.logs = logs;
251
+ this.unsubscribeNotification = client.onNotification((notification) => this.onNotification(notification));
252
+ this.unsubscribeRequest = client.onServerRequest((request) => this.onServerRequest(request));
253
+ this.unsubscribeTransportClose = client.onTransportClose((error) => this.onTransportClose(error));
254
+ }
255
+ runTurn(input) {
256
+ if (this.queue)
257
+ throw new Error("Codex session already has an active turn");
258
+ const queue = new AsyncQueue();
259
+ this.queue = queue;
260
+ void this.startTurn(input, queue);
261
+ return queue;
262
+ }
263
+ async steer(input) {
264
+ if (!this.queue || !this.activeTurnId)
265
+ throw new Error("Codex session has no active turn to steer");
266
+ await this.client.request("turn/steer", {
267
+ threadId: this.providerSessionId,
268
+ expectedTurnId: this.activeTurnId,
269
+ input: codexInput(input, this.workspaceRoot),
270
+ });
271
+ }
272
+ runReview(target) {
273
+ if (this.queue)
274
+ throw new Error("Codex session already has an active turn");
275
+ const queue = new AsyncQueue();
276
+ this.queue = queue;
277
+ void this.startReview(target, queue);
278
+ return queue;
279
+ }
280
+ async interrupt() {
281
+ if (!this.activeTurnId)
282
+ return;
283
+ await this.client.request("turn/interrupt", {
284
+ threadId: this.providerSessionId,
285
+ turnId: this.activeTurnId,
286
+ }, 5_000);
287
+ }
288
+ async configure(config) {
289
+ const permissionsChanged = JSON.stringify(this.config.approvalPolicy) !==
290
+ JSON.stringify(config.approvalPolicy) ||
291
+ this.config.approvalsReviewer !== config.approvalsReviewer ||
292
+ this.config.permissionMode !== config.permissionMode;
293
+ this.config = { ...config };
294
+ if (permissionsChanged)
295
+ this.needsResume = true;
296
+ }
297
+ async resolveInteraction(interactionId, response) {
298
+ const pending = this.pendingInteractions.get(interactionId);
299
+ if (!pending)
300
+ throw new Error("Unknown Codex interaction: " + interactionId);
301
+ if (pending.kind === "input") {
302
+ if (response.type !== "input")
303
+ throw new Error("Codex interaction requires input answers");
304
+ const answers = {};
305
+ for (const [id, values] of Object.entries(response.answers))
306
+ answers[id] = { answers: values };
307
+ this.client.respond(pending.requestId, { answers });
308
+ this.pendingInteractions.delete(interactionId);
309
+ return;
310
+ }
311
+ if (response.type !== "approval")
312
+ throw new Error("Codex interaction requires an approval decision");
313
+ if (!pending.choices.has(response.decision))
314
+ throw new Error("Unknown Codex approval option: " + response.decision);
315
+ this.client.respond(pending.requestId, pending.choices.get(response.decision));
316
+ this.pendingInteractions.delete(interactionId);
317
+ }
318
+ async close() {
319
+ this.unsubscribeNotification();
320
+ this.unsubscribeRequest();
321
+ this.unsubscribeTransportClose();
322
+ if (this.activeTurnId) {
323
+ await this.interrupt().catch(() => undefined);
324
+ }
325
+ if (this.queue)
326
+ this.completeTurnItems(this.queue);
327
+ this.queue?.close();
328
+ this.queue = null;
329
+ this.activeTurnId = null;
330
+ this.pendingInteractions.clear();
331
+ }
332
+ async startTurn(input, queue) {
333
+ const config = { ...this.config };
334
+ try {
335
+ await this.resumeIfNeeded(config);
336
+ const response = await this.client.request("turn/start", {
337
+ threadId: this.providerSessionId,
338
+ input: codexInput(input, this.workspaceRoot),
339
+ outputSchema: input.outputSchema,
340
+ cwd: this.workspaceRoot,
341
+ approvalPolicy: config.approvalPolicy ?? "on-request",
342
+ approvalsReviewer: config.approvalsReviewer ?? "user",
343
+ model: await this.resolveModel(config.model),
344
+ serviceTier: config.serviceTier,
345
+ effort: config.reasoningEffort,
346
+ summary: config.reasoningSummary,
347
+ });
348
+ if (this.queue === queue && !this.activeTurnId) {
349
+ this.activeTurnId = response.turn.id;
350
+ queue.push({
351
+ type: "turn.started",
352
+ providerTurnId: response.turn.id,
353
+ });
354
+ }
355
+ }
356
+ catch (error) {
357
+ if (this.queue === queue) {
358
+ this.completeTurnItems(queue);
359
+ queue.push({
360
+ type: "turn.completed",
361
+ status: "failed",
362
+ error: providerError(error),
363
+ });
364
+ queue.close();
365
+ this.queue = null;
366
+ this.activeTurnId = null;
367
+ this.pendingInteractions.clear();
368
+ }
369
+ }
370
+ }
371
+ async resumeIfNeeded(config) {
372
+ if (!this.needsResume)
373
+ return;
374
+ await this.client.request("thread/resume", {
375
+ threadId: this.providerSessionId,
376
+ cwd: this.workspaceRoot,
377
+ model: await this.resolveModel(config.model),
378
+ serviceTier: config.serviceTier,
379
+ approvalPolicy: config.approvalPolicy ?? "on-request",
380
+ approvalsReviewer: config.approvalsReviewer ?? "user",
381
+ sandbox: codexSandbox(config.permissionMode),
382
+ excludeTurns: true,
383
+ });
384
+ this.needsResume = false;
385
+ }
386
+ async startReview(target, queue) {
387
+ try {
388
+ await this.resumeIfNeeded({ ...this.config });
389
+ const response = await this.client.request("review/start", {
390
+ threadId: this.providerSessionId,
391
+ target: reviewTarget(target),
392
+ delivery: "inline",
393
+ });
394
+ if (this.queue === queue && !this.activeTurnId) {
395
+ this.activeTurnId = response.turn.id;
396
+ queue.push({ type: "turn.started", providerTurnId: response.turn.id });
397
+ }
398
+ }
399
+ catch (error) {
400
+ if (this.queue !== queue)
401
+ return;
402
+ this.completeTurnItems(queue);
403
+ queue.push({
404
+ type: "turn.completed",
405
+ status: "failed",
406
+ error: providerError(error),
407
+ });
408
+ queue.close();
409
+ this.queue = null;
410
+ this.activeTurnId = null;
411
+ this.pendingInteractions.clear();
412
+ }
413
+ }
414
+ onTransportClose(error) {
415
+ this.needsResume = true;
416
+ const queue = this.queue;
417
+ if (!queue)
418
+ return;
419
+ this.completeTurnItems(queue);
420
+ queue.push({
421
+ type: "turn.completed",
422
+ status: "failed",
423
+ error: providerError(error),
424
+ });
425
+ queue.close();
426
+ this.queue = null;
427
+ this.activeTurnId = null;
428
+ this.pendingInteractions.clear();
429
+ }
430
+ onNotification(notification) {
431
+ const params = record(notification.params);
432
+ if (!params)
433
+ return;
434
+ // Summary parts carry no text. Raw reasoning is never exposed.
435
+ if (notification.method === "item/reasoning/textDelta" ||
436
+ notification.method === "item/reasoning/summaryPartAdded")
437
+ return;
438
+ const warning = codexWarning(notification.method, params);
439
+ if (warning !== null) {
440
+ const global = notification.method === "configWarning" ||
441
+ notification.method === "deprecationNotice" ||
442
+ (notification.method === "warning" && params.threadId === null);
443
+ if (global)
444
+ return;
445
+ if (params.threadId !== this.providerSessionId)
446
+ return;
447
+ this.logs.warn("agent.codex", "codex." + notification.method, warning, {
448
+ threadId: this.providerSessionId,
449
+ });
450
+ this.emitNotice("warning", warning);
451
+ return;
452
+ }
453
+ if (params.threadId !== this.providerSessionId)
454
+ return;
455
+ if (notification.method === "serverRequest/resolved") {
456
+ if (typeof params.requestId !== "string" &&
457
+ typeof params.requestId !== "number")
458
+ return;
459
+ const interactionId = String(params.requestId);
460
+ this.pendingInteractions.delete(interactionId);
461
+ this.queue?.push({
462
+ type: "interaction.resolved",
463
+ providerRequestId: interactionId,
464
+ });
465
+ return;
466
+ }
467
+ if (notification.method === "error") {
468
+ const error = turnError(params.error);
469
+ if (!error)
470
+ return;
471
+ this.logs.warn("agent.codex", "codex.error", error.message, {
472
+ threadId: this.providerSessionId,
473
+ error,
474
+ willRetry: params.willRetry === true,
475
+ });
476
+ this.emitNotice(params.willRetry === true ? "warning" : "error", [error.message, error.details].filter(Boolean).join("\n"));
477
+ return;
478
+ }
479
+ const queue = this.queue;
480
+ if (!queue)
481
+ return;
482
+ if (notification.method === "turn/diff/updated") {
483
+ if (typeof params.turnId !== "string" || typeof params.diff !== "string")
484
+ return;
485
+ const id = "turn-diff:" + params.turnId;
486
+ const item = {
487
+ type: "turn_diff",
488
+ diff: params.diff,
489
+ };
490
+ if (this.turnDiff && this.turnDiff.id !== id)
491
+ this.completeDiff(queue);
492
+ queue.push({
493
+ type: this.turnDiff ? "item.updated" : "item.started",
494
+ providerItemId: id,
495
+ item,
496
+ });
497
+ this.turnDiff = { id, item };
498
+ return;
499
+ }
500
+ if (notification.method === "item/mcpToolCall/progress") {
501
+ if (typeof params.itemId !== "string" ||
502
+ typeof params.message !== "string")
503
+ return;
504
+ const previous = this.toolItems.get(params.itemId);
505
+ if (!previous)
506
+ return;
507
+ const item = { ...previous, progress: params.message };
508
+ this.toolItems.set(params.itemId, item);
509
+ queue.push({ type: "item.updated", providerItemId: params.itemId, item });
510
+ return;
511
+ }
512
+ if (notification.method === "item/reasoning/summaryTextDelta") {
513
+ if (typeof params.itemId === "string" && typeof params.delta === "string")
514
+ queue.push({
515
+ type: "item.delta",
516
+ providerItemId: params.itemId,
517
+ field: "text",
518
+ delta: params.delta,
519
+ });
520
+ return;
521
+ }
522
+ if (notification.method === "turn/plan/updated") {
523
+ if (typeof params.turnId !== "string")
524
+ return;
525
+ const id = "turn-plan:" + params.turnId;
526
+ const item = {
527
+ type: "plan",
528
+ text: stringOrNull(params.explanation) ?? "",
529
+ steps: array(params.plan).flatMap((value) => {
530
+ const step = record(value);
531
+ if (!step ||
532
+ typeof step.step !== "string" ||
533
+ (step.status !== "pending" &&
534
+ step.status !== "inProgress" &&
535
+ step.status !== "completed"))
536
+ return [];
537
+ return [{ step: step.step, status: step.status }];
538
+ }),
539
+ };
540
+ if (this.turnPlan && this.turnPlan.id !== id)
541
+ this.completePlan(queue);
542
+ queue.push({
543
+ type: this.turnPlan ? "item.updated" : "item.started",
544
+ providerItemId: id,
545
+ item,
546
+ });
547
+ this.turnPlan = { id, item };
548
+ return;
549
+ }
550
+ if (notification.method === "item/fileChange/patchUpdated") {
551
+ if (typeof params.itemId === "string")
552
+ queue.push({
553
+ type: "item.updated",
554
+ providerItemId: params.itemId,
555
+ item: {
556
+ type: "file_change",
557
+ changes: codexFileChanges(params.changes),
558
+ },
559
+ });
560
+ return;
561
+ }
562
+ if (notification.method === "thread/tokenUsage/updated") {
563
+ const usage = tokenUsage(params.tokenUsage);
564
+ if (usage)
565
+ queue.push({ type: "token.usage", usage });
566
+ return;
567
+ }
568
+ if (notification.method === "model/rerouted") {
569
+ if (typeof params.fromModel === "string" &&
570
+ typeof params.toModel === "string" &&
571
+ params.reason === "highRiskCyberActivity")
572
+ queue.push({
573
+ type: "model.rerouted",
574
+ fromModel: params.fromModel,
575
+ toModel: params.toModel,
576
+ reason: params.reason,
577
+ });
578
+ return;
579
+ }
580
+ if (notification.method === "turn/started") {
581
+ const turn = record(params.turn);
582
+ if (turn && typeof turn.id === "string") {
583
+ this.activeTurnId = turn.id;
584
+ queue.push({ type: "turn.started", providerTurnId: turn.id });
585
+ }
586
+ return;
587
+ }
588
+ if (notification.method === "item/started") {
589
+ const item = record(params.item);
590
+ const converted = item ? codexItem(item, this.workspaceRoot) : null;
591
+ if (converted?.type === "tool" && typeof item?.id === "string")
592
+ this.toolItems.set(item.id, converted);
593
+ if (converted && typeof item?.id === "string")
594
+ queue.push({
595
+ type: "item.started",
596
+ providerItemId: item.id,
597
+ item: converted,
598
+ });
599
+ return;
600
+ }
601
+ if (notification.method === "item/agentMessage/delta") {
602
+ if (typeof params.itemId === "string" && typeof params.delta === "string")
603
+ queue.push({
604
+ type: "item.delta",
605
+ providerItemId: params.itemId,
606
+ field: "text",
607
+ delta: params.delta,
608
+ });
609
+ return;
610
+ }
611
+ if (notification.method === "item/plan/delta") {
612
+ if (typeof params.itemId === "string" && typeof params.delta === "string")
613
+ queue.push({
614
+ type: "item.delta",
615
+ providerItemId: params.itemId,
616
+ field: "text",
617
+ delta: params.delta,
618
+ });
619
+ return;
620
+ }
621
+ if (notification.method === "item/commandExecution/outputDelta") {
622
+ if (typeof params.itemId === "string" && typeof params.delta === "string")
623
+ queue.push({
624
+ type: "item.delta",
625
+ providerItemId: params.itemId,
626
+ field: "output",
627
+ delta: params.delta,
628
+ });
629
+ return;
630
+ }
631
+ if (notification.method === "item/completed") {
632
+ const item = record(params.item);
633
+ const converted = item ? codexItem(item, this.workspaceRoot) : null;
634
+ if (typeof item?.id === "string")
635
+ this.toolItems.delete(item.id);
636
+ if (converted && typeof item?.id === "string")
637
+ queue.push({
638
+ type: "item.completed",
639
+ providerItemId: item.id,
640
+ item: converted,
641
+ failed: codexItemFailed(item),
642
+ });
643
+ return;
644
+ }
645
+ if (notification.method === "turn/completed") {
646
+ const turn = record(params.turn);
647
+ const status = typeof turn?.status === "string" ? turn.status : "";
648
+ this.completeTurnItems(queue);
649
+ queue.push({
650
+ type: "turn.completed",
651
+ status: status === "interrupted"
652
+ ? "interrupted"
653
+ : status === "failed"
654
+ ? "failed"
655
+ : "completed",
656
+ error: turnError(turn?.error),
657
+ });
658
+ queue.close();
659
+ this.queue = null;
660
+ this.activeTurnId = null;
661
+ this.pendingInteractions.clear();
662
+ return;
663
+ }
664
+ }
665
+ emitNotice(level, text) {
666
+ this.queue?.push({
667
+ type: "item.completed",
668
+ providerItemId: "notice:" + ++this.noticeSequence,
669
+ item: { type: "notice", level, text },
670
+ });
671
+ }
672
+ completeDiff(queue) {
673
+ if (!this.turnDiff)
674
+ return;
675
+ queue.push({
676
+ type: "item.completed",
677
+ providerItemId: this.turnDiff.id,
678
+ item: this.turnDiff.item,
679
+ });
680
+ this.turnDiff = null;
681
+ }
682
+ completeTurnItems(queue) {
683
+ this.completePlan(queue);
684
+ this.completeDiff(queue);
685
+ this.toolItems.clear();
686
+ }
687
+ completePlan(queue) {
688
+ if (!this.turnPlan)
689
+ return;
690
+ queue.push({
691
+ type: "item.completed",
692
+ providerItemId: this.turnPlan.id,
693
+ item: this.turnPlan.item,
694
+ });
695
+ this.turnPlan = null;
696
+ }
697
+ requestApproval(request, title, description, choices) {
698
+ const id = String(request.id);
699
+ this.pendingInteractions.set(id, {
700
+ requestId: request.id,
701
+ kind: "approval",
702
+ choices: new Map(choices.map((choice) => [choice.id, choice.result])),
703
+ });
704
+ this.queue?.push({
705
+ type: "interaction.requested",
706
+ providerItemId: "interaction:" + id,
707
+ interaction: {
708
+ id,
709
+ kind: "approval",
710
+ title,
711
+ description,
712
+ questions: [],
713
+ approvalOptions: choices.map(({ id, label, description }) => ({
714
+ id,
715
+ label,
716
+ description,
717
+ })),
718
+ },
719
+ });
720
+ }
721
+ onServerRequest(request) {
722
+ const params = record(request.params);
723
+ if (!params || params.threadId !== this.providerSessionId)
724
+ return;
725
+ if (!this.queue) {
726
+ this.client.respondError(request.id, -32000, "No active ChatRoom turn");
727
+ return;
728
+ }
729
+ const interactionId = String(request.id);
730
+ if (request.method === "item/commandExecution/requestApproval") {
731
+ const choices = standardApprovalChoices();
732
+ const amendment = params.proposedExecpolicyAmendment;
733
+ if (Array.isArray(amendment) &&
734
+ amendment.every((part) => typeof part === "string"))
735
+ choices.push({
736
+ id: "acceptWithExecpolicyAmendment",
737
+ label: "Allow similar commands",
738
+ description: stringifyOptional(amendment),
739
+ result: {
740
+ decision: {
741
+ acceptWithExecpolicyAmendment: {
742
+ execpolicy_amendment: amendment,
743
+ },
744
+ },
745
+ },
746
+ });
747
+ array(params.proposedNetworkPolicyAmendments).forEach((value, index) => {
748
+ const amendment = record(value);
749
+ if (!amendment ||
750
+ typeof amendment.host !== "string" ||
751
+ (amendment.action !== "allow" && amendment.action !== "deny"))
752
+ return;
753
+ choices.push({
754
+ id: "applyNetworkPolicyAmendment:" + index,
755
+ label: (amendment.action === "allow" ? "Allow " : "Deny ") +
756
+ amendment.host,
757
+ description: "Apply this network policy to future requests",
758
+ result: {
759
+ decision: {
760
+ applyNetworkPolicyAmendment: {
761
+ network_policy_amendment: {
762
+ host: amendment.host,
763
+ action: amendment.action,
764
+ },
765
+ },
766
+ },
767
+ },
768
+ });
769
+ });
770
+ this.requestApproval(request, "Approve command", stringOrNull(params.command) ?? stringOrNull(params.reason), choices);
771
+ return;
772
+ }
773
+ if (request.method === "item/fileChange/requestApproval") {
774
+ this.requestApproval(request, "Approve file changes", stringOrNull(params.reason), standardApprovalChoices());
775
+ return;
776
+ }
777
+ if (request.method === "item/tool/requestUserInput") {
778
+ this.pendingInteractions.set(interactionId, {
779
+ requestId: request.id,
780
+ kind: "input",
781
+ });
782
+ this.queue.push({
783
+ type: "interaction.requested",
784
+ providerItemId: "interaction:" + interactionId,
785
+ interaction: {
786
+ id: interactionId,
787
+ kind: "input",
788
+ title: "Input required",
789
+ description: null,
790
+ questions: codexQuestions(params.questions),
791
+ approvalOptions: [],
792
+ },
793
+ });
794
+ return;
795
+ }
796
+ if (request.method === "item/permissions/requestApproval") {
797
+ const permissions = grantedPermissions(record(params.permissions) ?? {});
798
+ this.requestApproval(request, "Approve additional permissions", stringOrNull(params.reason) ?? stringifyOptional(permissions), [
799
+ {
800
+ id: "accept",
801
+ label: "Allow for this turn",
802
+ description: null,
803
+ result: { permissions, scope: "turn" },
804
+ },
805
+ {
806
+ id: "acceptForSession",
807
+ label: "Allow for this session",
808
+ description: null,
809
+ result: { permissions, scope: "session" },
810
+ },
811
+ {
812
+ id: "strictAutoReview",
813
+ label: "Allow with strict turn review",
814
+ description: "Review every subsequent command in this turn",
815
+ result: { permissions, scope: "turn", strictAutoReview: true },
816
+ },
817
+ {
818
+ id: "decline",
819
+ label: "Deny",
820
+ description: null,
821
+ result: { permissions: {}, scope: "turn" },
822
+ },
823
+ ]);
824
+ return;
825
+ }
826
+ this.client.respondError(request.id, -32601, "ChatRoom does not support this Codex server request");
827
+ }
828
+ }