@covro/covro 0.0.0-stage → 0.1.0-dev.1

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 (133) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +173 -2
  3. package/README.zh-CN.md +174 -0
  4. package/bin/covro.mjs +8 -0
  5. package/dist/cli/control.js +66 -0
  6. package/dist/cli/control.js.map +7 -0
  7. package/dist/cli/daemon.js +118 -0
  8. package/dist/cli/daemon.js.map +7 -0
  9. package/dist/cli/main.js +152 -0
  10. package/dist/cli/main.js.map +7 -0
  11. package/dist/cli/paths.js +60 -0
  12. package/dist/cli/paths.js.map +7 -0
  13. package/dist/cli/service.js +87 -0
  14. package/dist/cli/service.js.map +7 -0
  15. package/dist/codex/adapter.js +1195 -0
  16. package/dist/codex/adapter.js.map +7 -0
  17. package/dist/codex/auto-review.js +102 -0
  18. package/dist/codex/auto-review.js.map +7 -0
  19. package/dist/codex/compatibility.js +7 -0
  20. package/dist/codex/compatibility.js.map +7 -0
  21. package/dist/codex/mapping.js +210 -0
  22. package/dist/codex/mapping.js.map +7 -0
  23. package/dist/codex/permissions.js +51 -0
  24. package/dist/codex/permissions.js.map +7 -0
  25. package/dist/codex/thread-title-policy.js +52 -0
  26. package/dist/codex/thread-title-policy.js.map +7 -0
  27. package/dist/codex/thread-titles.js +259 -0
  28. package/dist/codex/thread-titles.js.map +7 -0
  29. package/dist/codex/transport.js +236 -0
  30. package/dist/codex/transport.js.map +7 -0
  31. package/dist/codex/turn-token-usage.js +46 -0
  32. package/dist/codex/turn-token-usage.js.map +7 -0
  33. package/dist/codex/types.js +1 -0
  34. package/dist/codex/types.js.map +7 -0
  35. package/dist/drizzle/0000_past_wildside.sql +45 -0
  36. package/dist/drizzle/0001_shocking_ender_wiggin.sql +2 -0
  37. package/dist/drizzle/0002_allow_parallel_threads.sql +1 -0
  38. package/dist/drizzle/0003_project_registrations.sql +11 -0
  39. package/dist/drizzle/0004_turn_usage.sql +6 -0
  40. package/dist/drizzle/meta/0000_snapshot.json +309 -0
  41. package/dist/drizzle/meta/0001_snapshot.json +323 -0
  42. package/dist/drizzle/meta/0002_snapshot.json +315 -0
  43. package/dist/drizzle/meta/0003_snapshot.json +381 -0
  44. package/dist/drizzle/meta/0004_snapshot.json +420 -0
  45. package/dist/drizzle/meta/_journal.json +41 -0
  46. package/dist/server/auth.js +47 -0
  47. package/dist/server/auth.js.map +7 -0
  48. package/dist/server/compaction.js +40 -0
  49. package/dist/server/compaction.js.map +7 -0
  50. package/dist/server/config-store.js +147 -0
  51. package/dist/server/config-store.js.map +7 -0
  52. package/dist/server/config.js +124 -0
  53. package/dist/server/config.js.map +7 -0
  54. package/dist/server/db/index.js +21 -0
  55. package/dist/server/db/index.js.map +7 -0
  56. package/dist/server/db/migrate.js +26 -0
  57. package/dist/server/db/migrate.js.map +7 -0
  58. package/dist/server/db/operations.js +201 -0
  59. package/dist/server/db/operations.js.map +7 -0
  60. package/dist/server/db/schema.js +99 -0
  61. package/dist/server/db/schema.js.map +7 -0
  62. package/dist/server/db/turn-usage.js +31 -0
  63. package/dist/server/db/turn-usage.js.map +7 -0
  64. package/dist/server/events.js +123 -0
  65. package/dist/server/events.js.map +7 -0
  66. package/dist/server/http.js +305 -0
  67. package/dist/server/http.js.map +7 -0
  68. package/dist/server/images.js +49 -0
  69. package/dist/server/images.js.map +7 -0
  70. package/dist/server/instance-lock.js +25 -0
  71. package/dist/server/instance-lock.js.map +7 -0
  72. package/dist/server/local-files.js +100 -0
  73. package/dist/server/local-files.js.map +7 -0
  74. package/dist/server/main.js +125 -0
  75. package/dist/server/main.js.map +7 -0
  76. package/dist/server/markdown.js +28 -0
  77. package/dist/server/markdown.js.map +7 -0
  78. package/dist/server/projection.js +348 -0
  79. package/dist/server/projection.js.map +7 -0
  80. package/dist/server/projects.js +230 -0
  81. package/dist/server/projects.js.map +7 -0
  82. package/dist/server/service.js +1221 -0
  83. package/dist/server/service.js.map +7 -0
  84. package/dist/server/snapshot.js +98 -0
  85. package/dist/server/snapshot.js.map +7 -0
  86. package/dist/server/thread-export.js +97 -0
  87. package/dist/server/thread-export.js.map +7 -0
  88. package/dist/server/websocket.js +126 -0
  89. package/dist/server/websocket.js.map +7 -0
  90. package/dist/shared/async-questions.js +39 -0
  91. package/dist/shared/async-questions.js.map +7 -0
  92. package/dist/shared/codex-compatibility.js +56 -0
  93. package/dist/shared/codex-compatibility.js.map +7 -0
  94. package/dist/shared/elicitation.js +314 -0
  95. package/dist/shared/elicitation.js.map +7 -0
  96. package/dist/shared/errors.js +24 -0
  97. package/dist/shared/errors.js.map +7 -0
  98. package/dist/shared/gateway-config.js +1 -0
  99. package/dist/shared/gateway-config.js.map +7 -0
  100. package/dist/shared/images.js +21 -0
  101. package/dist/shared/images.js.map +7 -0
  102. package/dist/shared/package.js +5 -0
  103. package/dist/shared/package.js.map +7 -0
  104. package/dist/shared/permissions.js +63 -0
  105. package/dist/shared/permissions.js.map +7 -0
  106. package/dist/shared/thread-name.js +14 -0
  107. package/dist/shared/thread-name.js.map +7 -0
  108. package/dist/shared/thread-summary.js +24 -0
  109. package/dist/shared/thread-summary.js.map +7 -0
  110. package/dist/shared/types.js +1 -0
  111. package/dist/shared/types.js.map +7 -0
  112. package/dist/shared/updates.js +7 -0
  113. package/dist/shared/updates.js.map +7 -0
  114. package/dist/shared/validation.js +116 -0
  115. package/dist/shared/validation.js.map +7 -0
  116. package/dist/shared/versions.js +36 -0
  117. package/dist/shared/versions.js.map +7 -0
  118. package/dist/update-worker.cjs +21968 -0
  119. package/dist/updates/files.js +54 -0
  120. package/dist/updates/files.js.map +7 -0
  121. package/dist/updates/manager.js +315 -0
  122. package/dist/updates/manager.js.map +7 -0
  123. package/dist/updates/registry.js +71 -0
  124. package/dist/updates/registry.js.map +7 -0
  125. package/dist/updates/transaction.js +96 -0
  126. package/dist/updates/transaction.js.map +7 -0
  127. package/dist/updates/worker.js +345 -0
  128. package/dist/updates/worker.js.map +7 -0
  129. package/dist/web/assets/index-BW9afH_Y.js +83 -0
  130. package/dist/web/assets/index-L3S1ZiO0.css +1 -0
  131. package/dist/web/covro.svg +6 -0
  132. package/dist/web/index.html +15 -0
  133. package/package.json +88 -4
