@claudexor/cli 3.24.0 → 4.0.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 (281) hide show
  1. package/dist/account-management.d.ts +17 -0
  2. package/dist/account-management.d.ts.map +1 -0
  3. package/dist/account-management.js +35 -0
  4. package/dist/account-management.js.map +1 -0
  5. package/dist/account-reset-command.d.ts +3 -0
  6. package/dist/account-reset-command.d.ts.map +1 -0
  7. package/dist/account-reset-command.js +32 -0
  8. package/dist/account-reset-command.js.map +1 -0
  9. package/dist/account-reset-services.d.ts +341 -0
  10. package/dist/account-reset-services.d.ts.map +1 -0
  11. package/dist/account-reset-services.js +183 -0
  12. package/dist/account-reset-services.js.map +1 -0
  13. package/dist/account-resource-query.d.ts +18 -0
  14. package/dist/account-resource-query.d.ts.map +1 -0
  15. package/dist/account-resource-query.js +62 -0
  16. package/dist/account-resource-query.js.map +1 -0
  17. package/dist/accounts-services.d.ts +1 -0
  18. package/dist/accounts-services.d.ts.map +1 -1
  19. package/dist/accounts-services.js +4 -2
  20. package/dist/accounts-services.js.map +1 -1
  21. package/dist/claude-account-resources.d.ts +5 -0
  22. package/dist/claude-account-resources.d.ts.map +1 -0
  23. package/dist/claude-account-resources.js +134 -0
  24. package/dist/claude-account-resources.js.map +1 -0
  25. package/dist/claude-oauth-codec.d.ts +4 -0
  26. package/dist/claude-oauth-codec.d.ts.map +1 -0
  27. package/dist/claude-oauth-codec.js +71 -0
  28. package/dist/claude-oauth-codec.js.map +1 -0
  29. package/dist/claude-oauth-credential.d.ts +6 -0
  30. package/dist/claude-oauth-credential.d.ts.map +1 -1
  31. package/dist/claude-oauth-credential.js +18 -0
  32. package/dist/claude-oauth-credential.js.map +1 -1
  33. package/dist/claude-oauth-fetch.d.ts +1 -1
  34. package/dist/claude-oauth-fetch.d.ts.map +1 -1
  35. package/dist/claude-oauth-fetch.js +3 -2
  36. package/dist/claude-oauth-fetch.js.map +1 -1
  37. package/dist/claude-oauth-usage.d.ts +5 -4
  38. package/dist/claude-oauth-usage.d.ts.map +1 -1
  39. package/dist/claude-oauth-usage.js +47 -75
  40. package/dist/claude-oauth-usage.js.map +1 -1
  41. package/dist/claude-resource-source.d.ts +18 -0
  42. package/dist/claude-resource-source.d.ts.map +1 -0
  43. package/dist/claude-resource-source.js +108 -0
  44. package/dist/claude-resource-source.js.map +1 -0
  45. package/dist/claude-resource-transport.d.ts +12 -0
  46. package/dist/claude-resource-transport.d.ts.map +1 -0
  47. package/dist/claude-resource-transport.js +47 -0
  48. package/dist/claude-resource-transport.js.map +1 -0
  49. package/dist/claude-statusline.d.ts +2 -4
  50. package/dist/claude-statusline.d.ts.map +1 -1
  51. package/dist/claude-statusline.js +6 -5
  52. package/dist/claude-statusline.js.map +1 -1
  53. package/dist/claudexord-entry.d.ts +7 -2
  54. package/dist/claudexord-entry.d.ts.map +1 -1
  55. package/dist/claudexord-entry.js +10 -3
  56. package/dist/claudexord-entry.js.map +1 -1
  57. package/dist/claudexord.d.ts +0 -1
  58. package/dist/claudexord.d.ts.map +1 -1
  59. package/dist/claudexord.js +308 -239
  60. package/dist/claudexord.js.map +1 -1
  61. package/dist/cli-error.js +3 -3
  62. package/dist/cli-error.js.map +1 -1
  63. package/dist/cli.js +7 -49
  64. package/dist/cli.js.map +1 -1
  65. package/dist/codex-quota-source.d.ts +7 -2
  66. package/dist/codex-quota-source.d.ts.map +1 -1
  67. package/dist/codex-quota-source.js +74 -14
  68. package/dist/codex-quota-source.js.map +1 -1
  69. package/dist/command-flags.d.ts.map +1 -1
  70. package/dist/command-flags.js +6 -0
  71. package/dist/command-flags.js.map +1 -1
  72. package/dist/command-registry.js +1 -1
  73. package/dist/command-registry.js.map +1 -1
  74. package/dist/continue-from-run.d.ts +7 -8
  75. package/dist/continue-from-run.d.ts.map +1 -1
  76. package/dist/continue-from-run.js +3 -4
  77. package/dist/continue-from-run.js.map +1 -1
  78. package/dist/control-api-address.d.ts +6 -0
  79. package/dist/control-api-address.d.ts.map +1 -0
  80. package/dist/control-api-address.js +53 -0
  81. package/dist/control-api-address.js.map +1 -0
  82. package/dist/control-services.d.ts +400 -230
  83. package/dist/control-services.d.ts.map +1 -1
  84. package/dist/control-services.js +11 -32
  85. package/dist/control-services.js.map +1 -1
  86. package/dist/daemon-admission-runtime.d.ts +0 -60
  87. package/dist/daemon-admission-runtime.d.ts.map +1 -1
  88. package/dist/daemon-admission-runtime.js +3 -168
  89. package/dist/daemon-admission-runtime.js.map +1 -1
  90. package/dist/daemon-agent-runner.d.ts +6 -3
  91. package/dist/daemon-agent-runner.d.ts.map +1 -1
  92. package/dist/daemon-agent-runner.js +2 -1
  93. package/dist/daemon-agent-runner.js.map +1 -1
  94. package/dist/daemon-lifecycle.d.ts +10 -3
  95. package/dist/daemon-lifecycle.d.ts.map +1 -1
  96. package/dist/daemon-lifecycle.js +16 -7
  97. package/dist/daemon-lifecycle.js.map +1 -1
  98. package/dist/daemon-owner.d.ts +7 -0
  99. package/dist/daemon-owner.d.ts.map +1 -0
  100. package/dist/daemon-owner.js +39 -0
  101. package/dist/daemon-owner.js.map +1 -0
  102. package/dist/daemon-run.d.ts +8 -1
  103. package/dist/daemon-run.d.ts.map +1 -1
  104. package/dist/daemon-run.js +31 -3
  105. package/dist/daemon-run.js.map +1 -1
  106. package/dist/daemon-runtime-shutdown.d.ts +1 -1
  107. package/dist/daemon-runtime-shutdown.d.ts.map +1 -1
  108. package/dist/daemon-runtime-shutdown.js +2 -2
  109. package/dist/daemon-runtime-shutdown.js.map +1 -1
  110. package/dist/daemon-startup.d.ts +5 -42
  111. package/dist/daemon-startup.d.ts.map +1 -1
  112. package/dist/daemon-startup.js +7 -85
  113. package/dist/daemon-startup.js.map +1 -1
  114. package/dist/daemon-status-report.d.ts +4 -0
  115. package/dist/daemon-status-report.d.ts.map +1 -0
  116. package/dist/daemon-status-report.js +39 -0
  117. package/dist/daemon-status-report.js.map +1 -0
  118. package/dist/engine-skew.d.ts +1 -0
  119. package/dist/engine-skew.d.ts.map +1 -1
  120. package/dist/engine-skew.js +6 -2
  121. package/dist/engine-skew.js.map +1 -1
  122. package/dist/harness-maintenance-service.d.ts +9 -222
  123. package/dist/harness-maintenance-service.d.ts.map +1 -1
  124. package/dist/harness-maintenance-service.js +7 -37
  125. package/dist/harness-maintenance-service.js.map +1 -1
  126. package/dist/live.d.ts +2 -5
  127. package/dist/live.d.ts.map +1 -1
  128. package/dist/live.js +2 -44
  129. package/dist/live.js.map +1 -1
  130. package/dist/mcp-catalog-query.d.ts +2 -1
  131. package/dist/mcp-catalog-query.d.ts.map +1 -1
  132. package/dist/mcp-catalog-query.js +6 -5
  133. package/dist/mcp-catalog-query.js.map +1 -1
  134. package/dist/mcp-daemon-unavailable.d.ts.map +1 -1
  135. package/dist/mcp-daemon-unavailable.js +4 -3
  136. package/dist/mcp-daemon-unavailable.js.map +1 -1
  137. package/dist/mcp-journal-recovery.d.ts.map +1 -1
  138. package/dist/mcp-journal-recovery.js +4 -4
  139. package/dist/mcp-journal-recovery.js.map +1 -1
  140. package/dist/mcp-runner.d.ts +1 -1
  141. package/dist/mcp-runner.d.ts.map +1 -1
  142. package/dist/mcp-runner.js +11 -4
  143. package/dist/mcp-runner.js.map +1 -1
  144. package/dist/mcp-thread-query.d.ts +1 -1
  145. package/dist/mcp-thread-query.d.ts.map +1 -1
  146. package/dist/mcp-thread-query.js +2 -2
  147. package/dist/mcp-thread-query.js.map +1 -1
  148. package/dist/model-operation-control.d.ts +375 -0
  149. package/dist/model-operation-control.d.ts.map +1 -0
  150. package/dist/model-operation-control.js +16 -0
  151. package/dist/model-operation-control.js.map +1 -0
  152. package/dist/model-services.d.ts +302 -91
  153. package/dist/model-services.d.ts.map +1 -1
  154. package/dist/model-services.js +5 -7
  155. package/dist/model-services.js.map +1 -1
  156. package/dist/ops-command-specs.d.ts.map +1 -1
  157. package/dist/ops-command-specs.js +11 -2
  158. package/dist/ops-command-specs.js.map +1 -1
  159. package/dist/ops-commands.d.ts +1 -8
  160. package/dist/ops-commands.d.ts.map +1 -1
  161. package/dist/ops-commands.js +13 -16
  162. package/dist/ops-commands.js.map +1 -1
  163. package/dist/orphan-reaper.d.ts +18 -0
  164. package/dist/orphan-reaper.d.ts.map +1 -1
  165. package/dist/orphan-reaper.js +50 -0
  166. package/dist/orphan-reaper.js.map +1 -1
  167. package/dist/plugin-cli.d.ts +3 -0
  168. package/dist/plugin-cli.d.ts.map +1 -0
  169. package/dist/plugin-cli.js +57 -0
  170. package/dist/plugin-cli.js.map +1 -0
  171. package/dist/plugin-mcp.d.ts +3 -0
  172. package/dist/plugin-mcp.d.ts.map +1 -0
  173. package/dist/plugin-mcp.js +116 -0
  174. package/dist/plugin-mcp.js.map +1 -0
  175. package/dist/plugin-runtime.d.ts +24 -0
  176. package/dist/plugin-runtime.d.ts.map +1 -0
  177. package/dist/plugin-runtime.js +113 -0
  178. package/dist/plugin-runtime.js.map +1 -0
  179. package/dist/plugins.d.ts +2 -0
  180. package/dist/plugins.d.ts.map +1 -1
  181. package/dist/plugins.js +92 -184
  182. package/dist/plugins.js.map +1 -1
  183. package/dist/project-command.d.ts +4 -1
  184. package/dist/project-command.d.ts.map +1 -1
  185. package/dist/project-command.js +14 -5
  186. package/dist/project-command.js.map +1 -1
  187. package/dist/quota-command.d.ts.map +1 -1
  188. package/dist/quota-command.js +38 -2
  189. package/dist/quota-command.js.map +1 -1
  190. package/dist/quota-refreshers.d.ts +3 -1
  191. package/dist/quota-refreshers.d.ts.map +1 -1
  192. package/dist/quota-refreshers.js +6 -2
  193. package/dist/quota-refreshers.js.map +1 -1
  194. package/dist/quota-services.d.ts +236 -2
  195. package/dist/quota-services.d.ts.map +1 -1
  196. package/dist/quota-services.js +84 -4
  197. package/dist/quota-services.js.map +1 -1
  198. package/dist/recovery-control-services.d.ts +99 -0
  199. package/dist/recovery-control-services.d.ts.map +1 -0
  200. package/dist/recovery-control-services.js +32 -0
  201. package/dist/recovery-control-services.js.map +1 -0
  202. package/dist/remote-filesystem.d.ts +3 -3
  203. package/dist/remote-filesystem.d.ts.map +1 -1
  204. package/dist/remote-filesystem.js.map +1 -1
  205. package/dist/request-preflight.d.ts +2 -2
  206. package/dist/request-preflight.d.ts.map +1 -1
  207. package/dist/request-preflight.js.map +1 -1
  208. package/dist/retention-service.d.ts +3 -3
  209. package/dist/retention-service.d.ts.map +1 -1
  210. package/dist/settings-command.d.ts.map +1 -1
  211. package/dist/settings-command.js +7 -2
  212. package/dist/settings-command.js.map +1 -1
  213. package/dist/settings-service.d.ts +66 -6
  214. package/dist/settings-service.d.ts.map +1 -1
  215. package/dist/settings-service.js +1 -1
  216. package/dist/settings-service.js.map +1 -1
  217. package/dist/setup-client-pty.js +1 -1
  218. package/dist/setup-client-pty.js.map +1 -1
  219. package/dist/setup-credential-window.d.ts +4 -4
  220. package/dist/setup-credential-window.d.ts.map +1 -1
  221. package/dist/setup-credential-window.js +1 -1
  222. package/dist/setup-credential-window.js.map +1 -1
  223. package/dist/setup-device-code-disclosure.js +1 -1
  224. package/dist/setup-device-code-disclosure.js.map +1 -1
  225. package/dist/setup-job-persistence.d.ts +30 -0
  226. package/dist/setup-job-persistence.d.ts.map +1 -0
  227. package/dist/setup-job-persistence.js +3 -0
  228. package/dist/setup-job-persistence.js.map +1 -0
  229. package/dist/{setup-job-store.d.ts → setup-job-projection.d.ts} +9 -17
  230. package/dist/setup-job-projection.d.ts.map +1 -0
  231. package/dist/{setup-job-store.js → setup-job-projection.js} +29 -68
  232. package/dist/setup-job-projection.js.map +1 -0
  233. package/dist/setup-job-support.js +1 -1
  234. package/dist/setup-job-support.js.map +1 -1
  235. package/dist/setup-jobs.d.ts +3 -4
  236. package/dist/setup-jobs.d.ts.map +1 -1
  237. package/dist/setup-jobs.js +3 -3
  238. package/dist/setup-jobs.js.map +1 -1
  239. package/dist/setup-lifecycle-binding.js +1 -1
  240. package/dist/setup-lifecycle-binding.js.map +1 -1
  241. package/dist/setup-login-completion.js +1 -1
  242. package/dist/setup-login-completion.js.map +1 -1
  243. package/dist/sql-daemon-queries.d.ts +24 -0
  244. package/dist/sql-daemon-queries.d.ts.map +1 -0
  245. package/dist/sql-daemon-queries.js +41 -0
  246. package/dist/sql-daemon-queries.js.map +1 -0
  247. package/dist/sql-daemon-runtime.d.ts +1415 -0
  248. package/dist/sql-daemon-runtime.d.ts.map +1 -0
  249. package/dist/sql-daemon-runtime.js +94 -0
  250. package/dist/sql-daemon-runtime.js.map +1 -0
  251. package/dist/sql-daemon-storage.d.ts +40 -0
  252. package/dist/sql-daemon-storage.d.ts.map +1 -0
  253. package/dist/sql-daemon-storage.js +203 -0
  254. package/dist/sql-daemon-storage.js.map +1 -0
  255. package/dist/sql-import-publication.d.ts +19 -0
  256. package/dist/sql-import-publication.d.ts.map +1 -0
  257. package/dist/sql-import-publication.js +54 -0
  258. package/dist/sql-import-publication.js.map +1 -0
  259. package/dist/sql-setup-lifecycle.d.ts +15 -0
  260. package/dist/sql-setup-lifecycle.d.ts.map +1 -0
  261. package/dist/sql-setup-lifecycle.js +28 -0
  262. package/dist/sql-setup-lifecycle.js.map +1 -0
  263. package/dist/sql-setup-store.d.ts +7 -0
  264. package/dist/sql-setup-store.d.ts.map +1 -0
  265. package/dist/sql-setup-store.js +102 -0
  266. package/dist/sql-setup-store.js.map +1 -0
  267. package/dist/sql-startup-storage.d.ts +15 -0
  268. package/dist/sql-startup-storage.d.ts.map +1 -0
  269. package/dist/sql-startup-storage.js +75 -0
  270. package/dist/sql-startup-storage.js.map +1 -0
  271. package/dist/thread-delivery.d.ts +2 -2
  272. package/dist/thread-purge.d.ts +8 -2
  273. package/dist/thread-purge.d.ts.map +1 -1
  274. package/dist/thread-purge.js +37 -8
  275. package/dist/thread-purge.js.map +1 -1
  276. package/dist/thread-turn-services.d.ts +2 -2
  277. package/dist/thread-turn-services.d.ts.map +1 -1
  278. package/dist/thread-turn-services.js.map +1 -1
  279. package/package.json +25 -26
  280. package/dist/setup-job-store.d.ts.map +0 -1
  281. package/dist/setup-job-store.js.map +0 -1
