@bitkyc08/opencodex 2.12.0 → 2.13.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 (173) hide show
  1. package/README.md +1 -1
  2. package/gui/dist/assets/index-BHldBl6_.js +76 -0
  3. package/gui/dist/index.html +1 -1
  4. package/package.json +1 -1
  5. package/src/adapters/anthropic-output-schema.ts +137 -0
  6. package/src/adapters/anthropic.ts +15 -0
  7. package/src/adapters/google-antigravity-replay.ts +269 -5
  8. package/src/adapters/openai-chat.ts +25 -18
  9. package/src/adapters/openai-responses.ts +63 -17
  10. package/src/bridge.ts +11 -5
  11. package/src/claude/inbound.ts +24 -3
  12. package/src/cli/account-api.ts +2 -0
  13. package/src/cli/account-extended.ts +262 -1
  14. package/src/cli/account.ts +3 -1
  15. package/src/cli/dispatch.ts +532 -0
  16. package/src/cli/doctor.ts +26 -21
  17. package/src/cli/help.ts +9 -278
  18. package/src/cli/index.ts +45 -506
  19. package/src/cli/lab.ts +171 -65
  20. package/src/cli/registry.ts +384 -0
  21. package/src/cli/root.ts +86 -0
  22. package/src/cli/status.ts +9 -2
  23. package/src/cli/v2.ts +37 -2
  24. package/src/codex/account-label.ts +14 -1
  25. package/src/codex/account-store.ts +12 -1
  26. package/src/codex/admission.ts +2 -1
  27. package/src/codex/auth-api.ts +3 -2
  28. package/src/codex/catalog/metadata.ts +6 -4
  29. package/src/codex/catalog/parsing.ts +31 -7
  30. package/src/codex/catalog/provider-fetch.ts +2 -1
  31. package/src/codex/catalog/sync.ts +65 -11
  32. package/src/codex/features.ts +437 -31
  33. package/src/codex/history-job.ts +6 -11
  34. package/src/codex/history-provider.ts +10 -9
  35. package/src/codex/inject.ts +14 -1
  36. package/src/codex/native-profile-startup.ts +49 -3
  37. package/src/codex/native-residue.ts +14 -2
  38. package/src/codex/paths.ts +75 -1
  39. package/src/codex/reset-credit-recovery.ts +1044 -0
  40. package/src/codex/shim.ts +876 -16
  41. package/src/codex/sync.ts +27 -2
  42. package/src/config.ts +148 -10
  43. package/src/generated/compatibility-version.json +2572 -0
  44. package/src/images/loop.ts +1 -1
  45. package/src/lab/artifacts/sanitize.ts +3 -3
  46. package/src/lab/automation/budgets.ts +78 -0
  47. package/src/lab/automation/config-persistence.ts +256 -0
  48. package/src/lab/automation/constants.ts +39 -0
  49. package/src/lab/automation/cooldown.ts +103 -0
  50. package/src/lab/automation/dispatch.ts +211 -0
  51. package/src/lab/automation/index.ts +13 -0
  52. package/src/lab/automation/orchestrator.ts +480 -0
  53. package/src/lab/automation/persistence.ts +512 -0
  54. package/src/lab/automation/planner.ts +371 -0
  55. package/src/lab/automation/policy.ts +136 -0
  56. package/src/lab/automation/queue.ts +191 -0
  57. package/src/lab/automation/recovery.ts +24 -0
  58. package/src/lab/automation/route-context.ts +21 -0
  59. package/src/lab/automation/run-key.ts +44 -0
  60. package/src/lab/automation/runs-query.ts +34 -0
  61. package/src/lab/automation/types.ts +160 -0
  62. package/src/lab/conformance/types.ts +23 -0
  63. package/src/lab/events/types.ts +2 -1
  64. package/src/lab/events/validate.ts +2 -1
  65. package/src/lab/fabric/constants.ts +40 -0
  66. package/src/lab/fabric/executor.ts +492 -0
  67. package/src/lab/fabric/index.ts +80 -0
  68. package/src/lab/fabric/manifest.ts +222 -0
  69. package/src/lab/fabric/observe.ts +454 -0
  70. package/src/lab/fabric/patch.ts +79 -0
  71. package/src/lab/fabric/producer-child.ts +139 -0
  72. package/src/lab/fabric/producer-isolate.ts +276 -0
  73. package/src/lab/fabric/producer-protocol.ts +61 -0
  74. package/src/lab/fabric/scratch.ts +439 -0
  75. package/src/lab/fabric/subject.ts +106 -0
  76. package/src/lab/fabric/types.ts +134 -0
  77. package/src/lab/fabric/verifier.ts +98 -0
  78. package/src/lab/index.ts +2 -0
  79. package/src/lab/ledger/store.ts +173 -0
  80. package/src/lab/live/executor.ts +29 -4
  81. package/src/lab/live/transport.ts +3 -1
  82. package/src/lab/observe/from-conformance.ts +12 -57
  83. package/src/lab/observe/from-live.ts +9 -1
  84. package/src/lab/paths.ts +74 -9
  85. package/src/lab/projection/schema.ts +14 -1
  86. package/src/lab/projection/verdicts.ts +89 -53
  87. package/src/lab/projection/verification.ts +83 -3
  88. package/src/lab/query/catalog.ts +34 -5
  89. package/src/lab/query/freshness.ts +53 -0
  90. package/src/lab/query/index.ts +11 -0
  91. package/src/lab/query/latest-observation.ts +59 -0
  92. package/src/lab/query/passive-production.ts +159 -0
  93. package/src/lab/subject/installation-salt.ts +24 -4
  94. package/src/lab/subject/protocol-subject.ts +80 -0
  95. package/src/lab/subject/route-subject.ts +2 -1
  96. package/src/lib/fabric-task-execution-authority.ts +7 -0
  97. package/src/lib/fabric-task-host.ts +29 -0
  98. package/src/lib/lab-live-route-production.ts +130 -0
  99. package/src/lib/local-management-capability.ts +100 -0
  100. package/src/lib/server-resource-ownership.ts +71 -0
  101. package/src/lib/windows-elevation.ts +8 -1
  102. package/src/lib/windows-user-principal.ts +62 -2
  103. package/src/lib/winsw.ts +8 -2
  104. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  105. package/src/oauth/account-import/index.ts +15 -0
  106. package/src/oauth/account-import/parser.ts +83 -0
  107. package/src/oauth/account-import/registry.ts +18 -0
  108. package/src/oauth/account-import/service.ts +75 -0
  109. package/src/oauth/account-import/types.ts +91 -0
  110. package/src/oauth/anthropic.ts +12 -1
  111. package/src/oauth/callback-server.ts +8 -2
  112. package/src/oauth/chatgpt.ts +12 -1
  113. package/src/oauth/google-antigravity.ts +27 -0
  114. package/src/oauth/health.ts +12 -48
  115. package/src/oauth/index.ts +122 -0
  116. package/src/oauth/kimi.ts +16 -2
  117. package/src/oauth/nous.ts +749 -0
  118. package/src/oauth/store.ts +56 -3
  119. package/src/providers/codex-capacity.ts +1 -0
  120. package/src/providers/registry.ts +161 -16
  121. package/src/responses/custom-tool-compat.ts +182 -0
  122. package/src/responses/parser.ts +22 -0
  123. package/src/responses/reasoning-replay-cache.ts +168 -11
  124. package/src/responses/schema.ts +1 -1
  125. package/src/router.ts +5 -20
  126. package/src/routing/capability.ts +9 -3
  127. package/src/routing/compatibility/assemble.ts +159 -0
  128. package/src/routing/compatibility/behavior.ts +224 -0
  129. package/src/routing/compatibility/catalog.ts +99 -0
  130. package/src/routing/compatibility/endpoint.ts +52 -0
  131. package/src/routing/compatibility/policy.ts +181 -0
  132. package/src/routing/compatibility/reader.ts +110 -0
  133. package/src/routing/compatibility/subject.ts +191 -0
  134. package/src/routing/compatibility/types.ts +64 -0
  135. package/src/routing/compatibility/version.ts +104 -0
  136. package/src/routing/evaluator.ts +27 -32
  137. package/src/routing/profile.ts +118 -4
  138. package/src/routing/trace.ts +82 -28
  139. package/src/server/auth-cors.ts +7 -0
  140. package/src/server/background-lifecycle.ts +20 -7
  141. package/src/server/chat-completions.ts +2 -2
  142. package/src/server/claude-messages.ts +6 -3
  143. package/src/server/direct-local-http.ts +343 -0
  144. package/src/server/index.ts +80 -18
  145. package/src/server/lifecycle.ts +12 -4
  146. package/src/server/local-management-read-client.ts +90 -0
  147. package/src/server/management/agent-settings-routes.ts +27 -3
  148. package/src/server/management/lab-automation-routes.ts +206 -0
  149. package/src/server/management/lab-routes.ts +13 -0
  150. package/src/server/management/logs-usage-routes.ts +1 -0
  151. package/src/server/management/oauth-account-routes.ts +57 -1
  152. package/src/server/management/provider-routes.ts +16 -0
  153. package/src/server/management/routing-profile-routes.ts +12 -24
  154. package/src/server/management-api.ts +2 -0
  155. package/src/server/management-auth.ts +68 -3
  156. package/src/server/proxy-liveness.ts +3 -2
  157. package/src/server/request-decompress.ts +9 -3
  158. package/src/server/request-log.ts +53 -4
  159. package/src/server/responses/collaboration.ts +93 -17
  160. package/src/server/responses/compact.ts +3 -0
  161. package/src/server/responses/core.ts +202 -15
  162. package/src/server/responses/policy-fallback.ts +1 -0
  163. package/src/server/responses-custom-tool-repair.ts +280 -0
  164. package/src/server/responses-terminal-repair.ts +342 -0
  165. package/src/server/startup-action-control.ts +8 -1
  166. package/src/service.ts +278 -12
  167. package/src/types.ts +49 -3
  168. package/src/usage/expected-prices.ts +30 -0
  169. package/src/usage/log.ts +30 -4
  170. package/src/usage/summary.ts +153 -2
  171. package/src/web-search/loop.ts +1 -1
  172. package/gui/dist/assets/index-Cw1_mxO8.js +0 -70
  173. package/src/cli/internal-dispatch.ts +0 -20
