@bitkyc08/opencodex 2.10.0 → 2.10.1

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 (274) hide show
  1. package/AGENTS_INSTALL.md +77 -0
  2. package/README.md +4 -10
  3. package/bin/ocx.mjs +71 -18
  4. package/gui/dist/assets/index-Cd6_PBKn.css +1 -0
  5. package/gui/dist/assets/index-ChZQsmBY.js +70 -0
  6. package/gui/dist/index.html +2 -2
  7. package/gui/dist/provider-icons/alibaba-color.svg +1 -1
  8. package/gui/dist/provider-icons/antigravity-color.svg +1 -1
  9. package/gui/dist/provider-icons/claude-color.svg +1 -1
  10. package/gui/dist/provider-icons/cline-color.svg +16 -0
  11. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
  12. package/gui/dist/provider-icons/copilot-color.svg +1 -1
  13. package/gui/dist/provider-icons/cursor-color.svg +1 -1
  14. package/gui/dist/provider-icons/deepseek-color.svg +1 -1
  15. package/gui/dist/provider-icons/firepass-color.svg +1 -1
  16. package/gui/dist/provider-icons/fireworks-color.svg +1 -1
  17. package/gui/dist/provider-icons/gemini-color.svg +1 -1
  18. package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
  19. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
  20. package/gui/dist/provider-icons/grok.svg +1 -1
  21. package/gui/dist/provider-icons/groq-color.svg +1 -1
  22. package/gui/dist/provider-icons/huggingface-color.svg +1 -1
  23. package/gui/dist/provider-icons/kimi-color.svg +1 -1
  24. package/gui/dist/provider-icons/kiro-color.svg +2 -2
  25. package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
  26. package/gui/dist/provider-icons/mistral-color.svg +1 -1
  27. package/gui/dist/provider-icons/moonshot-color.svg +1 -1
  28. package/gui/dist/provider-icons/nvidia-color.svg +1 -1
  29. package/gui/dist/provider-icons/ollama-color.svg +1 -1
  30. package/gui/dist/provider-icons/openai.svg +1 -1
  31. package/gui/dist/provider-icons/opencode.svg +2 -1
  32. package/gui/dist/provider-icons/openrouter-color.svg +1 -1
  33. package/gui/dist/provider-icons/pi.svg +2 -2
  34. package/gui/dist/provider-icons/qianfan-color.svg +1 -1
  35. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
  36. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
  37. package/gui/dist/provider-icons/vllm-color.svg +1 -1
  38. package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
  39. package/package.json +8 -4
  40. package/src/adapters/anthropic.ts +208 -14
  41. package/src/adapters/base.ts +16 -5
  42. package/src/adapters/cursor/effort-map.ts +3 -2
  43. package/src/adapters/cursor/framing.ts +39 -0
  44. package/src/adapters/cursor/live-transport.ts +105 -95
  45. package/src/adapters/cursor/native-exec.ts +32 -6
  46. package/src/adapters/cursor/protobuf-request.ts +20 -15
  47. package/src/adapters/cursor/request-builder.ts +21 -7
  48. package/src/adapters/cursor/types.ts +7 -0
  49. package/src/adapters/google-antigravity-replay.ts +237 -21
  50. package/src/adapters/google-truncation.ts +11 -0
  51. package/src/adapters/google.ts +50 -9
  52. package/src/adapters/identity.ts +39 -6
  53. package/src/adapters/kiro-errors.ts +11 -0
  54. package/src/adapters/kiro-events.ts +19 -1
  55. package/src/adapters/kiro-thinking.ts +10 -2
  56. package/src/adapters/kiro-tools.ts +10 -1
  57. package/src/adapters/kiro.ts +37 -11
  58. package/src/adapters/openai-chat.ts +284 -83
  59. package/src/adapters/openai-responses.ts +182 -24
  60. package/src/bridge.ts +177 -7
  61. package/src/chat/outbound.ts +78 -23
  62. package/src/claude/agents-inject.ts +27 -5
  63. package/src/claude/inbound.ts +11 -1
  64. package/src/claude/model-info.ts +13 -10
  65. package/src/claude/outbound.ts +17 -0
  66. package/src/cli/account-api.ts +24 -0
  67. package/src/cli/account-auth.ts +31 -6
  68. package/src/cli/account-main.ts +317 -0
  69. package/src/cli/account.ts +5 -0
  70. package/src/cli/claude.ts +2 -1
  71. package/src/cli/doctor.ts +93 -22
  72. package/src/cli/export-command.ts +26 -12
  73. package/src/cli/help.ts +8 -6
  74. package/src/cli/index.ts +56 -22
  75. package/src/cli/integrations.ts +84 -1
  76. package/src/cli/observe.ts +54 -1
  77. package/src/cli/opencode.ts +2 -1
  78. package/src/cli/provider-runtime.ts +18 -1
  79. package/src/cli/route-policy.ts +92 -0
  80. package/src/cli/runtime-api.ts +6 -3
  81. package/src/cli/star-prompt.ts +71 -15
  82. package/src/cli/status.ts +1 -1
  83. package/src/cli/v2.ts +36 -9
  84. package/src/clients/config-export.ts +687 -10
  85. package/src/codex/account-lifecycle.ts +30 -5
  86. package/src/codex/account-usability.ts +22 -2
  87. package/src/codex/admission.ts +255 -0
  88. package/src/codex/auth-api.ts +427 -140
  89. package/src/codex/auth-context.ts +155 -30
  90. package/src/codex/autostart-health.ts +8 -1
  91. package/src/codex/catalog/account-models.ts +62 -0
  92. package/src/codex/catalog/aggregation.ts +14 -1
  93. package/src/codex/catalog/bundled.ts +282 -32
  94. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  95. package/src/codex/catalog/metadata.ts +51 -6
  96. package/src/codex/catalog/parsing.ts +6 -3
  97. package/src/codex/catalog/provider-fetch.ts +576 -41
  98. package/src/codex/catalog/sync.ts +505 -66
  99. package/src/codex/catalog-admission.ts +197 -0
  100. package/src/codex/catalog-write-serialization.ts +241 -0
  101. package/src/codex/catalog.ts +2 -1
  102. package/src/codex/codex-write-lock.ts +372 -0
  103. package/src/codex/convergence-types.ts +593 -0
  104. package/src/codex/convergence.ts +441 -0
  105. package/src/codex/desired-state.ts +177 -0
  106. package/src/codex/features.ts +52 -8
  107. package/src/codex/generation.ts +202 -0
  108. package/src/codex/history-job.ts +257 -0
  109. package/src/codex/history-lock.ts +241 -0
  110. package/src/codex/history-migration-guardian.ts +18 -5
  111. package/src/codex/history-provider.ts +9 -2
  112. package/src/codex/history-transition.ts +105 -0
  113. package/src/codex/history-worker.ts +176 -0
  114. package/src/codex/inject-coordination.ts +245 -0
  115. package/src/codex/inject.ts +605 -124
  116. package/src/codex/integration-record.ts +266 -0
  117. package/src/codex/internal/catalog-writer.ts +203 -0
  118. package/src/codex/internal/history-writer.ts +80 -0
  119. package/src/codex/journal.ts +10 -1
  120. package/src/codex/main-account-cache.ts +24 -0
  121. package/src/codex/management-convergence.ts +114 -0
  122. package/src/codex/native-main-admission.ts +47 -0
  123. package/src/codex/native-main-auth-temp.ts +187 -0
  124. package/src/codex/native-main-claim.ts +167 -0
  125. package/src/codex/native-main-lock-file.ts +158 -0
  126. package/src/codex/native-main-owner.ts +315 -0
  127. package/src/codex/native-profile-api.ts +247 -0
  128. package/src/codex/native-profile-manager.ts +1512 -0
  129. package/src/codex/native-profile-processes.ts +121 -0
  130. package/src/codex/native-profile-recovery.ts +99 -0
  131. package/src/codex/native-profile-stage-store.ts +387 -0
  132. package/src/codex/native-profile-startup.ts +340 -0
  133. package/src/codex/native-profile-store.ts +855 -0
  134. package/src/codex/native-profile-types.ts +120 -0
  135. package/src/codex/native-residue.ts +557 -0
  136. package/src/codex/project-config-warnings.ts +18 -4
  137. package/src/codex/prompt-journal.ts +311 -0
  138. package/src/codex/prompt-layers.ts +967 -0
  139. package/src/codex/prompt-lock.ts +143 -0
  140. package/src/codex/quota-rejection.ts +224 -0
  141. package/src/codex/quota.ts +86 -3
  142. package/src/codex/routing.ts +299 -62
  143. package/src/codex/runtime.ts +159 -38
  144. package/src/codex/shim.ts +39 -13
  145. package/src/codex/subagent-model-fallback.ts +73 -12
  146. package/src/codex/transition-state.ts +604 -0
  147. package/src/codex/upstream-host-health.ts +70 -0
  148. package/src/codex/user-identity.ts +266 -0
  149. package/src/codex/write-coordination.ts +114 -0
  150. package/src/config.ts +562 -26
  151. package/src/generated/jawcode-model-metadata.ts +2 -2
  152. package/src/grok/inject.ts +15 -4
  153. package/src/grok/inspect.ts +45 -0
  154. package/src/images/loop.ts +113 -20
  155. package/src/integrations/config-io.ts +151 -0
  156. package/src/integrations/journal.ts +315 -0
  157. package/src/integrations/merge.ts +135 -0
  158. package/src/integrations/native/ownership-preflight.ts +165 -0
  159. package/src/integrations/ownership.ts +111 -0
  160. package/src/integrations/registry.ts +101 -0
  161. package/src/integrations/serialize.ts +235 -0
  162. package/src/integrations/state.ts +290 -0
  163. package/src/integrations/store.ts +103 -0
  164. package/src/integrations/writer.ts +492 -0
  165. package/src/lib/bounded-body.ts +46 -8
  166. package/src/lib/bun-runtime.ts +110 -1
  167. package/src/lib/bun-stream-caps.ts +2 -1
  168. package/src/lib/redact.ts +407 -2
  169. package/src/lib/shadow-call.ts +24 -0
  170. package/src/lib/translator-budget.ts +10 -0
  171. package/src/lib/upstream-reachability.ts +91 -0
  172. package/src/lib/upstream-retry.ts +154 -2
  173. package/src/lib/windows-secret-acl.ts +212 -11
  174. package/src/lib/winsw.ts +9 -3
  175. package/src/oauth/index.ts +61 -3
  176. package/src/oauth/key-providers.ts +4 -0
  177. package/src/oauth/kiro.ts +50 -6
  178. package/src/oauth/store.ts +31 -6
  179. package/src/oauth/token-guardian.ts +9 -3
  180. package/src/providers/codex-capacity.ts +288 -0
  181. package/src/providers/derive.ts +33 -1
  182. package/src/providers/free-directory.ts +3 -1
  183. package/src/providers/key-failover.ts +71 -3
  184. package/src/providers/openai-sidecar.ts +64 -4
  185. package/src/providers/openai-virtual-models.ts +1 -0
  186. package/src/providers/quota.ts +334 -26
  187. package/src/providers/registry.ts +284 -16
  188. package/src/providers/xai-transport.ts +11 -4
  189. package/src/responses/compaction.ts +8 -1
  190. package/src/responses/hosted-tool-policy.ts +9 -0
  191. package/src/responses/parser.ts +44 -2
  192. package/src/responses/reasoning-envelope.ts +9 -1
  193. package/src/responses/reasoning-replay-cache.ts +105 -0
  194. package/src/responses/spill-store.ts +45 -8
  195. package/src/responses/state.ts +161 -17
  196. package/src/router.ts +302 -16
  197. package/src/routing/analytics.ts +377 -0
  198. package/src/routing/capability.ts +204 -0
  199. package/src/routing/cost.ts +77 -0
  200. package/src/routing/evaluator.ts +444 -0
  201. package/src/routing/health.ts +401 -0
  202. package/src/routing/history/cursor.ts +43 -0
  203. package/src/routing/history/indexer.ts +590 -0
  204. package/src/routing/history/schema.ts +72 -0
  205. package/src/routing/profile.ts +423 -0
  206. package/src/routing/quota.ts +124 -0
  207. package/src/routing/request-evidence.ts +45 -0
  208. package/src/routing/trace.ts +686 -0
  209. package/src/server/auth-cors.ts +46 -6
  210. package/src/server/chat-completions.ts +28 -13
  211. package/src/server/claude-messages.ts +23 -15
  212. package/src/server/gui-static.ts +39 -10
  213. package/src/server/images.ts +10 -1
  214. package/src/server/index.ts +238 -52
  215. package/src/server/lifecycle.ts +228 -9
  216. package/src/server/live.ts +14 -3
  217. package/src/server/management/agent-settings-routes.ts +64 -14
  218. package/src/server/management/combo-routes.ts +5 -5
  219. package/src/server/management/config-routes.ts +1 -1
  220. package/src/server/management/context.ts +42 -2
  221. package/src/server/management/integration-routes.ts +538 -0
  222. package/src/server/management/logs-usage-routes.ts +1 -1
  223. package/src/server/management/model-routes.ts +32 -113
  224. package/src/server/management/model-rows.ts +117 -0
  225. package/src/server/management/native-integration-routes.ts +587 -0
  226. package/src/server/management/oauth-account-routes.ts +1 -1
  227. package/src/server/management/provider-routes.ts +218 -117
  228. package/src/server/management/request-history-routes.ts +191 -0
  229. package/src/server/management/routing-analytics-routes.ts +74 -0
  230. package/src/server/management/routing-profile-routes.ts +177 -0
  231. package/src/server/management/shared.ts +2 -2
  232. package/src/server/management/sidebar-routes.ts +47 -31
  233. package/src/server/management/sync-response.ts +69 -0
  234. package/src/server/management/system-restart.ts +276 -24
  235. package/src/server/management/system-routes.ts +4 -0
  236. package/src/server/management-api.ts +84 -9
  237. package/src/server/management-auth.ts +43 -5
  238. package/src/server/relay-eager.ts +82 -42
  239. package/src/server/relay.ts +120 -6
  240. package/src/server/request-log.ts +26 -6
  241. package/src/server/responses/collaboration.ts +63 -8
  242. package/src/server/responses/compact.ts +272 -41
  243. package/src/server/responses/core.ts +730 -132
  244. package/src/server/responses/fetch-helpers.ts +15 -1
  245. package/src/server/responses-item-id-repair.ts +32 -3
  246. package/src/server/responses-json-events.ts +52 -0
  247. package/src/server/responses-snapshot-repair.ts +621 -0
  248. package/src/server/search.ts +51 -6
  249. package/src/server/sse-payload-rewrite.ts +89 -12
  250. package/src/server/startup-health-cache.ts +7 -1
  251. package/src/server/ws-bridge.ts +11 -17
  252. package/src/service-manager-probe.ts +297 -0
  253. package/src/service.ts +222 -32
  254. package/src/tray/windows-tray.ps1 +9 -0
  255. package/src/tray/windows.ts +15 -7
  256. package/src/types.ts +194 -14
  257. package/src/update/index.ts +13 -13
  258. package/src/update/job.ts +24 -21
  259. package/src/update/notify.ts +7 -3
  260. package/src/usage/cost.ts +0 -0
  261. package/src/usage/expected-prices.ts +129 -10
  262. package/src/usage/log.ts +50 -15
  263. package/src/usage/summary.ts +4 -4
  264. package/src/vision/index.ts +6 -1
  265. package/src/web-search/loop.ts +161 -34
  266. package/gui/dist/assets/index-OY43ubAq.css +0 -1
  267. package/gui/dist/assets/index-YwNnKZcL.js +0 -67
  268. package/gui/dist/provider-icons/antigravity.svg +0 -1
  269. package/gui/dist/provider-icons/claude.svg +0 -1
  270. package/gui/dist/provider-icons/copilot.svg +0 -1
  271. package/gui/dist/provider-icons/cursor.svg +0 -2
  272. package/gui/dist/provider-icons/gemini.svg +0 -1
  273. package/gui/dist/provider-icons/grok-color.svg +0 -1
  274. package/gui/dist/provider-icons/kiro.svg +0 -14
