@bitkyc08/opencodex 2.29.0 → 2.31.0-preview.20260822

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 (68) hide show
  1. package/README.md +5 -5
  2. package/gui/dist/assets/index-DyWYnr-t.js +102 -0
  3. package/gui/dist/index.html +1 -1
  4. package/package.json +3 -3
  5. package/src/adapters/cursor/cursor-errors.ts +65 -6
  6. package/src/adapters/cursor/discovery.ts +29 -2
  7. package/src/adapters/cursor/effort-map.ts +6 -0
  8. package/src/adapters/cursor/h2-pool.ts +123 -0
  9. package/src/adapters/cursor/images.ts +704 -0
  10. package/src/adapters/cursor/live-models.ts +21 -26
  11. package/src/adapters/cursor/live-transport.ts +239 -8
  12. package/src/adapters/cursor/native-exec-common.ts +17 -0
  13. package/src/adapters/cursor/native-exec.ts +9 -4
  14. package/src/adapters/cursor/protobuf-events.ts +5 -1
  15. package/src/adapters/cursor/protobuf-request.ts +46 -9
  16. package/src/adapters/cursor/request-builder.ts +29 -14
  17. package/src/adapters/cursor/tool-definitions.ts +20 -0
  18. package/src/adapters/cursor/transport.ts +10 -0
  19. package/src/adapters/cursor/types.ts +8 -1
  20. package/src/adapters/cursor.ts +23 -5
  21. package/src/adapters/google.ts +16 -3
  22. package/src/adapters/openai-responses.ts +66 -20
  23. package/src/adapters/xai-web-search.ts +185 -0
  24. package/src/cli/agent.ts +2 -1
  25. package/src/cli/dispatch.ts +2 -2
  26. package/src/cli/doctor.ts +89 -0
  27. package/src/cli/help.ts +2 -0
  28. package/src/cli/registry.ts +7 -2
  29. package/src/codex/auth-context.ts +41 -2
  30. package/src/codex/catalog/effort.ts +1 -1
  31. package/src/codex/catalog/parsing.ts +2 -0
  32. package/src/codex/catalog/provider-fetch.ts +20 -5
  33. package/src/codex/coordinator-doctor.ts +332 -0
  34. package/src/codex/features.ts +58 -0
  35. package/src/codex/inject-coordination.ts +39 -6
  36. package/src/codex/transition-state.ts +12 -12
  37. package/src/generated/compatibility-version.json +86 -58
  38. package/src/lib/bun-stream-caps.ts +7 -4
  39. package/src/lib/errors.ts +8 -2
  40. package/src/oauth/cursor.ts +21 -0
  41. package/src/providers/command-code-efforts.ts +7 -0
  42. package/src/providers/cursor-pool.ts +72 -0
  43. package/src/providers/derive.ts +3 -0
  44. package/src/providers/fastwire.ts +12 -1
  45. package/src/providers/openai-sidecar.ts +1 -0
  46. package/src/providers/quota.ts +98 -25
  47. package/src/providers/registry.ts +115 -10
  48. package/src/providers/service-tier.ts +22 -7
  49. package/src/responses/custom-tool-compat.ts +24 -8
  50. package/src/responses/namespace-tool-compat.ts +2 -3
  51. package/src/router.ts +3 -0
  52. package/src/server/chat-completions.ts +4 -0
  53. package/src/server/chat-native.ts +20 -0
  54. package/src/server/management/agent-settings-routes.ts +16 -5
  55. package/src/server/management/config-routes.ts +25 -5
  56. package/src/server/management/vision-sidecar-options.ts +54 -19
  57. package/src/server/responses/compact.ts +1 -2
  58. package/src/server/responses/core.ts +54 -13
  59. package/src/service.ts +122 -14
  60. package/src/types/config.ts +9 -3
  61. package/src/types/provider.ts +6 -0
  62. package/src/usage/cost.ts +52 -38
  63. package/src/usage/expected-prices.ts +79 -9
  64. package/src/vision/backends.ts +97 -0
  65. package/src/vision/eligibility.ts +43 -22
  66. package/src/vision/index.ts +73 -5
  67. package/src/vision/routed-describe.ts +175 -0
  68. package/gui/dist/assets/index-BNESwCzn.js +0 -102
@@ -155,6 +155,29 @@ export function multiAgentV2EnabledFromConfigText(content: string | null): boole
155
155
  return false;
156
156
  }
157
157
 
