@phnx-labs/agents-cli 1.22.117 → 1.22.118

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 (248) hide show
  1. package/CHANGELOG.md +235 -0
  2. package/dist/bootstrap.js +4 -11
  3. package/dist/commands/browser.d.ts +52 -70
  4. package/dist/commands/browser.js +192 -3562
  5. package/dist/commands/daemon.d.ts +39 -2
  6. package/dist/commands/daemon.js +72 -73
  7. package/dist/commands/events.js +1 -1
  8. package/dist/commands/exec.js +53 -1
  9. package/dist/commands/repo.js +13 -49
  10. package/dist/commands/route.js +5 -5
  11. package/dist/commands/routines.js +39 -12
  12. package/dist/commands/sessions-render.d.ts +1 -1
  13. package/dist/commands/sessions-render.js +0 -1
  14. package/dist/commands/sessions.test-fixture.d.ts +0 -1
  15. package/dist/commands/sessions.test-fixture.js +0 -37
  16. package/dist/commands/setup-browser.d.ts +17 -12
  17. package/dist/commands/setup-browser.js +52 -87
  18. package/dist/commands/setup-preferences.d.ts +0 -17
  19. package/dist/commands/setup-preferences.js +18 -49
  20. package/dist/commands/setup.js +9 -9
  21. package/dist/commands/ssh.js +10 -1
  22. package/dist/commands/sync.js +6 -44
  23. package/dist/commands/teams.js +0 -1
  24. package/dist/commands/update.d.ts +19 -0
  25. package/dist/commands/update.js +34 -6
  26. package/dist/commands/view.d.ts +12 -1
  27. package/dist/commands/view.js +47 -2
  28. package/dist/index.js +107 -26
  29. package/dist/lib/account-capabilities.js +0 -2
  30. package/dist/lib/account-catalog.d.ts +38 -0
  31. package/dist/lib/account-catalog.js +110 -26
  32. package/dist/lib/account-provider-registry.js +1 -1
  33. package/dist/lib/account-registry.d.ts +1 -1
  34. package/dist/lib/account-registry.js +36 -2
  35. package/dist/lib/accounting/rotate.js +22 -8
  36. package/dist/lib/accounting/usage-ingest.d.ts +6 -1
  37. package/dist/lib/accounting/usage-ingest.js +115 -45
  38. package/dist/lib/accounting/usage-sync.d.ts +134 -31
  39. package/dist/lib/accounting/usage-sync.js +227 -66
  40. package/dist/lib/accounting/usage.d.ts +16 -3
  41. package/dist/lib/accounting/usage.js +26 -8
  42. package/dist/lib/accounts/slots.d.ts +8 -0
  43. package/dist/lib/accounts/slots.js +17 -0
  44. package/dist/lib/add-dir.js +0 -1
  45. package/dist/lib/agent-cli-commands.js +0 -1
  46. package/dist/lib/agent-spec/agents.d.ts +6 -7
  47. package/dist/lib/agent-spec/agents.js +6 -60
  48. package/dist/lib/auth-health.d.ts +69 -1
  49. package/dist/lib/auth-health.js +133 -4
  50. package/dist/lib/browser/context.d.ts +82 -0
  51. package/dist/lib/browser/context.js +88 -0
  52. package/dist/lib/browser/paths.d.ts +25 -0
  53. package/dist/lib/browser/paths.js +51 -0
  54. package/dist/lib/browser/record.d.ts +38 -0
  55. package/dist/lib/browser/record.js +57 -0
  56. package/dist/lib/browser/sessions-list.js +1 -2
  57. package/dist/lib/browser-client.d.ts +158 -0
  58. package/dist/lib/browser-client.js +234 -0
  59. package/dist/lib/channels/owner-sink.d.ts +1 -18
  60. package/dist/lib/channels/owner-sink.js +29 -82
  61. package/dist/lib/channels/providers/rush.d.ts +7 -7
  62. package/dist/lib/channels/providers/rush.js +127 -57
  63. package/dist/lib/claude-statusline.d.ts +1 -1
  64. package/dist/lib/claude-statusline.js +15 -3
  65. package/dist/lib/commands.js +1 -1
  66. package/dist/lib/crabbox/lease.js +1 -1
  67. package/dist/lib/crabbox/runtimes.js +1 -3
  68. package/dist/lib/daemon/account-state-daemon-service.js +1 -0
  69. package/dist/lib/daemon/auth-sync-service.d.ts +21 -18
  70. package/dist/lib/daemon/auth-sync-service.js +37 -43
  71. package/dist/lib/daemon/daemon.d.ts +25 -14
  72. package/dist/lib/daemon/daemon.js +92 -154
  73. package/dist/lib/daemon/runner.d.ts +2 -2
  74. package/dist/lib/daemon/runner.js +32 -9
  75. package/dist/lib/daemon/self-update-service.d.ts +35 -34
  76. package/dist/lib/daemon/self-update-service.js +40 -36
  77. package/dist/lib/daemon/service.d.ts +12 -8
  78. package/dist/lib/daemon/supervisor.d.ts +65 -54
  79. package/dist/lib/daemon/supervisor.js +136 -171
  80. package/dist/lib/daemon/testdata/process-view-start.js +5 -2
  81. package/dist/lib/daemon/usage-sync-service.d.ts +21 -30
  82. package/dist/lib/daemon/usage-sync-service.js +46 -78
  83. package/dist/lib/daemon-health.d.ts +22 -5
  84. package/dist/lib/daemon-health.js +47 -3
  85. package/dist/lib/daemon-services.d.ts +1 -1
  86. package/dist/lib/daemon-services.js +0 -10
  87. package/dist/lib/daemon-ticks.js +4 -2
  88. package/dist/lib/devices/doctor-findings.js +8 -11
  89. package/dist/lib/devices/harness-inventory.js +14 -5
  90. package/dist/lib/exec-account-home.js +5 -0
  91. package/dist/lib/exec.js +0 -11
  92. package/dist/lib/feed/events.js +0 -1
  93. package/dist/lib/feed/tool-activity.js +5 -2
  94. package/dist/lib/fleet-shared-state.d.ts +45 -0
  95. package/dist/lib/fleet-shared-state.js +78 -9
  96. package/dist/lib/fs-atomic.js +25 -0
  97. package/dist/lib/harness-auth-capabilities.js +0 -1
  98. package/dist/lib/helper-versions.js +1 -1
  99. package/dist/lib/hooks/cache.d.ts +7 -0
  100. package/dist/lib/hooks/cache.js +27 -2
  101. package/dist/lib/hooks/install.js +7 -1
  102. package/dist/lib/hosts/ready.d.ts +38 -8
  103. package/dist/lib/hosts/ready.js +62 -14
  104. package/dist/lib/installations/active-check.d.ts +27 -0
  105. package/dist/lib/installations/active-check.js +61 -0
  106. package/dist/lib/installations/migrate.js +6 -3
  107. package/dist/lib/installations/shims.js +9 -7
  108. package/dist/lib/installations/update.js +21 -15
  109. package/dist/lib/installations/versions.js +12 -1
  110. package/dist/lib/manifest.js +1 -1
  111. package/dist/lib/models.js +0 -2
  112. package/dist/lib/open-url.js +28 -54
  113. package/dist/lib/profiles.js +0 -2
  114. package/dist/lib/refresh.js +1 -1
  115. package/dist/lib/routine-readiness.d.ts +35 -0
  116. package/dist/lib/routine-readiness.js +74 -16
  117. package/dist/lib/rules/compile.js +6 -0
  118. package/dist/lib/sandbox.js +0 -2
  119. package/dist/lib/secrets-policy.d.ts +89 -11
  120. package/dist/lib/secrets-policy.js +175 -28
  121. package/dist/lib/session/db.d.ts +1 -1
  122. package/dist/lib/session/db.js +65 -18
  123. package/dist/lib/session/discover.js +1 -283
  124. package/dist/lib/session/mirror.d.ts +4 -3
  125. package/dist/lib/session/mirror.js +4 -3
  126. package/dist/lib/session/parse.d.ts +0 -2
  127. package/dist/lib/session/parse.js +1 -138
  128. package/dist/lib/session/throughput.d.ts +1 -3
  129. package/dist/lib/session/throughput.js +0 -20
  130. package/dist/lib/session/types.d.ts +2 -2
  131. package/dist/lib/session/types.js +2 -2
  132. package/dist/lib/sessions-client.d.ts +60 -1
  133. package/dist/lib/sessions-client.js +227 -4
  134. package/dist/lib/setup-tool-status.js +5 -10
  135. package/dist/lib/signin-badge.d.ts +3 -2
  136. package/dist/lib/signin-badge.js +6 -3
  137. package/dist/lib/smart-launch.js +4 -1
  138. package/dist/lib/staleness/detectors/commands.d.ts +1 -1
  139. package/dist/lib/staleness/detectors/hooks.d.ts +1 -1
  140. package/dist/lib/staleness/detectors/mcp.d.ts +1 -1
  141. package/dist/lib/staleness/detectors/permissions.d.ts +1 -1
  142. package/dist/lib/staleness/detectors/plugins.d.ts +1 -1
  143. package/dist/lib/staleness/detectors/rules.d.ts +1 -1
  144. package/dist/lib/staleness/detectors/skills.d.ts +1 -1
  145. package/dist/lib/staleness/detectors/subagents.d.ts +1 -1
  146. package/dist/lib/staleness/detectors/workflows.d.ts +1 -1
  147. package/dist/lib/staleness/writers/commands.d.ts +1 -1
  148. package/dist/lib/staleness/writers/hooks.d.ts +1 -1
  149. package/dist/lib/staleness/writers/mcp.d.ts +1 -1
  150. package/dist/lib/staleness/writers/permissions.d.ts +1 -1
  151. package/dist/lib/staleness/writers/plugins.d.ts +1 -1
  152. package/dist/lib/staleness/writers/rules.d.ts +1 -1
  153. package/dist/lib/staleness/writers/skills.d.ts +1 -1
  154. package/dist/lib/staleness/writers/sources.js +5 -0
  155. package/dist/lib/staleness/writers/subagents.d.ts +1 -1
  156. package/dist/lib/staleness/writers/workflows.d.ts +1 -1
  157. package/dist/lib/state.d.ts +6 -4
  158. package/dist/lib/state.js +7 -17
  159. package/dist/lib/teams/parsers.d.ts +1 -1
  160. package/dist/lib/teams/parsers.js +1 -136
  161. package/dist/lib/teams/placement-probe.js +50 -15
  162. package/dist/lib/teams/scheduler.d.ts +7 -0
  163. package/dist/lib/types.d.ts +10 -1
  164. package/dist/lib/types.js +1 -1
  165. package/dist/lib/view-types.d.ts +32 -0
  166. package/dist/lib/watchdog/read.js +0 -1
  167. package/dist/lib/watchdog/rotate.js +1 -2
  168. package/dist/lib/watchdog/runner.js +2 -2
  169. package/dist/lib/watchdog/watchdog.d.ts +1 -1
  170. package/dist/lib/watchdog/watchdogTail.js +0 -31
  171. package/package.json +1 -1
  172. package/scripts/postinstall.js +8 -8
  173. package/dist/browser.d.ts +0 -2
  174. package/dist/browser.js +0 -17
  175. package/dist/commands/browser-picker.d.ts +0 -18
  176. package/dist/commands/browser-picker.js +0 -97
  177. package/dist/lib/browser/arc-discovery.d.ts +0 -52
  178. package/dist/lib/browser/arc-discovery.js +0 -197
  179. package/dist/lib/browser/arc-dom.d.ts +0 -14
  180. package/dist/lib/browser/arc-dom.js +0 -121
  181. package/dist/lib/browser/caller-identity.d.ts +0 -40
  182. package/dist/lib/browser/caller-identity.js +0 -175
  183. package/dist/lib/browser/cdp.d.ts +0 -51
  184. package/dist/lib/browser/cdp.js +0 -254
  185. package/dist/lib/browser/chrome.d.ts +0 -141
  186. package/dist/lib/browser/chrome.js +0 -704
  187. package/dist/lib/browser/chromium-discovery.d.ts +0 -28
  188. package/dist/lib/browser/chromium-discovery.js +0 -96
  189. package/dist/lib/browser/devices.d.ts +0 -34
  190. package/dist/lib/browser/devices.js +0 -61
  191. package/dist/lib/browser/domain-skills.d.ts +0 -71
  192. package/dist/lib/browser/domain-skills.js +0 -195
  193. package/dist/lib/browser/drivers/arc.d.ts +0 -40
  194. package/dist/lib/browser/drivers/arc.js +0 -276
  195. package/dist/lib/browser/drivers/firefox.d.ts +0 -99
  196. package/dist/lib/browser/drivers/firefox.js +0 -377
  197. package/dist/lib/browser/drivers/local.d.ts +0 -55
  198. package/dist/lib/browser/drivers/local.js +0 -266
  199. package/dist/lib/browser/drivers/ssh.d.ts +0 -120
  200. package/dist/lib/browser/drivers/ssh.js +0 -467
  201. package/dist/lib/browser/editor.d.ts +0 -3
  202. package/dist/lib/browser/editor.js +0 -50
  203. package/dist/lib/browser/ffmpeg.d.ts +0 -12
  204. package/dist/lib/browser/ffmpeg.js +0 -184
  205. package/dist/lib/browser/firefox-discovery.d.ts +0 -69
  206. package/dist/lib/browser/firefox-discovery.js +0 -162
  207. package/dist/lib/browser/har.d.ts +0 -85
  208. package/dist/lib/browser/har.js +0 -77
  209. package/dist/lib/browser/hygiene.d.ts +0 -97
  210. package/dist/lib/browser/hygiene.js +0 -153
  211. package/dist/lib/browser/index.d.ts +0 -5
  212. package/dist/lib/browser/index.js +0 -5
  213. package/dist/lib/browser/input.d.ts +0 -7
  214. package/dist/lib/browser/input.js +0 -92
  215. package/dist/lib/browser/ipc.d.ts +0 -204
  216. package/dist/lib/browser/ipc.js +0 -1453
  217. package/dist/lib/browser/login-detection.d.ts +0 -87
  218. package/dist/lib/browser/login-detection.js +0 -274
  219. package/dist/lib/browser/profiles.d.ts +0 -422
  220. package/dist/lib/browser/profiles.js +0 -1157
  221. package/dist/lib/browser/refs.d.ts +0 -89
  222. package/dist/lib/browser/refs.js +0 -191
  223. package/dist/lib/browser/registry.d.ts +0 -70
  224. package/dist/lib/browser/registry.js +0 -252
  225. package/dist/lib/browser/remote-control.d.ts +0 -59
  226. package/dist/lib/browser/remote-control.js +0 -85
  227. package/dist/lib/browser/resolve-target.d.ts +0 -66
  228. package/dist/lib/browser/resolve-target.js +0 -264
  229. package/dist/lib/browser/runtime-state.d.ts +0 -291
  230. package/dist/lib/browser/runtime-state.js +0 -584
  231. package/dist/lib/browser/secret-ref.d.ts +0 -10
  232. package/dist/lib/browser/secret-ref.js +0 -14
  233. package/dist/lib/browser/service.d.ts +0 -812
  234. package/dist/lib/browser/service.js +0 -4693
  235. package/dist/lib/browser/stream.d.ts +0 -17
  236. package/dist/lib/browser/stream.js +0 -72
  237. package/dist/lib/browser/task-index.d.ts +0 -78
  238. package/dist/lib/browser/task-index.js +0 -199
  239. package/dist/lib/browser/types.d.ts +0 -647
  240. package/dist/lib/browser/types.js +0 -61
  241. package/dist/lib/browser/upload.d.ts +0 -29
  242. package/dist/lib/browser/upload.js +0 -298
  243. package/dist/lib/daemon/browser-ipc-service.d.ts +0 -20
  244. package/dist/lib/daemon/browser-ipc-service.js +0 -46
  245. package/dist/lib/daemon/browser-task-reap-service.d.ts +0 -14
  246. package/dist/lib/daemon/browser-task-reap-service.js +0 -26
  247. package/dist/lib/fleet-shared-repo-sync.d.ts +0 -55
  248. package/dist/lib/fleet-shared-repo-sync.js +0 -490
