@bitkyc08/opencodex 2.35.0 → 2.36.0-preview.20260830

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 (155) hide show
  1. package/gui/dist/assets/index-Cy7Z_pl0.css +1 -0
  2. package/gui/dist/assets/index-DPl4nBMA.js +112 -0
  3. package/gui/dist/index.html +2 -2
  4. package/package.json +2 -1
  5. package/src/AGENTS.md +2 -1
  6. package/src/adapters/agentrouter.ts +50 -0
  7. package/src/adapters/anthropic.ts +1 -51
  8. package/src/adapters/cursor/call-id.ts +76 -8
  9. package/src/adapters/cursor/checkpoint-store.ts +6 -1
  10. package/src/adapters/cursor/cursor-errors.ts +44 -0
  11. package/src/adapters/cursor/native-exec.ts +13 -0
  12. package/src/adapters/cursor/protobuf-request.ts +651 -29
  13. package/src/adapters/cursor/tool-result-normalize.ts +3 -3
  14. package/src/adapters/cursor/transport-retry.ts +5 -1
  15. package/src/adapters/cursor.ts +15 -1
  16. package/src/adapters/empty-tool-output-annotation.ts +43 -0
  17. package/src/adapters/exec-tool-result-normalize.ts +70 -5
  18. package/src/adapters/google.ts +22 -2
  19. package/src/adapters/kiro.ts +26 -2
  20. package/src/adapters/ollama-native-url.ts +111 -0
  21. package/src/adapters/ollama-native.ts +1131 -0
  22. package/src/adapters/openai-chat.ts +30 -7
  23. package/src/adapters/openai-responses.ts +72 -4
  24. package/src/adapters/registry.ts +7 -0
  25. package/src/adapters/xai-web-search.ts +58 -0
  26. package/src/claude/desktop-3p.ts +21 -1
  27. package/src/claude/desktop-policy.ts +149 -0
  28. package/src/cli/account.ts +16 -2
  29. package/src/cli/claude-desktop.ts +13 -3
  30. package/src/cli/combo.ts +8 -5
  31. package/src/cli/doctor.ts +77 -11
  32. package/src/cli/help.ts +1 -1
  33. package/src/cli/index.ts +16 -0
  34. package/src/cli/models.ts +20 -3
  35. package/src/cli/registry.ts +2 -1
  36. package/src/cli/status.ts +140 -2
  37. package/src/cli/storage.ts +10 -1
  38. package/src/codex/account-runtime-state.ts +39 -5
  39. package/src/codex/account-store.ts +393 -13
  40. package/src/codex/account-usability.ts +11 -4
  41. package/src/codex/app-server-processes.ts +46 -5
  42. package/src/codex/auth-context.ts +160 -32
  43. package/src/codex/catalog/bundled.ts +7 -5
  44. package/src/codex/catalog/metadata.ts +1 -1
  45. package/src/codex/catalog/parsing.ts +57 -1
  46. package/src/codex/catalog/provider-fetch.ts +61 -4
  47. package/src/codex/catalog/sync.ts +4 -3
  48. package/src/codex/convergence.ts +3 -2
  49. package/src/codex/data/upstream-models.json +40 -8
  50. package/src/codex/inject-coordination.ts +111 -14
  51. package/src/codex/integration-record.ts +12 -2
  52. package/src/codex/main-account.ts +225 -1
  53. package/src/codex/model-entitlements.ts +339 -27
  54. package/src/codex/prompt-layers.ts +346 -7
  55. package/src/codex/prompt-text-probe.ts +272 -21
  56. package/src/codex/routing.ts +693 -132
  57. package/src/codex/runtime.ts +12 -0
  58. package/src/codex/subagent-model-fallback.ts +62 -24
  59. package/src/codex/user-identity.ts +33 -25
  60. package/src/combos/index.ts +1 -0
  61. package/src/combos/reset-window.ts +46 -0
  62. package/src/combos/resolve.ts +84 -2
  63. package/src/combos/types.ts +5 -2
  64. package/src/config/atomic-write.ts +104 -22
  65. package/src/config/provider-validation.ts +11 -0
  66. package/src/config.ts +75 -3
  67. package/src/generated/compatibility-version.json +207 -131
  68. package/src/generated/model-metadata.ts +1 -1
  69. package/src/grok/catalog.ts +71 -0
  70. package/src/grok/effort.ts +83 -0
  71. package/src/grok/inject.ts +952 -127
  72. package/src/grok/models.ts +56 -0
  73. package/src/grok/status.ts +21 -8
  74. package/src/grok/sync.ts +10 -18
  75. package/src/images/loop.ts +6 -3
  76. package/src/integrations/native/ownership-preflight.ts +4 -1
  77. package/src/lab/fabric/producer-isolate.ts +36 -3
  78. package/src/lib/destination-policy.ts +93 -7
  79. package/src/lib/redact.ts +6 -1
  80. package/src/lib/shadow-call.ts +38 -3
  81. package/src/lib/test-home-guard.ts +18 -3
  82. package/src/lib/upstream-retry.ts +43 -6
  83. package/src/lib/windows-secret-acl.ts +66 -0
  84. package/src/lib/windows-text.ts +28 -2
  85. package/src/lib/windows-user-principal.ts +35 -23
  86. package/src/oauth/account-quota-rank.ts +107 -0
  87. package/src/oauth/anthropic-routing.ts +125 -30
  88. package/src/oauth/chatgpt.ts +5 -1
  89. package/src/oauth/generic-account-failover.ts +114 -7
  90. package/src/oauth/index.ts +15 -8
  91. package/src/oauth/store.ts +16 -0
  92. package/src/providers/account-quota-disk.ts +79 -0
  93. package/src/providers/command-code-efforts.ts +24 -0
  94. package/src/providers/derive.ts +6 -0
  95. package/src/providers/key-failover.ts +33 -1
  96. package/src/providers/kiro-usage.ts +272 -0
  97. package/src/providers/ollama-show.ts +311 -0
  98. package/src/providers/openai-sidecar.ts +5 -0
  99. package/src/providers/quota-routing-cache.ts +32 -0
  100. package/src/providers/quota-types.ts +36 -0
  101. package/src/providers/quota-wire.ts +102 -0
  102. package/src/providers/quota.ts +208 -147
  103. package/src/providers/registry.ts +68 -8
  104. package/src/providers/slug-codec.ts +12 -4
  105. package/src/providers/vercel-gateway-routing.ts +108 -0
  106. package/src/router.ts +22 -12
  107. package/src/server/auth-cors.ts +26 -0
  108. package/src/server/catalog-download.ts +73 -0
  109. package/src/server/chat-native.ts +12 -2
  110. package/src/server/gui-static.ts +4 -1
  111. package/src/server/index.ts +132 -9
  112. package/src/server/management/agent-settings-routes.ts +38 -5
  113. package/src/server/management/codex-prompt-routes.ts +7 -1
  114. package/src/server/management/combo-routes.ts +10 -1
  115. package/src/server/management/config-routes.ts +9 -1
  116. package/src/server/management/context.ts +5 -0
  117. package/src/server/management/model-routes.ts +16 -6
  118. package/src/server/management/native-integration-routes.ts +12 -17
  119. package/src/server/management/oauth-account-routes.ts +13 -0
  120. package/src/server/management/provider-routes.ts +32 -5
  121. package/src/server/management/routing-profile-routes.ts +15 -0
  122. package/src/server/management/shadow-call-validation.ts +29 -0
  123. package/src/server/management-api.ts +7 -3
  124. package/src/server/request-log.ts +3 -5
  125. package/src/server/responses/agent-task-recovery-cache.ts +8 -0
  126. package/src/server/responses/agent-task-recovery.ts +52 -20
  127. package/src/server/responses/codex-auth-error.ts +26 -0
  128. package/src/server/responses/compact.ts +345 -10
  129. package/src/server/responses/core.ts +736 -108
  130. package/src/server/responses/empty-completion-guard.ts +16 -0
  131. package/src/server/responses/fetch-helpers.ts +42 -0
  132. package/src/server/responses/policy-fallback.ts +11 -6
  133. package/src/server/responses-undeclared-tool-guard.ts +16 -3
  134. package/src/server/startup-health-cache.ts +59 -13
  135. package/src/service-manager-probe.ts +115 -9
  136. package/src/service.ts +139 -40
  137. package/src/storage/cleanup.ts +10 -0
  138. package/src/storage/storage-mutation-coordinator.ts +14 -3
  139. package/src/tray/windows-tray.ps1 +10 -4
  140. package/src/tray/windows.ts +30 -2
  141. package/src/types/config.ts +27 -14
  142. package/src/types/provider.ts +54 -0
  143. package/src/types/tools.ts +13 -3
  144. package/src/types.ts +4 -0
  145. package/src/usage/summary.ts +421 -177
  146. package/src/vision/anthropic-describe.ts +3 -3
  147. package/src/vision/describe.ts +5 -3
  148. package/src/web-search/anthropic-executor.ts +9 -2
  149. package/src/web-search/exa-executor.ts +3 -3
  150. package/src/web-search/executor.ts +8 -3
  151. package/src/web-search/gemini-executor.ts +3 -3
  152. package/src/web-search/loop.ts +11 -3
  153. package/src/web-search/xai-executor.ts +3 -3
  154. package/gui/dist/assets/index-DNdRKXK9.js +0 -112
  155. package/gui/dist/assets/index-DQ-Ie18T.css +0 -1