@@ -1,22 +1,23 @@
1
1
  #!/usr/bin/env node
2
+ import { existsSync } from "node:fs";
2
3
  import { join } from "node:path";
3
- import { DaemonClient, commandActivityRecords, commandProjection, commandExecutionRoots, commandScopeRoots, interactionProjection, operatorDecisionProjection, runEventProjection, JournalManager, JournalMaintenance, DaemonServer, InteractionRegistry, ProjectPartitions, projectProjection, RunEventBus, ResourceStore, quotaPacerFileStore, quotaProjection, daemonDir, defaultSocketPath, acquireRootAuthority, ensureToken, ensureDaemonRuntimeRoot, logPath, socketAlive, LiveInputRegistry, } from "@claudexor/daemon";
4
+ import { DaemonLocalClient, DaemonServer, RunEventBus, quotaPacerFileStore, daemonDir, defaultSocketPath, acquireRootAuthority, ensureToken, ensureDaemonRuntimeRoot, logPath, socketAlive, loadEngineRuntime, recordAdmissionMemory, processMemoryFields, recoveryOnlyRefusal, } from "@claudexor/daemon";
4
5
  import { DaemonControlApiServer } from "@claudexor/control-api";
5
- import { createDaemonQuotaPoller, createStartupAdmissionRuntime, openStartupDiagnostics, registerDaemonThreadProjection, } from "./daemon-admission-runtime.js";
6
- import { armDaemonLifecycle, logLine } from "./daemon-lifecycle.js";
7
- import { bindRecoveryTransport, controlApiEnabledForStartup, DaemonStartupAdmission, proveRecoveryTransport, quarantineGhostProjectsAtStartup, recoveryBlockedPartitions, } from "./daemon-startup.js";
6
+ import { createDaemonQuotaPoller, openStartupDiagnostics } from "./daemon-admission-runtime.js";
7
+ import { armDaemonLifecycle, runStartupCrashGc } from "./daemon-lifecycle.js";
8
+ import { bindRecoveryTransport, DaemonStartupAdmission, proveRecoveryTransport, quarantineGhostProjectsAtStartup, } from "./daemon-startup.js";
8
9
  import { engineBuildIdentity, noProjectRepoRoot, redactSecrets } from "@claudexor/util";