@@ -3,7 +3,7 @@ import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, re
3
3
  import { dirname, join, resolve } from "node:path";
4
4
  import { zstdDecompressSync } from "node:zlib";
5
5
  import { Database } from "bun:sqlite";
6
- import { CODEX_HOME } from "./paths";
6
+ import { resolveCodexStateDbPath } from "./paths";
7
7
  import { atomicWriteFile, getConfigDir } from "../config";
8
8
 
9
9
  /**
@@ -13,7 +13,6 @@ import { atomicWriteFile, getConfigDir } from "../config";
13
13
  */
14
14
  export const MAX_ROLLOUT_ZST_DECOMPRESSED_BYTES = 64 * 1024 * 1024;
15
15
 
16
- const STATE_DB_PATH = join(CODEX_HOME, "state_5.sqlite");
17
16
  /**
18
17
  * The manifest that shadows one state database.
19
18
  *
@@ -26,7 +25,6 @@ export function historyBackupPathFor(stateDbPath: string): string {
26
25
  const id = createHash("sha256").update(normalized).digest("hex").slice(0, 16);
27
26
  return join(getConfigDir(), `codex-history-backup-${id}.json`);
28
27
  }
29
- const HISTORY_BACKUP_PATH = historyBackupPathFor(STATE_DB_PATH);
30
28
  const RESUMABLE_SOURCES = ["cli", "vscode"] as const;
31
29
 
32
30
  /**
@@ -690,8 +688,8 @@ function openaiRestoreIsNoop(stateDbPath: string, backupPath: string): boolean {
690
688
 
691
689
  export function syncCodexHistoryProvider(
692
690
  provider: CodexHistoryProvider,
693
- stateDbPath = STATE_DB_PATH,
694
- backupPath = HISTORY_BACKUP_PATH,
691
+ stateDbPath = resolveCodexStateDbPath(),
692
+ backupPath = historyBackupPathFor(stateDbPath),
695
693
  opts: { skipWhenProvablyNoop?: boolean } = {},
696
694
  ): CodexHistorySyncResult {
697
695
  // Opt-in steady-state gate (Design B loopback callers only): default semantics of
@@ -824,7 +822,7 @@ function restoreCodexHistoryProvider(stateDbPath: string, backupPath: string): C
824
822
  }
825
823
  }
826
824
 
827
- export function restoreLegacyOpenaiHistory(stateDbPath = STATE_DB_PATH): CodexHistorySyncResult {
825
+ export function restoreLegacyOpenaiHistory(stateDbPath = resolveCodexStateDbPath()): CodexHistorySyncResult {
828
826
  if (!existsSync(stateDbPath)) return { rows: 0, files: 0 };
829
827
  const retried = withHistoryRetryResult(() => {
830
828
  const db = openStateDb(stateDbPath);
@@ -844,8 +842,8 @@ export function restoreLegacyOpenaiHistory(stateDbPath = STATE_DB_PATH): CodexHi
844
842
  * per tick so a locked DB never stalls the event loop beyond one sqlite busy wait.
845
843
  */