@@ -0,0 +1,1195 @@
1
+ import { CODEX_COMPATIBILITY } from "./compatibility.js";
2
+ import { spawn, execFile } from "node:child_process";
3
+ import { randomUUID } from "node:crypto";
4
+ import { promisify } from "node:util";
5
+ import { ThreadTitles } from "./thread-titles.js";
6
+ import { TurnTokenUsage } from "./turn-token-usage.js";
7
+ import { AppError } from "../shared/errors.js";
8
+ import { threadNameError } from "../shared/thread-name.js";
9
+ import { permissionModeUnavailable, permissionReviewerUnavailable } from "../shared/permissions.js";
10
+ import {
11
+ permissionCapabilities
12
+ } from "./permissions.js";
13
+ import { mapAutoReview } from "./auto-review.js";
14
+ import {
15
+ normalizeElicitationRequest,
16
+ validateElicitationResolution,
17
+ validatePermissionResolution
18
+ } from "../shared/elicitation.js";
19
+ import {
20
+ mapItem,
21
+ mapThread,
22
+ mapTurn,
23
+ permissionSettings,
24
+ serviceTierSettings,
25
+ threadSettings,
26
+ threadStatus,
27
+ turnSettings,
28
+ taskInput
29
+ } from "./mapping.js";
30
+ import {
31
+ asCodexError,
32
+ CodexError,
33
+ isRecord,
34
+ RpcTransport
35
+ } from "./transport.js";
36
+ import { CodexError as CodexError2 } from "./transport.js";
37
+ const VERIFIED_CODEX_VERSION = CODEX_COMPATIBILITY.recommended;
38
+ const CAPABILITIES = [
39
+ "thread/name/set",
40
+ "thread/start",
41
+ "thread/fork",
42
+ "thread/read",
43
+ "thread/list",
44
+ "thread/resume",
45
+ "thread/archive",
46
+ "thread/loaded/list",
47
+ "thread/unarchive",
48
+ "thread/compact/start",
49
+ "thread/turns/list",
50
+ "thread/items/list",
51
+ "turn/start",
52
+ "review/start",
53
+ "turn/steer",
54
+ "turn/interrupt",
55
+ "model/list",
56
+ "config/read",
57
+ "config/batchWrite",
58
+ "configRequirements/read",
59
+ "experimentalFeature/list",
60
+ "account/read"
61
+ ];
62
+ function permissionPathLabel(path) {
63
+ if (path.type === "path") return path.path;
64
+ if (path.type === "glob_pattern") return `\u5339\u914D\uFF1A${path.pattern}`;
65
+ const special = path.value;
66
+ const subpath = "subpath" in special && special.subpath !== null ? `\uFF08\u5B50\u8DEF\u5F84\uFF1A${special.subpath}\uFF09` : "";
67
+ switch (special.kind) {
68
+ case "root":
69
+ return "\u6574\u4E2A\u6587\u4EF6\u7CFB\u7EDF\uFF08/\uFF09";
70
+ case "minimal":
71
+ return "\u7CFB\u7EDF\u57FA\u7840\u8FD0\u884C\u76EE\u5F55";
72
+ case "project_roots":
73
+ return `\u9879\u76EE\u6839\u76EE\u5F55${subpath}`;
74
+ case "tmpdir":
75
+ return "\u7CFB\u7EDF\u4E34\u65F6\u76EE\u5F55\uFF08TMPDIR\uFF09";
76
+ case "slash_tmp":
77
+ return "/tmp";
78
+ case "unknown":
79
+ return `\u7279\u6B8A\u8DEF\u5F84\uFF1A${special.path}${subpath}`;
80
+ }
81
+ }
82
+ class CodexAdapter {
83
+ constructor(options) {
84
+ this.options = options;
85
+ }
86
+ options;
87
+ epoch = randomUUID();
88
+ child;
89
+ rpc;
90
+ stopped = true;
91
+ starting;
92
+ recovering;
93
+ restartTimer;
94
+ restartAttempt = 0;
95
+ titles;
96
+ tokenUsage = new TurnTokenUsage();
97
+ // Ordering only. Persisted archive state belongs exclusively to Codex.
98
+ archiveSequence = 0;
99
+ archives = /* @__PURE__ */ new Map();
100
+ listeners = /* @__PURE__ */ new Set();
101
+ actions = /* @__PURE__ */ new Map();
102
+ actionIds = /* @__PURE__ */ new Map();
103
+ stderrTail = Buffer.alloc(0);
104
+ disabled = /* @__PURE__ */ new Set();
105
+ permissionState = /* @__PURE__ */ new Map();
106
+ state = {
107
+ ready: false,
108
+ connection: "starting",
109
+ codexVersion: null,
110
+ verifiedVersion: VERIFIED_CODEX_VERSION,
111
+ compatibility: "unavailable",
112
+ codexEpoch: this.epoch,
113
+ authenticated: null,
114
+ models: [],
115
+ capabilities: []
116
+ };
117
+ get pid() {
118
+ return this.child?.pid;
119
+ }
120
+ get ready() {
121
+ return this.state.ready;
122
+ }
123
+ diagnostics() {
124
+ return structuredClone(this.state);
125
+ }
126
+ onEvent(listener) {
127
+ this.listeners.add(listener);
128
+ return () => this.listeners.delete(listener);
129
+ }
130
+ emit(event) {
131
+ for (const listener of this.listeners) listener(event);
132
+ }
133
+ publishRuntime() {
134
+ this.emit({ type: "runtime", diagnostics: this.diagnostics() });
135
+ }
136
+ async start() {
137
+ if (this.starting) return this.starting;
138
+ this.stopped = false;
139
+ this.starting = this.startProcess();
140
+ try {
141
+ await this.starting;
142
+ } finally {
143
+ this.starting = void 0;
144
+ }
145
+ }
146
+ async startProcess() {
147
+ if (this.child) throw new CodexError("CODEX_BUSY", "Codex app-server is already started");
148
+ this.epoch = randomUUID();
149
+ this.disabled.clear();
150
+ this.archives.clear();
151
+ this.state = {
152
+ ...this.state,
153
+ codexEpoch: this.epoch,
154
+ ready: false,
155
+ connection: "starting",
156
+ error: void 0,
157
+ capabilities: []
158
+ };
159
+ this.publishRuntime();
160
+ try {
161
+ const env = {
162
+ ...process.env,
163
+ ...this.options.home === void 0 ? {} : { CODEX_HOME: this.options.home }
164
+ };
165
+ const { stdout } = await promisify(execFile)(this.options.binary, ["--version"], {
166
+ env,
167
+ timeout: 5e3,
168
+ maxBuffer: 64 * 1024
169
+ });
170
+ const version = stdout.trim().replace(/^codex-cli\s+/, "");
171
+ this.state.codexVersion = version;
172
+ this.permissionState.clear();
173
+ this.state.compatibility = CODEX_COMPATIBILITY.verified.includes(version) ? "verified" : "unverified";
174
+ const child = spawn(
175
+ this.options.binary,
176
+ this.options.args ?? ["app-server", "--listen", "stdio://"],
177
+ { env, stdio: ["pipe", "pipe", "pipe"], shell: false }
178
+ );
179
+ this.child = child;
180
+ const rpc = new RpcTransport(child.stdin, child.stdout, {
181
+ timeoutMs: this.options.requestTimeoutMs,
182
+ maxLineBytes: this.options.maxLineBytes
183
+ });
184
+ this.rpc = rpc;
185
+ this.titles = new ThreadTitles(rpc, this.options.titleTimeoutMs);
186
+ child.stderr.on("data", (chunk) => {
187
+ const max = this.options.maxStderrBytes ?? 64 * 1024;
188
+ this.stderrTail = Buffer.concat([this.stderrTail, chunk.subarray(-max)]).subarray(-max);
189
+ });
190
+ child.on(
191
+ "error",
192
+ (error) => rpc.fail(new CodexError("CODEX_UNAVAILABLE", `Cannot start Codex: ${error.message}`, true))
193
+ );
194
+ child.on("exit", (code, signal) => {
195
+ rpc.fail(
196
+ new CodexError(
197
+ "CODEX_UNAVAILABLE",
198
+ `Codex exited (${signal ?? code ?? "unknown"})`,
199
+ true
200
+ )
201
+ );
202
+ if (this.child === child) this.child = void 0;
203
+ });
204
+ rpc.on("notification", (message) => {
205
+ try {
206
+ this.notification(message);
207
+ } catch (error) {
208
+ rpc.fail(asCodexError(error));
209
+ }
210
+ });
211
+ rpc.on("request", (message) => {
212
+ void this.serverRequest(message).catch((error) => rpc.fail(asCodexError(error)));
213
+ });
214
+ rpc.on("failure", (error) => {
215
+ if (this.rpc !== rpc) return;
216
+ const wasReady = this.ready;
217
+ this.state.ready = false;
218
+ this.tokenUsage.clear();
219
+ this.state.connection = "unavailable";
220
+ this.state.error = error.message;
221
+ this.expireActions();
222
+ this.publishRuntime();
223
+ if (!child.killed) child.kill("SIGTERM");
224
+ const kill = setTimeout(() => {
225
+ if (child.exitCode === null && child.signalCode === null) child.kill("SIGKILL");
226
+ }, 2500);
227
+ kill.unref();
228
+ child.once("exit", () => clearTimeout(kill));
229
+ if (wasReady && !this.stopped) {
230
+ console.error("Codex transport failed", {
231
+ code: error.code,
232
+ ...error.details
233
+ });
234
+ this.scheduleRestart();
235
+ }
236
+ });
237
+ const initialized = await this.call(
238
+ "initialize",
239
+ {
240
+ clientInfo: { name: "codex_web_ui", title: "Codex Web UI", version: "0.1.0" },
241
+ capabilities: {
242
+ experimentalApi: true,
243
+ requestAttestation: false,
244
+ explicitGatewayOauth: true
245
+ }
246
+ },
247
+ true
248
+ );
249
+ if (!isRecord(initialized) || typeof initialized.userAgent !== "string")
250
+ throw new CodexError(
251
+ "PROTOCOL_INCOMPATIBLE",
252
+ "Codex initialize returned a malformed result",
253
+ true
254
+ );
255
+ await rpc.notify("initialized");
256
+ const [account, models] = await Promise.all([
257
+ this.call("account/read", { refreshToken: false }, true),
258
+ this.call("model/list", { limit: 100 }, true)
259
+ ]);
260
+ if (!isRecord(account) || !Object.hasOwn(account, "account") || typeof account.requiresOpenaiAuth !== "boolean")
261
+ throw new CodexError(
262
+ "PROTOCOL_INCOMPATIBLE",
263
+ "Codex account/read returned a malformed result",
264
+ true
265
+ );
266
+ this.state.authenticated = account.account !== null || !account.requiresOpenaiAuth;
267
+ this.state.models = models.data.map((model) => ({
268
+ id: model.model,
269
+ name: model.displayName,
270
+ description: model.description,
271
+ reasoningEfforts: model.supportedReasoningEfforts.map((effort) => effort.reasoningEffort),
272
+ defaultReasoningEffort: model.defaultReasoningEffort,
273
+ serviceTiers: model.serviceTiers?.map((tier) => tier.id === "priority" ? "fast" : tier.id) ?? model.additionalSpeedTiers
274
+ }));
275
+ let cursor = models.nextCursor;
276
+ const seen = /* @__PURE__ */ new Set();
277
+ while (cursor) {
278
+ if (seen.has(cursor) || seen.size >= 100)
279
+ throw new CodexError("PROTOCOL_INCOMPATIBLE", "Invalid model pagination");
280
+ seen.add(cursor);
281
+ const page = await this.call("model/list", { limit: 100, cursor }, true);
282
+ this.state.models.push(
283
+ ...page.data.map((model) => ({
284
+ id: model.model,
285
+ name: model.displayName,
286
+ description: model.description,
287
+ reasoningEfforts: model.supportedReasoningEfforts.map(
288
+ (effort) => effort.reasoningEffort
289
+ ),
290
+ defaultReasoningEffort: model.defaultReasoningEffort,
291
+ serviceTiers: model.serviceTiers?.map((tier) => tier.id === "priority" ? "fast" : tier.id) ?? model.additionalSpeedTiers
292
+ }))
293
+ );
294
+ cursor = page.nextCursor;
295
+ }
296
+ this.titles?.configure(
297
+ account.account?.type === "chatgpt",
298
+ this.state.models.map((model) => model.id)
299
+ );
300
+ this.state.ready = true;
301
+ this.state.connection = "ready";
302
+ this.state.capabilities = CAPABILITIES.filter((method) => !this.disabled.has(method));
303
+ this.publishRuntime();
304
+ } catch (error) {
305
+ const failure = asCodexError(error);
306
+ this.state.ready = false;
307
+ this.state.connection = "unavailable";
308
+ this.state.error = failure.message;
309
+ this.publishRuntime();
310
+ await this.stopProcess(failure);
311
+ this.scheduleRestart();
312
+ throw failure;
313
+ }
314
+ }
315
+ async stop() {
316
+ this.stopped = true;
317
+ clearTimeout(this.restartTimer);
318
+ this.restartTimer = void 0;
319
+ await this.starting?.catch(() => {
320
+ });
321
+ await this.recovering;
322
+ await this.stopProcess();
323
+ }
324
+ scheduleRestart() {
325
+ if (this.stopped || this.restartTimer) return;
326
+ const delay = Math.min(1e3 * 2 ** Math.min(this.restartAttempt++, 5), 3e4);
327
+ this.restartTimer = setTimeout(() => {
328
+ this.restartTimer = void 0;
329
+ this.recovering = (async () => {
330
+ await this.stopProcess();
331
+ if (this.stopped) return;
332
+ try {
333
+ await this.start();
334
+ this.restartAttempt = 0;
335
+ } catch {
336
+ this.scheduleRestart();
337
+ }
338
+ })();
339
+ void this.recovering.finally(() => {
340
+ this.recovering = void 0;
341
+ });
342
+ }, delay);
343
+ this.restartTimer.unref();
344
+ }
345
+ async stopProcess(reason = new CodexError("CODEX_UNAVAILABLE", "Codex connection closed by this service", true)) {
346
+ const child = this.child;
347
+ this.state.ready = false;
348
+ this.state.connection = "unavailable";
349
+ this.expireActions();
350
+ this.rpc?.fail(reason);
351
+ if (!child || child.exitCode !== null || child.signalCode !== null) return;
352
+ child.stdin.end();
353
+ await new Promise((resolve) => {
354
+ const term = setTimeout(() => child.kill("SIGTERM"), 1e3);
355
+ const kill = setTimeout(() => child.kill("SIGKILL"), 2500);
356
+ const done = setTimeout(() => {
357
+ cleanup();
358
+ resolve();
359
+ }, 3500);
360
+ const cleanup = () => {
361
+ clearTimeout(term);
362
+ clearTimeout(kill);
363
+ clearTimeout(done);
364
+ };
365
+ child.once("exit", () => {
366
+ cleanup();
367
+ resolve();
368
+ });
369
+ });
370
+ if (this.child === child) this.child = void 0;
371
+ }
372
+ async call(method, params, initializing = false) {
373
+ if (!this.rpc || !initializing && !this.ready)
374
+ throw new CodexError("CODEX_UNAVAILABLE", "Codex is not ready");
375
+ if (this.disabled.has(method))
376
+ throw new CodexError("PROTOCOL_INCOMPATIBLE", `Codex does not support ${method}`);
377
+ try {
378
+ return await this.rpc.request(method, params);
379
+ } catch (error) {
380
+ if (!initializing && error instanceof CodexError && error.rpcCode === -32601) {
381
+ this.disabled.add(method);
382
+ this.state.capabilities = this.state.capabilities.filter((name) => name !== method);
383
+ this.state.error = `\u5F53\u524D Codex \u7F3A\u5C11 ${method} \u80FD\u529B`;
384
+ this.publishRuntime();
385
+ }
386
+ throw error;
387
+ }
388
+ }
389
+ validateSettings(settings = {}) {
390
+ const model = settings.model === void 0 ? void 0 : this.state.models.find((entry) => entry.id === settings.model);
391
+ if (settings.model !== void 0 && !model)
392
+ throw new AppError("VALIDATION_ERROR", "\u6A21\u578B\u4E0D\u5728\u5F53\u524D Codex \u6A21\u578B\u5217\u8868\u4E2D");
393
+ if (settings.reasoningEffort !== void 0 && !(model?.reasoningEfforts ?? this.state.models.flatMap((entry) => entry.reasoningEfforts)).includes(settings.reasoningEffort))
394
+ throw new AppError("VALIDATION_ERROR", "\u6240\u9009\u63A8\u7406\u5F3A\u5EA6\u4E0D\u53D7\u5F53\u524D\u6A21\u578B\u652F\u6301");
395
+ }
396
+ async listThreads(cwd, cursor, archived = false) {
397
+ const sequence = ++this.archiveSequence;
398
+ const result = await this.call("thread/list", {
399
+ cwd,
400
+ cursor,
401
+ limit: 30,
402
+ archived,
403
+ sourceKinds: ["cli", "appServer", "vscode"],
404
+ sortKey: "updated_at",
405
+ sortDirection: "desc"
406
+ });
407
+ return {
408
+ data: result.data.map((thread) => ({
409
+ ...mapThread(thread),
410
+ archived: this.observeArchive(thread.id, archived, sequence)
411
+ })),
412
+ nextCursor: result.nextCursor
413
+ };
414
+ }
415
+ observeArchive(id, archived, sequence) {
416
+ const current = this.archives.get(id);
417
+ if (current && current.sequence > sequence) return current.archived;
418
+ this.archives.set(id, { archived, sequence });
419
+ return archived;
420
+ }
421
+ archiveChanged(id, archived) {
422
+ this.observeArchive(id, archived, ++this.archiveSequence);
423
+ this.emit({ type: "threadArchive", threadId: id, archived });
424
+ }
425
+ /** thread/read has no archive field. Query the native index, including paginated history,
426
+ * instead of guessing from the unstable rollout path or maintaining a second database. */
427
+ async storedThread(thread, sequence) {
428
+ let cursor = null;
429
+ const seen = /* @__PURE__ */ new Set();
430
+ let archived = false;
431
+ do {
432
+ const page = await this.call("thread/list", {
433
+ archived: true,
434
+ cwd: thread.cwd,
435
+ cursor,
436
+ limit: 100,
437
+ sourceKinds: [
438
+ "cli",
439
+ "vscode",
440
+ "exec",
441
+ "appServer",
442
+ "subAgent",
443
+ "subAgentReview",
444
+ "subAgentCompact",
445
+ "subAgentThreadSpawn",
446
+ "subAgentOther",
447
+ "unknown"
448
+ ]
449
+ });
450
+ if (page.data.some((entry) => entry.id === thread.id)) {
451
+ archived = true;
452
+ break;
453
+ }
454
+ cursor = page.nextCursor;
455
+ if (cursor && (seen.has(cursor) || seen.size >= 1e3))
456
+ throw new CodexError("PROTOCOL_INCOMPATIBLE", "Invalid archive pagination");
457
+ if (cursor) seen.add(cursor);
458
+ } while (cursor);
459
+ return { ...mapThread(thread), archived: this.observeArchive(thread.id, archived, sequence) };
460
+ }
461
+ /** Native archive also shuts down spawned descendants, even already-archived ones. */
462
+ async assertArchiveIdle(rootId) {
463
+ const threads = /* @__PURE__ */ new Map();
464
+ const read = async (id) => {
465
+ let thread = threads.get(id);
466
+ if (!thread) {
467
+ thread = (await this.call("thread/read", { threadId: id, includeTurns: false })).thread;
468
+ threads.set(id, thread);
469
+ }
470
+ return thread;
471
+ };
472
+ let cursor = null;
473
+ const seen = /* @__PURE__ */ new Set();
474
+ do {
475
+ const page = await this.call("thread/loaded/list", {
476
+ cursor,
477
+ limit: 100
478
+ });
479
+ for (const id of page.data) {
480
+ let thread = await read(id);
481
+ if (thread.status.type !== "active") continue;
482
+ const ancestors = /* @__PURE__ */ new Set();
483
+ for (; ; ) {
484
+ if (thread.id === rootId)
485
+ throw new AppError("THREAD_BUSY", "\u4F1A\u8BDD\u6216\u5176\u5B50\u4EFB\u52A1\u4ECD\u5728\u8FD0\u884C\uFF0C\u7ED3\u675F\u540E\u624D\u80FD\u5F52\u6863", 409);
486
+ if (!thread.parentThreadId) break;
487
+ if (ancestors.has(thread.id))
488
+ throw new CodexError("PROTOCOL_INCOMPATIBLE", "Invalid thread ancestry");
489
+ ancestors.add(thread.id);
490
+ thread = await read(thread.parentThreadId);
491
+ }
492
+ }
493
+ cursor = page.nextCursor;
494
+ if (cursor && (seen.has(cursor) || seen.size >= 1e3))
495
+ throw new CodexError("PROTOCOL_INCOMPATIBLE", "Invalid loaded thread pagination");
496
+ if (cursor) seen.add(cursor);
497
+ } while (cursor);
498
+ }
499
+ async renameThread(id, name) {
500
+ const problem = threadNameError(name);
501
+ if (problem) throw new AppError("VALIDATION_ERROR", problem);
502
+ const epoch = this.epoch;
503
+ await this.titles?.prepareManualName(id);
504
+ if (this.epoch !== epoch || !this.ready)
505
+ throw new CodexError(
506
+ "CODEX_UNAVAILABLE",
507
+ "\u91CD\u547D\u540D\u7B49\u5F85\u671F\u95F4 Codex \u5B9E\u4F8B\u5DF2\u53D8\u5316\uFF0C\u8BF7\u6838\u5BF9\u540D\u79F0",
508
+ true
509
+ );
510
+ await this.call("thread/name/set", { threadId: id, name: name.trim() });
511
+ }
512
+ async archiveThread(id) {
513
+ await this.assertArchiveIdle(id);
514
+ await this.call("thread/archive", { threadId: id });
515
+ this.archiveChanged(id, true);
516
+ }
517
+ async unarchiveThread(id) {
518
+ const { thread } = await this.call("thread/unarchive", { threadId: id });
519
+ this.archiveChanged(id, false);
520
+ return { ...mapThread(thread), archived: false };
521
+ }
522
+ async readThreadHeader(id) {
523
+ try {
524
+ return (await this.call("thread/read", { threadId: id, includeTurns: false })).thread;
525
+ } catch (error) {
526
+ if (error instanceof CodexError && error.rpcCode === -32600 && error.message === `thread not loaded: ${id}`)
527
+ throw new AppError("THREAD_NOT_FOUND", "\u4F1A\u8BDD\u5C1A\u672A\u4FDD\u5B58\u6216\u5DF2\u4E0D\u5B58\u5728", 404, { threadId: id });
528
+ throw error;
529
+ }
530
+ }
531
+ async readThreadMetadata(id) {
532
+ const sequence = ++this.archiveSequence;
533
+ return this.storedThread(await this.readThreadHeader(id), sequence);
534
+ }
535
+ async readThread(id) {
536
+ const sequence = ++this.archiveSequence;
537
+ const thread = await this.readThreadHeader(id);
538
+ if (thread.historyMode !== "paginated")
539
+ return this.storedThread(
540
+ (await this.call("thread/read", { threadId: id, includeTurns: true })).thread,
541
+ sequence
542
+ );
543
+ let cursor = null;
544
+ const seen = /* @__PURE__ */ new Set();
545
+ thread.turns = [];
546
+ do {
547
+ const page = await this.call("thread/turns/list", {
548
+ threadId: id,
549
+ cursor,
550
+ limit: 100,
551
+ sortDirection: "asc",
552
+ itemsView: "summary"
553
+ });
554
+ for (const turn of page.data) {
555
+ if (turn.itemsView !== "full") {
556
+ let itemCursor = null;
557
+ const itemSeen = /* @__PURE__ */ new Set();
558
+ turn.items = [];
559
+ do {
560
+ const items = await this.call("thread/items/list", {
561
+ threadId: id,
562
+ turnId: turn.id,
563
+ cursor: itemCursor,
564
+ limit: 20,
565
+ sortDirection: "asc"
566
+ });
567
+ turn.items.push(...items.data.map((entry) => entry.item));
568
+ itemCursor = items.nextCursor;
569
+ if (itemCursor && (itemSeen.has(itemCursor) || itemSeen.size >= 1e3))
570
+ throw new CodexError("PROTOCOL_INCOMPATIBLE", "Invalid item pagination");
571
+ if (itemCursor) itemSeen.add(itemCursor);
572
+ } while (itemCursor);
573
+ }
574
+ }
575
+ thread.turns.push(...page.data);
576
+ cursor = page.nextCursor;
577
+ if (cursor && (seen.has(cursor) || seen.size >= 1e3))
578
+ throw new CodexError("PROTOCOL_INCOMPATIBLE", "Invalid turn pagination");
579
+ if (cursor) seen.add(cursor);
580
+ } while (cursor);
581
+ return this.storedThread(thread, sequence);
582
+ }
583
+ async createThread(cwd, settings) {
584
+ this.validateSettings(settings);
585
+ await this.validatePermissionSettings(cwd, settings);
586
+ const titles = this.titles;
587
+ const result = await this.call("thread/start", { cwd, ...threadSettings(settings) });
588
+ this.tokenUsage.created(result.thread.id);
589
+ titles?.register(result);
590
+ return {
591
+ ...mapThread(result.thread),
592
+ runtimeSettings: await this.responseRuntimeSettings(result)
593
+ };
594
+ }
595
+ async saveModelDefaults(settings) {
596
+ this.validateSettings({
597
+ model: settings.model,
598
+ ...settings.reasoningEffort === null ? {} : { reasoningEffort: settings.reasoningEffort }
599
+ });
600
+ const result = await this.call("config/batchWrite", {
601
+ edits: [
602
+ { keyPath: "model", value: settings.model, mergeStrategy: "replace" },
603
+ {
604
+ keyPath: "model_reasoning_effort",
605
+ value: settings.reasoningEffort,
606
+ mergeStrategy: "replace"
607
+ }
608
+ ],
609
+ reloadUserConfig: true
610
+ });
611
+ return { status: result.status };
612
+ }
613
+ async readRuntimeSettings(cwd) {
614
+ const { config } = await this.call("config/read", { cwd, includeLayers: false });
615
+ return {
616
+ model: typeof config.model === "string" ? config.model : null,
617
+ reasoningEffort: typeof config.model_reasoning_effort === "string" ? config.model_reasoning_effort : null,
618
+ ...serviceTierSettings(config.service_tier),
619
+ sandbox: config.sandbox_mode ?? null,
620
+ approvalPolicy: typeof config.approval_policy === "string" ? config.approval_policy : config.approval_policy ? "granular" : null,
621
+ approvalReviewer: config.approvals_reviewer ?? null,
622
+ networkAccess: config.sandbox_mode === "workspace-write" ? config.sandbox_workspace_write?.network_access ?? null : null,
623
+ permissionMode: typeof config.default_permissions === "string" && ![":workspace", ":read-only", ":danger-full-access"].includes(config.default_permissions) ? "custom" : null,
624
+ permissionProfile: typeof config.default_permissions === "string" ? { id: config.default_permissions, extends: null } : null,
625
+ writableRoots: config.sandbox_workspace_write?.writable_roots ?? null,
626
+ excludeTmpdirEnvVar: config.sandbox_workspace_write?.exclude_tmpdir_env_var ?? null,
627
+ excludeSlashTmp: config.sandbox_workspace_write?.exclude_slash_tmp ?? null,
628
+ permissionCapabilities: await this.readPermissionCapabilities(void 0, config)
629
+ };
630
+ }
631
+ async readPermissionCapabilities(threadId, config) {
632
+ if (!CODEX_COMPATIBILITY.verified.includes(this.state.codexVersion ?? ""))
633
+ return permissionCapabilities(
634
+ null,
635
+ false,
636
+ `\u6743\u9650\u6A21\u5F0F\u4EC5\u6838\u9A8C\u8FC7 Codex ${VERIFIED_CODEX_VERSION}\uFF1B\u5F53\u524D\u7248\u672C ${this.state.codexVersion ?? "\u672A\u77E5"}`
637
+ );
638
+ try {
639
+ const [requirements, first] = await Promise.all([
640
+ this.call("configRequirements/read", void 0),
641
+ this.call("experimentalFeature/list", { limit: 200, ...threadId ? { threadId } : {} })
642
+ ]);
643
+ if (!Object.hasOwn(requirements, "requirements") || !Array.isArray(first.data))
644
+ throw new Error("Permission capability response is incomplete");
645
+ const features = [...first.data];
646
+ let cursor = first.nextCursor;
647
+ const seen = /* @__PURE__ */ new Set();
648
+ while (cursor) {
649
+ if (seen.has(cursor) || seen.size >= 100) throw new Error("Invalid feature pagination");
650
+ seen.add(cursor);
651
+ const page = await this.call("experimentalFeature/list", {
652
+ limit: 200,
653
+ cursor,
654
+ ...threadId ? { threadId } : {}
655
+ });
656
+ features.push(...page.data);
657
+ cursor = page.nextCursor;
658
+ }
659
+ const configFeatures = config?.features;
660
+ const explicit = isRecord(configFeatures) ? configFeatures.guardian_approval : void 0;
661
+ const autoReview = typeof explicit === "boolean" ? explicit : features.find((entry) => entry.name === "guardian_approval")?.enabled === true;
662
+ return permissionCapabilities(
663
+ requirements.requirements,
664
+ autoReview
665
+ );
666
+ } catch {
667
+ return permissionCapabilities(
668
+ null,
669
+ false,
670
+ "\u65E0\u6CD5\u6838\u5B9E\u5F53\u524D Codex \u7684\u529F\u80FD\u548C\u53D7\u7BA1\u8981\u6C42\uFF0C\u8BF7\u91CD\u8BD5\u6216\u68C0\u67E5\u7248\u672C\u652F\u6301"
671
+ );
672
+ }
673
+ }
674
+ async validatePermissionSettings(cwd, settings, threadId) {
675
+ if (!settings?.permissionMode && settings?.approvalReviewer === void 0) return;
676
+ const epoch = this.epoch;
677
+ const defaults = !threadId || !this.permissionState.has(threadId) ? await this.readRuntimeSettings(cwd) : void 0;
678
+ const capabilities = defaults ? defaults.permissionCapabilities : await this.readPermissionCapabilities(threadId);
679
+ if (epoch !== this.epoch)
680
+ throw new AppError("CODEX_UNAVAILABLE", "Codex \u8FD0\u884C\u5B9E\u4F8B\u5DF2\u53D8\u5316\uFF0C\u8BF7\u91CD\u8BD5");
681
+ const model = settings.model ?? (threadId ? this.permissionState.get(threadId)?.model : void 0) ?? defaults?.model;
682
+ const reason = settings.permissionMode ? permissionModeUnavailable(settings.permissionMode, capabilities, model) : permissionReviewerUnavailable(settings.approvalReviewer, capabilities, model);
683
+ if (reason) throw new AppError("PERMISSION_MODE_UNAVAILABLE", reason);
684
+ }
685
+ async responseRuntimeSettings(result) {
686
+ const previous = this.permissionState.get(result.thread.id);
687
+ const settings = {
688
+ model: result.model ?? result.thread.model ?? null,
689
+ reasoningEffort: result.reasoningEffort ?? null,
690
+ ...serviceTierSettings(result.serviceTier),
691
+ ...permissionSettings(
692
+ result.sandbox,
693
+ result.approvalPolicy,
694
+ result.approvalsReviewer,
695
+ result.activePermissionProfile,
696
+ result.cwd
697
+ ),
698
+ permissionCapabilities: await this.readPermissionCapabilities(result.thread.id)
699
+ };
700
+ const latest = this.permissionState.get(result.thread.id);
701
+ const confirmed = latest && latest !== previous ? { ...latest, permissionCapabilities: settings.permissionCapabilities } : settings;
702
+ this.permissionState.set(result.thread.id, confirmed);
703
+ if (confirmed !== settings)
704
+ this.emit({ type: "threadSettings", threadId: result.thread.id, settings: confirmed });
705
+ return settings;
706
+ }
707
+ async resumeThread(id, cwd, settings) {
708
+ this.validateSettings(settings);
709
+ await this.validatePermissionSettings(cwd, settings, id);
710
+ const titles = this.titles;
711
+ let result;
712
+ try {
713
+ result = await this.call("thread/resume", {
714
+ threadId: id,
715
+ cwd,
716
+ ...threadSettings(settings),
717
+ excludeTurns: true
718
+ });
719
+ } catch (error) {
720
+ if (error instanceof CodexError && error.rpcCode === -32600 && error.message === `thread ${id} already has an active writer`)
721
+ throw new AppError("THREAD_IN_USE", "\u6B64\u4F1A\u8BDD\u6B63\u88AB\u5176\u4ED6\u8FDB\u7A0B\u4F7F\u7528", 409, { threadId: id });
722
+ throw error;
723
+ }
724
+ titles?.register(result);
725
+ const view = await this.readThread(result.thread.id);
726
+ return { ...view, runtimeSettings: await this.responseRuntimeSettings(result) };
727
+ }
728
+ async forkThread(id, cwd, settings) {
729
+ this.validateSettings(settings);
730
+ await this.validatePermissionSettings(cwd, settings, id);
731
+ const titles = this.titles;
732
+ const result = await this.call("thread/fork", {
733
+ threadId: id,
734
+ cwd,
735
+ ...threadSettings(settings),
736
+ // Omitting lastTurnId copies the persisted history, including an active turn's
737
+ // interruption marker in the fork. The source is never resumed or interrupted.
738
+ excludeTurns: true
739
+ });
740
+ let forkedId;
741
+ try {
742
+ if (isRecord(result) && isRecord(result.thread) && typeof result.thread.id === "string")
743
+ forkedId = result.thread.id === id ? void 0 : result.thread.id;
744
+ const metadata = mapThread(result.thread);
745
+ if (metadata.id === id)
746
+ throw new CodexError("PROTOCOL_INCOMPATIBLE", "Codex fork returned the source thread ID");
747
+ forkedId = metadata.id;
748
+ const thread = await this.readThread(forkedId);
749
+ if (thread.id !== forkedId)
750
+ throw new CodexError("PROTOCOL_INCOMPATIBLE", "Codex returned a different fork thread ID");
751
+ titles?.register(result);
752
+ return { ...thread, runtimeSettings: await this.responseRuntimeSettings(result) };
753
+ } catch (error) {
754
+ const failure = asCodexError(error);
755
+ throw new CodexError(
756
+ failure.code,
757
+ forkedId ? `\u5DF2\u521B\u5EFA\u5206\u652F\u4F1A\u8BDD ${forkedId}\uFF0C\u4F46\u65E0\u6CD5\u52A0\u8F7D\u5386\u53F2\uFF1B\u8BF7\u4ECE\u4F1A\u8BDD\u5217\u8868\u91CD\u65B0\u6253\u5F00\uFF0C\u4E0D\u8981\u91CD\u590D fork\u3002${failure.message}` : `Codex fork \u5DF2\u8FD4\u56DE\u7ED3\u679C\uFF0C\u4F46\u65E0\u6CD5\u786E\u8BA4\u65B0\u4F1A\u8BDD\uFF1B\u8BF7\u68C0\u67E5\u4F1A\u8BDD\u5217\u8868\uFF0C\u4E0D\u8981\u91CD\u590D fork\u3002${failure.message}`,
758
+ true,
759
+ failure.rpcCode
760
+ );
761
+ }
762
+ }
763
+ async startTurn(id, cwd, text, settings, images) {
764
+ this.validateSettings(settings);
765
+ await this.validatePermissionSettings(cwd, settings, id);
766
+ const turn = mapTurn(
767
+ id,
768
+ (await this.call("turn/start", {
769
+ threadId: id,
770
+ cwd,
771
+ input: taskInput(text, images),
772
+ ...turnSettings(cwd, settings)
773
+ })).turn
774
+ );
775
+ return turn;
776
+ }
777
+ async startReview(id) {
778
+ const result = await this.call("review/start", {
779
+ threadId: id,
780
+ target: { type: "uncommittedChanges" },
781
+ delivery: "inline"
782
+ });
783
+ if (!isRecord(result) || result.reviewThreadId !== id)
784
+ throw new CodexError(
785
+ "PROTOCOL_INCOMPATIBLE",
786
+ "Codex review returned a different thread; review result needs reconciliation",
787
+ true
788
+ );
789
+ return mapTurn(id, result.turn);
790
+ }
791
+ steerTurn(id, turnId, text, images) {
792
+ return this.call("turn/steer", {
793
+ threadId: id,
794
+ expectedTurnId: turnId,
795
+ input: taskInput(text, images)
796
+ });
797
+ }
798
+ async interruptTurn(id, turnId) {
799
+ await this.call("turn/interrupt", { threadId: id, turnId });
800
+ }
801
+ async compactThread(id) {
802
+ await this.call("thread/compact/start", { threadId: id });
803
+ }
804
+ notification(message) {
805
+ if (this.titles?.notification(message)) return;
806
+ const event = message;
807
+ const p = event.params;
808
+ switch (event.method) {
809
+ case "thread/settings/updated":
810
+ {
811
+ const observed = {
812
+ model: event.params.threadSettings.model ?? null,
813
+ reasoningEffort: event.params.threadSettings.effort ?? null,
814
+ ...serviceTierSettings(event.params.threadSettings.serviceTier),
815
+ ...permissionSettings(
816
+ event.params.threadSettings.sandboxPolicy,
817
+ event.params.threadSettings.approvalPolicy,
818
+ event.params.threadSettings.approvalsReviewer,
819
+ event.params.threadSettings.activePermissionProfile,
820
+ event.params.threadSettings.cwd
821
+ ),
822
+ permissionCapabilities: this.permissionState.get(event.params.threadId)?.permissionCapabilities
823
+ };
824
+ this.permissionState.set(event.params.threadId, observed);
825
+ this.emit({
826
+ type: "threadSettings",
827
+ threadId: event.params.threadId,
828
+ settings: observed
829
+ });
830
+ }
831
+ break;
832
+ case "item/autoApprovalReview/started":
833
+ case "item/autoApprovalReview/completed":
834
+ this.emit({
835
+ type: "autoReview",
836
+ threadId: event.params.threadId,
837
+ turnId: event.params.turnId,
838
+ review: mapAutoReview(event.params)
839
+ });
840
+ break;
841
+ case "thread/started":
842
+ this.emit({ type: "thread", thread: mapThread(event.params.thread) });
843
+ break;
844
+ case "thread/archived":
845
+ this.archiveChanged(event.params.threadId, true);
846
+ break;
847
+ case "thread/unarchived":
848
+ this.archiveChanged(event.params.threadId, false);
849
+ break;
850
+ case "thread/name/updated":
851
+ this.emit({
852
+ type: "threadName",
853
+ threadId: event.params.threadId,
854
+ name: event.params.threadName
855
+ });
856
+ break;
857
+ case "thread/status/changed":
858
+ this.emit({
859
+ type: "threadStatus",
860
+ threadId: event.params.threadId,
861
+ status: threadStatus(event.params.status)
862
+ });
863
+ break;
864
+ case "turn/started":
865
+ case "turn/completed": {
866
+ const { threadId, turn } = event.params;
867
+ if (event.method === "turn/started") this.tokenUsage.started(threadId, turn.id);
868
+ const outputTokens = event.method === "turn/completed" ? this.tokenUsage.completed(threadId, turn.id) : void 0;
869
+ this.emit({
870
+ type: "turn",
871
+ threadId,
872
+ turn: {
873
+ ...mapTurn(threadId, turn),
874
+ ...outputTokens !== void 0 ? { outputTokens } : {}
875
+ }
876
+ });
877
+ if (event.method === "turn/completed")
878
+ this.expireActions(event.params.threadId, event.params.turn.id);
879
+ break;
880
+ }
881
+ case "thread/tokenUsage/updated":
882
+ this.tokenUsage.updated(
883
+ event.params.threadId,
884
+ event.params.turnId,
885
+ event.params.tokenUsage?.total?.outputTokens
886
+ );
887
+ break;
888
+ case "thread/compacted":
889
+ this.tokenUsage.compacted(event.params.threadId, event.params.turnId);
890
+ this.emit({ type: "compactionCompleted", ...event.params });
891
+ break;
892
+ case "item/started":
893
+ case "item/completed":
894
+ if (event.params.item.type === "contextCompaction")
895
+ this.tokenUsage.compacted(event.params.threadId, event.params.turnId);
896
+ if (["agentMessage", "plan", "reasoning"].includes(event.params.item.type))
897
+ this.emit({
898
+ type: "turnProgress",
899
+ threadId: event.params.threadId,
900
+ turnId: event.params.turnId
901
+ });
902
+ this.emit({
903
+ type: "item",
904
+ threadId: event.params.threadId,
905
+ turnId: event.params.turnId,
906
+ item: {
907
+ ...mapItem(event.params.item),
908
+ ...event.params.item.type === "contextCompaction" ? { status: event.method === "item/started" ? "inProgress" : "completed" } : {}
909
+ }
910
+ });
911
+ break;
912
+ case "item/agentMessage/delta":
913
+ case "item/plan/delta":
914
+ case "item/reasoning/summaryTextDelta":
915
+ case "item/reasoning/textDelta":
916
+ case "item/commandExecution/outputDelta":
917
+ case "item/fileChange/outputDelta":
918
+ if (!event.method.includes("outputDelta"))
919
+ this.emit({
920
+ type: "turnProgress",
921
+ threadId: event.params.threadId,
922
+ turnId: event.params.turnId
923
+ });
924
+ this.emit({
925
+ type: "delta",
926
+ threadId: event.params.threadId,
927
+ turnId: event.params.turnId,
928
+ itemId: event.params.itemId,
929
+ field: event.method.includes("outputDelta") ? "output" : "text",
930
+ delta: event.params.delta,
931
+ itemType: event.method.split("/")[1]
932
+ });
933
+ break;
934
+ case "item/reasoning/summaryPartAdded":
935
+ case "rawResponse/completed":
936
+ this.emit({
937
+ type: "turnProgress",
938
+ threadId: event.params.threadId,
939
+ turnId: event.params.turnId
940
+ });
941
+ break;
942
+ case "turn/diff/updated":
943
+ this.emit({
944
+ type: "diff",
945
+ threadId: event.params.threadId,
946
+ turnId: event.params.turnId,
947
+ diff: event.params.diff
948
+ });
949
+ break;
950
+ case "item/fileChange/patchUpdated":
951
+ this.emit({
952
+ type: "item",
953
+ threadId: event.params.threadId,
954
+ turnId: event.params.turnId,
955
+ item: mapItem({
956
+ type: "fileChange",
957
+ id: event.params.itemId,
958
+ changes: event.params.changes,
959
+ status: "inProgress"
960
+ })
961
+ });
962
+ break;
963
+ case "serverRequest/resolved": {
964
+ const actionId = this.actionIds.get(event.params.requestId);
965
+ if (actionId) {
966
+ this.emit({ type: "actionResolved", actionId });
967
+ this.actions.delete(actionId);
968
+ this.actionIds.delete(event.params.requestId);
969
+ }
970
+ break;
971
+ }
972
+ case "error":
973
+ this.emit({
974
+ type: "turnError",
975
+ threadId: event.params.threadId,
976
+ turnId: event.params.turnId,
977
+ message: event.params.error.message,
978
+ willRetry: event.params.willRetry
979
+ });
980
+ break;
981
+ default:
982
+ if (!isRecord(p)) break;
983
+ }
984
+ }
985
+ async serverRequest(message) {
986
+ if (isRecord(message.params) && this.titles?.isInternal(message.params.threadId)) {
987
+ await this.rpc?.reject(
988
+ message.id,
989
+ -32601,
990
+ "Interactive tools are disabled for title generation"
991
+ );
992
+ return;
993
+ }
994
+ const request = message;
995
+ const params = isRecord(message.params) ? message.params : {};
996
+ const action = {
997
+ actionId: randomUUID(),
998
+ codexEpoch: this.epoch,
999
+ rpcId: message.id,
1000
+ kind: "unsupported",
1001
+ status: "pending",
1002
+ title: "\u4E0D\u652F\u6301\u7684 Codex \u4EA4\u4E92\u8BF7\u6C42",
1003
+ options: [],
1004
+ ...typeof params.threadId === "string" ? { threadId: params.threadId } : {},
1005
+ ...typeof params.turnId === "string" ? { turnId: params.turnId } : {}
1006
+ };
1007
+ const decisions = /* @__PURE__ */ new Map();
1008
+ if (request.method === "item/commandExecution/requestApproval" || request.method === "item/fileChange/requestApproval") {
1009
+ for (const [id, label] of [
1010
+ ["accept", "\u5141\u8BB8\u672C\u6B21"],
1011
+ ["acceptForSession", "\u672C\u4F1A\u8BDD\u5141\u8BB8"],
1012
+ ["decline", "\u62D2\u7EDD"],
1013
+ ["cancel", "\u53D6\u6D88\u5F53\u524D\u4EFB\u52A1"]
1014
+ ]) {
1015
+ action.options.push({ id, label });
1016
+ decisions.set(id, id);
1017
+ }
1018
+ action.reason = request.params.reason ?? void 0;
1019
+ if (request.method === "item/commandExecution/requestApproval") {
1020
+ const p = request.params;
1021
+ action.kind = p.networkApprovalContext ? "network" : "command";
1022
+ action.title = p.networkApprovalContext ? `\u7F51\u7EDC\u6743\u9650\uFF1A${p.networkApprovalContext.protocol}://${p.networkApprovalContext.host}` : "\u547D\u4EE4\u5BA1\u6279";
1023
+ action.command = p.command ?? void 0;
1024
+ action.cwd = p.cwd ?? void 0;
1025
+ action.details = {
1026
+ kind: p.kind,
1027
+ network: p.networkApprovalContext,
1028
+ commandActions: p.commandActions
1029
+ };
1030
+ if (p.proposedExecpolicyAmendment) {
1031
+ action.options.push({
1032
+ id: "acceptWithRule",
1033
+ label: `\u5141\u8BB8\u5E76\u8BB0\u4F4F\u547D\u4EE4\u89C4\u5219\uFF1A${p.proposedExecpolicyAmendment.join(" ")}`
1034
+ });
1035
+ decisions.set("acceptWithRule", {
1036
+ acceptWithExecpolicyAmendment: { execpolicy_amendment: p.proposedExecpolicyAmendment }
1037
+ });
1038
+ }
1039
+ p.proposedNetworkPolicyAmendments?.forEach((amendment, index) => {
1040
+ const id = `networkRule:${index}`;
1041
+ action.options.push({ id, label: `\u5E94\u7528\u7F51\u7EDC\u89C4\u5219\uFF1A${JSON.stringify(amendment)}` });
1042
+ decisions.set(id, {
1043
+ applyNetworkPolicyAmendment: { network_policy_amendment: amendment }
1044
+ });
1045
+ });
1046
+ } else {
1047
+ action.kind = "file";
1048
+ action.title = "\u6587\u4EF6\u53D8\u66F4\u5BA1\u6279";
1049
+ action.details = { itemId: request.params.itemId, grantRoot: request.params.grantRoot };
1050
+ }
1051
+ } else if (request.method === "item/permissions/requestApproval") {
1052
+ const p = request.params;
1053
+ action.kind = "permissions";
1054
+ action.title = "\u989D\u5916\u6743\u9650\u5BA1\u6279";
1055
+ action.reason = p.reason ?? void 0;
1056
+ action.cwd = p.cwd;
1057
+ action.options = [
1058
+ { id: "accept", label: "\u5F53\u524D\u4EFB\u52A1\u5141\u8BB8" },
1059
+ { id: "acceptForSession", label: "\u672C\u4F1A\u8BDD\u5141\u8BB8" },
1060
+ { id: "decline", label: "\u62D2\u7EDD" }
1061
+ ];
1062
+ action.permissionRequest = {
1063
+ ...p.permissions.network === null ? {} : { network: p.permissions.network.enabled },
1064
+ ...p.permissions.fileSystem === null ? {} : {
1065
+ fileSystem: {
1066
+ read: p.permissions.fileSystem.read ?? [],
1067
+ write: p.permissions.fileSystem.write ?? [],
1068
+ entries: (p.permissions.fileSystem.entries ?? []).map((entry) => ({
1069
+ path: permissionPathLabel(entry.path),
1070
+ access: entry.access
1071
+ })),
1072
+ ...p.permissions.fileSystem.globScanMaxDepth === void 0 ? {} : {
1073
+ globScanMaxDepth: p.permissions.fileSystem.globScanMaxDepth
1074
+ }
1075
+ }
1076
+ }
1077
+ };
1078
+ action.details = {
1079
+ itemId: p.itemId,
1080
+ environmentId: p.environmentId,
1081
+ permissions: p.permissions
1082
+ };
1083
+ } else if (request.method === "mcpServer/elicitation/request") {
1084
+ action.kind = "elicitation";
1085
+ action.reason = request.params.message;
1086
+ action.elicitation = normalizeElicitationRequest(params);
1087
+ action.title = `${action.elicitation.approval ? "MCP \u5DE5\u5177\u5BA1\u6279" : "MCP \u4EA4\u4E92"}\uFF1A${request.params.serverName}`;
1088
+ action.options = [
1089
+ {
1090
+ id: "accept",
1091
+ label: action.elicitation.approval ? "\u5141\u8BB8\u672C\u6B21" : action.elicitation.mode === "url" ? "\u5DF2\u5B8C\u6210\uFF0C\u7EE7\u7EED" : "\u63D0\u4EA4"
1092
+ },
1093
+ ...action.elicitation.approval?.allowSession ? [{ id: "acceptSession", label: "\u672C\u4F1A\u8BDD\u5141\u8BB8" }] : [],
1094
+ { id: "decline", label: "\u62D2\u7EDD" },
1095
+ { id: "cancel", label: "\u53D6\u6D88" }
1096
+ ];
1097
+ if (action.elicitation.schemaError)
1098
+ action.details = { requestedSchema: params.requestedSchema, mode: params.mode };
1099
+ } else if (request.method === "item/tool/requestUserInput") {
1100
+ action.kind = "questions";
1101
+ action.title = "\u9700\u8981\u4F60\u7684\u56DE\u7B54";
1102
+ action.blocking = request.params.isBlocking;
1103
+ action.questions = request.params.questions.map((question) => ({
1104
+ ...question,
1105
+ options: question.options ?? []
1106
+ }));
1107
+ } else {
1108
+ action.status = "expired";
1109
+ action.reason = `\u5F53\u524D Web UI \u5C1A\u672A\u5B9E\u73B0 ${message.method}`;
1110
+ this.emit({ type: "action", action });
1111
+ this.state.error = action.reason;
1112
+ this.publishRuntime();
1113
+ await this.rpc.reject(
1114
+ message.id,
1115
+ -32601,
1116
+ `Unsupported interactive request: ${message.method}`
1117
+ );
1118
+ return;
1119
+ }
1120
+ this.actions.set(action.actionId, { action, request, decisions, responding: false });
1121
+ this.actionIds.set(message.id, action.actionId);
1122
+ this.emit({ type: "action", action: structuredClone(action) });
1123
+ }
1124
+ async respond(actionId, resolution) {
1125
+ const context = this.actions.get(actionId);
1126
+ if (!context || context.action.codexEpoch !== this.epoch || !this.ready)
1127
+ throw new AppError("ACTION_EXPIRED", "\u6B64\u4EBA\u5DE5\u8BF7\u6C42\u5DF2\u5931\u6548", 409);
1128
+ if (context.responding) throw new AppError("ACTION_CONFLICT", "\u6B64\u4EBA\u5DE5\u8BF7\u6C42\u5DF2\u63D0\u4EA4\u56DE\u7B54", 409);
1129
+ let response;
1130
+ if (context.request.method === "item/permissions/requestApproval") {
1131
+ const decision = validatePermissionResolution(resolution);
1132
+ const requested = context.request.params.permissions;
1133
+ response = {
1134
+ permissions: decision === "decline" ? {} : structuredClone({
1135
+ ...requested.network === null ? {} : { network: requested.network },
1136
+ ...requested.fileSystem === null ? {} : { fileSystem: requested.fileSystem }
1137
+ }),
1138
+ scope: decision === "acceptForSession" ? "session" : "turn"
1139
+ };
1140
+ } else if (context.request.method === "mcpServer/elicitation/request") {
1141
+ const content = validateElicitationResolution(
1142
+ normalizeElicitationRequest({ ...context.request.params }),
1143
+ resolution
1144
+ );
1145
+ response = {
1146
+ action: resolution.decision,
1147
+ content,
1148
+ _meta: "decision" in resolution && resolution.persist === "session" ? { persist: "session" } : null
1149
+ };
1150
+ } else if (context.request.method === "item/tool/requestUserInput") {
1151
+ if (!("answers" in resolution))
1152
+ throw new AppError("VALIDATION_ERROR", "\u6B64\u8BF7\u6C42\u9700\u8981\u7ED3\u6784\u5316\u56DE\u7B54");
1153
+ const questions = context.request.params.questions;
1154
+ const ids = new Set(questions.map((question) => question.id));
1155
+ if (Object.keys(resolution.answers).some((id) => !ids.has(id)))
1156
+ throw new AppError("VALIDATION_ERROR", "\u56DE\u7B54\u5305\u542B\u672A\u77E5\u9898\u76EE");
1157
+ const answers = {};
1158
+ for (const question of questions) {
1159
+ const values = resolution.answers[question.id];
1160
+ if (!Array.isArray(values) || !values.length || values.some((value) => typeof value !== "string" || !value.trim()))
1161
+ throw new AppError("VALIDATION_ERROR", `\u8BF7\u56DE\u7B54 ${question.header}`);
1162
+ if (question.options?.length && !question.isOther && values.some((value) => !question.options.some((option) => option.label === value)))
1163
+ throw new AppError("VALIDATION_ERROR", `\u9898\u76EE ${question.header} \u53EA\u63A5\u53D7\u6240\u5217\u9009\u9879`);
1164
+ answers[question.id] = { answers: values };
1165
+ }
1166
+ response = { answers };
1167
+ } else {
1168
+ if (!("decision" in resolution) || "content" in resolution || "persist" in resolution || !context.decisions.has(resolution.decision))
1169
+ throw new AppError("VALIDATION_ERROR", "\u6B64\u8BF7\u6C42\u4E0D\u652F\u6301\u6240\u63D0\u4EA4\u7684\u51B3\u5B9A");
1170
+ response = { decision: context.decisions.get(resolution.decision) };
1171
+ }
1172
+ context.responding = true;
1173
+ try {
1174
+ await this.rpc.respond(context.action.rpcId, response);
1175
+ } catch (error) {
1176
+ if (error instanceof CodexError && !error.uncertain) context.responding = false;
1177
+ throw error;
1178
+ }
1179
+ }
1180
+ expireActions(threadId, turnId) {
1181
+ for (const [id, context] of this.actions) {
1182
+ if (threadId && context.action.threadId !== threadId) continue;
1183
+ if (turnId && context.action.turnId !== turnId) continue;
1184
+ this.emit({ type: "action", action: { ...context.action, status: "expired" } });
1185
+ this.actions.delete(id);
1186
+ this.actionIds.delete(context.action.rpcId);
1187
+ }
1188
+ }
1189
+ }
1190
+ export {
1191
+ CodexAdapter,
1192
+ CodexError2 as CodexError,
1193
+ VERIFIED_CODEX_VERSION
1194
+ };
1195
+ //# sourceMappingURL=adapter.js.map