@phnx-labs/agents-cli 1.22.47 → 1.22.49

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 (249) hide show
  1. package/CHANGELOG.md +401 -17
  2. package/README.md +66 -32
  3. package/dist/bootstrap.js +4 -2
  4. package/dist/cli/command-registry.d.ts +1 -0
  5. package/dist/cli/command-registry.js +2 -0
  6. package/dist/commands/accounts.d.ts +58 -0
  7. package/dist/commands/accounts.js +206 -32
  8. package/dist/commands/artifacts.js +7 -6
  9. package/dist/commands/attach.js +1 -1
  10. package/dist/commands/browser.js +501 -103
  11. package/dist/commands/config.d.ts +0 -1
  12. package/dist/commands/config.js +0 -20
  13. package/dist/commands/daemon.d.ts +21 -0
  14. package/dist/commands/daemon.js +147 -12
  15. package/dist/commands/exec.js +52 -15
  16. package/dist/commands/focus.d.ts +14 -0
  17. package/dist/commands/focus.js +48 -5
  18. package/dist/commands/go.d.ts +3 -0
  19. package/dist/commands/go.js +16 -3
  20. package/dist/commands/inspect.js +2 -2
  21. package/dist/commands/lease.d.ts +1 -1
  22. package/dist/commands/lease.js +4 -3
  23. package/dist/commands/mailboxes.js +4 -3
  24. package/dist/commands/menubar.js +11 -4
  25. package/dist/commands/projects.d.ts +16 -1
  26. package/dist/commands/projects.js +58 -64
  27. package/dist/commands/resume.js +1 -1
  28. package/dist/commands/route.js +4 -4
  29. package/dist/commands/routines.js +3 -2
  30. package/dist/commands/run-broadcast.d.ts +1 -1
  31. package/dist/commands/run-broadcast.js +1 -1
  32. package/dist/commands/sessions-picker.d.ts +22 -0
  33. package/dist/commands/sessions-picker.js +48 -9
  34. package/dist/commands/sessions-trace.js +1 -1
  35. package/dist/commands/sessions.js +1 -0
  36. package/dist/commands/setup-secrets.js +23 -0
  37. package/dist/commands/share.d.ts +20 -5
  38. package/dist/commands/share.js +152 -52
  39. package/dist/commands/ssh.js +54 -7
  40. package/dist/commands/traces.d.ts +2 -0
  41. package/dist/commands/traces.js +184 -0
  42. package/dist/commands/view.d.ts +1 -1
  43. package/dist/commands/view.js +17 -34
  44. package/dist/index.d.ts +1 -0
  45. package/dist/index.js +5 -0
  46. package/dist/lib/accounting/usage.d.ts +14 -3
  47. package/dist/lib/accounting/usage.js +64 -18
  48. package/dist/lib/agent-spec/agents.js +11 -9
  49. package/dist/lib/browser/drivers/ssh.d.ts +16 -2
  50. package/dist/lib/browser/drivers/ssh.js +36 -9
  51. package/dist/lib/browser/ipc.js +8 -3
  52. package/dist/lib/browser/profiles.d.ts +35 -101
  53. package/dist/lib/browser/profiles.js +130 -301
  54. package/dist/lib/browser/registry.d.ts +39 -0
  55. package/dist/lib/browser/registry.js +149 -0
  56. package/dist/lib/browser/remote-control.d.ts +8 -6
  57. package/dist/lib/browser/remote-control.js +8 -6
  58. package/dist/lib/browser/resolve-target.d.ts +68 -0
  59. package/dist/lib/browser/resolve-target.js +250 -0
  60. package/dist/lib/browser/runtime-state.d.ts +40 -16
  61. package/dist/lib/browser/runtime-state.js +92 -25
  62. package/dist/lib/browser/service.d.ts +28 -9
  63. package/dist/lib/browser/service.js +139 -62
  64. package/dist/lib/browser/task-index.d.ts +70 -0
  65. package/dist/lib/browser/task-index.js +180 -0
  66. package/dist/lib/byok-usage.js +2 -2
  67. package/dist/lib/claude-statusline.d.ts +29 -0
  68. package/dist/lib/claude-statusline.js +139 -0
  69. package/dist/lib/cloud/host.d.ts +1 -1
  70. package/dist/lib/cloud/host.js +1 -1
  71. package/dist/lib/cloud/rush.js +1 -1
  72. package/dist/lib/computer/computer-rpc.js +2 -2
  73. package/dist/lib/computer/download.d.ts +15 -15
  74. package/dist/lib/computer/download.js +35 -98
  75. package/dist/lib/computer/ssh-tunnel.d.ts +0 -3
  76. package/dist/lib/computer/ssh-tunnel.js +12 -22
  77. package/dist/lib/config-keys.d.ts +2 -8
  78. package/dist/lib/config-keys.js +2 -13
  79. package/dist/lib/crabbox/cli.d.ts +1 -1
  80. package/dist/lib/crabbox/cli.js +2 -2
  81. package/dist/lib/crabbox/lease.d.ts +9 -0
  82. package/dist/lib/crabbox/lease.js +70 -15
  83. package/dist/lib/crabbox/runtimes.js +1 -1
  84. package/dist/lib/daemon/account-state-daemon-service.d.ts +16 -0
  85. package/dist/lib/daemon/account-state-daemon-service.js +26 -0
  86. package/dist/lib/daemon/browser-ipc-service.d.ts +20 -0
  87. package/dist/lib/daemon/browser-ipc-service.js +46 -0
  88. package/dist/lib/daemon/daemon.d.ts +3 -0
  89. package/dist/lib/daemon/daemon.js +212 -444
  90. package/dist/lib/daemon/device-probe-service.d.ts +26 -0
  91. package/dist/lib/daemon/device-probe-service.js +47 -0
  92. package/dist/lib/daemon/keychain-reap-service.d.ts +17 -0
  93. package/dist/lib/daemon/keychain-reap-service.js +32 -0
  94. package/dist/lib/daemon/monitor-engine-service.d.ts +22 -0
  95. package/dist/lib/daemon/monitor-engine-service.js +29 -0
  96. package/dist/lib/daemon/secrets-broker-service.d.ts +20 -0
  97. package/dist/lib/daemon/secrets-broker-service.js +66 -0
  98. package/dist/lib/daemon/self-heal-service.d.ts +27 -0
  99. package/dist/lib/daemon/self-heal-service.js +48 -0
  100. package/dist/lib/daemon/service.d.ts +102 -0
  101. package/dist/lib/daemon/service.js +98 -0
  102. package/dist/lib/daemon/session-index-service.d.ts +22 -0
  103. package/dist/lib/daemon/session-index-service.js +40 -0
  104. package/dist/lib/daemon/state-dir-check-service.d.ts +43 -0
  105. package/dist/lib/daemon/state-dir-check-service.js +63 -0
  106. package/dist/lib/daemon/supervisor.d.ts +87 -0
  107. package/dist/lib/daemon/supervisor.js +298 -0
  108. package/dist/lib/daemon/watchdog-service.d.ts +19 -0
  109. package/dist/lib/daemon/watchdog-service.js +40 -0
  110. package/dist/lib/daemon-health.d.ts +15 -0
  111. package/dist/lib/daemon-health.js +29 -5
  112. package/dist/lib/daemon-services.d.ts +8 -1
  113. package/dist/lib/daemon-services.js +55 -0
  114. package/dist/lib/daemon-ticks.d.ts +13 -2
  115. package/dist/lib/daemon-ticks.js +20 -18
  116. package/dist/lib/device-config.d.ts +0 -2
  117. package/dist/lib/device-config.js +1 -23
  118. package/dist/lib/devices/registry.d.ts +1 -1
  119. package/dist/lib/devices/registry.js +1 -1
  120. package/dist/lib/devices/stats-cache.d.ts +20 -0
  121. package/dist/lib/devices/stats-cache.js +49 -5
  122. package/dist/lib/devices/worker-pick.d.ts +48 -0
  123. package/dist/lib/devices/worker-pick.js +113 -0
  124. package/dist/lib/exec.d.ts +67 -13
  125. package/dist/lib/exec.js +85 -29
  126. package/dist/lib/harness/adapters/cursor.js +8 -11
  127. package/dist/lib/helper-download.d.ts +108 -0
  128. package/dist/lib/helper-download.js +194 -0
  129. package/dist/lib/helper-versions.d.ts +52 -0
  130. package/dist/lib/helper-versions.js +58 -0
  131. package/dist/lib/hooks/cache.js +11 -0
  132. package/dist/lib/hooks/install.js +2 -2
  133. package/dist/lib/hosts/credentials.d.ts +1 -1
  134. package/dist/lib/hosts/credentials.js +1 -1
  135. package/dist/lib/hosts/dispatch.d.ts +7 -1
  136. package/dist/lib/hosts/dispatch.js +24 -5
  137. package/dist/lib/hosts/passthrough.d.ts +8 -15
  138. package/dist/lib/hosts/passthrough.js +19 -16
  139. package/dist/lib/hosts/providers/devices.d.ts +1 -1
  140. package/dist/lib/hosts/providers/devices.js +1 -1
  141. package/dist/lib/hosts/reconnect.d.ts +179 -21
  142. package/dist/lib/hosts/reconnect.js +266 -45
  143. package/dist/lib/installations/migrate.d.ts +4 -5
  144. package/dist/lib/installations/migrate.js +6 -7
  145. package/dist/lib/installations/shims.d.ts +1 -1
  146. package/dist/lib/installations/shims.js +23 -13
  147. package/dist/lib/installations/versions.js +25 -2
  148. package/dist/lib/linear-autoclose.d.ts +1 -1
  149. package/dist/lib/linear-autoclose.js +1 -1
  150. package/dist/lib/loop.d.ts +2 -2
  151. package/dist/lib/loop.js +1 -1
  152. package/dist/lib/menubar/download-menubar.d.ts +47 -0
  153. package/dist/lib/menubar/download-menubar.js +60 -0
  154. package/dist/lib/menubar/install-menubar.d.ts +31 -7
  155. package/dist/lib/menubar/install-menubar.js +79 -21
  156. package/dist/lib/menubar/notify-desktop.d.ts +3 -3
  157. package/dist/lib/menubar/notify-desktop.js +6 -6
  158. package/dist/lib/model-tiers.d.ts +1 -1
  159. package/dist/lib/project-probe.d.ts +1 -1
  160. package/dist/lib/project-probe.js +1 -1
  161. package/dist/lib/project-pull.d.ts +1 -1
  162. package/dist/lib/project-pull.js +2 -2
  163. package/dist/lib/projects.d.ts +1 -1
  164. package/dist/lib/projects.js +1 -1
  165. package/dist/lib/sandbox.js +6 -7
  166. package/dist/lib/secrets/download-keychain.d.ts +47 -0
  167. package/dist/lib/secrets/download-keychain.js +70 -0
  168. package/dist/lib/secrets/install-helper.d.ts +20 -1
  169. package/dist/lib/secrets/install-helper.js +44 -3
  170. package/dist/lib/session/active.js +17 -1
  171. package/dist/lib/session/db.d.ts +11 -1
  172. package/dist/lib/session/db.js +138 -6
  173. package/dist/lib/session/discover.d.ts +21 -1
  174. package/dist/lib/session/discover.js +33 -5
  175. package/dist/lib/session/highlights.d.ts +49 -1
  176. package/dist/lib/session/highlights.js +95 -0
  177. package/dist/lib/session/host-link.d.ts +16 -2
  178. package/dist/lib/session/host-link.js +7 -1
  179. package/dist/lib/session/parse.js +33 -1
  180. package/dist/lib/session/remote/remote-list.d.ts +9 -0
  181. package/dist/lib/session/remote/remote-list.js +19 -2
  182. package/dist/lib/session/remote/watch.js +11 -2
  183. package/dist/lib/session/session-cache.d.ts +53 -0
  184. package/dist/lib/session/session-cache.js +88 -0
  185. package/dist/lib/session/state.js +1 -1
  186. package/dist/lib/session/tool-calls.d.ts +2 -0
  187. package/dist/lib/session/tool-calls.js +1 -1
  188. package/dist/lib/session/trajectory-html.d.ts +5 -1
  189. package/dist/lib/session/trajectory-html.js +42 -22
  190. package/dist/lib/session/trajectory-text.js +9 -2
  191. package/dist/lib/session/trajectory.js +53 -4
  192. package/dist/lib/session/types.d.ts +15 -0
  193. package/dist/lib/sha256-asset.d.ts +26 -0
  194. package/dist/lib/sha256-asset.js +41 -0
  195. package/dist/lib/share/backend.d.ts +95 -0
  196. package/dist/lib/share/backend.js +139 -0
  197. package/dist/lib/share/delete.d.ts +6 -2
  198. package/dist/lib/share/delete.js +8 -9
  199. package/dist/lib/share/html.d.ts +22 -0
  200. package/dist/lib/share/html.js +88 -0
  201. package/dist/lib/share/provision.d.ts +18 -4
  202. package/dist/lib/share/provision.js +38 -4
  203. package/dist/lib/share/publish.d.ts +39 -15
  204. package/dist/lib/share/publish.js +55 -26
  205. package/dist/lib/share/worker-template.js +171 -11
  206. package/dist/lib/sqlite.js +16 -3
  207. package/dist/lib/ssh-exec.d.ts +65 -0
  208. package/dist/lib/ssh-exec.js +120 -4
  209. package/dist/lib/startup/command-registry.js +1 -1
  210. package/dist/lib/startup/dev-build.js +3 -3
  211. package/dist/lib/state.d.ts +1 -1
  212. package/dist/lib/state.js +7 -3
  213. package/dist/lib/subagents-registry.js +1 -1
  214. package/dist/lib/terminal/index.d.ts +1 -1
  215. package/dist/lib/terminal/types.d.ts +1 -1
  216. package/dist/lib/terminal/types.js +1 -1
  217. package/dist/lib/tmux/orphan-reap.js +1 -1
  218. package/dist/lib/tmux/session.js +31 -0
  219. package/dist/lib/traces/backend.d.ts +29 -0
  220. package/dist/lib/traces/backend.js +46 -0
  221. package/dist/lib/traces/classify.d.ts +52 -0
  222. package/dist/lib/traces/classify.js +72 -0
  223. package/dist/lib/traces/config.d.ts +10 -0
  224. package/dist/lib/traces/config.js +2 -0
  225. package/dist/lib/traces/provision.d.ts +19 -0
  226. package/dist/lib/traces/provision.js +30 -0
  227. package/dist/lib/traces/sync.d.ts +150 -0
  228. package/dist/lib/traces/sync.js +447 -0
  229. package/dist/lib/traces/worker-template.d.ts +4 -0
  230. package/dist/lib/traces/worker-template.js +159 -0
  231. package/dist/lib/types.d.ts +18 -19
  232. package/dist/lib/types.js +14 -0
  233. package/dist/lib/usage-refresh.d.ts +1 -1
  234. package/dist/lib/usage-refresh.js +11 -8
  235. package/dist/lib/workflows.d.ts +1 -1
  236. package/package.json +4 -6
  237. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  238. package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +0 -24
  239. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  240. package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
  241. package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +0 -128
  242. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  243. package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -26
  244. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  245. package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
  246. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +0 -135
  247. package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
  248. package/dist/lib/usage-fleet.d.ts +0 -32
  249. package/dist/lib/usage-fleet.js +0 -125
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Device-probe tick as a `PeriodicService` (RUSH-3193 P3).
3
+ *
4
+ * Refreshes registered devices' reachability and detects newly appeared
5
+ * tailnet nodes, dropping a sentinel per pending device so the menu-bar helper
6
+ * can surface "NEW DEVICES -> Register / Ignore". Refresh mode never
7
+ * auto-registers a newcomer; a machine without tailscale is a clean no-op.
8
+ * `reconcilePendingSentinels` re-subtracts the ignore-list AND the registered
9
+ * roster, so a dismissed or already-known device is never re-surfaced. On
10
+ * soft-fail (no tailscale) sentinels are still pruned so a hermetic test leak
11
+ * cannot leave fleet boxes in NEW DEVICES forever.
12
+ *
13
+ * The supervisor fires an immediate tick on start (see `service.ts`), which
14
+ * replaces the previous inline `void runDeviceProbeTick()` kick-off that
15
+ * cleared any leftover pollution set without waiting for the first interval.
16
+ */
17
+ import { BasePeriodicService, type DaemonContext } from './service.js';
18
+ import type { DaemonServiceId } from '../daemon-services.js';
19
+ export declare class DeviceProbeService extends BasePeriodicService {
20
+ readonly id: DaemonServiceId;
21
+ readonly intervalMs: number;
22
+ readonly deadlineMs = 90000;
23
+ protected onStart(_ctx: DaemonContext): Promise<void>;
24
+ protected onStop(): Promise<void>;
25
+ protected onTick(ctx: DaemonContext): Promise<void>;
26
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Device-probe tick as a `PeriodicService` (RUSH-3193 P3).
3
+ *
4
+ * Refreshes registered devices' reachability and detects newly appeared
5
+ * tailnet nodes, dropping a sentinel per pending device so the menu-bar helper
6
+ * can surface "NEW DEVICES -> Register / Ignore". Refresh mode never
7
+ * auto-registers a newcomer; a machine without tailscale is a clean no-op.
8
+ * `reconcilePendingSentinels` re-subtracts the ignore-list AND the registered
9
+ * roster, so a dismissed or already-known device is never re-surfaced. On
10
+ * soft-fail (no tailscale) sentinels are still pruned so a hermetic test leak
11
+ * cannot leave fleet boxes in NEW DEVICES forever.
12
+ *
13
+ * The supervisor fires an immediate tick on start (see `service.ts`), which
14
+ * replaces the previous inline `void runDeviceProbeTick()` kick-off that
15
+ * cleared any leftover pollution set without waiting for the first interval.
16
+ */
17
+ import { BasePeriodicService } from './service.js';
18
+ /** Matches the historical inline interval (daemon.ts DEVICE_PROBE_TICK_MS). */
19
+ const DEVICE_PROBE_TICK_MS = 3 * 60_000;
20
+ /** Hard cap per tick — a tailnet + reachability sweep, short enough that a hang never freezes the service for long. */
21
+ const DEVICE_PROBE_DEADLINE_MS = 90_000;
22
+ export class DeviceProbeService extends BasePeriodicService {
23
+ id = 'device-probe';
24
+ intervalMs = DEVICE_PROBE_TICK_MS;
25
+ deadlineMs = DEVICE_PROBE_DEADLINE_MS;
26
+ async onStart(_ctx) {
27
+ // No connections/handles to open — each tick performs a fresh soft sync.
28
+ }
29
+ async onStop() {
30
+ // Nothing to release — the supervisor's timer teardown is the only cleanup needed.
31
+ }
32
+ async onTick(ctx) {
33
+ const { runDeviceSync } = await import('../devices/sync.js');
34
+ const { reconcilePendingSentinels, pruneDismissedPendingSentinels } = await import('../devices/pending.js');
35
+ const dev = await runDeviceSync({ soft: true, mode: 'refresh' });
36
+ if (!dev.ok) {
37
+ await pruneDismissedPendingSentinels();
38
+ if (dev.reason)
39
+ ctx.log('WARN', `device probe soft-fail: ${dev.reason}`);
40
+ return;
41
+ }
42
+ await reconcilePendingSentinels(dev.pending);
43
+ if (dev.pending.length) {
44
+ ctx.log('INFO', `devices: ${dev.pending.length} new pending (${dev.pending.map((p) => p.name).join(', ')})`);
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Keychain orphan reaper tick as a `PeriodicService` (RUSH-3193 P3).
3
+ *
4
+ * Kills stuck keychain helper and `agents` processes whose keychain call
5
+ * never returned (RUSH-2232). Single-executor: only the daemon runs this, so
6
+ * no cross-device race.
7
+ */
8
+ import { BasePeriodicService, type DaemonContext } from './service.js';
9
+ import type { DaemonServiceId } from '../daemon-services.js';
10
+ export declare class KeychainReapService extends BasePeriodicService {
11
+ readonly id: DaemonServiceId;
12
+ readonly intervalMs: number;
13
+ readonly deadlineMs = 30000;
14
+ protected onStart(_ctx: DaemonContext): Promise<void>;
15
+ protected onStop(): Promise<void>;
16
+ protected onTick(ctx: DaemonContext): Promise<void>;
17
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Keychain orphan reaper tick as a `PeriodicService` (RUSH-3193 P3).
3
+ *
4
+ * Kills stuck keychain helper and `agents` processes whose keychain call
5
+ * never returned (RUSH-2232). Single-executor: only the daemon runs this, so
6
+ * no cross-device race.
7
+ */
8
+ import { BasePeriodicService } from './service.js';
9
+ /** Matches the historical inline interval (daemon.ts KEYCHAIN_REAP_TICK_MS). */
10
+ const KEYCHAIN_REAP_TICK_MS = 5 * 60_000;
11
+ /** Hard cap per tick — a process-table scan + targeted kills, short enough a hang never freezes the service for long. */
12
+ const KEYCHAIN_REAP_DEADLINE_MS = 30_000;
13
+ export class KeychainReapService extends BasePeriodicService {
14
+ id = 'keychain-reap';
15
+ intervalMs = KEYCHAIN_REAP_TICK_MS;
16
+ deadlineMs = KEYCHAIN_REAP_DEADLINE_MS;
17
+ async onStart(_ctx) {
18
+ // No connections/handles to open — each tick performs a fresh process scan.
19
+ }
20
+ async onStop() {
21
+ // Nothing to release — the supervisor's timer teardown is the only cleanup needed.
22
+ }
23
+ async onTick(ctx) {
24
+ const { reapOrphanedKeychainProcesses } = await import('../secrets/reaper.js');
25
+ const result = reapOrphanedKeychainProcesses();
26
+ if (result.reaped > 0) {
27
+ ctx.log('WARN', `Reaped ${result.reaped} keychain orphan/stuck process(es)`);
28
+ for (const d of result.details)
29
+ ctx.log('WARN', ` ${d}`);
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * MonitorEngine lifecycle as a `DaemonService` (RUSH-3193 P2).
3
+ *
4
+ * Wraps the `MonitorEngine` (event-triggered watchers) under the
5
+ * `ServiceSupervisor` contract. This is a lifecycle-only service — the engine
6
+ * runs its own internal event loop; the supervisor just calls `start()` at
7
+ * boot and `stop()` at shutdown.
8
+ *
9
+ * `getEngine()` exposes the underlying `MonitorEngine` so `daemon.ts`'s
10
+ * SIGHUP handler can call `engine.reload()` when needed.
11
+ */
12
+ import { BaseDaemonService, type DaemonContext } from './service.js';
13
+ import type { DaemonServiceId } from '../daemon-services.js';
14
+ import { MonitorEngine } from '../monitors/engine.js';
15
+ export declare class MonitorEngineService extends BaseDaemonService {
16
+ readonly id: DaemonServiceId;
17
+ private engine;
18
+ /** Returns the live engine after `start()`, or `null` before/after. */
19
+ getEngine(): MonitorEngine | null;
20
+ protected onStart(ctx: DaemonContext): Promise<void>;
21
+ protected onStop(): Promise<void>;
22
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * MonitorEngine lifecycle as a `DaemonService` (RUSH-3193 P2).
3
+ *
4
+ * Wraps the `MonitorEngine` (event-triggered watchers) under the
5
+ * `ServiceSupervisor` contract. This is a lifecycle-only service — the engine
6
+ * runs its own internal event loop; the supervisor just calls `start()` at
7
+ * boot and `stop()` at shutdown.
8
+ *
9
+ * `getEngine()` exposes the underlying `MonitorEngine` so `daemon.ts`'s
10
+ * SIGHUP handler can call `engine.reload()` when needed.
11
+ */
12
+ import { BaseDaemonService } from './service.js';
13
+ import { MonitorEngine } from '../monitors/engine.js';
14
+ export class MonitorEngineService extends BaseDaemonService {
15
+ id = 'monitors';
16
+ engine = null;
17
+ /** Returns the live engine after `start()`, or `null` before/after. */
18
+ getEngine() {
19
+ return this.engine;
20
+ }
21
+ async onStart(ctx) {
22
+ this.engine = new MonitorEngine((level, message) => ctx.log(level, message));
23
+ this.engine.start();
24
+ }
25
+ async onStop() {
26
+ this.engine?.stop();
27
+ this.engine = null;
28
+ }
29
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Secrets-broker lifecycle as a `DaemonService` (RUSH-3193 P2).
3
+ *
4
+ * Wraps the daemon's secrets-broker hosting under the `ServiceSupervisor`
5
+ * contract so the supervisor owns start/stop and reports health through the
6
+ * uniform `daemon-health.ts` path. The broker self-heal (RUSH-1817) is
7
+ * encapsulated here: a background interval re-probes the socket and takes
8
+ * over hosting whenever the daemon is NOT already hosting AND no healthy
9
+ * standalone broker answers a ping.
10
+ */
11
+ import { BaseDaemonService, type DaemonContext } from './service.js';
12
+ import type { DaemonServiceId } from '../daemon-services.js';
13
+ export declare class SecretsBrokerService extends BaseDaemonService {
14
+ readonly id: DaemonServiceId;
15
+ private hostedBroker;
16
+ private selfHealTimer;
17
+ private selfHealInFlight;
18
+ protected onStart(ctx: DaemonContext): Promise<void>;
19
+ protected onStop(): Promise<void>;
20
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Secrets-broker lifecycle as a `DaemonService` (RUSH-3193 P2).
3
+ *
4
+ * Wraps the daemon's secrets-broker hosting under the `ServiceSupervisor`
5
+ * contract so the supervisor owns start/stop and reports health through the
6
+ * uniform `daemon-health.ts` path. The broker self-heal (RUSH-1817) is
7
+ * encapsulated here: a background interval re-probes the socket and takes
8
+ * over hosting whenever the daemon is NOT already hosting AND no healthy
9
+ * standalone broker answers a ping.
10
+ */
11
+ import { BaseDaemonService } from './service.js';
12
+ /** Matches the constant previously in daemon.ts. */
13
+ const BROKER_SELF_HEAL_TICK_MS = 60_000;
14
+ /** Take over only when we are not already hosting AND no broker is reachable. */
15
+ function shouldTakeOver(isHosting, brokerReachable) {
16
+ return !isHosting && !brokerReachable;
17
+ }
18
+ export class SecretsBrokerService extends BaseDaemonService {
19
+ id = 'secrets-broker';
20
+ hostedBroker = null;
21
+ selfHealTimer;
22
+ selfHealInFlight = false;
23
+ async onStart(ctx) {
24
+ const { agentPing, startHostedBroker } = await import('../secrets/agent.js');
25
+ if ((await agentPing()).reachable) {
26
+ ctx.log('INFO', 'Secrets broker already running (standalone); daemon not hosting it');
27
+ }
28
+ else {
29
+ this.hostedBroker = await startHostedBroker();
30
+ if (this.hostedBroker)
31
+ ctx.log('INFO', 'Secrets broker hosted in daemon (socket-first)');
32
+ }
33
+ // RUSH-1817: if the standalone the daemon deferred to at start later dies,
34
+ // take over hosting on the next self-heal probe.
35
+ const runSelfHeal = async () => {
36
+ if (this.selfHealInFlight)
37
+ return;
38
+ this.selfHealInFlight = true;
39
+ try {
40
+ const { agentPing: ping, startHostedBroker: startBroker } = await import('../secrets/agent.js');
41
+ const reachable = (await ping()).reachable;
42
+ if (!shouldTakeOver(this.hostedBroker != null, reachable))
43
+ return;
44
+ this.hostedBroker = await startBroker();
45
+ if (this.hostedBroker) {
46
+ ctx.log('WARN', 'Secrets broker was unreachable; daemon took over hosting (self-heal)');
47
+ }
48
+ }
49
+ catch (err) {
50
+ ctx.log('WARN', `Secrets broker self-heal skipped: ${err.message}`);
51
+ }
52
+ finally {
53
+ this.selfHealInFlight = false;
54
+ }
55
+ };
56
+ this.selfHealTimer = setInterval(() => { void runSelfHeal(); }, BROKER_SELF_HEAL_TICK_MS);
57
+ }
58
+ async onStop() {
59
+ if (this.selfHealTimer !== undefined) {
60
+ clearInterval(this.selfHealTimer);
61
+ this.selfHealTimer = undefined;
62
+ }
63
+ this.hostedBroker?.close();
64
+ this.hostedBroker = null;
65
+ }
66
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Resource self-heal tick as a `PeriodicService` (RUSH-3193 P3).
3
+ *
4
+ * Fills missing resources, repairs invalid manifests, and fast-forwards
5
+ * pristine stale plugins. Conservative 'safe' mode: never overwrites
6
+ * hand-edited content. Does not run when the daemon's state directory no
7
+ * longer exists — that is the state-dir self-check's signal to shut down;
8
+ * background maintenance must not recreate the tree while it is mid-exit.
9
+ *
10
+ * The pre-migration inline timer also delayed its first fire by
11
+ * `SELF_HEAL_KICKOFF_MS` (30s) so shims/PATH could settle shortly after daemon
12
+ * start, without making launch itself busy. `startupDelayMs` below reproduces
13
+ * that stagger through the supervisor's generic per-service contract
14
+ * (`service.ts`) — every other tick still fires on the normal `intervalMs`
15
+ * cadence; only self-heal's boot-time tick is deliberately staggered.
16
+ */
17
+ import { BasePeriodicService, type DaemonContext } from './service.js';
18
+ import type { DaemonServiceId } from '../daemon-services.js';
19
+ export declare class SelfHealService extends BasePeriodicService {
20
+ readonly id: DaemonServiceId;
21
+ readonly intervalMs: number;
22
+ readonly deadlineMs: number;
23
+ readonly startupDelayMs = 30000;
24
+ protected onStart(_ctx: DaemonContext): Promise<void>;
25
+ protected onStop(): Promise<void>;
26
+ protected onTick(ctx: DaemonContext): Promise<void>;
27
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Resource self-heal tick as a `PeriodicService` (RUSH-3193 P3).
3
+ *
4
+ * Fills missing resources, repairs invalid manifests, and fast-forwards
5
+ * pristine stale plugins. Conservative 'safe' mode: never overwrites
6
+ * hand-edited content. Does not run when the daemon's state directory no
7
+ * longer exists — that is the state-dir self-check's signal to shut down;
8
+ * background maintenance must not recreate the tree while it is mid-exit.
9
+ *
10
+ * The pre-migration inline timer also delayed its first fire by
11
+ * `SELF_HEAL_KICKOFF_MS` (30s) so shims/PATH could settle shortly after daemon
12
+ * start, without making launch itself busy. `startupDelayMs` below reproduces
13
+ * that stagger through the supervisor's generic per-service contract
14
+ * (`service.ts`) — every other tick still fires on the normal `intervalMs`
15
+ * cadence; only self-heal's boot-time tick is deliberately staggered.
16
+ */
17
+ import { BasePeriodicService } from './service.js';
18
+ import { getDaemonDir } from '../state.js';
19
+ import * as fs from 'fs';
20
+ /** Matches the historical inline interval (daemon.ts SELF_HEAL_TICK_MS). Runs ~every 6h. */
21
+ const SELF_HEAL_TICK_MS = 6 * 60 * 60_000;
22
+ /** Hard cap per tick — a full resource repair sweep across every version home, short enough a hang never freezes the service for long relative to its 6h cadence. */
23
+ const SELF_HEAL_DEADLINE_MS = 10 * 60_000;
24
+ /** Matches the historical inline kickoff delay (daemon.ts SELF_HEAL_KICKOFF_MS). Staggers self-heal's first tick after shims/PATH settle, so launch itself isn't made busy. */
25
+ const SELF_HEAL_KICKOFF_MS = 30_000;
26
+ export class SelfHealService extends BasePeriodicService {
27
+ id = 'self-heal';
28
+ intervalMs = SELF_HEAL_TICK_MS;
29
+ deadlineMs = SELF_HEAL_DEADLINE_MS;
30
+ startupDelayMs = SELF_HEAL_KICKOFF_MS;
31
+ async onStart(_ctx) {
32
+ // No connections/handles to open — each tick re-checks the state dir itself.
33
+ }
34
+ async onStop() {
35
+ // Nothing to release — the supervisor's timer teardown is the only cleanup needed.
36
+ }
37
+ async onTick(ctx) {
38
+ if (!fs.existsSync(getDaemonDir()))
39
+ return;
40
+ const { runSelfHeal, selfHealChangedAnything, selfHealNeedsAttention, summarizeSelfHeal } = await import('../self-heal/registry.js');
41
+ if (!fs.existsSync(getDaemonDir()))
42
+ return;
43
+ const report = await runSelfHeal({ mode: 'safe' });
44
+ if (selfHealChangedAnything(report) || selfHealNeedsAttention(report)) {
45
+ ctx.log('INFO', `self-heal: ${summarizeSelfHeal(report)}`);
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * DaemonService contract (RUSH-3193 P1).
3
+ *
4
+ * Today the daemon's ~13 background services are bare `setInterval` closures
5
+ * inside one long `runDaemon()` (daemon.ts), sharing one event loop with no
6
+ * per-service error boundary or deadline. A throw escaping a tick's local
7
+ * try/catch hits the process-wide handler and kills every service; a tick that
8
+ * hangs on an unbounded await latches its in-flight guard `true` forever and
9
+ * silently freezes that service for the daemon's life.
10
+ *
11
+ * This module defines the service shape a `ServiceSupervisor` (supervisor.ts)
12
+ * drives instead: one contract per service, owned timer, per-tick deadline,
13
+ * and a health record the supervisor can report without the service having to
14
+ * know it is being supervised.
15
+ */
16
+ import type { DaemonServiceId } from '../daemon-services.js';
17
+ /** Lifecycle state a supervised service can be in. */
18
+ export type ServiceState = 'idle' | 'running' | 'parked' | 'stopped';
19
+ /** A service's most recently observed health, as reported by the supervisor. */
20
+ export interface ServiceHealth {
21
+ state: ServiceState;
22
+ /** `Date.now()` of the last tick that completed without throwing or timing out. */
23
+ lastRunMs: number;
24
+ lastError?: string;
25
+ /** Consecutive failed/timed-out ticks since the last success. */
26
+ consecutiveFailures: number;
27
+ }
28
+ /** Shared context every service receives at start/tick time. */
29
+ export interface DaemonContext {
30
+ log: (level: string, message: string) => void;
31
+ }
32
+ /** Base contract every supervised daemon service implements. */
33
+ export interface DaemonService {
34
+ readonly id: DaemonServiceId;
35
+ start(ctx: DaemonContext): Promise<void>;
36
+ stop(): Promise<void>;
37
+ /** `stop()` then `start()` — used by the supervisor's circuit-breaker (and, in future, an on-demand `daemon services restart <id>` command — not wired up yet). */
38
+ restart(): Promise<void>;
39
+ health(): ServiceHealth;
40
+ }
41
+ /** A service the supervisor ticks on a fixed interval, under a hard per-tick deadline. */
42
+ export interface PeriodicService extends DaemonService {
43
+ readonly intervalMs: number;
44
+ /** Hard cap per tick. An over-budget tick is abandoned (never awaited past this) so its in-flight guard always releases. */
45
+ readonly deadlineMs: number;
46
+ /**
47
+ * Delay before the FIRST tick after `start()`, in ms. Every later tick
48
+ * still fires on the normal `intervalMs` cadence — this only staggers the
49
+ * boot-time tick. Default (omitted) is 0, the supervisor's baseline
50
+ * immediate-first-tick behavior. Set this when a service's first-boot work
51
+ * needs something else (shims, PATH) to settle before it runs, rather than
52
+ * firing at daemon startup.
53
+ */
54
+ readonly startupDelayMs?: number;
55
+ tick(ctx: DaemonContext): Promise<void>;
56
+ }
57
+ export declare function isPeriodicService(service: DaemonService): service is PeriodicService;
58
+ /**
59
+ * Convenience base for a lifecycle-only (non-periodic) daemon service.
60
+ *
61
+ * Concrete subclasses implement `onStart` and `onStop`; the base owns the
62
+ * `ServiceHealth` record and the `restart()` default (`stop` + `start`).
63
+ * Unlike `BasePeriodicService` there is no tick — the supervisor just calls
64
+ * `start()` once at boot, keeps the service marked `running`, and calls
65
+ * `stop()` at shutdown.
66
+ *
67
+ * `lastRunMs` is set to the time `start()` completed, so `supervisor.health()`
68
+ * carries a meaningful "last known healthy" timestamp even with no periodic ticks.
69
+ */
70
+ export declare abstract class BaseDaemonService implements DaemonService {
71
+ abstract readonly id: DaemonServiceId;
72
+ protected ctx: DaemonContext | null;
73
+ private healthRecord;
74
+ protected abstract onStart(ctx: DaemonContext): Promise<void>;
75
+ protected abstract onStop(): Promise<void>;
76
+ start(ctx: DaemonContext): Promise<void>;
77
+ stop(): Promise<void>;
78
+ restart(): Promise<void>;
79
+ health(): ServiceHealth;
80
+ }
81
+ /**
82
+ * Convenience base for a periodic service: owns the `ServiceHealth` record so
83
+ * concrete services only implement the three lifecycle hooks. `restart()`
84
+ * defaults to `stop()` then `start()` against the last context passed to
85
+ * `start()`, which is what every current-generation service needs — a
86
+ * concrete service overrides it only if it must reuse state across a restart.
87
+ */
88
+ export declare abstract class BasePeriodicService implements PeriodicService {
89
+ abstract readonly id: DaemonServiceId;
90
+ abstract readonly intervalMs: number;
91
+ abstract readonly deadlineMs: number;
92
+ protected ctx: DaemonContext | null;
93
+ private healthRecord;
94
+ protected abstract onStart(ctx: DaemonContext): Promise<void>;
95
+ protected abstract onStop(): Promise<void>;
96
+ protected abstract onTick(ctx: DaemonContext): Promise<void>;
97
+ start(ctx: DaemonContext): Promise<void>;
98
+ stop(): Promise<void>;
99
+ restart(): Promise<void>;
100
+ tick(ctx: DaemonContext): Promise<void>;
101
+ health(): ServiceHealth;
102
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * DaemonService contract (RUSH-3193 P1).
3
+ *
4
+ * Today the daemon's ~13 background services are bare `setInterval` closures
5
+ * inside one long `runDaemon()` (daemon.ts), sharing one event loop with no
6
+ * per-service error boundary or deadline. A throw escaping a tick's local
7
+ * try/catch hits the process-wide handler and kills every service; a tick that
8
+ * hangs on an unbounded await latches its in-flight guard `true` forever and
9
+ * silently freezes that service for the daemon's life.
10
+ *
11
+ * This module defines the service shape a `ServiceSupervisor` (supervisor.ts)
12
+ * drives instead: one contract per service, owned timer, per-tick deadline,
13
+ * and a health record the supervisor can report without the service having to
14
+ * know it is being supervised.
15
+ */
16
+ export function isPeriodicService(service) {
17
+ const candidate = service;
18
+ return typeof candidate.tick === 'function' && typeof candidate.intervalMs === 'number' && typeof candidate.deadlineMs === 'number';
19
+ }
20
+ function blankHealth() {
21
+ return { state: 'idle', lastRunMs: 0, consecutiveFailures: 0 };
22
+ }
23
+ /**
24
+ * Convenience base for a lifecycle-only (non-periodic) daemon service.
25
+ *
26
+ * Concrete subclasses implement `onStart` and `onStop`; the base owns the
27
+ * `ServiceHealth` record and the `restart()` default (`stop` + `start`).
28
+ * Unlike `BasePeriodicService` there is no tick — the supervisor just calls
29
+ * `start()` once at boot, keeps the service marked `running`, and calls
30
+ * `stop()` at shutdown.
31
+ *
32
+ * `lastRunMs` is set to the time `start()` completed, so `supervisor.health()`
33
+ * carries a meaningful "last known healthy" timestamp even with no periodic ticks.
34
+ */
35
+ export class BaseDaemonService {
36
+ ctx = null;
37
+ healthRecord = blankHealth();
38
+ async start(ctx) {
39
+ this.ctx = ctx;
40
+ await this.onStart(ctx);
41
+ this.healthRecord = { ...this.healthRecord, state: 'running', lastRunMs: Date.now() };
42
+ }
43
+ async stop() {
44
+ await this.onStop();
45
+ this.healthRecord = { ...this.healthRecord, state: 'stopped' };
46
+ }
47
+ async restart() {
48
+ const ctx = this.ctx;
49
+ if (!ctx)
50
+ throw new Error(`service '${this.id}' cannot restart before it has started once`);
51
+ await this.stop();
52
+ await this.start(ctx);
53
+ }
54
+ health() {
55
+ return { ...this.healthRecord };
56
+ }
57
+ }
58
+ /**
59
+ * Convenience base for a periodic service: owns the `ServiceHealth` record so
60
+ * concrete services only implement the three lifecycle hooks. `restart()`
61
+ * defaults to `stop()` then `start()` against the last context passed to
62
+ * `start()`, which is what every current-generation service needs — a
63
+ * concrete service overrides it only if it must reuse state across a restart.
64
+ */
65
+ export class BasePeriodicService {
66
+ ctx = null;
67
+ healthRecord = blankHealth();
68
+ async start(ctx) {
69
+ this.ctx = ctx;
70
+ await this.onStart(ctx);
71
+ this.healthRecord = { ...this.healthRecord, state: 'running' };
72
+ }
73
+ async stop() {
74
+ await this.onStop();
75
+ this.healthRecord = { ...this.healthRecord, state: 'stopped' };
76
+ }
77
+ async restart() {
78
+ const ctx = this.ctx;
79
+ if (!ctx)
80
+ throw new Error(`service '${this.id}' cannot restart before it has started once`);
81
+ await this.stop();
82
+ await this.start(ctx);
83
+ }
84
+ async tick(ctx) {
85
+ try {
86
+ await this.onTick(ctx);
87
+ this.healthRecord = { ...this.healthRecord, lastRunMs: Date.now(), consecutiveFailures: 0, lastError: undefined };
88
+ }
89
+ catch (err) {
90
+ const message = err instanceof Error ? err.message : String(err);
91
+ this.healthRecord = { ...this.healthRecord, lastError: message, consecutiveFailures: this.healthRecord.consecutiveFailures + 1 };
92
+ throw err;
93
+ }
94
+ }
95
+ health() {
96
+ return { ...this.healthRecord };
97
+ }
98
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Session-index warm service (RUSH-2682), migrated onto `ServiceSupervisor`
3
+ * (RUSH-3193 P1) as the proof-of-concept periodic service.
4
+ *
5
+ * Incrementally scans this host's transcript dirs into the local index so a
6
+ * locally-started session is discoverable within seconds, not on the next
7
+ * unrelated `agents sessions*` call. Single-flight via the DB scan claim in
8
+ * `scanSessionsIncremental` — a concurrent foreground scan is a skip, not a
9
+ * failure. Previously a bare `setInterval` in `runDaemon()`
10
+ * (daemon.ts:1118-1137); the supervisor now owns its timer, error boundary,
11
+ * and deadline instead.
12
+ */
13
+ import { BasePeriodicService, type DaemonContext } from './service.js';
14
+ import type { DaemonServiceId } from '../daemon-services.js';
15
+ export declare class SessionIndexService extends BasePeriodicService {
16
+ readonly id: DaemonServiceId;
17
+ readonly intervalMs = 20000;
18
+ readonly deadlineMs = 60000;
19
+ protected onStart(_ctx: DaemonContext): Promise<void>;
20
+ protected onStop(): Promise<void>;
21
+ protected onTick(ctx: DaemonContext): Promise<void>;
22
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Session-index warm service (RUSH-2682), migrated onto `ServiceSupervisor`
3
+ * (RUSH-3193 P1) as the proof-of-concept periodic service.
4
+ *
5
+ * Incrementally scans this host's transcript dirs into the local index so a
6
+ * locally-started session is discoverable within seconds, not on the next
7
+ * unrelated `agents sessions*` call. Single-flight via the DB scan claim in
8
+ * `scanSessionsIncremental` — a concurrent foreground scan is a skip, not a
9
+ * failure. Previously a bare `setInterval` in `runDaemon()`
10
+ * (daemon.ts:1118-1137); the supervisor now owns its timer, error boundary,
11
+ * and deadline instead.
12
+ */
13
+ import { BasePeriodicService } from './service.js';
14
+ import { runSessionIndexWarmTick } from '../daemon-ticks.js';
15
+ /** Matches the historical inline interval (daemon.ts SESSION_INDEX_WARM_TICK_MS). */
16
+ const SESSION_INDEX_WARM_TICK_MS = 20_000;
17
+ /** Hard cap per tick — well above a healthy incremental scan, short enough that a hang never freezes the service for long. */
18
+ const SESSION_INDEX_WARM_DEADLINE_MS = 60_000;
19
+ export class SessionIndexService extends BasePeriodicService {
20
+ id = 'session-index';
21
+ intervalMs = SESSION_INDEX_WARM_TICK_MS;
22
+ deadlineMs = SESSION_INDEX_WARM_DEADLINE_MS;
23
+ async onStart(_ctx) {
24
+ // No connections/handles to open — the scan claim itself is per-tick state.
25
+ }
26
+ async onStop() {
27
+ // Nothing to release — the supervisor's timer teardown is the only cleanup needed.
28
+ }
29
+ async onTick(ctx) {
30
+ const { indexed, claimed } = await runSessionIndexWarmTick();
31
+ // Log only when the tick did something. A silent tick is what let it
32
+ // report 0 forever unnoticed (RUSH-2691); a line on every idle 20s tick
33
+ // would drown the log, so the steady state (claimed, nothing changed)
34
+ // stays quiet and both interesting outcomes are visible.
35
+ if (!claimed)
36
+ ctx.log('INFO', 'session-index warm: skipped, another process holds the scan claim');
37
+ else if (indexed > 0)
38
+ ctx.log('INFO', `session-index warm: indexed ${indexed} transcript(s)`);
39
+ }
40
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * State-dir self-terminate guard as a `PeriodicService` (RUSH-3193 P3).
3
+ *
4
+ * RUSH-2367: self-terminate if this daemon's own state directory has been
5
+ * removed out from under it — the shape of a leaked test-fixture daemon whose
6
+ * /tmp HOME was deleted by its test's own cleanup while the process itself
7
+ * somehow survived. Nothing else can reach a daemon in that state: a
8
+ * different HOME resolves a different `ensureDaemonDir()` and therefore a
9
+ * different instance registry — without this it runs forever. Reads the
10
+ * lifetime marker directly, never the local `ensureDaemonDir()` wrapper,
11
+ * which recreates the directory as a side effect and would defeat the check.
12
+ * Heartbeat/status paths may recreate the directory and pid file after a
13
+ * deletion; they never recreate this per-lifetime token.
14
+ *
15
+ * `lifetimePath`/`lifetimeToken` are per-boot constants computed once at
16
+ * `runDaemon()` start and passed in via the constructor, along with the
17
+ * shutdown callback. Registering (and starting) this service happens AFTER
18
+ * `runDaemon()` defines its `handleShutdown` — unlike the other four migrated
19
+ * services, which register before `supervisor.startAll()` — because the
20
+ * supervisor fires an immediate first tick on start, and calling an
21
+ * as-yet-undefined `handleShutdown` const at that point would throw. See the
22
+ * registration site in daemon.ts for the deferred `supervisor.register()` +
23
+ * `supervisor.start()` pairing this requires.
24
+ */
25
+ import { BasePeriodicService, type DaemonContext } from './service.js';
26
+ import type { DaemonServiceId } from '../daemon-services.js';
27
+ export interface StateDirCheckServiceOptions {
28
+ lifetimePath: string;
29
+ lifetimeToken: string;
30
+ onMissing: () => void;
31
+ }
32
+ export declare class StateDirCheckService extends BasePeriodicService {
33
+ readonly id: DaemonServiceId;
34
+ readonly intervalMs: number;
35
+ readonly deadlineMs = 5000;
36
+ private readonly lifetimePath;
37
+ private readonly lifetimeToken;
38
+ private readonly onMissing;
39
+ constructor(opts: StateDirCheckServiceOptions);
40
+ protected onStart(_ctx: DaemonContext): Promise<void>;
41
+ protected onStop(): Promise<void>;
42
+ protected onTick(ctx: DaemonContext): Promise<void>;
43
+ }