158
+ // Bun 1.4 enforces TOML's "value must begin on the assignment line" rule that
159
+ // 1.3.14 did not, so `hint =` followed by `[` on the next line now fails the
160
+ // real parse and reaches the line-based fallback below — which reads that `[`
161
+ // as a table header and truncates the table before `enabled`. Codex's own
162
+ // parser accepts the document, so answering "disabled" would report a parser
163
+ // disagreement as a feature state (#1295, #1691).
164
+ //
165
+ // Joining a dangling `=` to the line that follows is the smallest repair that
166
+ // keeps the scanner untouched: widening `tomlTableBody` to be string-aware is
167
+ // what previously broke `getAgentsEnabled`, `getAgentsMaxDepth`, and
168
+ // `getMaxConcurrentThreads` (see its comment). If the joined document parses,
169
+ // that answer is authoritative; if it does not, nothing is lost.
170
+ const joined = joinDanglingTomlAssignments(content);
171
+ if (joined !== content) {
172
+ const reparsed = parsedTomlTable(joined, "features");
173
+ if (reparsed !== null) {
174
+ const table = plainTomlRecord(reparsed.multi_agent_v2);
175
+ if (table !== null) return table.enabled === true;
176
+ if (typeof reparsed.multi_agent_v2 === "boolean") return reparsed.multi_agent_v2;
177
+ return false;
178
+ }
179
+ }
180
+
158
181
  const table = tomlTableBody(content, "features.multi_agent_v2");