@@ -19,12 +19,12 @@
19
19
  * package on disk, and only once that succeeds does it `process.exit(0)` —
20
20
  * the OS supervisor (launchd `KeepAlive` / systemd `Restart=always`, see
21
21
  * `daemon/AGENTS.md`'s crash-recovery model) relaunches the daemon, which
22
- * then boots the new code. Clients do not need to be told anything: browser
23
- * IPC clients re-probe the socket via `waitForBrowserService`
24
- * (`browser/ipc.ts`), and the scheduler's atomic `(routine, scheduledFor)`
25
- * claim (see `docs/specifications.md` §Scheduling & execution singularity)
26
- * means a routine mid-fire at the moment of exit is deduped safely across the
27
- * restart rather than double-fired.
22
+ * then boots the new code. Clients do not need to be told anything: a socket
23
+ * client re-probes and reconnects when the daemon relaunches, and the
24
+ * scheduler's atomic `(routine, scheduledFor)` claim (see
25
+ * `docs/specifications.md` §Scheduling & execution singularity) means a routine
26
+ * mid-fire at the moment of exit is deduped safely across the restart rather
27
+ * than double-fired.
28
28
  *
29
29
  * Fail-closed is the whole point: every step below that can fail — the
30
30
  * registry check, the install, the post-install verify — leaves the OLD