@@ -100,41 +100,236 @@ function resolveCodexBinary(): string | null {
100
100
  /** 8 MiB is far above any real prompt and far below anything that hurts the server. */
101
101
  const MAX_PROBE_OUTPUT_BYTES = 8 * 1024 * 1024;
102
102
 
103
- function runProbe(binary: string, cwd: string, timeoutMs: number): Promise<string | null> {
104
- return new Promise(resolve => {
103
+ interface ProbeCommand {
104
+ binary: string;
105
+ args: string[];
106
+ cwd: string;
107
+ timeoutMs: number;
108
+ promptStateFingerprint: string | null;
109
+ }
110
+
111
+ interface PromptProbeFlight {
112
+ key: string;
113
+ controller: AbortController;
114
+ result: Promise<string | null>;
115
+ closed: Promise<void>;
116
+ waiters: number;
117
+ joinable: boolean;
118
+ resultSettled: boolean;
119
+ settled: boolean;
120
+ }
121
+
122
+ interface PromptProbeExecution {
123
+ result: Promise<string | null>;
124
+ closed: Promise<void>;
125
+ }
126
+
127
+ type SharedPromptProbeOutcome =
128
+ | { kind: "output"; raw: string }
129
+ | { kind: "failed" }
130
+ | { kind: "busy" };
131
+
132
+ let activePromptProbe: PromptProbeFlight | null = null;
133
+ let probeCommandForTests: { binary: string; args: string[] } | null = null;
134
+ let probeSpawnAttemptsForTests = 0;
135
+ let probeCloseBarrierForTests: Promise<void> | null = null;
136
+
137
+ function commandKey(command: ProbeCommand): string {
138
+ return JSON.stringify([
139
+ command.binary,
140
+ command.args,
141
+ command.cwd,
142
+ command.timeoutMs,
143
+ command.promptStateFingerprint,
144
+ ]);
145
+ }
146
+
147
+ function completedExecution(value: string | null): PromptProbeExecution {
148
+ return { result: Promise.resolve(value), closed: Promise.resolve() };
149
+ }
150
+
151
+ function runProbe(
152
+ command: ProbeCommand,
153
+ signal: AbortSignal,
154
+ onStopping: () => void,
155
+ ): PromptProbeExecution {
156
+ if (signal.aborted) return completedExecution(null);
157
+ let resolveResult!: (value: string | null) => void;
158
+ let resolveClosed!: () => void;
159
+ const result = new Promise<string | null>(resolve => { resolveResult = resolve; });
160
+ const closed = new Promise<void>(resolve => { resolveClosed = resolve; });
161
+ let resultSettled = false;
162
+ let closeSettled = false;
163
+
164
+ const finishResult = (value: string | null) => {
165
+ if (resultSettled) return;
166
+ resultSettled = true;
167
+ resolveResult(value);
168
+ };
169
+ const finishClosed = () => {
170
+ if (closeSettled) return;
171
+ closeSettled = true;
172
+ resolveClosed();
173
+ };
174
+
175
+ try {
105
176
  // A probe must never hang OR balloon the management API: it is bounded in
106
177
  // time AND in bytes, and every failure degrades to "unavailable" rather than
107
178
  // an error page.
108
- const child = spawn(binary, ["debug", "prompt-input"], {
109
- cwd,
110
- stdio: ["ignore", "pipe", "ignore"],
111
- });
179
+ let child: ReturnType<typeof spawn>;
180
+ try {
181
+ if (probeCommandForTests) probeSpawnAttemptsForTests += 1;
182
+ child = spawn(command.binary, command.args, {
183
+ cwd: command.cwd,
184
+ stdio: ["ignore", "pipe", "ignore"],
185
+ });
186
+ } catch {
187
+ finishResult(null);
188
+ finishClosed();
189
+ return { result, closed };
190
+ }
112
191
  const chunks: Buffer[] = [];
113
192
  let size = 0;
114
193
  let settled = false;
115
- // One settlement path: a timeout that resolved before `close` used to leave
116
- // the child streaming into a buffer nobody would ever read.
117
- const settle = (value: string | null) => {
194
+ let stopping = false;
195
+ let timer: ReturnType<typeof setTimeout> | undefined;
196
+
197
+ const finish = (value: string | null) => {
118
198
  if (settled) return;
119
199
  settled = true;
120
- clearTimeout(timer);
200
+ if (timer) clearTimeout(timer);
201
+ signal.removeEventListener("abort", onAbort);
202
+ finishResult(value);
203
+ finishClosed();
204
+ };
205
+
206
+ // Keep the flight admitted until `close`: kill() only requests termination
207
+ // and does not prove the exact child has released its process and stdio.
208
+ const terminate = () => {
209
+ if (settled || stopping) return;
210
+ stopping = true;
211
+ onStopping();
212
+ if (timer) clearTimeout(timer);
213
+ signal.removeEventListener("abort", onAbort);
121
214
  child.stdout?.destroy();
122
- if (child.exitCode === null && child.signalCode === null) child.kill("SIGKILL");
123
- resolve(value);
215
+ // The caller is bounded even if OS termination later fails. Admission is
216
+ // retained separately by `closed`, and later probes fail soft while this
217
+ // exact child remains unproven terminal.
218
+ finishResult(null);
219
+ if (child.exitCode !== null || child.signalCode !== null) return;
220
+ try {
221
+ child.kill("SIGKILL");
222
+ } catch {
223
+ // Exact child state is ambiguous. Keep admission non-joinable until its
224
+ // own `close` proves terminal instead of targeting a reusable numeric PID.
225
+ }
124
226
  };
125
- const timer = setTimeout(() => settle(null), timeoutMs);
227
+ const onAbort = () => terminate();
228
+
229
+ timer = setTimeout(terminate, command.timeoutMs);
230
+ signal.addEventListener("abort", onAbort, { once: true });
126
231
  child.stdout?.on("data", (chunk: Buffer) => {
127
232
  size += chunk.length;
128
- if (size > MAX_PROBE_OUTPUT_BYTES) { settle(null); return; }
233
+ if (size > MAX_PROBE_OUTPUT_BYTES) { terminate(); return; }
129
234
  chunks.push(chunk);
130
235
  });
131
- child.on("error", () => settle(null));
236
+ child.on("error", () => {
237
+ // No PID means spawn itself failed, so there is no live child to drain.
238
+ if (child.pid === undefined) {
239
+ finish(null);
240
+ }
241
+ else terminate();
242
+ });
132
243
  child.on("close", code => {
133
244
  // Decode once, at the end: `String(chunk)` per chunk corrupts any UTF-8
134
245
  // character that straddles a chunk boundary.
135
- settle(code === 0 ? Buffer.concat(chunks).toString("utf8") : null);
246
+ const recordClose = () => {
247
+ finish(!stopping && code === 0 ? Buffer.concat(chunks).toString("utf8") : null);
248
+ };
249
+ const barrier = probeCloseBarrierForTests;
250
+ if (barrier) void barrier.then(recordClose, recordClose);
251
+ else recordClose();
136
252
  });
253
+ // Close the race between the pre-spawn check and listener registration.
254
+ if (signal.aborted) terminate();
255
+ } catch {
256
+ finishResult(null);
257
+ finishClosed();
258
+ }
259
+ return { result, closed };
260
+ }
261
+
262
+ function startPromptProbeFlight(command: ProbeCommand): PromptProbeFlight {
263
+ const controller = new AbortController();
264
+ const flight: PromptProbeFlight = {
265
+ key: commandKey(command),
266
+ controller,
267
+ result: Promise.resolve(null),
268
+ closed: Promise.resolve(),
269
+ waiters: 0,
270
+ joinable: true,
271
+ resultSettled: false,
272
+ settled: false,
273
+ };
274
+ const execution = runProbe(command, controller.signal, () => {
275
+ flight.joinable = false;
137
276
  });
277
+ flight.result = execution.result
278
+ .catch(() => null)
279
+ .finally(() => {
280
+ flight.resultSettled = true;
281
+ });
282
+ flight.closed = execution.closed
283
+ .finally(() => {
284
+ flight.settled = true;
285
+ if (activePromptProbe === flight) activePromptProbe = null;
286
+ });
287
+ activePromptProbe = flight;
288
+ return flight;
289
+ }
290
+
291
+ async function runSharedPromptProbe(
292
+ command: ProbeCommand,
293
+ signal?: AbortSignal,
294
+ ): Promise<SharedPromptProbeOutcome> {
295
+ const key = commandKey(command);
296
+ if (signal?.aborted) return { kind: "failed" };
297
+ const active = activePromptProbe;
298
+ if (!active) {
299
+ const raw = await waitForPromptProbeFlight(startPromptProbeFlight(command), signal);
300
+ return raw === null ? { kind: "failed" } : { kind: "output", raw };
301
+ }
302
+ if (active.key === key && active.joinable && !active.controller.signal.aborted) {
303
+ const raw = await waitForPromptProbeFlight(active, signal);
304
+ return raw === null ? { kind: "failed" } : { kind: "output", raw };
305
+ }
306
+ // A different or terminating flight still owns the sole process slot. Never
307
+ // wait unboundedly for an unproven close and never launch beside it.
308
+ return { kind: "busy" };
309
+ }
310
+
311
+ async function waitForPromptProbeFlight(flight: PromptProbeFlight, signal?: AbortSignal): Promise<string | null> {
312
+ if (signal?.aborted) {
313
+ if (flight.waiters === 0 && !flight.settled) flight.controller.abort();
314
+ return null;
315
+ }
316
+ flight.waiters += 1;
317
+ let onAbort: (() => void) | undefined;
318
+ try {
319
+ if (!signal) return await flight.result;
320
+ const aborted = new Promise<null>(resolve => {
321
+ onAbort = () => resolve(null);
322
+ signal.addEventListener("abort", onAbort, { once: true });
323
+ if (signal.aborted) onAbort();
324
+ });
325
+ return await Promise.race([flight.result, aborted]);
326
+ } finally {
327
+ if (onAbort) signal?.removeEventListener("abort", onAbort);
328
+ flight.waiters = Math.max(0, flight.waiters - 1);
329
+ if (flight.waiters === 0 && !flight.resultSettled) {
330
+ flight.controller.abort(new DOMException("All prompt probe callers cancelled", "AbortError"));
331
+ }
332
+ }
138
333
  }
139
334
 
140
335
  /** Pull every `<tag>...</tag>` section out of the rendered developer messages. */
@@ -182,20 +377,44 @@ export const extractSectionsForTests = extractSections;
182
377
  * `cwd` matters: AGENTS.md and environment context are directory-dependent, so a
183
378
  * probe from the wrong place would describe a prompt the user never sees.
184
379
  */
185
- export async function probePromptText(timeoutMs = 15_000): Promise<PromptTextProbe> {
380
+ export async function probePromptText(
381
+ timeoutMs = 15_000,
382
+ signal?: AbortSignal,
383
+ promptStateFingerprint: string | null = null,
384
+ ): Promise<PromptTextProbe> {
186
385
  // The probe runs in CODEX_HOME, never in a caller-supplied directory. A `cwd`
187
386
  // parameter let an authenticated request read any readable folder's AGENTS.md,
188
387
  // and it also described a prompt that depends on where Codex happened to run.
189
388
  // The global home is the one context this page can honestly report on.
190
389
  const codexHome = resolveCodexHomeDir();
191
- const binary = resolveCodexBinary();
390
+ if (signal?.aborted) {
391
+ return { ok: false, codexHome, layers: {}, detail: "prompt probe cancelled" };
392
+ }
393
+ const binary = probeCommandForTests?.binary ?? resolveCodexBinary();
192
394
  if (!binary) {
193
395
  return { ok: false, codexHome, layers: {}, detail: "codex binary not found" };
194
396
  }
195
- const raw = await runProbe(binary, codexHome, timeoutMs);
196
- if (raw === null) {
197
- return { ok: false, codexHome, layers: {}, detail: "codex debug prompt-input failed" };
397
+ const command: ProbeCommand = {
398
+ binary,
399
+ args: probeCommandForTests?.args ?? ["debug", "prompt-input"],
400
+ cwd: codexHome,
401
+ timeoutMs,
402
+ promptStateFingerprint,
403
+ };
404
+ const outcome = await runSharedPromptProbe(command, signal);
405
+ if (outcome.kind !== "output") {
406
+ return {
407
+ ok: false,
408
+ codexHome,
409
+ layers: {},
410
+ detail: signal?.aborted
411
+ ? "prompt probe cancelled"
412
+ : outcome.kind === "busy"
413
+ ? "another prompt probe is still finishing; retry shortly"
414
+ : "codex debug prompt-input failed",
415
+ };
198
416
  }
417
+ const raw = outcome.raw;
199
418
  const sections = extractSections(raw);
200
419
  if (sections.size === 0) {
201
420
  // Zero sections from a zero-exit probe means the output did not parse, which
@@ -236,3 +455,35 @@ export async function probePromptText(timeoutMs = 15_000): Promise<PromptTextPro
236
455
  }
237
456
  return { ok: true, codexHome, layers };
238
457
  }
458
+
459
+ /** Test-only command seam; production always resolves the installed Codex binary. */
460
+ export function setPromptTextProbeCommandForTests(command: { binary: string; args: string[] } | null): void {
461
+ probeCommandForTests = command ? { binary: command.binary, args: [...command.args] } : null;
462
+ }
463
+
464
+ /** Test-only process-start counter for proving admission without timing guesses. */
465
+ export function promptTextProbeSpawnAttemptsForTests(): number {
466
+ return probeSpawnAttemptsForTests;
467
+ }
468
+
469
+ /** Test-only close barrier for proving admission is not released at process exit. */
470
+ export function setPromptTextProbeCloseBarrierForTests(barrier: Promise<void> | null): void {
471
+ probeCloseBarrierForTests = barrier;
472
+ }
473
+
474
+ /** Test-only fail-closed drain so one failed lifecycle case cannot poison another. */
475
+ export async function resetPromptTextProbeForTests(): Promise<void> {
476
+ const active = activePromptProbe;
477
+ if (active && !active.settled) {
478
+ active.controller.abort(new DOMException("Prompt probe test reset", "AbortError"));
479
+ const drained = await Promise.race([
480
+ active.closed.then(() => true),
481
+ Bun.sleep(2_000).then(() => false),
482
+ ]);
483
+ if (!drained) throw new Error("prompt probe child did not close during test reset");
484
+ }
485
+ if (activePromptProbe === active) activePromptProbe = null;
486
+ probeCommandForTests = null;
487
+ probeSpawnAttemptsForTests = 0;
488
+ probeCloseBarrierForTests = null;
489
+ }