9
- import { loadConfig } from "@claudexor/config";
10
- import { runtimeConcurrencyCaps } from "@claudexor/schema";
10
+ import { loadRuntimeConcurrencyCaps, sweepRetiredConfigKeysAtStartup } from "@claudexor/config";
11
11
  import { scheduleStartupRetention } from "./retention-service.js";
12
12
  import { controlServices } from "./control-services.js";
13
+ import { recoveryControlServices } from "./recovery-control-services.js";
13
14
  import { AuthReadinessService } from "@claudexor/gateway";
14
15
  import { bindCredentialMutationWindow } from "@claudexor/core";
15
16
  import { buildGateway } from "./registry.js";
16
17
  import { createSetupJobManager } from "./setup-jobs.js";
17
18
  import { bustLoginCredentialState } from "./credential-status-invalidation.js";
18
- import { SetupJobStore } from "./setup-job-store.js";
19
19
  import { SetupLifecycleBinding } from "./setup-lifecycle-binding.js";
20
+ import { SqlSetupLifecycleSlot } from "./sql-setup-lifecycle.js";
20
21
  import { DaemonRuntimeShutdown } from "./daemon-runtime-shutdown.js";
21
22
  import { quotaRefreshers } from "./quota-refreshers.js";
22
23
  import { dispatchClaudexordEntry, runIfDirectEntry, runProbeIfRequested, } from "./claudexord-entry.js";