846
844
  export function migrateHistoryToOpenai(
847
- stateDbPath = STATE_DB_PATH,
848
- backupPath = HISTORY_BACKUP_PATH,
845
+ stateDbPath = resolveCodexStateDbPath(),
846
+ backupPath = historyBackupPathFor(stateDbPath),
849
847
  opts: { attempts?: number; delayMs?: number; sleepFn?: (ms: number) => void } = {},
850
848
  ): CodexHistorySyncResult {
851
849
  if (!existsSync(stateDbPath)) return { rows: 0, files: 0 };
@@ -948,7 +946,10 @@ export interface PendingHistoryCount {
948
946
  * pending predicate mirrors ejectRemainingOpencodexHistory exactly — rows eject ignores
949
947
  * (empty first_user_message) are not counted, so 0 really means "migration done".
950
948
  */
951
- export function countPendingOpencodexHistory(stateDbPath = STATE_DB_PATH, backupPath = HISTORY_BACKUP_PATH): PendingHistoryCount {
949
+ export function countPendingOpencodexHistory(
950
+ stateDbPath = resolveCodexStateDbPath(),
951
+ backupPath = historyBackupPathFor(stateDbPath),
952
+ ): PendingHistoryCount {
952
953
  let backupEntries = 0;
953
954
  try {
954
955
  const manifest = readBackup(backupPath, stateDbPath);
@@ -129,6 +129,12 @@ export interface InjectCodexOptions {
129
129
  * caller that is willing to wait can raise it.
130
130
  */
131
131
  lockTimeoutMs?: number;
132
+ /**
133
+ * Validate the same config transformations and write-coordination eligibility without
134
+ * changing the journal, config, profile, catalog, cache, or history. Sync uses this before
135
+ * provider discovery so a deterministic config refusal cannot degrade an existing catalog.
136
+ */
137
+ validateOnly?: boolean;
132
138
  }
133
139
 
134
140
  function configuredManagedSubagentDefaults(
@@ -660,7 +666,7 @@ export async function injectCodexConfig(
660
666
  if (activeProvider) {
661
667
  // A launcher may have journaled before the provider manager took ownership. Never let shutdown
662
668
  // replay that stale snapshot over externally managed config.
663
- removeJournal();
669
+ if (!options.validateOnly) removeJournal();
664
670
  const nativeSubagentDefaultsWarning = configuredManagedSubagentDefaults(
665
671
  config,
666
672
  )
@@ -867,6 +873,13 @@ export async function injectCodexConfig(
867
873
  };
868
874
  }
869
875
 
876
+ if (options.validateOnly) {
877
+ return {
878
+ success: true,
879
+ message: "Codex config injection preflight passed; no files were changed.",
880
+ };
881
+ }
882
+
870
883
  const applyNativeArtifacts = (): void => {
871
884
  writeJournal({
872
885
  currentStateIsNative: !hasInjectedCodexRouting(rawContent),
@@ -20,8 +20,15 @@ export type NativeMainStartupGateSnapshot =
20
20
  | { status: "ready"; homeId: string | null }
21
21
  | {
22
22
  status: "blocked";
23
- homeId: string;
24
- reason: "recovery-pending" | "manual-recovery" | "owner-conflict" | "owner-unavailable" | "stage-cleanup-required";
23
+ homeId: string | null;
24
+ reason:
25
+ | "foreign-ownership"
26
+ | "ownership-unknown"
27
+ | "recovery-pending"
28
+ | "manual-recovery"
29
+ | "owner-conflict"
30
+ | "owner-unavailable"
31
+ | "stage-cleanup-required";
25
32
  };
26
33
 
27
34
  export interface NativeMainStartupGateDeps {
@@ -42,6 +49,10 @@ export interface NativeMainStartupLifecycle {
42
49
  let epoch = 0;
43
50
  let snapshot: NativeMainStartupGateSnapshot = { status: "ready", homeId: null };
44
51
  let settled: Promise<NativeMainStartupGateSnapshot> = Promise.resolve(snapshot);
52
+ export type NativeMainServiceOwnershipBlockReason =
53
+ | "foreign-ownership"
54
+ | "ownership-unknown";
55
+ const serviceOwnershipRefs = new Map<NativeMainServiceOwnershipBlockReason, number>();
45
56
  interface StartupEntry {
46
57
  homeId: string;
47
58
  refs: number;
@@ -295,6 +306,37 @@ export function startNativeMainStartupLifecycle(
295
306
  };
296
307
  }
297
308
 
309
+ function activeServiceOwnershipBlockReason(): NativeMainServiceOwnershipBlockReason | null {
310
+ if ((serviceOwnershipRefs.get("foreign-ownership") ?? 0) > 0) return "foreign-ownership";
311
+ if ((serviceOwnershipRefs.get("ownership-unknown") ?? 0) > 0) return "ownership-unknown";
312
+ return null;
313
+ }
314
+
315
+ function serviceOwnershipSnapshot(
316
+ reason: NativeMainServiceOwnershipBlockReason,
317
+ ): NativeMainStartupGateSnapshot {
318
+ return { status: "blocked", homeId: null, reason };
319
+ }
320
+
321
+ /** Close native-main admission without resolving or creating any CODEX_HOME artifacts. */
322
+ export function blockNativeMainStartupForUnownedServiceHome(
323
+ reason: NativeMainServiceOwnershipBlockReason,
324
+ ): NativeMainStartupLifecycle {
325
+ serviceOwnershipRefs.set(reason, (serviceOwnershipRefs.get(reason) ?? 0) + 1);
326
+ let released = false;
327
+ return {
328
+ homeId: null,
329
+ settled: Promise.resolve(serviceOwnershipSnapshot(reason)),
330
+ async release() {
331
+ if (released) return;
332
+ released = true;
333
+ const remaining = Math.max(0, (serviceOwnershipRefs.get(reason) ?? 0) - 1);
334
+ if (remaining === 0) serviceOwnershipRefs.delete(reason);
335
+ else serviceOwnershipRefs.set(reason, remaining);
336
+ },
337
+ };
338
+ }
339
+
298
340
  export function bindNativeMainStartupLifecycle(server: object, lifecycle: NativeMainStartupLifecycle): void {
299
341
  serverLifecycles.set(server, lifecycle);
300
342
  }
@@ -307,7 +349,7 @@ export async function releaseNativeMainStartupLifecycle(server: object): Promise
307
349
  }
308
350
 
309
351
  export function isNativeMainTrafficBlocked(): boolean {
310
- return snapshot.status === "blocked";
352
+ return activeServiceOwnershipBlockReason() !== null || snapshot.status === "blocked";
311
353
  }
312
354
 
313
355
  /**
@@ -340,9 +382,13 @@ export function completeNativeMainRecovery(homeId: string): boolean {
340
382
  }
341
383
 
342
384
  export function nativeMainStartupGateSnapshot(): NativeMainStartupGateSnapshot {
385
+ const reason = activeServiceOwnershipBlockReason();
386
+ if (reason) return serviceOwnershipSnapshot(reason);
343
387
  return { ...snapshot };
344
388
  }
345
389
 
346
390
  export function waitForNativeMainStartupGate(): Promise<NativeMainStartupGateSnapshot> {
391
+ const reason = activeServiceOwnershipBlockReason();
392
+ if (reason) return Promise.resolve(serviceOwnershipSnapshot(reason));
347
393
  return settled;
348
394
  }
@@ -30,6 +30,7 @@ import {
30
30
  DEFAULT_CATALOG_PATH,
31
31
  getCodexHome,
32
32
  readRootTomlString,
33
+ resolveCodexStateDbPath,
33
34
  } from "./paths";
34
35
 
35
36
  export type NativeResidueSurface =
@@ -77,7 +78,6 @@ const PROFILE_FILE_NAME = basename(CODEX_PROFILE_PATH);
77
78
  const CATALOG_FILE_NAME = basename(DEFAULT_CATALOG_PATH);
78
79
  const MODELS_CACHE_FILE_NAME = basename(CODEX_MODELS_CACHE_PATH);
79
80
  const JOURNAL_FILE_NAME = "opencodex-journal.json";
80
- const HISTORY_DATABASE_FILE_NAME = "state_5.sqlite";
81
81
  const ROUTED_CATALOG_DESCRIPTION_PREFIX = "Routed via opencodex → ";
82
82
  const MAX_ROLLOUT_INSPECTION_BYTES = 64 * 1024 * 1024;
83
83
  const ROLLOUT_READ_CHUNK_BYTES = 64 * 1024;
@@ -640,8 +640,20 @@ export function classifyNativeRoutedResidue(): NativeRoutedResidueResult {
640
640
  return indeterminate("partial-write", unresolved, `CODEX_HOME cannot be resolved: ${errorReason(error)}`);
641
641
  }
642
642
 
643
- const stateDatabasePath = join(codexHome, HISTORY_DATABASE_FILE_NAME);
644
643
  const configPath = join(codexHome, CONFIG_FILE_NAME);
644
+ let stateDatabasePath: string;
645
+ try {
646
+ stateDatabasePath = resolveCodexStateDbPath({ codexHome });
647
+ } catch (error) {
648
+ // Residue classification is a total, read-only safety boundary. An
649
+ // indeterminate SQLite authority must refuse coordination without escaping
650
+ // as an exception or falling through to a different state database.
651
+ return indeterminate(
652
+ "config",
653
+ configPath,
654
+ `SQLite home cannot be resolved: ${errorReason(error)}`,
655
+ );
656
+ }
645
657
  const profilePath = join(codexHome, PROFILE_FILE_NAME);
646
658
  const modelsCachePath = join(codexHome, MODELS_CACHE_FILE_NAME);
647
659
  const journalPath = join(codexHome, JOURNAL_FILE_NAME);
@@ -1,4 +1,4 @@
1
- import { realpathSync, statSync } from "node:fs";
1
+ import { readFileSync, realpathSync, statSync } from "node:fs";
2
2
  import { isAbsolute, join, resolve } from "node:path";
3
3
  import { expandUserPath } from "../config";
4
4
  import { defaultCodexHome } from "./home";
@@ -34,6 +34,80 @@ export function getCodexHome(): string {
34
34
  return resolveCodexHome();
35
35
  }
36
36
 
37
+ export interface CodexSqliteHomeDeps {
38
+ env?: NodeJS.ProcessEnv;
39
+ cwd?: () => string;
40
+ codexHome?: string;
41
+ readConfig?: (path: string) => string;
42
+ }
43
+
44
+ type RootTomlStringState =
45
+ | { kind: "absent" }
46
+ | { kind: "value"; value: string }
47
+ | { kind: "invalid" };
48
+
49
+ /**
50
+ * Parse the authoritative SQLite setting without changing the tolerant helper
51
+ * used by injection/catalog readers. History ownership must distinguish a
52
+ * missing key from a present value that Codex cannot interpret as a path.
53
+ */
54
+ function readAuthoritativeRootTomlString(content: string, key: string): RootTomlStringState {
55
+ let parsed: unknown;
56
+ try {
57
+ parsed = Bun.TOML.parse(content);
58
+ } catch {
59
+ return { kind: "invalid" };
60
+ }
61
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return { kind: "invalid" };
62
+ const root = parsed as Record<string, unknown>;
63
+ if (!Object.prototype.hasOwnProperty.call(root, key)) return { kind: "absent" };
64
+ const value = root[key];
65
+ if (typeof value !== "string" || value.trim() === "") return { kind: "invalid" };
66
+ return { kind: "value", value: value.trim() };
67
+ }
68
+
69
+ /**
70
+ * Resolve Codex's SQLite state root at call time.
71
+ *
72
+ * Codex permits SQLite-backed state to live outside CODEX_HOME, especially for
73
+ * Windows Desktop sessions whose app-server runs in WSL. Keep the precedence
74
+ * identical to Codex: root config, then environment, then the effective home.
75
+ */
76
+ export function resolveCodexSqliteHome(deps: CodexSqliteHomeDeps = {}): string {
77
+ const codexHome = deps.codexHome ?? getCodexHome();
78
+ const readConfig = deps.readConfig ?? (path => readFileSync(path, "utf8"));
79
+ const configPath = join(codexHome, "config.toml");
80
+ let configured: RootTomlStringState = { kind: "absent" };
81
+ try {
82
+ configured = readAuthoritativeRootTomlString(readConfig(configPath), "sqlite_home");
83
+ } catch (cause) {
84
+ if ((cause as NodeJS.ErrnoException | undefined)?.code !== "ENOENT") {
85
+ throw new Error(
86
+ `Codex config could not be read while resolving sqlite_home: ${configPath}`,
87
+ { cause },
88
+ );
89
+ }
90
+ // A genuinely absent config cannot contain the authoritative root override,
91
+ // so only ENOENT may continue to the documented environment/home fallback.
92
+ }
93
+ if (configured.kind === "invalid") {
94
+ throw new Error(`Codex config has an invalid sqlite_home setting: ${configPath}`);
95
+ }
96
+ const raw = configured.kind === "value"
97
+ ? configured.value
98
+ : (deps.env ?? process.env).CODEX_SQLITE_HOME?.trim() || "";
99
+ if (!raw) return codexHome;
100
+ const expanded = expandUserPath(raw);
101
+ return isAbsolute(expanded)
102
+ ? resolve(expanded)
103
+ : resolve((deps.cwd ?? process.cwd)(), expanded);
104
+ }
105
+
106
+ /** Active Codex thread-state database, derived from the call-time SQLite root. */
107
+ export function resolveCodexStateDbPath(deps: CodexSqliteHomeDeps = {}): string {
108
+ return join(resolveCodexSqliteHome(deps), "state_5.sqlite");
109
+ }
110
+
37
111
  export function tomlString(value: string): string {
38
112
  return JSON.stringify(value);
39
113
  }