@bitkyc08/opencodex 2.61.0 → 2.63.0-preview.20260923

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 (80) hide show
  1. package/gui/dist/assets/App-EJxiUFMq.js +50 -0
  2. package/gui/dist/assets/{Tray-CLZh48fM.js → Tray-B03pW-Uf.js} +1 -1
  3. package/gui/dist/assets/index-BmJwNBHL.js +86 -0
  4. package/gui/dist/assets/index-DdDunwDb.css +1 -0
  5. package/gui/dist/assets/{usage-companion-chart-a0N58rRI.js → usage-companion-chart-IftE60UK.js} +1 -1
  6. package/gui/dist/index.html +2 -2
  7. package/package.json +1 -1
  8. package/src/adapters/cursor/catalog.ts +15 -0
  9. package/src/adapters/cursor/effort-map.ts +7 -0
  10. package/src/adapters/cursor/envelope-echo.ts +51 -25
  11. package/src/adapters/cursor/protobuf-request.ts +39 -13
  12. package/src/adapters/cursor.ts +52 -4
  13. package/src/adapters/devin/cloud-direct/chat.ts +50 -16
  14. package/src/adapters/devin/cloud-direct/stated-reset-retry.ts +4 -1
  15. package/src/adapters/devin/live-models.ts +7 -0
  16. package/src/adapters/devin.ts +7 -5
  17. package/src/adapters/kiro/reasoning.ts +5 -0
  18. package/src/adapters/openai-responses/passthrough.ts +4 -4
  19. package/src/adapters/openai-responses/tool-output-recovery.ts +5 -3
  20. package/src/claude/desktop-gateway-state.ts +7 -2
  21. package/src/claude/desktop-policy.ts +105 -1
  22. package/src/cli/config-command.ts +22 -7
  23. package/src/cli/doctor.ts +14 -4
  24. package/src/codex/catalog/effort.ts +35 -4
  25. package/src/codex/catalog/metadata.ts +33 -5
  26. package/src/codex/catalog/native-models.ts +43 -2
  27. package/src/codex/catalog/pinned-models.ts +37 -0
  28. package/src/codex/catalog-auto-refresh.ts +6 -0
  29. package/src/codex/data/roster-pinned-models.json +359 -0
  30. package/src/codex/data/upstream-models.json +365 -271
  31. package/src/codex/inject/provider-table.ts +108 -0
  32. package/src/codex/inject/remove.ts +2 -114
  33. package/src/codex/model-entitlements.ts +14 -10
  34. package/src/codex/subagent-defaults.ts +2 -109
  35. package/src/codex/toml-source-lines.ts +112 -0
  36. package/src/config/live-reconcile.ts +145 -27
  37. package/src/config/load-degrade.ts +3 -4
  38. package/src/config.ts +2 -2
  39. package/src/generated/compatibility-version.json +83 -67
  40. package/src/generated/model-metadata.ts +5 -5
  41. package/src/lab/artifacts/sanitize.ts +60 -19
  42. package/src/lib/app-owned-memory-stores.ts +7 -0
  43. package/src/lib/app-owned-memory.ts +13 -2
  44. package/src/lib/errors.ts +6 -4
  45. package/src/lib/upstream-retry.ts +35 -5
  46. package/src/oauth/devin.ts +43 -37
  47. package/src/providers/codebuddy-models.ts +11 -0
  48. package/src/providers/kiro-models.ts +9 -0
  49. package/src/providers/quota/vendor-probes-oauth.ts +9 -2
  50. package/src/providers/registry/entries-core.ts +19 -8
  51. package/src/providers/registry/entries-extended.ts +4 -1
  52. package/src/providers/registry/model-seeds.ts +22 -2
  53. package/src/responses/bridge-search-replay-cache.ts +20 -10
  54. package/src/responses/plaintext-v2-agent-messages.ts +10 -1
  55. package/src/routing/identity-domains.ts +22 -15
  56. package/src/server/index/websocket-handler.ts +22 -2
  57. package/src/server/management/agent-settings-routes.ts +19 -10
  58. package/src/server/management/context.ts +4 -2
  59. package/src/server/responses/codex-ws-exchange.ts +24 -8
  60. package/src/server/responses/core-codex-account.ts +4 -0
  61. package/src/server/responses/core-combo-failure.ts +16 -9
  62. package/src/server/responses/core-combo.ts +7 -3
  63. package/src/server/responses/core-options.ts +6 -0
  64. package/src/server/responses/native-injection-replay.ts +13 -1
  65. package/src/server/responses/native-injection.ts +66 -7
  66. package/src/server/responses/native-response-control.ts +6 -2
  67. package/src/server/responses/native-steering-replay.ts +60 -0
  68. package/src/server/responses/native-steering.ts +9 -1
  69. package/src/server/responses/passthrough-delivery.ts +3 -4
  70. package/src/server/responses/passthrough-dispatch.ts +7 -0
  71. package/src/server/responses/request-prepare.ts +12 -0
  72. package/src/server/responses/request-transport.ts +5 -2
  73. package/src/server/responses/ws-upstream.ts +1 -1
  74. package/src/server/ws-bridge.ts +17 -1
  75. package/src/types/request.ts +5 -0
  76. package/src/usage/expected-prices.ts +38 -8
  77. package/src/web-search/executor.ts +38 -13
  78. package/gui/dist/assets/App-CH6C5H7x.js +0 -50
  79. package/gui/dist/assets/index-_bpvxJu0.css +0 -1
  80. package/gui/dist/assets/index-wpTOyepx.js +0 -86