159
182
  if (table !== null) {
160
183
  const enabled = tomlBoolInBody(table, "enabled");
@@ -184,6 +207,41 @@ function plainTomlRecord(value: unknown): Record<string, unknown> | null {
184
207
  : null;
185
208
  }
186
209
 
210
+ /**
211
+ * Join `key =` to the following line when the value was written on the next
212
+ * line, so a parser enforcing TOML's same-line rule can read the document.
213
+ *
214
+ * Bun 1.3.14 accepted this shape; Bun 1.4 rejects it, correctly — TOML requires
215
+ * the value to begin on the assignment line. Codex's parser still accepts it, so
216
+ * this exists to keep the two readers agreeing rather than to endorse the shape.
217
+ *
218
+ * Deliberately narrow: it only acts on a line whose LAST non-comment character
219
+ * is `=`, which cannot occur in a valid assignment. Lines inside multi-line
220
+ * strings are left alone — a `"""` body line ending in `=` would be rewritten,
221
+ * but the result is only used when it PARSES, and the unmodified document is
222
+ * always tried first, so a wrong join cannot displace a correct read.
223
+ */
224
+ function joinDanglingTomlAssignments(content: string): string {
225
+ const lines = content.split("\n");
226
+ const out: string[] = [];
227
+ for (let i = 0; i < lines.length; i++) {
228
+ const line = lines[i]!;
229
+ // A dangling assignment: trailing `=` with nothing after it on this line.
230
+ if (/^[^#]*[^=!<>]=\s*$/.test(line) && i + 1 < lines.length) {
231
+ let j = i + 1;
232
+ // Skip blank and comment-only lines between the `=` and its value.
233
+ while (j < lines.length && /^\s*(?:#.*)?$/.test(lines[j]!)) j++;
234
+ if (j < lines.length) {
235
+ out.push(`${line.replace(/\s*$/, "")} ${lines[j]!.replace(/^\s*/, "")}`);
236
+ i = j;
237
+ continue;
238
+ }
239
+ }
240
+ out.push(line);
241
+ }
242
+ return out.join("\n");
243
+ }
244
+
187
245
  /**
188
246
  * A top-level table from a full TOML parse, or null when the document does not
189
247
  * parse. A parsed document with no such table yields `{}` rather than null: that
@@ -5,10 +5,11 @@
5
5
  * sequence it is, rather than doubling in length around the lock.
6
6
  */
7
7
  import { createHash } from "node:crypto";
8
- import { existsSync, readFileSync } from "node:fs";
8
+ import { existsSync, lstatSync, readFileSync } from "node:fs";
9
9
 
10
10
  import { atomicWriteFile } from "../config";
11
11
  import type { CodexWriteLockResult } from "./codex-write-lock";
12
+ import { inspectCodexCoordinatorPath } from "./coordinator-doctor";
12
13
  import { JOURNAL_PATH } from "./journal";
13
14
  import { CODEX_CONFIG_PATH, CODEX_PROFILE_PATH } from "./paths";
14
15
  import {
@@ -43,21 +44,51 @@ export type CodexWriteCoordinationEligibility =
43
44
  | { kind: "legacy-uncoordinated"; reason: string }
44
45
  | { kind: "refused"; reason: string };
45
46
 
47
+ /**
48
+ * A live SQLite creator exposes a zero-byte pathname before BEGIN IMMEDIATE.
49
+ * Requiring a settled filesystem age makes that scheduling window remain on
50
+ * the coordinated path while old crash remnants can use the legacy boundary.
51
+ */
52
+ export const STABLE_ZERO_BYTE_COORDINATOR_AGE_MS = 1_000;
53
+
46
54
  export function codexWriteCoordinationEligibility(deps: {
47
55
  coordinatorPath: () => string;
48
56
  residue: () => { kind: string };
49
57
  integrationRecord: () => { kind: string };
58
+ nowMs?: () => number;
50
59
  }): CodexWriteCoordinationEligibility {
51
60
  let coordinatorExists: boolean;
61
+ let coordinatorIsStableZeroByte = false;
52
62
  try {
53
- coordinatorExists = existsSync(deps.coordinatorPath());
63
+ const path = deps.coordinatorPath();
64
+ coordinatorExists = existsSync(path);
65
+ if (coordinatorExists) {
66
+ const entry = lstatSync(path);
67
+ if (entry.isFile() && !entry.isSymbolicLink() && entry.size === 0) {
68
+ const diagnostic = inspectCodexCoordinatorPath(path);
69
+ if (diagnostic.kind === "zero-byte") {
70
+ const lastIdentityChange = Math.max(diagnostic.identity.mtimeMs, diagnostic.identity.ctimeMs);
71
+ coordinatorIsStableZeroByte = (deps.nowMs?.() ?? Date.now()) - lastIdentityChange
72
+ >= STABLE_ZERO_BYTE_COORDINATOR_AGE_MS;
73
+ }
74
+ }
75
+ }
54
76
  } catch (error) {
55
77
  return { kind: "refused", reason: `the coordinator path could not be resolved: ${String(error)}` };
56
78
  }
57
79
 
58
- // An existing coordinator is authoritative, and the lock owns validating it —
59
- // including the unversioned and rowless cases it must refuse rather than adopt.
60
- if (coordinatorExists) return { kind: "coordinated" };
80
+ // Every existing coordinator remains authoritative unless it is proven to be
81
+ // an old, immutable SQLite-empty remnant. The age gate is part of that proof:
82
+ // a live creator exposes the same zero-byte pathname briefly before taking N,
83
+ // and sending that fresh file down the legacy path would bypass its lock.
84
+ // Non-empty, fresh, unsafe, changed, unversioned, and rowless files therefore
85
+ // stay coordinated and are validated/refused by the transaction owner.
86
+ //
87
+ // We do NOT initialize or adopt it here. Clean homes still enter the
88
+ // coordinated path, whose SQLite transaction safely initializes it. Routed
89
+ // or indeterminate legacy homes keep the same uncoordinated compatibility
90
+ // boundary they would have had if the remnant pathname were absent.
91
+ if (coordinatorExists && !coordinatorIsStableZeroByte) return { kind: "coordinated" };
61
92
 
62
93
  const record = deps.integrationRecord();
63
94
  if (record.kind === "invalid") {
@@ -83,7 +114,9 @@ export function codexWriteCoordinationEligibility(deps: {
83
114
  */
84
115
  return {
85
116
  kind: "legacy-uncoordinated",
86
- reason: residue.kind === "residue"
117
+ reason: coordinatorIsStableZeroByte
118
+ ? "the coordinator is a zero-byte non-authoritative remnant and this routed home has not been adopted yet"
119
+ : residue.kind === "residue"
87
120
  ? "this home was routed before write coordination existed and has not been adopted yet"
88
121
  : "the existing native Codex state could not be classified, so it cannot seed a coordinator row",
89
122
  };
@@ -37,7 +37,7 @@ import {
37
37
  samePathIdentity,
38
38
  } from "./user-identity";
39
39
 
40
- const COORDINATOR_SCHEMA_VERSION = 1;
40
+ export const CODEX_COORDINATOR_SCHEMA_VERSION = 1;
41
41
  const DURABLE_HISTORY_STATUSES = new Set(["converged", "pending", "running", "blocked", "unknown"]);
42
42
  const DURABLE_HISTORY_REASONS = new Set([
43
43
  "db-busy",
@@ -241,7 +241,7 @@ function rowToState(row: TransitionRow | null): CodexTransitionState {
241
241
  };
242
242
  }
243
243
 
244
- function readState(database: Database): CodexTransitionState {
244
+ export function readCodexCoordinatorState(database: Database): CodexTransitionState {
245
245
  const row = database.query<TransitionRow, []>(SELECT_TRANSITION_ROW).get();
246
246
  return rowToState(row);
247
247
  }
@@ -282,7 +282,7 @@ function assertInitialStateCanBeCreated(): void {
282
282
 
283
283
  function initialize(database: Database, databaseWasAbsent: boolean): void {
284
284
  const version = database.query<{ user_version: number }, []>("PRAGMA user_version").get()?.user_version;
285
- if (version !== 0 && version !== COORDINATOR_SCHEMA_VERSION) {
285
+ if (version !== 0 && version !== CODEX_COORDINATOR_SCHEMA_VERSION) {
286
286
  throw new CodexCoordinatorTransactionError("The coordinator database schema version is unsupported.");
287
287
  }
288
288
  if (!databaseWasAbsent && version === 0) {
@@ -301,8 +301,8 @@ function initialize(database: Database, databaseWasAbsent: boolean): void {
301
301
  assertInitialStateCanBeCreated();
302
302
  database.query(INITIALIZE_TRANSITION_ROW).run(new Date().toISOString());
303
303
  }
304
- if (version === 0) database.exec(`PRAGMA user_version = ${COORDINATOR_SCHEMA_VERSION}`);
305
- readState(database);
304
+ if (version === 0) database.exec(`PRAGMA user_version = ${CODEX_COORDINATOR_SCHEMA_VERSION}`);
305
+ readCodexCoordinatorState(database);
306
306
  }
307
307
 
308
308
  function createCapability(
@@ -336,7 +336,7 @@ function createCapability(
336
336
  expected.nativeGeneration,
337
337
  expected.currentTxId,
338
338
  );
339
- const state = readState(database);
339
+ const state = readCodexCoordinatorState(database);
340
340
  const update: TransitionStateUpdate = result.changes === 1
341
341
  ? { kind: "updated", state }
342
342
  : { kind: "conflict", current: state };
@@ -451,7 +451,7 @@ export function openCodexCoordinatorTransaction(finalDatabasePath: string): Code
451
451
  capability,
452
452
  expectation() {
453
453
  requireOpen();
454
- const state = readState(db);
454
+ const state = readCodexCoordinatorState(db);
455
455
  return {
456
456
  nativeBefore: state.nativeGeneration,
457
457
  nativeAfter: state.nativeGeneration + 1,
@@ -460,7 +460,7 @@ export function openCodexCoordinatorTransaction(finalDatabasePath: string): Code
460
460
  },
461
461
  version() {
462
462
  requireOpen();
463
- const state = readState(db);
463
+ const state = readCodexCoordinatorState(db);
464
464
  return { nativeGeneration: state.nativeGeneration, currentTxId: state.currentTxId };
465
465
  },
466
466
  assertPublished(expectation) {
@@ -468,7 +468,7 @@ export function openCodexCoordinatorTransaction(finalDatabasePath: string): Code
468
468
  if (lastResult?.kind !== "updated") {
469
469
  throw new CodexCoordinatorTransactionError("The coordinator transition was not published.");
470
470
  }
471
- const state = readState(db);
471
+ const state = readCodexCoordinatorState(db);
472
472
  if (state.nativeGeneration !== expectation.nativeAfter || state.currentTxId !== expectation.txId) {
473
473
  throw new CodexCoordinatorTransactionError("The coordinator published a different transition.");
474
474
  }
@@ -540,7 +540,7 @@ function readCommittedState(): TransitionStateRead {
540
540
  try {
541
541
  database = new Database(path, { readonly: true });
542
542
  database.exec("PRAGMA busy_timeout = 0");
543
- return { kind: "ready", state: readState(database) };
543
+ return { kind: "ready", state: readCodexCoordinatorState(database) };
544
544
  } catch (error) {
545
545
  return mapUnavailable(error);
546
546
  } finally {
@@ -577,7 +577,7 @@ export const updateCodexHistoryTransition: UpdateCodexHistoryTransition = (expec
577
577
  database = new Database(currentCoordinatorDatabasePath(), { readwrite: true, create: false });
578
578
  database.exec("PRAGMA busy_timeout = 0; BEGIN IMMEDIATE");
579
579
  transactionOpen = true;
580
- const current = readState(database);
580
+ const current = readCodexCoordinatorState(database);
581
581
  if (current.nativeGeneration > 0 && current.historySchedule === null) {
582
582
  throw new CodexCoordinatorTransactionError("A positive transition cannot lose its direction.");
583
583
  }
@@ -594,7 +594,7 @@ export const updateCodexHistoryTransition: UpdateCodexHistoryTransition = (expec
594
594
  expected.currentTxId,
595
595
  expected.currentTxId,
596
596
  );
597
- const state = readState(database);
597
+ const state = readCodexCoordinatorState(database);
598
598
  database.exec("COMMIT");
599
599
  transactionOpen = false;
600
600
  return result.changes === 1