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