@@ -47,6 +47,16 @@ type PersistedServerBinding = Pick<OcxConfig, "port" | "hostname">;
47
47
 
48
48
  const persistedLiveServerBinding = new WeakMap<OcxConfig, PersistedServerBinding>();
49
49
 
50
+ /**
51
+ * Config instances nobody holds long-term — the catalog auto-refresh tick's
52
+ * per-tick `loadConfig()` snapshot. A detached snapshot cannot express a
53
+ * deliberate deletion and owns no live listener socket, so the live policy's
54
+ * `hostname`/`port` and disk-only-key skips would only discard concurrent hand
55
+ * edits wholesale. Keyed on the instance so the mode cannot leak into the
56
+ * long-lived server config.
57
+ */
58
+ const detachedConfigSnapshots = new WeakSet<OcxConfig>();
59
+
50
60
  /**
51
61
  * Arm the baseline for a long-lived config. MANDATORY at `startServer`, not lazy on
52
62
  * first save — arming lazily would lose exactly the hand edit made before that first
@@ -57,6 +67,17 @@ export function armClaudeCodeBaseline(config: OcxConfig): void {
57
67
  claudeCodeBaseline.set(config, structuredClone(config.claudeCode));
58
68
  }
59
69
 
70
+ /**
71
+ * Arm a freshly loaded config instance no long-lived server owns. The save path
72
+ * then reconciles every field — the listener binding and keys that exist only
73
+ * on disk included — against this arming baseline, so a concurrent hand edit is
74
+ * adopted rather than overwritten by the snapshot's stale values.
75
+ */
76
+ export function armDetachedConfigBaseline(config: OcxConfig): void {
77
+ armClaudeCodeBaseline(config);
78
+ detachedConfigSnapshots.add(config);
79
+ }
80
+
60
81
  /**
61
82
  * Adopt one schema-validated provider that was read from the authoritative disk
62
83
  * config into a long-lived server config without rebasing any unrelated field.
@@ -80,6 +101,42 @@ export function claudeCodeBaselineArmed(config: OcxConfig): boolean {
80
101
  return claudeCodeBaseline.has(config);
81
102
  }
82
103
 
104
+ /**
105
+ * Adopt a field-scoped Claude Code write into a long-lived config snapshot.
106
+ *
107
+ * Scoped writers commit against the current file rather than serializing the
108
+ * whole snapshot. Mirror that committed subtree and rebase the hand-edit guard
109
+ * together so a later unrelated save does not mistake the scoped write for an
110
+ * outstanding in-memory mutation.
111
+ *
112
+ * The live subtree may already hold pending mutations a concurrent request
113
+ * assigned but has not saved yet — the Claude settings PUT yields between
114
+ * assigning `config.claudeCode` and saving. Adopt through the same three-way
115
+ * reconcile guarded saves use, so pending live leaves survive, disjoint
116
+ * committed changes merge in, and only the baseline moves wholesale to the
117
+ * committed subtree.
118
+ */
119
+ export function adoptPersistedClaudeCode(
120
+ config: OcxConfig,
121
+ persistedClaudeCode: OcxConfig["claudeCode"],
122
+ ): void {
123
+ const storedBaseline: ConfigMergeValue = claudeCodeBaseline.has(config)
124
+ ? claudeCodeBaseline.get(config)
125
+ : MISSING_CONFIG_VALUE;
126
+ const merged = reconcileConfigValue(
127
+ storedBaseline === undefined ? MISSING_CONFIG_VALUE : storedBaseline,
128
+ config.claudeCode === undefined ? MISSING_CONFIG_VALUE : config.claudeCode,
129
+ persistedClaudeCode === undefined ? MISSING_CONFIG_VALUE : persistedClaudeCode,
130
+ );
131
+ if (merged === MISSING_CONFIG_VALUE) delete config.claudeCode;
132
+ else config.claudeCode = merged as OcxConfig["claudeCode"];
133
+ const baseline = liveConfigBaseline.get(config);
134
+ if (baseline) baseline.claudeCode = structuredClone(persistedClaudeCode);
135
+ if (claudeCodeBaseline.has(config)) {
136
+ claudeCodeBaseline.set(config, structuredClone(persistedClaudeCode));
137
+ }
138
+ }
139
+
83
140
  /**
84
141
  * Structural compare of parsed subtrees. NOT `JSON.stringify`: key order must not
85
142
  * decide whether a user's hand edit survives.
@@ -124,6 +181,42 @@ type IndexedCustomModels = {
124
181
  byId: Map<string, Record<string, unknown>>;
125
182
  };
126
183
 
184
+ function indexDisabledModels(value: ConfigMergeValue): { order: string[]; members: Set<string> } | null {
185
+ if (value === MISSING_CONFIG_VALUE) return { order: [], members: new Set() };
186
+ if (!Array.isArray(value)) return null;
187
+ const members = new Set<string>();
188
+ for (const item of value) {
189
+ if (typeof item !== "string" || members.has(item)) return null;
190
+ members.add(item);
191
+ }
192
+ return { order: value as string[], members };
193
+ }
194
+
195
+ /**
196
+ * Merge disabled-model lists by membership instead of treating the array as one
197
+ * opaque leaf: a slug either side deleted stays deleted (deletion wins over the
198
+ * other side's unchanged retention) while slugs added on either side are kept.
199
+ * Discovery only appends, so live additions are its arrivals; a hand edit that
200
+ * hid or un-hid a model mid-refresh survives the discovery save.
201
+ */
202
+ function reconcileDisabledModels(
203
+ baseline: ConfigMergeValue,
204
+ live: ConfigMergeValue,
205
+ persisted: ConfigMergeValue,
206
+ ): ConfigMergeValue | null {
207
+ const baselineSet = indexDisabledModels(baseline);
208
+ const liveSet = indexDisabledModels(live);
209
+ const persistedSet = indexDisabledModels(persisted);
210
+ if (!baselineSet || !liveSet || !persistedSet) return null;
211
+ const order = [...liveSet.order, ...persistedSet.order.filter(id => !liveSet.members.has(id))];
212
+ const merged: string[] = [];
213
+ for (const id of order) {
214
+ if (baselineSet.members.has(id) && (!liveSet.members.has(id) || !persistedSet.members.has(id))) continue;
215
+ merged.push(id);
216
+ }
217
+ return merged;
218
+ }
219
+
127
220
  function indexCustomModels(value: ConfigMergeValue): IndexedCustomModels | null {
128
221
  if (!Array.isArray(value)) return null;
129
222
  const order: string[] = [];
@@ -192,7 +285,10 @@ function reconcileConfigRecord(
192
285
  : key === "customModels"
193
286
  ? reconcileCustomModels(baselineValue, liveValue, persistedValue)
194
287
  ?? reconcileConfigValue(baselineValue, liveValue, persistedValue)
195
- : reconcileConfigValue(baselineValue, liveValue, persistedValue, key === "providers");
288
+ : key === "disabledModels"
289
+ ? reconcileDisabledModels(baselineValue, liveValue, persistedValue)
290
+ ?? reconcileConfigValue(baselineValue, liveValue, persistedValue)
291
+ : reconcileConfigValue(baselineValue, liveValue, persistedValue, key === "providers");
196
292
  if (merged === MISSING_CONFIG_VALUE) delete live[key];
197
293
  else live[key] = merged;
198
294
  }
@@ -302,7 +398,11 @@ function readPersistedServerBinding(
302
398
  }
303
399
 
304
400
  /**
305
- * The save entry point for every writer holding a LIVE server config.
401
+ * The save entry point for every writer holding a LIVE server config, and for a
402
+ * detached snapshot armed through {@link armDetachedConfigBaseline}. The live
403
+ * policy keeps `hostname`/`port` and disk-only keys out of the merge; a detached
404
+ * snapshot has neither hazard, so it rebases every field against its arming
405
+ * baseline instead.
306
406
  *
307
407
  * Conflict policy, chosen deliberately:
308
408
  * - disk changed, we did not → their hand edit wins;
@@ -312,8 +412,9 @@ function readPersistedServerBinding(
312
412
  * live state edited that same row;
313
413
  * - file missing/unreadable → save what we have, no throw.
314
414
  *
315
- * Custom-model rows are merged by their stable `id`, preserving independent
316
- * edits and deletions across stale whole-config saves.
415
+ * Custom-model rows are merged by their stable `id`, and `disabledModels` by
416
+ * member, preserving independent edits and deletions across stale whole-config
417
+ * saves.
317
418
  */
318
419
  export function saveConfigPreservingClaudeCode(config: OcxConfig): void {
319
420
  const pinError = configReasoningPinsConfigError(config);
@@ -327,28 +428,43 @@ export function saveConfigPreservingClaudeCode(config: OcxConfig): void {
327
428
  if (baseline && onDisk !== undefined) {
328
429
  const persistedDiagnostics = configDiagnosticsFromRaw(JSON.stringify(onDisk));
329
430
  if (persistedDiagnostics.source === "file") {
330
- const deletedKeys = configRebaseDeletionKeys(config);
331
- const provenanceExists = configHasRebaseProvenance(config);
332
- // Only keys this live config is actually known to have diverged on may be
333
- // rebased. The baseline is captured once when the server arms it, so any key
334
- // that appeared on disk afterwards — through saveConfig(), a hand edit, or
335
- // another process — is absent from the baseline as well as from the live
336
- // config. Reconciling those keys reads "live never changed this" and adopts
337
- // the disk value, which resurrects a field the live writer had deliberately
338
- // deleted (#1462 regression: PUT /api/grok/selection with an empty list).
339
- // Restrict the merge to keys the baseline knew about, plus keys the live
340
- // config still carries; a key that exists only on disk is left to the
341
- // ordinary whole-config write below.
342
- const rebaseableKeys = new Set([
343
- ...Object.keys(baseline as unknown as Record<string, unknown>),
344
- ...Object.keys(config as unknown as Record<string, unknown>),
345
- ...(provenanceExists
346
- ? Object.keys(persistedDiagnostics.config as unknown as Record<string, unknown>)
347
- : []),
348
- ]);
349
- const skipped = new Set(["hostname", "port", "claudeCode", CONFIG_REBASE_PROVENANCE_KEY]);
350
- for (const key of Object.keys(persistedDiagnostics.config as unknown as Record<string, unknown>)) {
351
- if (!rebaseableKeys.has(key)) skipped.add(key);
431
+ // A detached snapshot diverged only where this pipeline mutated it (model
432
+ // discovery fields for the auto-refresh tick): it cannot express a
433
+ // deliberate deletion, so the disk-only-key and listener-binding skips
434
+ // below would only discard concurrent hand edits. It merges every top-level
435
+ // key — including configRebaseProvenance, so a cooperating writer's
436
+ // deletion marker adopted from disk is honored instead of silently dropped —
437
+ // and captures deletion intent from the current disk snapshot before the
438
+ // merge can temporarily restore a key and invalidate its deletion marker.
439
+ const detached = detachedConfigSnapshots.has(config);
440
+ const deletedKeys = detached
441
+ ? configRebaseDeletionKeys(persistedDiagnostics.config)
442
+ : configRebaseDeletionKeys(config);
443
+ const skipped = detached
444
+ ? new Set(["claudeCode"])
445
+ : new Set(["hostname", "port", "claudeCode", CONFIG_REBASE_PROVENANCE_KEY]);
446
+ if (!detached) {
447
+ const provenanceExists = configHasRebaseProvenance(config);
448
+ // Only keys this live config is actually known to have diverged on may be
449
+ // rebased. The baseline is captured once when the server arms it, so any key
450
+ // that appeared on disk afterwards — through saveConfig(), a hand edit, or
451
+ // another process — is absent from the baseline as well as from the live
452
+ // config. Reconciling those keys reads "live never changed this" and adopts
453
+ // the disk value, which resurrects a field the live writer had deliberately
454
+ // deleted (#1462 regression: PUT /api/grok/selection with an empty list).
455
+ // Restrict the merge to keys the baseline knew about, plus keys the live
456
+ // config still carries; a key that exists only on disk is left to the
457
+ // ordinary whole-config write below.
458
+ const rebaseableKeys = new Set([
459
+ ...Object.keys(baseline as unknown as Record<string, unknown>),
460
+ ...Object.keys(config as unknown as Record<string, unknown>),
461
+ ...(provenanceExists
462
+ ? Object.keys(persistedDiagnostics.config as unknown as Record<string, unknown>)
463
+ : []),
464
+ ]);
465
+ for (const key of Object.keys(persistedDiagnostics.config as unknown as Record<string, unknown>)) {
466
+ if (!rebaseableKeys.has(key)) skipped.add(key);
467
+ }
352
468
  }
353
469
  reconcileConfigRecord(
354
470
  config as unknown as Record<string, unknown>,
@@ -356,7 +472,9 @@ export function saveConfigPreservingClaudeCode(config: OcxConfig): void {
356
472
  persistedDiagnostics.config as unknown as Record<string, unknown>,
357
473
  skipped,
358
474
  );
359
- for (const key of deletedKeys) delete (config as unknown as Record<string, unknown>)[key];
475
+ for (const key of deletedKeys) {
476
+ delete (config as unknown as Record<string, unknown>)[key];
477
+ }
360
478
  }
361
479
  }
362
480
  if (claudeCodeBaseline.has(config)) {
@@ -420,10 +420,9 @@ export function degradedCredentialGroupsWarning(rawParsed: unknown): string | nu
420
420
  if (!pool || pool.credentialGroups === undefined) return null;
421
421
  const parsed = credentialGroupsSchema.safeParse(pool.credentialGroups);
422
422
  if (parsed.success) return null;
423
- // Every issue message is redacted before it is joined. The custom messages embed the
424
- // offending member through `JSON.stringify`, so a malformed credential string that
425
- // happens to carry secret material would otherwise be printed verbatim at config load
426
- // — a config file is exactly where a pasted token ends up in the wrong field.
423
+ // Every issue message is redacted before it is joined. The custom messages now name
424
+ // group/member positions instead of the offending strings; the redaction stays as a
425
+ // second layer for any schema default message that still embeds a value.
427
426
  const details = parsed.error.issues.map(issue => redactSecretString(issue.message)).join("; ");
428
427
  return `pool.credentialGroups is invalid (${details}) — declared quota grouping is disabled; other pool settings were preserved`;
429
428
  }
package/src/config.ts CHANGED
@@ -133,8 +133,8 @@ export {
133
133
  withExpectedConfigGenerationSync,
134
134
  } from "./config/mutation-lock";
135
135
  export {
136
- armClaudeCodeBaseline,
137
- adoptPersistedProviderIntoLiveConfig,
136
+ armClaudeCodeBaseline, armDetachedConfigBaseline,
137
+ adoptPersistedClaudeCode, adoptPersistedProviderIntoLiveConfig,
138
138
  claudeCodeBaselineArmed,
139
139
  reconcileLiveConfigFromDisk,
140
140
  saveConfigPreservingClaudeCode,