@@ -24,289 +25,357 @@ import { createDelegationDaemonBinding } from "./delegation-daemon-binding.js";
24
25
  import { quotaSubjectUniverseFromConfig } from "./quota-subject-universe.js";
25
26
  import { runStartupAccountsMigration } from "./accounts-unified-migration.js";
26
27
  import { runStopIfRequested } from "./runtime-replacement-stop.js";
27
- import { createDaemonAgentRunner } from "./daemon-agent-runner.js";
28
- import { createModelServices } from "./model-services.js";
29
- import { daemonHarnessMaintenance } from "./harness-maintenance-service.js";
30
- import { isModelOperation } from "@claudexor/schema";
28
+ import { createSqlDaemonExecutors } from "./sql-daemon-runtime.js";
29
+ import { SqlDaemonStorage } from "./sql-daemon-storage.js";
30
+ import { deferredCommands, sqlActivityRecords, sqlStartupRoots } from "./sql-daemon-queries.js";
31
+ import { threadPurgeEffect } from "./thread-purge.js";
32
+ import { accountResetServices } from "./account-reset-services.js";
31
33
  export async function main() {
32
- // Probe and identity-proven stop must run before any durable startup.
33
34
  if (runProbeIfRequested(process.argv.slice(2)))
34
35
  return;
35
36
  if (await runStopIfRequested(process.argv.slice(2)))
36
37
  return;
37
- const servingIdentity = engineBuildIdentity();
38
+ // Refuse unsupported Node/SQLite before touching the root or lease.
39
+ await loadEngineRuntime();
40
+ const identity = engineBuildIdentity();
38
41
  ensureDaemonRuntimeRoot();
39
42
  const socketPath = defaultSocketPath();
40
- // D5 stage 1: permanent barrier (epoch/floor refusals) before ANY recovery.
41
- const rootAuthority = acquireRootAuthority({ socketPath, version: servingIdentity.version });
42
- // C8: private diagnostics open right after the authority win (never control lifecycle).
43
- const startupDiagnostics = openStartupDiagnostics(servingIdentity);
44
- let shutdownRuntime = null;
45
- // Release wave round-12 BLOCK: the single-writer lease may only be released
46
- // after a CLEAN shutdown — a failed/partial shutdown keeps components that
47
- // can still write, and releasing would let a successor acquire ownership
48
- // beside them. On failure the lease dies with the process instead.
49
- let releaseWriterLease = true;
43
+ const authority = acquireRootAuthority({ socketPath, version: identity.version });
44
+ const diagnostics = openStartupDiagnostics(identity);
45
+ const log = (message) => diagnostics.log("sql_runtime", message);
46
+ const admission = new DaemonStartupAdmission();
47
+ const abort = new AbortController();
48
+ let cleanShutdown = true;
49
+ let shutdown = null;
50
50
  let lifecycle = null;
51
- let quotaPoller = null;
52
- // Maintenance failures, typed declines and `journal.records_retired`
53
- // receipts land in the daemon log and the startup diagnostics record.
54
- const journalMaintenance = new JournalMaintenance(daemonDir(), (message) => startupDiagnostics.log("journal_maintenance", message));
51
+ let control = null;
52
+ let opening = null;
53
+ let storage;
54
+ let setup = null;
55
+ let executors = null;
56
+ let server;
57
+ let listening = false;
58
+ let admissionFlight = null;
59
+ let dutiesFor = null;
60
+ const graph = () => storage.graph();
61
+ const quotaPoller = createDaemonQuotaPoller(() => {
62
+ if (admission.snapshot() !== "normal")
63
+ return;
64
+ void Promise.resolve()
65
+ .then(() => graph().quota.pollStale())
66
+ .catch((error) => log(`quota poll: ${String(error)}`));
67
+ });
68
+ let readiness;
69
+ function setupFor(value) {
70
+ return new SetupLifecycleBinding(new SqlSetupLifecycleSlot(daemonDir(), value), (store) => createSetupJobManager({
71
+ rootDir: daemonDir(),
72
+ store,
73
+ onCredentialStateMayHaveChanged: (harness) => bustLoginCredentialState(() => value.quota, readiness, harness),
74
+ }));
75
+ }
55
76
  try {
77
+ readiness = new AuthReadinessService(buildGateway({ includeFakes: false }), {
78
+ cwd: noProjectRepoRoot(),
79
+ });
56
80
  const token = ensureToken();
57
- const startupConfig = loadConfig(noProjectRepoRoot()).global;
58
- const startupConcurrencyCaps = runtimeConcurrencyCaps(startupConfig);
59
- if (await socketAlive(socketPath)) {
81
+ const caps = loadRuntimeConcurrencyCaps(noProjectRepoRoot());
82
+ if (await socketAlive(socketPath))
60
83
  throw new Error(`a claudexor daemon is already listening on ${socketPath}; stop it first`);
61
- }
62
84
  const bus = new RunEventBus();
63
- const { authority: delegationBudgetAuthority, bind: bindDelegationDaemon } = createDelegationDaemonBinding();
64
- const journalManager = new JournalManager(daemonDir(), {
65
- requestMaintenance: journalMaintenance.request,
66
- });
67
- const commandStoreSlot = journalManager.registerProjection(commandProjection());
68
- const interactionStoreSlot = journalManager.registerProjection(interactionProjection());
69
- const operatorDecisionStoreSlot = journalManager.registerProjection(operatorDecisionProjection());
70
- const runEventStoreSlot = journalManager.registerProjection(runEventProjection());
71
- const projectStoreSlot = journalManager.registerProjection(projectProjection());
72
- const quotaStoreSlot = journalManager.registerProjection(quotaProjection(quotaRefreshers((record) => logLine(logPath(), `quota.observation ${JSON.stringify(record)}`)), quotaSubjectUniverseFromConfig, undefined,
73
- // Daemon-private subject and legacy vendor floors (never in the journal).
74
- quotaPacerFileStore(daemonDir())));
75
- const { threadStoreSlot, threadHeadPing } = registerDaemonThreadProjection(journalManager, () => commandStoreSlot.current().records());
76
- const setupStoreSlot = journalManager.registerProjection({
77
- name: "setup",
78
- create: (journal) => new SetupJobStore(daemonDir(), { journal }),
79
- validate: (store) => store.validateProjection(),
80
- });
81
- // D5 stage 2: read-only prepare + validate; zero recovery writes.
82
- const globalPreparation = journalManager.prepare();
83
- const admission = new DaemonStartupAdmission();
84
- quotaPoller = createDaemonQuotaPoller(() => {
85
- try {
86
- void quotaStoreSlot.current().pollStale();
87
- }
88
- catch { }
89
- });
90
- const threads = new ProjectPartitions(daemonDir(), projectStoreSlot, commandStoreSlot, interactionStoreSlot, operatorDecisionStoreSlot, runEventStoreSlot, threadStoreSlot, threadHeadPing, journalMaintenance.request);
91
- const partitionsPreparation = threads.prepare();
92
- const startupBlockedPartitions = recoveryBlockedPartitions({
93
- globalPreparation,
94
- partitionsPreparation,
95
- });
96
- const interactions = new InteractionRegistry({
97
- forRequest: (params) => threads.interactionsForRequest(params),
98
- all: () => threads.interactionStores(),
99
- });
100
- // Live-input targets (POST /v2/runs/:id/messages): in-process only, fed by
101
- // the agent runner per attempt; a pending question blocks a send (INV-048).
102
- const liveInputs = new LiveInputRegistry({
103
- pendingForRun: (runId) => interactions.pendingForRun(runId),
85
+ const delegation = createDelegationDaemonBinding();
86
+ const selfClient = new DaemonLocalClient(() => server);
87
+ const services = {};
88
+ const makeServer = () => {
89
+ const next = new DaemonServer({
90
+ socketPath,
91
+ token,
92
+ commands: deferredCommands(() => graph().commands),
93
+ runtimeConcurrencyCaps: caps,
94
+ servingMode: admission.snapshot,
95
+ storeFacts: () => storage.facts(),
96
+ delegationAuthority: delegation.authority,
97
+ runtimeIdentity: { version: identity.version, buildSha: identity.sha },
98
+ runtimeLeaseOwner: authority.lease.owner,
99
+ onCommandTerminal: (record) => executors?.onCommandTerminal(record),
100
+ onRunTerminal: (runId, threadId) => executors?.onRunTerminal(runId, threadId),
101
+ onTurnEnqueueFailed: (turnId, problem) => executors?.onTurnEnqueueFailed(turnId, problem),
102
+ onShutdownRequested: () => shutdown.beginShutdown("socket-rpc stop"),
103
+ onRuntimeReplacementRequested: () => shutdown.beginRuntimeReplacement(),
104
+ runner: (params, ctx) => {
105
+ if (!executors)
106
+ throw recoveryOnlyRefusal("runner");
107
+ return executors.runner(params, ctx);
108
+ },
109
+ });
110
+ delegation.bind(next);
111
+ return next;
112
+ };
113
+ server = makeServer();
114
+ const partition = (name) => storage.partition(name);
115
+ const recovery = recoveryControlServices({
116
+ partition,
117
+ setup: () => setup,
118
+ onValidated: (name, result) => {
119
+ if (result.status === "recovery_required" && name === "global")
120
+ admission.enterRecoveryOnly();
121
+ },
122
+ afterQuarantine: async () => {
123
+ try {
124
+ await completeAdmission();
125
+ }
126
+ catch (error) {
127
+ admission.enterRecoveryOnly();
128
+ log(`recovery completed; reopening failed: ${String(error)}`);
129
+ }
130
+ },
104
131
  });
105
- // C5b: construction mkdirs under the daemon dir and the recovery plane
106
- // serves no resources — the store materializes on first product use.
107
- let resourceStore = null;
108
- const resources = () => (resourceStore ??= new ResourceStore(join(daemonDir(), "resource-store")));
109
- const selfClient = new DaemonClient(socketPath, token);
110
- const models = createModelServices({
111
- commands: threads,
112
- resources,
113
- client: selfClient,
114
- quota: () => quotaStoreSlot.current(),
115
- warn: (message) => logLine(logPath(), message),
132
+ const attach = (value) => {
133
+ if (!listening)
134
+ server = makeServer();
135
+ setup = setupFor(value);
136
+ executors = createSqlDaemonExecutors(value, {
137
+ client: selfClient,
138
+ authority: delegation.authority,
139
+ admission: (id) => server.admission(id),
140
+ authReadiness: () => readiness,
141
+ bus,
142
+ runtimeConcurrencyCaps: caps,
143
+ warn: log,
144
+ });
145
+ const threads = Object.assign(value.threads, { journal: partition });
146
+ const product = controlServices(value.interactions, value.liveInputs, () => value.projects, threads, setup, partition("global"), readiness, () => value.resources, () => value.quota, () => sqlActivityRecords(value.store), caps, value.purgeFiles);
147
+ const retention = executors.models.withRetention(product.runRetention);
148
+ product.runRetention = async (request) => {
149
+ const result = await retention(request);
150
+ if (!request.dry_run)
151
+ value.resources.pruneUploadBindings();
152
+ return result;
153
+ };
154
+ executors.harnessMaintenance.bind(product);
155
+ Object.assign(services, product, executors.models.routes, accountResetServices({ current: () => value.commands.current() }, { current: () => value.quota }), recovery);
156
+ };
157
+ storage = new SqlDaemonStorage({
158
+ rootDir: daemonDir(),
159
+ advanceFloor: () => authority.advanceFloor(),
160
+ graph: {
161
+ purgeFiles: threadPurgeEffect(noProjectRepoRoot()),
162
+ log,
163
+ refreshers: quotaRefreshers((record) => log(`quota.observation ${JSON.stringify(record)}`)),
164
+ subjects: quotaSubjectUniverseFromConfig,
165
+ pacerStore: quotaPacerFileStore(daemonDir()),
166
+ },
167
+ log,
168
+ onOpen: attach,
169
+ onCorrupt: (error) => {
170
+ admission.enterRecoveryOnly();
171
+ quotaPoller.stop();
172
+ log(`engine store recovery required: ${error.message}`);
173
+ void ensureRecoveryControl().catch((problem) => log(`recovery transport unavailable: ${String(problem)}`));
174
+ },
175
+ beforeClose: async () => {
176
+ admission.enterRecoveryOnly();
177
+ quotaPoller.stop();
178
+ setup?.beginDrain();
179
+ executors?.models.close();
180
+ await Promise.all([server.stopForStoreRecovery(), setup?.shutdown()]);
181
+ listening = false;
182
+ setup = null;
183
+ executors = null;
184
+ dutiesFor = null;
185
+ },
116
186
  });
117
- const maintenance = daemonHarnessMaintenance(threads, selfClient, () => authReadiness);
118
- const agentRunner = createDaemonAgentRunner({
119
- delegationBudgetAuthority,
120
- quotaStore: () => quotaStoreSlot.current(),
121
- threads,
122
- interactions,
123
- liveInputs,
124
- resources,
125
- bus,
126
- runtimeConcurrencyCaps: startupConcurrencyCaps,
187
+ Object.assign(services, recovery);
188
+ bindCredentialMutationWindow((harness) => {
189
+ if (!setup)
190
+ throw recoveryOnlyRefusal("setup");
191
+ return setup.current().credentialMutationOpen(harness);
127
192
  });
128
- const server = new DaemonServer({
129
- socketPath,
193
+ let address = null;
194
+ let controlStarting = null;
195
+ const makeControl = () => new DaemonControlApiServer({
130
196
  token,
131
- commands: threads,
132
- runtimeConcurrencyCaps: startupConcurrencyCaps,
197
+ daemon: selfClient,
198
+ port: Number(process.env.CLAUDEXOR_CONTROL_PORT ?? 0),
133
199
  servingMode: admission.snapshot,
134
- delegationAuthority: delegationBudgetAuthority,
135
- onCommandTerminal: (record) => models.operations.onCommandTerminal(record),
136
- onRunTerminal: (runId, threadId) => {
137
- interactions.dropForRun(runId);
138
- liveInputs.dropForRun(runId);
139
- // Run-terminal is the one W12 path with no thread-store mutation to
140
- // ride — the terminal changes the thread's presented state, so ping.
141
- if (threadId)
142
- threads.pingThreadHead(threadId);
143
- },
144
- onTurnEnqueueFailed: (turnId, problem) => threads.setTurnEnqueueError(turnId, problem),
145
- onShutdownRequested: () => shutdownRuntime?.beginShutdown("socket-rpc stop") ??
146
- Promise.reject(new Error("daemon shutdown coordinator is not initialized")),
147
- onRuntimeReplacementRequested: () => {
148
- if (!shutdownRuntime) {
149
- throw Object.assign(new Error("daemon shutdown coordinator is not initialized"), {
150
- code: "runtime_activity_unknown",
151
- status: 503,
152
- retryable: true,
153
- });
154
- }
155
- return shutdownRuntime.beginRuntimeReplacement();
156
- },
157
- runtimeIdentity: { version: servingIdentity.version, buildSha: servingIdentity.sha },
158
- runtimeLeaseOwner: rootAuthority.lease.owner,
159
- runner: (params, ctx) => isModelOperation(params)
160
- ? models.operations.execute(params, ctx)
161
- : maintenance.owns(params)
162
- ? maintenance.execute(params, ctx)
163
- : agentRunner(params, ctx),
164
- });
165
- bindDelegationDaemon(server);
166
- const authReadiness = new AuthReadinessService(buildGateway({ includeFakes: false }), {
167
- cwd: noProjectRepoRoot(),
200
+ bus,
201
+ services,
202
+ terminalFilesPending: (id) => graph().terminalFiles.pending(id),
168
203
  });
169
- const setupBinding = new SetupLifecycleBinding(setupStoreSlot, (store) => createSetupJobManager({
170
- rootDir: daemonDir(),
171
- store,
172
- onCredentialStateMayHaveChanged: (harness) => bustLoginCredentialState(() => quotaStoreSlot.current(), authReadiness, harness),
173
- }));
174
- // #363: every process-local credential observer reads the login window from
175
- // the durable setup lifecycle; an unbound or recovering generation reads open.
176
- bindCredentialMutationWindow((harness) => setupBinding.current().credentialMutationOpen(harness));
177
- let control = null;
178
- shutdownRuntime = new DaemonRuntimeShutdown({
204
+ async function ensureRecoveryControl() {
205
+ if (shutdown?.requested())
206
+ return;
207
+ if (controlStarting)
208
+ return controlStarting;
209
+ if (control)
210
+ return;
211
+ control = makeControl();
212
+ log("recovery-required SQL state overrides CLAUDEXOR_NO_CONTROL_API=1 for recovery access");
213
+ controlStarting = bindRecoveryTransport({
214
+ server,
215
+ control,
216
+ requested: () => shutdown.requested(),
217
+ daemonDir: daemonDir(),
218
+ logPath: logPath(),
219
+ socketPath,
220
+ }).then((value) => {
221
+ address = value;
222
+ });
223
+ await controlStarting;
224
+ }
225
+ const importing = !existsSync(join(daemonDir(), "engine.sqlite")) && existsSync(join(daemonDir(), "journal"));
226
+ if (process.env.CLAUDEXOR_NO_CONTROL_API !== "1" || importing)
227
+ control = makeControl();
228
+ shutdown = new DaemonRuntimeShutdown({
179
229
  daemon: {
180
230
  stop: async () => {
181
- const maintenanceDrain = journalMaintenance.stop();
182
- models.close();
183
- await Promise.all([server.stop(), maintenanceDrain]);
231
+ abort.abort();
232
+ executors?.models.close();
233
+ await server.stop();
234
+ await opening?.catch(() => { });
235
+ },
236
+ },
237
+ setup: {
238
+ hasActiveWork: () => {
239
+ if (!setup)
240
+ throw recoveryOnlyRefusal("setup activity");
241
+ return setup.hasActiveWork();
242
+ },
243
+ beginDrain: () => setup?.beginDrain(),
244
+ shutdown: async () => {
245
+ await setup?.shutdown();
184
246
  },
185
247
  },
186
- setup: setupBinding,
187
248
  control: () => control,
188
249
  journal: {
189
- close: () => {
190
- quotaPoller?.stop();
191
- threads.close();
192
- journalManager.close();
250
+ close: async () => {
251
+ quotaPoller.stop();
252
+ await storage?.close();
193
253
  },
194
254
  },
195
- log: (message) => logLine(logPath(), message),
255
+ log,
196
256
  });
197
- // Services and retention read all partition activity directly, without self-RPC.
198
- const services = controlServices(interactions, liveInputs, () => projectStoreSlot.current(), threads, setupBinding, journalManager, authReadiness, resources, () => quotaStoreSlot.current(), () => commandActivityRecords(threads.all().flatMap((store) => store.records())), startupConcurrencyCaps);
199
- const runRetention = services.runRetention;
200
- services.runRetention = models.withRetention(runRetention);
201
- Object.assign(services, models.routes);
202
- maintenance.bind(services);
203
- control = !controlApiEnabledForStartup({
204
- disabledByEnv: process.env.CLAUDEXOR_NO_CONTROL_API === "1",
205
- blockedPartitions: startupBlockedPartitions,
206
- log: (message) => logLine(logPath(), message),
207
- })
208
- ? null
209
- : new DaemonControlApiServer({
210
- token,
211
- daemon: new DaemonClient(socketPath, token),
212
- port: Number(process.env.CLAUDEXOR_CONTROL_PORT ?? 0),
213
- servingMode: admission.snapshot,
214
- bus,
215
- services,
216
- });
217
257
  lifecycle = armDaemonLifecycle({
218
258
  daemonDir: daemonDir(),
219
259
  logPath: logPath(),
220
- ...(startupDiagnostics.diagnostics ? { diagnostics: startupDiagnostics.diagnostics } : {}),
221
- beginShutdown: (reason) => shutdownRuntime.beginShutdown(reason),
260
+ ...(diagnostics.diagnostics ? { diagnostics: diagnostics.diagnostics } : {}),
261
+ beginShutdown: (reason) => shutdown.beginShutdown(reason),
222
262
  });
223
- const { runAdmissionCompletion, wrapQuarantineWithReopen } = createStartupAdmissionRuntime({
224
- admission,
225
- grant: rootAuthority,
226
- global: journalManager,
227
- partitions: threads,
228
- diagnostics: startupDiagnostics,
229
- knownProjectRoots: () => {
230
- const commands = commandStoreSlot.prepared();
231
- return [...commandScopeRoots(commands.records()), ...commands.prunedScopeRoots()];
232
- },
233
- // Delegated runs (thread turns live in project partitions) keep their
234
- // runtime scratch under the caller-owned execution root.
235
- knownExecutionRoots: () => [
236
- ...commandExecutionRoots(commandStoreSlot.prepared().records()),
237
- ...threads.preparedExecutionRoots(),
238
- ],
239
- normalPlane: {
240
- requested: () => shutdownRuntime.requested(),
241
- armQuotaPolling: () => quotaPoller.arm(),
242
- beginPidSnapshots: () => lifecycle.beginPidSnapshots(),
243
- migrateAccounts: () => runStartupAccountsMigration(threads, quotaStoreSlot.current(), (m) => logLine(logPath(), redactSecrets(m))),
244
- startSetup: () => setupBinding.start(),
245
- quarantineGhosts: () => quarantineGhostProjectsAtStartup(threads, (message) => logLine(logPath(), message)),
246
- scheduleRetention: () => scheduleStartupRetention(services.runRetention, {
247
- logPath: logPath(),
248
- shuttingDown: () => shutdownRuntime.requested(),
249
- }),
250
- pruneCommandHistory: () => server.pruneHistory(),
251
- armJournalMaintenance: () => journalMaintenance.arm(),
252
- },
253
- });
254
- services.recoveryQuarantinePartition = wrapQuarantineWithReopen(services.recoveryQuarantinePartition);
255
- // D5 stage 3: bind the REAL transport with product admission CLOSED, then
256
- // prove self-health/exact identity through it before anything destructive.
257
- const controlAddr = await bindRecoveryTransport({
263
+ address = await bindRecoveryTransport({
258
264
  server,
259
265
  control,
260
- requested: () => shutdownRuntime.requested(),
266
+ requested: () => shutdown.requested(),
261
267
  daemonDir: daemonDir(),
262
268
  logPath: logPath(),
263
269
  socketPath,
264
270
  });
265
- if (!shutdownRuntime.requested()) {
266
- await proveRecoveryTransport({
267
- socket: selfClient,
268
- identity: servingIdentity,
269
- token,
270
- control: controlAddr,
271
- });
272
- // D5 stage 4: floor advance + destructive recovery + normal admission —
273
- // or stay recovery-only with the floor unchanged and cleanup off. The
274
- // normal-plane side effects run inside the single-flight completion.
275
- await runAdmissionCompletion(() => startupBlockedPartitions);
271
+ listening = !shutdown.requested();
272
+ async function completeAdmission() {
273
+ if (admissionFlight)
274
+ return admissionFlight;
275
+ if (shutdown.requested() || admission.snapshot() === "normal")
276
+ return;
277
+ const value = graph();
278
+ const blocked = storage.blockedPartitions();
279
+ if (blocked.length) {
280
+ admission.enterRecoveryOnly();
281
+ log(`recovery required: ${blocked.join(", ")}`);
282
+ await ensureRecoveryControl();
283
+ return;
284
+ }
285
+ const work = async () => {
286
+ if (!listening) {
287
+ await server.start();
288
+ listening = true;
289
+ await proveRecoveryTransport({ socketPath, identity, token, control: address });
290
+ }
291
+ if (dutiesFor !== value) {
292
+ await value.recoverAfterStartup();
293
+ const roots = sqlStartupRoots(value.store);
294
+ await runStartupCrashGc({
295
+ daemonDir: daemonDir(),
296
+ logPath: logPath(),
297
+ knownProjectRoots: roots.projects,
298
+ knownExecutionRoots: roots.execution,
299
+ ...(diagnostics.diagnostics ? { diagnostics: diagnostics.diagnostics } : {}),
300
+ });
301
+ if (shutdown.requested())
302
+ return;
303
+ for (const sweep of sweepRetiredConfigKeysAtStartup())
304
+ log(`swept retired config keys from ${sweep.path}: ${sweep.removed.join(", ")}`);
305
+ runStartupAccountsMigration(value.threads, value.quota, (message) => log(redactSecrets(message)));
306
+ server.pruneHistory();
307
+ await setup.start();
308
+ quarantineGhostProjectsAtStartup(value.threads, log);
309
+ if (shutdown.requested())
310
+ return;
311
+ dutiesFor = value;
312
+ }
313
+ admission.openNormal();
314
+ recordAdmissionMemory();
315
+ quotaPoller.arm();
316
+ lifecycle.beginPidSnapshots();
317
+ scheduleStartupRetention(services.runRetention, {
318
+ logPath: logPath(),
319
+ shuttingDown: () => shutdown.requested() || admission.snapshot() !== "normal",
320
+ });
321
+ void value.maintenance
322
+ .integrityCheck()
323
+ .then(async (result) => {
324
+ if (result.ok && admission.snapshot() === "normal")
325
+ await value.maintenance.sweepOrphans();
326
+ })
327
+ .catch((error) => log(`store maintenance: ${String(error)}`));
328
+ log(`startup admission: normal product admission open (${processMemoryFields()})`);
329
+ };
330
+ admissionFlight = work();
331
+ try {
332
+ await admissionFlight;
333
+ }
334
+ finally {
335
+ admissionFlight = null;
336
+ }
337
+ }
338
+ if (!shutdown.requested()) {
339
+ await proveRecoveryTransport({ socketPath, identity, token, control: address });
340
+ opening = storage.open(abort.signal).then(completeAdmission);
341
+ try {
342
+ await opening;
343
+ }
344
+ catch (error) {
345
+ admission.enterRecoveryOnly();
346
+ log(`SQL startup remains recovery-only: ${redactSecrets(String(error))}`);
347
+ diagnostics.recordFailure("SQL startup remains recovery-only", error);
348
+ if (!shutdown.requested())
349
+ await ensureRecoveryControl();
350
+ }
276
351
  }
277
- await shutdownRuntime.wait();
278
- lifecycle.finalize();
279
- logLine(logPath(), "claudexord shut down");
280
- startupDiagnostics.recordStage("shutdown_complete", "claudexord shut down");
352
+ await shutdown.wait();
353
+ await lifecycle.finalize();
354
+ log("claudexord shut down");
355
+ diagnostics.recordStage("shutdown_complete", "claudexord shut down");
281
356
  }
282
357
  catch (error) {
283
- // logLine is already best-effort; a failed diagnostic write never masks
284
- // the lifecycle failure itself.
285
- logLine(logPath(), `daemon lifecycle FAILED: ${redactSecrets(error instanceof Error ? `${error.name}: ${error.message}` : String(error))}`);
286
- startupDiagnostics.recordFailure("daemon lifecycle FAILED", error);
287
- if (shutdownRuntime) {
358
+ log(`daemon lifecycle FAILED: ${redactSecrets(String(error))}`);
359
+ diagnostics.recordFailure("daemon lifecycle FAILED", error);
360
+ if (shutdown) {
288
361
  try {
289
- await shutdownRuntime.beginShutdown("startup failure");
290
- lifecycle?.finalize();
362
+ await shutdown.beginShutdown("startup failure");
363
+ await lifecycle?.finalize();
291
364
  }
292
365
  catch (shutdownError) {
293
- logLine(logPath(), `shutdown FAILED: ${redactSecrets(shutdownError instanceof Error ? shutdownError.message : String(shutdownError))}`);
294
- releaseWriterLease = false;
366
+ cleanShutdown = false;
295
367
  throw new AggregateError([error, shutdownError], "claudexord failed and could not complete shutdown");
296
368
  }
297
369
  }
298
370
  throw error;
299
371
  }
300
372
  finally {
301
- await journalMaintenance.stop();
302
- quotaPoller?.stop();
303
- startupDiagnostics.close();
304
- // Drops only the live writer claim; the barrier itself persists (D1).
305
- if (releaseWriterLease)
306
- rootAuthority.release();
373
+ quotaPoller.stop();
374
+ diagnostics.close();
375
+ if (cleanShutdown)
376
+ authority.release();
307
377
  }
308
378
  }
309
- /** Explicit entry preserves import-side-effect freedom for daemon probes. */
310
379
  export function runClaudexordEntry() {
311
380
  dispatchClaudexordEntry(main);
312
381
  }