@@ -93,41 +93,42 @@ export interface SelfUpdateDeps {
93
93
  export declare function installAndVerifyDefault(metadata: NpmLatestMetadata, packageRoot: string, signal: AbortSignal): Promise<void>;
94
94
  /**
95
95
  * Core self-update decision + action, shared by the periodic tick
96
- * ({@link SelfUpdateService.onTick}) and the on-demand IPC path
97
- * (`request-self-update`, `browser/ipc.ts`) — one implementation, so a
98
- * version-skew client asking "update now" runs exactly the same fail-closed
99
- * logic as the scheduled sweep. Returns rather than throws so callers decide
100
- * their own exit timing (the periodic service exits immediately; the IPC
101
- * handler must respond to the client on the socket BEFORE exiting, or the
102
- * client hangs on a socket that is closing mid-write). Concurrent callers
103
- * share one in-flight attempt rather than racing separate installs.
96
+ * ({@link SelfUpdateService.onTick}) and the on-demand trigger
97
+ * ({@link triggerSelfUpdateInBackground}) — one implementation, so a
98
+ * version-skew "update now" caller runs exactly the same fail-closed logic as the
99
+ * scheduled sweep. Returns rather than throws so callers decide their own exit
100
+ * timing (the periodic service exits immediately; an on-demand caller that must
101
+ * respond to a client BEFORE exiting delays via {@link scheduleSelfUpdateExit}).
102
+ * Concurrent callers share one in-flight attempt rather than racing separate
103
+ * installs.
104
104
  */
105
105
  export declare function attemptSelfUpdateAndExit(ctx: DaemonContext, signal: AbortSignal, deps?: SelfUpdateDeps): Promise<SelfUpdateOutcome>;
106
106
  /**
107
107
  * Schedule the process exit for a verified self-update, exactly once, no
108
108
  * matter how many callers observe `outcome.updated` on the shared
109
- * `inFlightAttempt` promise. The periodic tick and an on-demand
110
- * `request-self-update` IPC call (`browser/ipc.ts`) can both be awaiting that
111
- * SAME promise — if the tick's continuation ran an immediate `process.exit(0)`
112
- * while the IPC handler's continuation had not yet reached `socket.write`,
113
- * the tick's exit could win the race and the client would see a closed socket
114
- * before any response (found in review, PHNX-3695). Routing every caller
115
- * through this one guarded, always-delayed scheduling point means the delay
116
- * protects EVERY caller's in-flight response, not just the IPC handler's own.
109
+ * `inFlightAttempt` promise. The periodic tick and an on-demand caller can both
110
+ * be awaiting that SAME promise — if the tick's continuation ran an immediate
111
+ * `process.exit(0)` while an on-demand caller's continuation had not yet flushed
112
+ * its response to a client, the tick's exit could win the race and the client
113
+ * would see a closed socket before any response (found in review, PHNX-3695).
114
+ * Routing every caller through this one guarded, always-delayed scheduling point
115
+ * means the delay protects EVERY caller's in-flight response.
117
116
  */
118
117
  export declare function scheduleSelfUpdateExit(): void;
119
118
  /**
120
- * Fire the on-demand self-update in the BACKGROUND and return its (bounded)
121
- * promise WITHOUT the caller having to await it. This is what keeps the
122
- * `request-self-update` IPC handler (`browser/ipc.ts`) from parking a
123
- * version-skewed `agents browser` verb behind the full
124
- * check→download→install→verify: that handler routes through
125
- * `reconcileDaemonVersion` on every version-skewed call, so awaiting the whole
126
- * install there reintroduces exactly the client-stall PHNX-3605 was written to
127
- * prevent (tens of seconds, worst case ~15 min). The handler instead responds
128
- * "triggered" immediately and lets this run in the background — the daemon does
129
- * install→verify→exit(0) on its own, the OS supervisor relaunches it, and the
130
- * browser reconnects.
119
+ * Fire an on-demand self-update in the BACKGROUND and return its (bounded)
120
+ * promise WITHOUT the caller having to await it — the reusable primitive for a
121
+ * "update now" trigger that must respond to a client before the daemon exits.
122
+ * Awaiting the full check→download→install→verify inline would park the caller
123
+ * for tens of seconds (worst case ~15 min), the client-stall PHNX-3605 was
124
+ * written to prevent; instead a trigger responds "triggered" immediately and
125
+ * lets this run in the background — the daemon does install→verify→exit(0) on its
126
+ * own, the OS supervisor relaunches it, and the client reconnects.
127
+ *
128
+ * (Its one former production caller was the `request-self-update` verb on the
129
+ * daemon's browser IPC socket, removed with the standalone `browser` CLI in
130
+ * PHNX-4101. The primitive stays — tested and reusable — for a future on-demand
131
+ * trigger; the periodic tick does not use it, exiting inline instead.)
131
132
  *
132
133
  * The work still shares the module-level {@link attemptSelfUpdateAndExit}
133
134
  * `inFlightAttempt` guard, so a concurrent trigger (or the periodic tick) can't
@@ -138,7 +139,7 @@ export declare function scheduleSelfUpdateExit(): void;
138
139
  * `runSelfUpdateAttempt` already turns an install/verify failure into a
139
140
  * not-updated outcome that leaves the running daemon untouched. The caller
140
141
  * schedules the one decoupled {@link scheduleSelfUpdateExit} off the returned
141
- * promise once `updated` is true, so the exit still fires after the IPC
142
+ * promise once `updated` is true, so the exit still fires after the caller's
142
143
  * response has flushed.
143
144
  */
144
145
  export declare function triggerSelfUpdateInBackground(ctx: DaemonContext, deps?: SelfUpdateDeps): Promise<SelfUpdateOutcome>;
@@ -19,12 +19,12 @@
19
19
  * package on disk, and only once that succeeds does it `process.exit(0)` —
20
20
  * the OS supervisor (launchd `KeepAlive` / systemd `Restart=always`, see
21
21
  * `daemon/AGENTS.md`'s crash-recovery model) relaunches the daemon, which
22
- * then boots the new code. Clients do not need to be told anything: browser
23
- * IPC clients re-probe the socket via `waitForBrowserService`
24
- * (`browser/ipc.ts`), and the scheduler's atomic `(routine, scheduledFor)`
25
- * claim (see `docs/specifications.md` §Scheduling & execution singularity)
26
- * means a routine mid-fire at the moment of exit is deduped safely across the
27
- * restart rather than double-fired.
22
+ * then boots the new code. Clients do not need to be told anything: a socket
23
+ * client re-probes and reconnects when the daemon relaunches, and the
24
+ * scheduler's atomic `(routine, scheduledFor)` claim (see
25
+ * `docs/specifications.md` §Scheduling & execution singularity) means a routine
26
+ * mid-fire at the moment of exit is deduped safely across the restart rather
27
+ * than double-fired.
28
28
  *
29
29
  * Fail-closed is the whole point: every step below that can fail — the
30
30
  * registry check, the install, the post-install verify — leaves the OLD
@@ -51,10 +51,13 @@ const SELF_UPDATE_TICK_MS = 75 * 60_000;
51
51
  /**
52
52
  * Hard cap per tick: a real download + npm/bun install + verify can
53
53
  * legitimately take minutes on a slow link. 15 minutes matches the task's
54
- * stated budget and is short relative to the ~75min cadence. Exported so the
55
- * on-demand `request-self-update` IPC handler (`browser/ipc.ts`) can bound
54
+ * stated budget and is short relative to the ~75min cadence. Exported so an
55
+ * on-demand self-update trigger ({@link triggerSelfUpdateInBackground}) can bound
56
56
  * its own `AbortController` on the SAME budget the periodic tick runs under —
57
57
  * one deadline, not two independently-tuned numbers that could drift apart.
58
+ * (Its former transport, the `request-self-update` verb on the daemon's browser
59
+ * IPC socket, left with the standalone `browser` CLI in PHNX-4101; the mechanism
60
+ * stays for a future on-demand caller.)
58
61
  */
59
62
  const SELF_UPDATE_DEADLINE_MS = 15 * 60_000;
60
63
  /**
@@ -193,14 +196,14 @@ function defaultSelfUpdateDeps() {
193
196
  let inFlightAttempt = null;
194
197
  /**
195
198
  * Core self-update decision + action, shared by the periodic tick
196
- * ({@link SelfUpdateService.onTick}) and the on-demand IPC path
197
- * (`request-self-update`, `browser/ipc.ts`) — one implementation, so a
198
- * version-skew client asking "update now" runs exactly the same fail-closed
199
- * logic as the scheduled sweep. Returns rather than throws so callers decide
200
- * their own exit timing (the periodic service exits immediately; the IPC
201
- * handler must respond to the client on the socket BEFORE exiting, or the
202
- * client hangs on a socket that is closing mid-write). Concurrent callers
203
- * share one in-flight attempt rather than racing separate installs.
199
+ * ({@link SelfUpdateService.onTick}) and the on-demand trigger
200
+ * ({@link triggerSelfUpdateInBackground}) — one implementation, so a
201
+ * version-skew "update now" caller runs exactly the same fail-closed logic as the
202
+ * scheduled sweep. Returns rather than throws so callers decide their own exit
203
+ * timing (the periodic service exits immediately; an on-demand caller that must
204
+ * respond to a client BEFORE exiting delays via {@link scheduleSelfUpdateExit}).
205
+ * Concurrent callers share one in-flight attempt rather than racing separate
206
+ * installs.
204
207
  */
205
208
  export async function attemptSelfUpdateAndExit(ctx, signal, deps = defaultSelfUpdateDeps()) {
206
209
  if (inFlightAttempt)
@@ -292,14 +295,13 @@ let exitScheduled = false;
292
295
  /**
293
296
  * Schedule the process exit for a verified self-update, exactly once, no
294
297
  * matter how many callers observe `outcome.updated` on the shared
295
- * `inFlightAttempt` promise. The periodic tick and an on-demand
296
- * `request-self-update` IPC call (`browser/ipc.ts`) can both be awaiting that
297
- * SAME promise — if the tick's continuation ran an immediate `process.exit(0)`
298
- * while the IPC handler's continuation had not yet reached `socket.write`,
299
- * the tick's exit could win the race and the client would see a closed socket
300
- * before any response (found in review, PHNX-3695). Routing every caller
301
- * through this one guarded, always-delayed scheduling point means the delay
302
- * protects EVERY caller's in-flight response, not just the IPC handler's own.
298
+ * `inFlightAttempt` promise. The periodic tick and an on-demand caller can both
299
+ * be awaiting that SAME promise — if the tick's continuation ran an immediate
300
+ * `process.exit(0)` while an on-demand caller's continuation had not yet flushed
301
+ * its response to a client, the tick's exit could win the race and the client
302
+ * would see a closed socket before any response (found in review, PHNX-3695).
303
+ * Routing every caller through this one guarded, always-delayed scheduling point
304
+ * means the delay protects EVERY caller's in-flight response.
303
305
  */
304
306
  export function scheduleSelfUpdateExit() {
305
307
  if (exitScheduled)
@@ -308,17 +310,19 @@ export function scheduleSelfUpdateExit() {
308
310
  setTimeout(() => process.exit(0), SELF_UPDATE_EXIT_DELAY_MS);
309
311
  }
310
312
  /**
311
- * Fire the on-demand self-update in the BACKGROUND and return its (bounded)
312
- * promise WITHOUT the caller having to await it. This is what keeps the
313
- * `request-self-update` IPC handler (`browser/ipc.ts`) from parking a
314
- * version-skewed `agents browser` verb behind the full
315
- * check→download→install→verify: that handler routes through
316
- * `reconcileDaemonVersion` on every version-skewed call, so awaiting the whole
317
- * install there reintroduces exactly the client-stall PHNX-3605 was written to
318
- * prevent (tens of seconds, worst case ~15 min). The handler instead responds
319
- * "triggered" immediately and lets this run in the background — the daemon does
320
- * install→verify→exit(0) on its own, the OS supervisor relaunches it, and the
321
- * browser reconnects.
313
+ * Fire an on-demand self-update in the BACKGROUND and return its (bounded)
314
+ * promise WITHOUT the caller having to await it — the reusable primitive for a
315
+ * "update now" trigger that must respond to a client before the daemon exits.
316
+ * Awaiting the full check→download→install→verify inline would park the caller
317
+ * for tens of seconds (worst case ~15 min), the client-stall PHNX-3605 was
318
+ * written to prevent; instead a trigger responds "triggered" immediately and
319
+ * lets this run in the background — the daemon does install→verify→exit(0) on its
320
+ * own, the OS supervisor relaunches it, and the client reconnects.
321
+ *
322
+ * (Its one former production caller was the `request-self-update` verb on the
323
+ * daemon's browser IPC socket, removed with the standalone `browser` CLI in
324
+ * PHNX-4101. The primitive stays — tested and reusable — for a future on-demand
325
+ * trigger; the periodic tick does not use it, exiting inline instead.)
322
326
  *
323
327
  * The work still shares the module-level {@link attemptSelfUpdateAndExit}
324
328
  * `inFlightAttempt` guard, so a concurrent trigger (or the periodic tick) can't
@@ -329,7 +333,7 @@ export function scheduleSelfUpdateExit() {
329
333
  * `runSelfUpdateAttempt` already turns an install/verify failure into a
330
334
  * not-updated outcome that leaves the running daemon untouched. The caller
331
335
  * schedules the one decoupled {@link scheduleSelfUpdateExit} off the returned
332
- * promise once `updated` is true, so the exit still fires after the IPC
336
+ * promise once `updated` is true, so the exit still fires after the caller's
333
337
  * response has flushed.
334
338
  */
335
339
  export function triggerSelfUpdateInBackground(ctx, deps = defaultSelfUpdateDeps()) {
@@ -14,8 +14,12 @@
14
14
  * know it is being supervised.
15
15
  */
16
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';
17
+ /**
18
+ * Lifecycle state a supervised service can be in. There is no `parked` state:
19
+ * a throw keeps the service `running` (it retries on the next tick) and a
20
+ * deadline breach exits the whole daemon for a supervised OS restart (PHNX-4116).
21
+ */
22
+ export type ServiceState = 'idle' | 'running' | 'stopped';
19
23
  /** A service's most recently observed health, as reported by the supervisor. */
20
24
  export interface ServiceHealth {
21
25
  state: ServiceState;
@@ -42,12 +46,12 @@ export interface DaemonService {
42
46
  export interface PeriodicService extends DaemonService {
43
47
  readonly intervalMs: number;
44
48
  /**
45
- * Hard cap per tick. An over-budget tick is ABANDONED: the supervisor aborts
46
- * the tick's {@link AbortSignal}, parks the service, and schedules a backoff
47
- * restart immediately — it does NOT wait for the real promise to settle
48
- * (PHNX-3608). A tick that awaits `signal` (or forwards it to its I/O) can
49
- * observe the deadline and unwind; one that ignores it is left to drain in the
50
- * background while the service is already being restarted.
49
+ * Hard cap per tick. An over-budget tick is a HANG that cannot be retried
50
+ * in-process (its promise may never settle), so the supervisor aborts the
51
+ * tick's {@link AbortSignal} and EXITS the daemon (code 70) for a supervised
52
+ * systemd/launchd restart (PHNX-4116) — there is no park or in-process backoff.
53
+ * A tick that awaits `signal` (or forwards it to its I/O) can observe the
54
+ * deadline and unwind cleanly before the process exits.
51
55
  */
52
56
  readonly deadlineMs: number;
53
57
  /**
@@ -1,52 +1,53 @@
1
1
  /**
2
- * ServiceSupervisor (RUSH-3193 P1).
2
+ * ServiceSupervisor (RUSH-3193 P1, PHNX-4116).
3
3
  *
4
4
  * Owns the timer for every registered `PeriodicService`, replacing the bare
5
- * `setInterval` closures in `runDaemon()`. Two failure modes motivated it,
6
- * both observed in production:
5
+ * `setInterval` closures in `runDaemon()`. Two failure modes motivated it, both
6
+ * observed in production, and each is now handled without ever leaving a service
7
+ * silently dark:
7
8
  *
8
9
  * - A throw escaping a tick's local try/catch used to hit the process-wide
9
- * `uncaughtException` handler and `process.exit(1)` the whole daemon,
10
- * taking every OTHER service down with it. Here, a tick failure is caught
11
- * per-service and never propagates past `runTick`.
12
- * - A tick that hangs on an unbounded await (SSH, keychain) used to latch its
13
- * local in-flight guard `true` forever, silently freezing that one service
14
- * for the daemon's life (observed ~51h). Here, every tick races a
15
- * `deadlineMs` timeout; when the deadline wins, the service is ABANDONED
16
- * (PHNX-3608): its `AbortSignal` is aborted so a cooperating tick can unwind
17
- * its own I/O, the in-flight guard is released immediately, and the backoff
18
- * restart is scheduled right away — it is NOT gated on the runaway promise
19
- * settling. An earlier revision kept the guard held until the real promise
20
- * settled, which meant a tick that never settles parked the service forever
21
- * and blocked backoff restart, `daemon services restart`, and SIGHUP reload —
22
- * the exact 12h-usage-dark class this exists to prevent. The orphaned promise
23
- * is drained separately in the background; `finishTick` is version-guarded so
24
- * its late settlement can never disturb the fresh tick the restart started.
10
+ * `uncaughtException` handler and `process.exit(1)` the whole daemon, taking
11
+ * every OTHER service down with it. Here a thrown tick is caught per-service,
12
+ * recorded via `recordFailure`, and the service keeps ticking on its own
13
+ * interval — a throw is recoverable, so it is retried in place on the next
14
+ * tick and no sibling is disturbed.
15
+ * - A tick that HANGS on an unbounded await (SSH, keychain) used to latch its
16
+ * in-flight guard `true` forever, silently freezing that one service for the
17
+ * daemon's life (observed ~51h). A hang cannot be retried in-process — the
18
+ * promise may never settle — so when a tick (or a `start()`/`restart()`
19
+ * lifecycle call) BREACHES its deadline the supervisor records the cause,
20
+ * flushes it to disk, and EXITS the process (code 70). systemd
21
+ * (`Restart=always`, `RestartSec=30`, `StartLimitIntervalSec=0`) and launchd
22
+ * (`KeepAlive` + `ThrottleInterval=30`) restart the daemon within ~30s and the
23
+ * wedged service comes back healthy with it.
25
24
  *
26
- * Repeated failures (thrown or timed-out) open a circuit breaker: the service
27
- * is parked (its timer stopped) and retried on exponential backoff via its own
28
- * `restart()`, while every sibling service keeps ticking on its own timer,
29
- * unaffected. `start()`/`stop()`/`restart()` are themselves bounded by
30
- * {@link ServiceSupervisorOptions.lifecycleDeadlineMs} so a wedged bind or close
31
- * cannot stall daemon startup or shutdown.
25
+ * This is the PHNX-4116 model: there is NO `parked` state and NO in-process
26
+ * backoff restart. A supervised service is `idle` before start, `running` while
27
+ * ticking, or `stopped` after a live disable / shutdown — a hang is not a fourth
28
+ * state to sit in, it is a reason to hand the daemon back to its OS supervisor,
29
+ * which is what actually "just works" like systemd/launchd. `start()`/`stop()`/
30
+ * `restart()` stay bounded by {@link ServiceSupervisorOptions.lifecycleDeadlineMs}
31
+ * so a wedged bind or close cannot stall daemon startup or shutdown; a start /
32
+ * restart lifecycle breach exits the same way a tick breach does.
32
33
  */
33
34
  import type { DaemonServiceId } from '../daemon-services.js';
34
35
  import type { DaemonContext, DaemonService, ServiceHealth } from './service.js';
35
36
  interface ServiceSupervisorOptions {
36
- /** Consecutive thrown tick failures before a service is parked. Deadline breaches park immediately. Default 3. */
37
- parkAfterFailures?: number;
38
- /** First restart backoff delay, doubled on each further failed restart attempt. Default 5s. */
39
- backoffBaseMs?: number;
40
- /** Backoff ceiling. Default 5 minutes. */
41
- backoffMaxMs?: number;
42
37
  /**
43
38
  * Hard cap on a service's `start()`/`stop()`/`restart()` call (PHNX-3608). A
44
39
  * wedged bind/close would otherwise stall `startAll()` (which awaits each
45
- * `startOne` in turn) or `stopAll()` at shutdown. A start/restart that
46
- * breaches it is treated as a failure (parked + backoff); a stop that breaches
47
- * it is logged and the service is left marked stopped. Default 30s.
40
+ * `startOne` in turn) or `stopAll()` at shutdown. A start/restart that breaches
41
+ * it exits the process for a supervised restart (PHNX-4116); a stop that
42
+ * breaches it is logged and the service is left marked stopped. Default 30s.
48
43
  */
49
44
  lifecycleDeadlineMs?: number;
45
+ /**
46
+ * How the supervisor ends the process on a deadline breach. Defaults to
47
+ * `process.exit`; injected in tests so a breach is observable without actually
48
+ * exiting the test runner (PHNX-4116).
49
+ */
50
+ exit?: (code: number) => never;
50
51
  }
51
52
  interface RegisterServiceOptions {
52
53
  /** Register the lifecycle owner but leave it stopped until a live enable. */
@@ -55,10 +56,8 @@ interface RegisterServiceOptions {
55
56
  export declare class ServiceSupervisor {
56
57
  private readonly registry;
57
58
  private ctx;
58
- private readonly parkAfterFailures;
59
- private readonly backoffBaseMs;
60
- private readonly backoffMaxMs;
61
59
  private readonly lifecycleDeadlineMs;
60
+ private readonly exit;
62
61
  constructor(opts?: ServiceSupervisorOptions);
63
62
  /**
64
63
  * Race `op` against a deadline. On breach the returned promise rejects with a
@@ -87,7 +86,11 @@ export declare class ServiceSupervisor {
87
86
  startAll(ctx: DaemonContext): Promise<void>;
88
87
  /** Stop every registered service and clear all timers. */
89
88
  stopAll(): Promise<void>;
90
- /** Force one service to restart right now, outside its normal backoff schedule. Drives `agents daemon services restart <id>` (RUSH-3193 P4). */
89
+ /**
90
+ * Force one service to restart right now — drives `agents daemon services
91
+ * restart <id>` (RUSH-3193 P4). A plain stop + start, with no intermediate
92
+ * `parked` state (PHNX-4116).
93
+ */
91
94
  restartOne(id: DaemonServiceId): Promise<void>;
92
95
  /** Whether `id` has a lifecycle owner on this supervisor, running or stopped. */
93
96
  isRegistered(id: DaemonServiceId): boolean;
@@ -95,8 +98,7 @@ export declare class ServiceSupervisor {
95
98
  registeredIds(): DaemonServiceId[];
96
99
  /**
97
100
  * Resolve when the service's current tick has really settled. Daemon control
98
- * edges use this to queue a requested live transition without polling or
99
- * treating a deadline race as cancellation.
101
+ * edges use this to queue a requested live transition without polling.
100
102
  */
101
103
  awaitIdle(id: DaemonServiceId): Promise<void>;
102
104
  /**
@@ -117,22 +119,31 @@ export declare class ServiceSupervisor {
117
119
  private stopOne;
118
120
  private scheduleTimer;
119
121
  /**
120
- * Run one tick under a hard deadline (PHNX-3608). The tick receives an
121
- * `AbortSignal` that is aborted when the deadline elapses, so a cooperating
122
- * tick can bound its own I/O and unwind. The deadline itself is still enforced
123
- * with `Promise.race` — JS cannot forcibly cancel an arbitrary await — but on
124
- * a breach the service is ABANDONED, not parked-and-held: the in-flight guard
125
- * is released immediately and the backoff restart is scheduled right away
126
- * (via `park()`), so a tick that never settles can no longer wedge backoff,
127
- * `daemon services restart`, or SIGHUP reload. The runaway promise drains in
128
- * the background; `finishTick` is version-guarded on `activeTick`, so its late
129
- * settlement can never disturb the fresh tick a restart has since started.
122
+ * Run one tick under a hard deadline (PHNX-3608, PHNX-4116). The tick receives
123
+ * an `AbortSignal` that is aborted when the deadline elapses, so a cooperating
124
+ * tick can bound its own I/O and unwind. The deadline itself is enforced with
125
+ * `Promise.race` — JS cannot forcibly cancel an arbitrary await.
126
+ *
127
+ * A tick that THROWS is recoverable: it is recorded and the interval keeps
128
+ * firing, so the service retries on its next tick. A tick that BREACHES its
129
+ * deadline is a hang that can never be retried in-process (the promise may
130
+ * never settle), so the supervisor exits the process (`exitForRestart`) and
131
+ * lets systemd/launchd restart the whole daemon.
130
132
  */
131
133
  private runTick;
132
- private park;
133
- private scheduleRestart;
134
- private attemptRestart;
135
134
  private recordFailure;
136
- private finishTick;
135
+ /**
136
+ * Record a deadline breach durably and exit the process so systemd/launchd
137
+ * restart the whole daemon (PHNX-4116). `recordSubsystemError` writes
138
+ * `health.json` synchronously (atomic write under a file lock), so the cause is
139
+ * on disk before we exit; `recordDaemonRestart` appends to the restart ledger
140
+ * `agents daemon status` reads for "restarts in the last 24h". Every timer is
141
+ * frozen first so no further tick fires between this decision and process death
142
+ * — and so an injected test `exit` (which does not actually exit) still observes
143
+ * exactly one call.
144
+ */
145
+ private exitForRestart;
146
+ /** Clear every service's interval / startup timer — the process is exiting. */
147
+ private freezeTimers;
137
148
  }
138
149
  export {};