@yansigit/opencodex 2.31.3 → 2.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/bin/ocx.mjs +99 -70
  2. package/gui/dist/assets/index-BG43zwVe.js +102 -0
  3. package/gui/dist/assets/index-CiSI-jrP.css +1 -0
  4. package/gui/dist/index.html +2 -2
  5. package/package.json +1 -1
  6. package/src/adapters/anthropic.ts +6 -8
  7. package/src/adapters/base.ts +2 -0
  8. package/src/adapters/command-code.ts +2 -3
  9. package/src/adapters/cursor/live-models.ts +8 -0
  10. package/src/adapters/cursor/native-exec-desktop.ts +16 -0
  11. package/src/adapters/cursor/tool-definitions.ts +1 -1
  12. package/src/adapters/google.ts +6 -7
  13. package/src/adapters/kiro.ts +0 -3
  14. package/src/adapters/openai-responses.ts +3 -0
  15. package/src/adapters/tool-catalog-nudge.ts +1 -1
  16. package/src/adapters/xai-web-search.ts +7 -2
  17. package/src/bridge.ts +21 -15
  18. package/src/cli/dispatch.ts +50 -2
  19. package/src/cli/doctor.ts +24 -11
  20. package/src/cli/help.ts +4 -3
  21. package/src/cli/index.ts +11 -4
  22. package/src/cli/models.ts +13 -3
  23. package/src/cli/observe.ts +20 -5
  24. package/src/cli/provider.ts +2 -1
  25. package/src/cli/registry.ts +7 -5
  26. package/src/cli/status.ts +2 -1
  27. package/src/cli/system-restart-client.ts +1 -1
  28. package/src/cli/usage-report.ts +134 -0
  29. package/src/codex/app-server-processes.ts +3 -1
  30. package/src/codex/catalog/aggregation.ts +13 -1
  31. package/src/codex/catalog/effort.ts +24 -9
  32. package/src/codex/catalog/model-metadata.ts +566 -0
  33. package/src/codex/catalog/parsing.ts +33 -0
  34. package/src/codex/catalog/provider-fetch.ts +123 -32
  35. package/src/codex/catalog/sync.ts +5 -4
  36. package/src/codex/desktop-app-restart.ts +342 -0
  37. package/src/codex/history-job.ts +32 -3
  38. package/src/codex/history-manifest.ts +112 -0
  39. package/src/codex/history-migration-guardian.ts +5 -5
  40. package/src/codex/history-provider.ts +825 -247
  41. package/src/codex/history-worker.ts +8 -5
  42. package/src/codex/inject.ts +49 -21
  43. package/src/codex/injected-marker.ts +1 -1
  44. package/src/codex/internal/history-writer.ts +4 -3
  45. package/src/codex/native-profile-startup.ts +157 -27
  46. package/src/codex/native-residue.ts +26 -33
  47. package/src/combos/failover.ts +27 -0
  48. package/src/compatibility/index.ts +26 -0
  49. package/src/compatibility/manifest.ts +253 -0
  50. package/src/compatibility/openai-responses.ts +81 -0
  51. package/src/config/atomic-write.ts +219 -0
  52. package/src/config/paths.ts +40 -0
  53. package/src/config/process-state.ts +308 -0
  54. package/src/config/provider-validation.ts +177 -0
  55. package/src/config.ts +75 -812
  56. package/src/generated/compatibility-version.json +145 -85
  57. package/src/images/plan.ts +5 -4
  58. package/src/integrations/ownership-policy.ts +141 -0
  59. package/src/integrations/ownership.ts +10 -0
  60. package/src/integrations/state.ts +44 -5
  61. package/src/integrations/writer.ts +6 -0
  62. package/src/lib/bounded-body.ts +14 -2
  63. package/src/lib/process-control.ts +2 -1
  64. package/src/lib/state-store-registrations.ts +2 -0
  65. package/src/lib/tool-argument-integers.ts +56 -5
  66. package/src/oauth/health.ts +1 -1
  67. package/src/providers/registry.ts +1 -1
  68. package/src/reasoning-effort.ts +19 -2
  69. package/src/responses/apply-patch-envelope.ts +63 -0
  70. package/src/responses/custom-tool-compat.ts +132 -38
  71. package/src/responses/parser.ts +3 -2
  72. package/src/responses/reasoning-replay-cache.ts +81 -3
  73. package/src/server/auth-cors.ts +9 -7
  74. package/src/server/index.ts +102 -21
  75. package/src/server/local-management-read-client.ts +1 -1
  76. package/src/server/local-provider-reload-client.ts +1 -1
  77. package/src/server/management/agent-settings-routes.ts +1 -1
  78. package/src/server/management/config-routes.ts +4 -1
  79. package/src/server/management/context.ts +1 -1
  80. package/src/server/management/logs-usage-routes.ts +27 -6
  81. package/src/server/management/model-routes.ts +8 -4
  82. package/src/server/management/native-integration-routes.ts +2 -1
  83. package/src/server/management/provider-capability-config.ts +1 -1
  84. package/src/server/management/system-restart.ts +1 -1
  85. package/src/server/port-reclaim.ts +1 -1
  86. package/src/server/proxy-liveness.ts +2 -1
  87. package/src/server/request-log-conversation.ts +30 -0
  88. package/src/server/responses/codex-auth-error.ts +55 -0
  89. package/src/server/responses/combo-stream-preflight.ts +171 -0
  90. package/src/server/responses/compact.ts +6 -21
  91. package/src/server/responses/core.ts +225 -94
  92. package/src/server/responses/fetch-helpers.ts +2 -97
  93. package/src/server/responses-custom-tool-repair.ts +41 -5
  94. package/src/server/responses-undeclared-tool-guard.ts +156 -15
  95. package/src/service.ts +8 -4
  96. package/src/types/request.ts +6 -1
  97. package/src/types/tools.ts +87 -11
  98. package/src/types.ts +1 -1
  99. package/src/update/index.ts +5 -4
  100. package/src/update/job.ts +3 -1
  101. package/src/update/transactional-install.mjs +8 -1
  102. package/src/usage/log.ts +16 -8
  103. package/src/usage/summary.ts +201 -8
  104. package/src/vision/describe.ts +18 -13
  105. package/src/web-search/executor.ts +10 -3
  106. package/gui/dist/assets/index-CGoDO3uO.css +0 -1
  107. package/gui/dist/assets/index-Cxt5fZMP.js +0 -102