@@ -0,0 +1,340 @@
1
+ import { NativeProfileManager } from "./native-profile-manager";
2
+ import { clearAccountNeedsReauth } from "./account-runtime-state";
3
+ import { MAIN_CODEX_ACCOUNT_ID } from "./main-account";
4
+ import {
5
+ probeNativeProfileRecoveryState,
6
+ type NativeProfileRecoveryState,
7
+ } from "./native-profile-store";
8
+ import {
9
+ retainNativeMainOwner,
10
+ withNativeMainOwnerOperation,
11
+ type NativeMainOwnerOptions,
12
+ type NativeMainOwnerReference,
13
+ type NativeMainOwnerSnapshot,
14
+ } from "./native-main-owner";
15
+ import { withNativeMainExclusiveClaim } from "./native-main-claim";
16
+ import { scrubNativeMainAuthTempResidues } from "./native-main-auth-temp";
17
+ import { NATIVE_STAGE_SWEEP_INTERVAL_MS } from "./native-profile-stage-store";
18
+
19
+ export type NativeMainStartupGateSnapshot =
20
+ | { status: "ready"; homeId: string | null }
21
+ | {
22
+ status: "blocked";
23
+ homeId: string;
24
+ reason: "recovery-pending" | "manual-recovery" | "owner-conflict" | "owner-unavailable" | "stage-cleanup-required";
25
+ };
26
+
27
+ export interface NativeMainStartupGateDeps {
28
+ manager?: NativeProfileManager;
29
+ /** Test-only barrier used to prove admission stays closed while startup recovery is pending. */
30
+ beforeRecovery?: () => void | Promise<void>;
31
+ probeRecoveryState?: typeof probeNativeProfileRecoveryState;
32
+ owner?: NativeMainOwnerOptions;
33
+ stageSweepIntervalMs?: number;
34
+ }
35
+
36
+ export interface NativeMainStartupLifecycle {
37
+ readonly homeId: string | null;
38
+ readonly settled: Promise<NativeMainStartupGateSnapshot>;
39
+ release(): Promise<void>;
40
+ }
41
+
42
+ let epoch = 0;
43
+ let snapshot: NativeMainStartupGateSnapshot = { status: "ready", homeId: null };
44
+ let settled: Promise<NativeMainStartupGateSnapshot> = Promise.resolve(snapshot);
45
+ interface StartupEntry {
46
+ homeId: string;
47
+ refs: number;
48
+ epoch: number;
49
+ owner: NativeMainOwnerReference;
50
+ unsubscribe: () => void;
51
+ recoveryStarted: boolean;
52
+ settled: Promise<NativeMainStartupGateSnapshot>;
53
+ resolveAcquisition?: (value: NativeMainStartupGateSnapshot) => void;
54
+ deps: NativeMainStartupGateDeps;
55
+ manager: NativeProfileManager;
56
+ sweepTimer?: ReturnType<typeof setTimeout>;
57
+ sweepInFlight?: Promise<void>;
58
+ sweepStopping: boolean;
59
+ }
60
+ const startupEntries = new Map<string, StartupEntry>();
61
+ const serverLifecycles = new WeakMap<object, NativeMainStartupLifecycle>();
62
+
63
+ function ready(homeId: string | null): NativeMainStartupGateSnapshot {
64
+ return { status: "ready", homeId };
65
+ }
66
+
67
+ /**
68
+ * Arm the native-main gate synchronously, then converge the encrypted journal in the background.
69
+ * No credential bytes are read here: journal/auth inspection remains inside NativeProfileManager.
70
+ */
71
+ export function initializeNativeMainStartupGate(
72
+ deps: NativeMainStartupGateDeps = {},
73
+ ): Promise<NativeMainStartupGateSnapshot> {
74
+ const currentEpoch = ++epoch;
75
+ let manager: NativeProfileManager;
76
+ try {
77
+ manager = deps.manager ?? new NativeProfileManager();
78
+ } catch {
79
+ // A missing/unresolvable Codex home cannot provide a usable native-main token either.
80
+ // Do not turn an unused opt-in feature into a process-wide startup outage.
81
+ snapshot = ready(null);
82
+ settled = Promise.resolve(snapshot);
83
+ return settled;
84
+ }
85
+
86
+ const homeId = manager.context.homeId;
87
+ const probe = deps.probeRecoveryState ?? probeNativeProfileRecoveryState;
88
+ let recoveryState: NativeProfileRecoveryState;
89
+ try {
90
+ recoveryState = probe(manager.context);
91
+ } catch {
92
+ snapshot = { status: "blocked", homeId, reason: "manual-recovery" };
93
+ settled = Promise.resolve(snapshot);
94
+ return settled;
95
+ }
96
+ if (recoveryState === "none") {
97
+ snapshot = ready(homeId);
98
+ settled = Promise.resolve(snapshot);
99
+ return settled;
100
+ }
101
+
102
+ if (recoveryState !== "journal") {
103
+ snapshot = { status: "blocked", homeId, reason: "manual-recovery" };
104
+ settled = Promise.resolve(snapshot);
105
+ return settled;
106
+ }
107
+
108
+ snapshot = { status: "blocked", homeId, reason: "recovery-pending" };
109
+ settled = (async () => {
110
+ try {
111
+ await deps.beforeRecovery?.();
112
+ await manager.recover(false);
113
+ if (epoch === currentEpoch && probe(manager.context) === "none") {
114
+ clearAccountNeedsReauth(MAIN_CODEX_ACCOUNT_ID);
115
+ snapshot = ready(homeId);
116
+ } else if (epoch === currentEpoch) {
117
+ snapshot = { status: "blocked", homeId, reason: "manual-recovery" };
118
+ }
119
+ } catch {
120
+ if (epoch === currentEpoch) snapshot = { status: "blocked", homeId, reason: "manual-recovery" };
121
+ }
122
+ return snapshot;
123
+ })();
124
+ return settled;
125
+ }
126
+
127
+ function ownerBlockedReason(owner: NativeMainOwnerSnapshot): "owner-conflict" | "owner-unavailable" {
128
+ return owner.status === "unavailable" ? "owner-unavailable" : "owner-conflict";
129
+ }
130
+
131
+ async function runOwnedStageSweep(entry: StartupEntry): Promise<boolean> {
132
+ if (typeof (entry.manager as Partial<NativeProfileManager>).sweepStages !== "function") return true;
133
+ try {
134
+ const result = await withNativeMainOwnerOperation(entry.manager.context, () => entry.manager.sweepStages());
135
+ return !result.plaintextMayRemain;
136
+ } catch {
137
+ return false;
138
+ }
139
+ }
140
+
141
+ function scheduleStageSweep(entry: StartupEntry): void {
142
+ if (entry.sweepStopping || entry.sweepTimer || startupEntries.get(entry.homeId) !== entry) return;
143
+ const intervalMs = Math.max(10, entry.deps.stageSweepIntervalMs ?? NATIVE_STAGE_SWEEP_INTERVAL_MS);
144
+ entry.sweepTimer = setTimeout(() => {
145
+ entry.sweepTimer = undefined;
146
+ if (entry.sweepStopping || startupEntries.get(entry.homeId) !== entry) return;
147
+ entry.sweepInFlight = (async () => {
148
+ const safe = await runOwnedStageSweep(entry);
149
+ if (entry.sweepStopping || startupEntries.get(entry.homeId) !== entry) return;
150
+ if (!safe) snapshot = { status: "blocked", homeId: entry.homeId, reason: "stage-cleanup-required" };
151
+ else if (snapshot.homeId === entry.homeId && snapshot.status === "blocked" && snapshot.reason === "stage-cleanup-required") {
152
+ snapshot = ready(entry.homeId);
153
+ }
154
+ })().finally(() => {
155
+ entry.sweepInFlight = undefined;
156
+ scheduleStageSweep(entry);
157
+ });
158
+ }, intervalMs);
159
+ entry.sweepTimer.unref?.();
160
+ }
161
+
162
+ function convergeOwnedStartup(entry: StartupEntry): void {
163
+ if (entry.recoveryStarted) return;
164
+ entry.recoveryStarted = true;
165
+ const currentEpoch = entry.epoch;
166
+ snapshot = { status: "blocked", homeId: entry.homeId, reason: "recovery-pending" };
167
+ const acquisitionWaiter = entry.resolveAcquisition;
168
+ entry.resolveAcquisition = undefined;
169
+ entry.settled = settled = (async () => {
170
+ const probe = entry.deps.probeRecoveryState ?? probeNativeProfileRecoveryState;
171
+ try {
172
+ const recoveryState = await withNativeMainOwnerOperation(entry.manager.context, () => withNativeMainExclusiveClaim(
173
+ entry.manager.context,
174
+ async () => {
175
+ scrubNativeMainAuthTempResidues(entry.manager.context);
176
+ let state = probe(entry.manager.context);
177
+ if (state === "journal") {
178
+ await entry.deps.beforeRecovery?.();
179
+ await entry.manager.recover(false);
180
+ state = probe(entry.manager.context);
181
+ }
182
+ return state;
183
+ },
184
+ { waitMs: 10_000 },
185
+ ));
186
+ const stageSweepSafe = recoveryState === "none" ? await runOwnedStageSweep(entry) : false;
187
+ if (startupEntries.get(entry.homeId) === entry && entry.epoch === currentEpoch && recoveryState === "none" && stageSweepSafe) {
188
+ clearAccountNeedsReauth(MAIN_CODEX_ACCOUNT_ID);
189
+ snapshot = ready(entry.homeId);
190
+ } else if (startupEntries.get(entry.homeId) === entry && entry.epoch === currentEpoch && recoveryState === "none") {
191
+ snapshot = { status: "blocked", homeId: entry.homeId, reason: "stage-cleanup-required" };
192
+ } else if (startupEntries.get(entry.homeId) === entry && entry.epoch === currentEpoch) {
193
+ snapshot = { status: "blocked", homeId: entry.homeId, reason: "manual-recovery" };
194
+ }
195
+ } catch {
196
+ if (startupEntries.get(entry.homeId) === entry && entry.epoch === currentEpoch) {
197
+ snapshot = { status: "blocked", homeId: entry.homeId, reason: "manual-recovery" };
198
+ }
199
+ }
200
+ if (startupEntries.get(entry.homeId) === entry && entry.epoch === currentEpoch) scheduleStageSweep(entry);
201
+ return snapshot;
202
+ })();
203
+ if (acquisitionWaiter) void entry.settled.then(acquisitionWaiter);
204
+ }
205
+
206
+ function observeOwner(entry: StartupEntry, owner: NativeMainOwnerSnapshot): void {
207
+ if (startupEntries.get(entry.homeId) !== entry) return;
208
+ if (owner.status === "acquiring") {
209
+ snapshot = { status: "blocked", homeId: entry.homeId, reason: "recovery-pending" };
210
+ return;
211
+ }
212
+ if (owner.status === "held") {
213
+ convergeOwnedStartup(entry);
214
+ return;
215
+ }
216
+ if (owner.status === "closing" || owner.status === "released") return;
217
+ snapshot = { status: "blocked", homeId: entry.homeId, reason: ownerBlockedReason(owner) };
218
+ entry.settled = settled = Promise.resolve(snapshot);
219
+ entry.resolveAcquisition?.(snapshot);
220
+ entry.resolveAcquisition = undefined;
221
+ }
222
+
223
+ /**
224
+ * Retain process ownership for one live server. The first reference acquires the
225
+ * canonical-home SQLite lease and owns recovery; later same-process references share it.
226
+ */
227
+ export function startNativeMainStartupLifecycle(
228
+ deps: NativeMainStartupGateDeps = {},
229
+ ): NativeMainStartupLifecycle {
230
+ let manager: NativeProfileManager;
231
+ try {
232
+ manager = deps.manager ?? new NativeProfileManager();
233
+ } catch {
234
+ snapshot = ready(null);
235
+ settled = Promise.resolve(snapshot);
236
+ return { homeId: null, settled, release: async () => {} };
237
+ }
238
+ const homeId = manager.context.homeId;
239
+ let entry = startupEntries.get(homeId);
240
+ if (!entry) {
241
+ const owner = retainNativeMainOwner(manager.context, deps.owner);
242
+ snapshot = { status: "blocked", homeId, reason: "recovery-pending" };
243
+ let resolveAcquisition!: (value: NativeMainStartupGateSnapshot) => void;
244
+ const acquisition = new Promise<NativeMainStartupGateSnapshot>(resolve => { resolveAcquisition = resolve; });
245
+ settled = acquisition;
246
+ entry = {
247
+ homeId,
248
+ refs: 0,
249
+ epoch: ++epoch,
250
+ owner,
251
+ unsubscribe: () => {},
252
+ recoveryStarted: false,
253
+ settled: acquisition,
254
+ resolveAcquisition,
255
+ deps,
256
+ manager,
257
+ sweepStopping: false,
258
+ };
259
+ startupEntries.set(homeId, entry);
260
+ entry.unsubscribe = owner.subscribe(ownerState => observeOwner(entry!, ownerState));
261
+ }
262
+ entry.refs += 1;
263
+ let released = false;
264
+ return {
265
+ homeId,
266
+ get settled() { return entry!.settled; },
267
+ async release() {
268
+ if (released) return;
269
+ released = true;
270
+ entry!.refs = Math.max(0, entry!.refs - 1);
271
+ if (entry!.refs !== 0) return;
272
+ entry!.epoch += 1;
273
+ entry!.sweepStopping = true;
274
+ if (entry!.sweepTimer) clearTimeout(entry!.sweepTimer);
275
+ entry!.sweepTimer = undefined;
276
+ entry!.unsubscribe();
277
+ startupEntries.delete(homeId);
278
+ entry!.resolveAcquisition?.(snapshot);
279
+ entry!.resolveAcquisition = undefined;
280
+ // Startup convergence can transition from the exclusive recovery claim
281
+ // into a stage sweep. Keep the owner registered until that entire chain
282
+ // settles so no cleanup transaction starts untracked after owner detach.
283
+ await Promise.allSettled([entry!.settled]);
284
+ if (entry!.sweepInFlight) await Promise.allSettled([entry!.sweepInFlight]);
285
+ await entry!.owner.release();
286
+ },
287
+ };
288
+ }
289
+
290
+ export function bindNativeMainStartupLifecycle(server: object, lifecycle: NativeMainStartupLifecycle): void {
291
+ serverLifecycles.set(server, lifecycle);
292
+ }
293
+
294
+ export async function releaseNativeMainStartupLifecycle(server: object): Promise<void> {
295
+ const lifecycle = serverLifecycles.get(server);
296
+ if (!lifecycle) return;
297
+ serverLifecycles.delete(server);
298
+ await lifecycle.release();
299
+ }
300
+
301
+ export function isNativeMainTrafficBlocked(): boolean {
302
+ return snapshot.status === "blocked";
303
+ }
304
+
305
+ /**
306
+ * Close the process-wide native-main admission gate after a live transaction
307
+ * leaves recovery state behind. A known different home owns a different
308
+ * native credential file, so it must not be fenced by this transition.
309
+ */
310
+ export function blockNativeMainRecovery(
311
+ homeId: string,
312
+ recoveryState?: Exclude<NativeProfileRecoveryState, "none">,
313
+ ): boolean {
314
+ if (snapshot.homeId !== null && snapshot.homeId !== homeId) return false;
315
+ epoch += 1;
316
+ snapshot = {
317
+ status: "blocked",
318
+ homeId,
319
+ reason: recoveryState === "journal" ? "recovery-pending" : "manual-recovery",
320
+ };
321
+ settled = Promise.resolve(snapshot);
322
+ return true;
323
+ }
324
+
325
+ export function completeNativeMainRecovery(homeId: string): boolean {
326
+ if (snapshot.status !== "blocked" || snapshot.homeId !== homeId) return false;
327
+ epoch += 1;
328
+ clearAccountNeedsReauth(MAIN_CODEX_ACCOUNT_ID);
329
+ snapshot = ready(homeId);
330
+ settled = Promise.resolve(snapshot);
331
+ return true;
332
+ }
333
+
334
+ export function nativeMainStartupGateSnapshot(): NativeMainStartupGateSnapshot {
335
+ return { ...snapshot };
336
+ }
337
+
338
+ export function waitForNativeMainStartupGate(): Promise<NativeMainStartupGateSnapshot> {
339
+ return settled;
340
+ }