@@ -109,7 +109,8 @@ export type CodexHistoryJobOutcome =
109
109
  | { readonly kind: "skipped" }
110
110
  | { readonly kind: "blocked"; readonly reason: "busy" | "database" | "unsafe-path" | "desired_disabled" | "desired_enabled" }
111
111
  | { readonly kind: "failed"; readonly reason: "worker-error" | "worker-died" | "timeout";
112
- readonly message: string; readonly historyFailureReason?: CodexHistoryFailureReason };
112
+ readonly message: string; readonly historyFailureReason?: CodexHistoryFailureReason;
113
+ readonly rows?: number; readonly files?: number };
113
114
 
114
115
  /**
115
116
  * Derive the durable history operation from admitted intent.
@@ -174,7 +175,14 @@ function isPlausibleWorkerResult(
174
175
  || message.reason === "desired_disabled" || message.reason === "desired_enabled";
175
176
  case "error":
176
177
  return typeof message.message === "string"
177
- && (message.reason === undefined || message.reason === "busy" || message.reason === "permission");
178
+ && (message.rows === undefined || (Number.isSafeInteger(message.rows) && Number(message.rows) >= 0))
179
+ && (message.files === undefined || (Number.isSafeInteger(message.files) && Number(message.files) >= 0))
180
+ && ((message.rows === undefined && message.files === undefined)
181
+ || (message.rows !== undefined && message.files !== undefined))
182
+ && (message.reason === undefined
183
+ || message.reason === "busy"
184
+ || message.reason === "permission"
185
+ || message.reason === "integrity");
178
186
  default:
179
187
  return false;
180
188
  }
@@ -224,6 +232,7 @@ export function describeHistoryJobFailure(
224
232
  : surface === "recover-legacy"
225
233
  ? "the Codex history DB is locked (Codex app/IDE open?). Close it and rerun this command."
226
234
  : "the Codex app appears to be holding the history database. Close Codex and run `ocx restore` again.";
235
+ const busyStateText = "Codex history state is busy (database, backup manifest, or rollout file); this is not enough evidence to blame the Codex app. It is retried automatically while the proxy runs; run 'ocx doctor' before forcing another attempt.";
227
236
  if (outcome.kind === "blocked") {
228
237
  if (outcome.reason === "busy") return busyText;
229
238
  switch (outcome.reason) {
@@ -237,10 +246,22 @@ export function describeHistoryJobFailure(
237
246
  return "Codex integration is enabled, so the history operation was skipped.";
238
247
  }
239
248
  }
240
- if (outcome.historyFailureReason === "busy") return busyText;
249
+ const partiallyChanged = (outcome.rows ?? 0) > 0 || (outcome.files ?? 0) > 0;
250
+ if (partiallyChanged && outcome.historyFailureReason === "busy") {
251
+ return "Codex history metadata changed but did not converge because manifest finalization remained busy; the manifest was retained for review and safe retry. Run 'ocx doctor'.";
252
+ }
253
+ if (partiallyChanged && outcome.historyFailureReason === "permission") {
254
+ return "Codex history metadata changed but did not converge because permission was denied while finalizing the manifest; the manifest was retained for review and safe retry. Run 'ocx doctor'.";
255
+ }
256
+ if (outcome.historyFailureReason === "busy") return busyStateText;
241
257
  if (outcome.historyFailureReason === "permission") {
242
258
  return "permission was denied while writing Codex history; this is not a Codex app lock. Run 'ocx doctor'.";
243
259
  }
260
+ if (outcome.historyFailureReason === "integrity") {
261
+ return partiallyChanged
262
+ ? "the history backup or its restore target changed after a partial restore; the manifest was retained for review and safe retry. Run 'ocx doctor'."
263
+ : "the history backup or its restore target failed integrity checks; no unverified provider metadata was applied. Run 'ocx doctor'.";
264
+ }
244
265
  switch (outcome.reason) {
245
266
  case "worker-error":
246
267
  return `the history worker failed (${outcome.message}). Run 'ocx doctor'.`;
@@ -277,6 +298,9 @@ function classifyWorkerResult(result: HistoryWorkerResult): CodexHistoryJobOutco
277
298
  reason: "worker-error",
278
299
  message: redactWorkerMessage(result.message),
279
300
  ...(result.reason ? { historyFailureReason: result.reason } : {}),
301
+ ...(result.rows !== undefined && result.files !== undefined
302
+ ? { rows: result.rows, files: result.files }
303
+ : {}),
280
304
  };
281
305
  }
282
306
  return result.outcome === "skipped"
@@ -284,6 +308,11 @@ function classifyWorkerResult(result: HistoryWorkerResult): CodexHistoryJobOutco
284
308
  : { kind: "converged", rows: result.rows, files: result.files, ...(result.proof ? { proof: result.proof } : {}) };
285
309
  }
286
310
 
311
+ /** Test seam for the parent-side Worker result classification contract. */
312
+ export function classifyWorkerResultForTests(result: HistoryWorkerResult): CodexHistoryJobOutcome {
313
+ return classifyWorkerResult(result);
314
+ }
315
+
287
316
  /**
288
317
  * Run one history unit in a Worker and join it before returning.
289
318
  *
@@ -0,0 +1,112 @@
1
+ import { createHash } from "node:crypto";
2
+ import { isAbsolute, resolve } from "node:path";
3
+
4
+ /** Sources whose native OpenAI provenance can be restored exactly. */
5
+ export const CODEX_HISTORY_RESUMABLE_SOURCES = ["cli", "vscode"] as const;
6
+
7
+ export interface CodexHistoryBackupEntry {
8
+ id: string;
9
+ rolloutPath: string;
10
+ modelProvider: string;
11
+ source: string;
12
+ hasUserEvent: 0 | 1;
13
+ }
14
+
15
+ export interface CodexHistoryBackupManifest {
16
+ version: 1;
17
+ stateDbPath: string;
18
+ entries: Record<string, CodexHistoryBackupEntry>;
19
+ }
20
+
21
+ export type CodexHistoryManifestValidation =
22
+ | { readonly ok: true; readonly manifest: CodexHistoryBackupManifest }
23
+ | { readonly ok: false; readonly reason: "foreign-database" }
24
+ | {
25
+ readonly ok: false;
26
+ readonly reason: "schema";
27
+ readonly scope: "manifest" | "entry-shape" | "entry-provenance";
28
+ };
29
+
30
+ function codexHistoryPathIdentity(path: string): string {
31
+ const canonical = resolve(path);
32
+ return process.platform === "win32" ? canonical.toLowerCase() : canonical;
33
+ }
34
+
35
+ /**
36
+ * Canonical identity used by both backup naming and manifest ownership checks.
37
+ * Windows paths are case-insensitive for this contract; other platforms keep
38
+ * the resolved path's case.
39
+ */
40
+ export function codexHistoryStateDbIdentity(path: string): string {
41
+ return codexHistoryPathIdentity(path);
42
+ }
43
+
44
+ /** Stable, non-secret file-name component for one state database. */
45
+ export function codexHistoryBackupId(stateDbPath: string): string {
46
+ return createHash("sha256")
47
+ .update(codexHistoryStateDbIdentity(stateDbPath))
48
+ .digest("hex")
49
+ .slice(0, 16);
50
+ }
51
+
52
+ /** Platform-aware identity comparison shared by database and rollout checks. */
53
+ export function sameCodexHistoryPath(left: string, right: string): boolean {
54
+ return codexHistoryPathIdentity(left) === codexHistoryPathIdentity(right);
55
+ }
56
+
57
+ function isRecord(value: unknown): value is Record<string, unknown> {
58
+ return typeof value === "object" && value !== null && !Array.isArray(value);
59
+ }
60
+
61
+ function hasAllowedProvenance(entry: Record<string, unknown>): boolean {
62
+ return (entry.modelProvider === "openai"
63
+ && CODEX_HISTORY_RESUMABLE_SOURCES.includes(
64
+ entry.source as (typeof CODEX_HISTORY_RESUMABLE_SOURCES)[number],
65
+ ))
66
+ || (entry.modelProvider === "opencodex" && entry.source === "exec");
67
+ }
68
+
69
+ /**
70
+ * Validate only the versioned data contract and database ownership identity.
71
+ * Filesystem type checks, reads, fingerprints, rollout inspection, SQLite, and
72
+ * mutation deliberately remain with the callers.
73
+ */
74
+ export function validateCodexHistoryBackupManifest(
75
+ raw: unknown,
76
+ expectedStateDbPath: string,
77
+ ): CodexHistoryManifestValidation {
78
+ if (!isRecord(raw)
79
+ || raw.version !== 1
80
+ || typeof raw.stateDbPath !== "string"
81
+ || !raw.stateDbPath.trim()
82
+ || !isAbsolute(raw.stateDbPath)
83
+ || !isRecord(raw.entries)) {
84
+ return { ok: false, reason: "schema", scope: "manifest" };
85
+ }
86
+ if (!sameCodexHistoryPath(raw.stateDbPath, expectedStateDbPath)) {
87
+ return { ok: false, reason: "foreign-database" };
88
+ }
89
+
90
+ for (const [id, value] of Object.entries(raw.entries)) {
91
+ if (!isRecord(value)) {
92
+ return { ok: false, reason: "schema", scope: "entry-shape" };
93
+ }
94
+ if (!id
95
+ || value.id !== id
96
+ || typeof value.rolloutPath !== "string"
97
+ || !value.rolloutPath.trim()
98
+ || !isAbsolute(value.rolloutPath)
99
+ || typeof value.modelProvider !== "string"
100
+ || !value.modelProvider.trim()
101
+ || typeof value.source !== "string"
102
+ || !value.source.trim()
103
+ || typeof value.hasUserEvent !== "number"
104
+ || !Number.isSafeInteger(value.hasUserEvent)
105
+ || (value.hasUserEvent !== 0 && value.hasUserEvent !== 1)
106
+ || !hasAllowedProvenance(value)) {
107
+ return { ok: false, reason: "schema", scope: "entry-provenance" };
108
+ }
109
+ }
110
+
111
+ return { ok: true, manifest: raw as unknown as CodexHistoryBackupManifest };
112
+ }
@@ -2,11 +2,11 @@ import { migrateHistoryToOpenai } from "./history-provider";
2
2
  import { resolveCodexHistoryJobTarget, runCodexHistoryJob } from "./history-job";
3
3
 
4
4
  /**
5
- * Daemon-side retry for the one-time Design-B history migration.
5
+ * Daemon-side retry for the one-time Design-B history metadata restoration.
6
6
  *
7
7
  * Most upgrades run `ocx start` while the Codex app still holds `state_5.sqlite`,
8
- * so the inject-time migration often fails on the FIRST start exactly the moment
9
- * every legacy thread is still tagged `opencodex` and invisible to the app. Instead
8
+ * so the inject-time restore can fail on the FIRST start while manifest-backed original
9
+ * metadata is still pending. Instead
10
10
  * of asking the user to close the app and rerun start, this guardian keeps retrying
11
11
  * in the background until the migration lands.
12
12
  *
@@ -77,7 +77,7 @@ export function startHistoryMigrationGuardian(deps: HistoryMigrationGuardianDeps
77
77
  if (!result.failed) {
78
78
  const moved = result.rows + ((result as { ejectedRows?: number }).ejectedRows ?? 0);
79
79
  if (moved > 0) {
80
- log.log(`🩹 history-migration: ${moved} legacy opencodex thread(s) migrated back to openai.`);
80
+ log.log(`🩹 history-migration: restored original provider metadata for ${moved} manifest-backed thread(s).`);
81
81
  }
82
82
  // Zero mutations are authoritative only when the worker verified the
83
83
  // exact DB/manifest state while H was held.
@@ -92,7 +92,7 @@ export function startHistoryMigrationGuardian(deps: HistoryMigrationGuardianDeps
92
92
  }
93
93
  if (ticks >= maxTicks) {
94
94
  stopped = true;
95
- log.log("⚠️ history-migration: Could not verify that legacy threads were migrated; the history database may be busy, unavailable, or not yet ready. Run 'ocx sync' (or check 'ocx doctor').");
95
+ log.log("⚠️ history-migration: Could not verify that backed-up provider metadata was restored; the history database may be busy, unavailable, or not yet ready. Run 'ocx sync' (or check 'ocx doctor').");
96
96
  return;
97
97
  }
98
98
  schedule();