@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
@@ -2,11 +2,12 @@
2
2
  * `agents daemon` — runtime, hosted services, and failure visibility for the
3
3
  * always-on daemon (RUSH-2354).
4
4
  *
5
- * The daemon holds the routines scheduler, the browser IPC server, and the
5
+ * The daemon holds the routines scheduler, the session/usage sync, and the
6
6
  * watchdog pass — but until this command group existed it had no user-facing
7
7
  * surface: no way to see it, restart it, or turn it off. The secrets broker
8
8
  * moved out of this daemon entirely with the standalone `secrets` engine
9
- * (PHNX-3989 OWN-1); `agents daemon status` only probes its reachability.
9
+ * (PHNX-3989 OWN-1), and the browser IPC server with the standalone `browser`
10
+ * CLI (PHNX-4101); `agents daemon status` only probes the broker's reachability.
10
11
  * `daemon.ts` (the runtime) has always implemented every mechanism this file
11
12
  * wires up; nothing here is new machinery, only the missing CLI surface.
12
13
  *
@@ -17,6 +18,42 @@
17
18
  * duplicating it.
18
19
  */
19
20
  import type { Command } from 'commander';
21
+ import { type SubsystemHealth } from '../lib/daemon-health.js';
20
22
  /** Elapsed wall-clock seconds since `pid` started, or null if unavailable (best-effort, POSIX only). */
21
23
  export declare function uptimeSeconds(pid: number): number | null;
24
+ interface SecretsBrokerHealth {
25
+ reachable: boolean;
26
+ /**
27
+ * True when this box routes `keychain`-backend items to the standalone's
28
+ * encrypted file store (a headless worker with no reachable keyring). The
29
+ * broker exists only to hold an UNLOCKED keychain across reads without a
30
+ * Touch ID prompt — a file-backed box reads every secret one-shot with no
31
+ * broker at all, so an absent broker there is normal, not a fault (PHNX-4116).
32
+ */
33
+ fileBacked: boolean;
34
+ /**
35
+ * Always `null` — the broker's socket now lives entirely inside the
36
+ * standalone `secrets` engine's own process (PHNX-3989 OWN-1); the daemon
37
+ * neither hosts it nor knows its transport details. Kept in the shape for
38
+ * `--json` compatibility.
39
+ */
40
+ socketPath: string | null;
41
+ heldBundles: number | null;
42
+ /**
43
+ * Always `null` — no daemon service writes a health record for the broker
44
+ * anymore (the daemon does not host or supervise it). Kept in the shape for
45
+ * `--json` compatibility.
46
+ */
47
+ record: SubsystemHealth | null;
48
+ }
49
+ /**
50
+ * The `secrets broker` health line. Reachable → healthy. Unreachable is a FAULT
51
+ * (`down`) only when the store is keychain-backed — there the broker is what
52
+ * holds the unlocked keychain across reads. On a file-backed box (every
53
+ * headless worker) no broker is needed: secrets read one-shot, so an absent
54
+ * broker is expected and reads as INFO, not `down` (PHNX-4116). Pure, so the
55
+ * three cases are unit-tested with no live daemon.
56
+ */
57
+ export declare function secretsBrokerHealthLine(secrets: SecretsBrokerHealth): string;
22
58
  export declare function registerDaemonCommand(program: Command): void;
59
+ export {};
@@ -2,11 +2,12 @@
2
2
  * `agents daemon` — runtime, hosted services, and failure visibility for the
3
3
  * always-on daemon (RUSH-2354).
4
4
  *
5
- * The daemon holds the routines scheduler, the browser IPC server, and the
5
+ * The daemon holds the routines scheduler, the session/usage sync, and the
6
6
  * watchdog pass — but until this command group existed it had no user-facing
7
7
  * surface: no way to see it, restart it, or turn it off. The secrets broker
8
8
  * moved out of this daemon entirely with the standalone `secrets` engine
9
- * (PHNX-3989 OWN-1); `agents daemon status` only probes its reachability.
9
+ * (PHNX-3989 OWN-1), and the browser IPC server with the standalone `browser`
10
+ * CLI (PHNX-4101); `agents daemon status` only probes the broker's reachability.
10
11
  * `daemon.ts` (the runtime) has always implemented every mechanism this file
11
12
  * wires up; nothing here is new machinery, only the missing CLI surface.
12
13
  *
@@ -21,10 +22,10 @@ import { execFileSync } from 'child_process';
21
22
  import * as fs from 'fs';
22
23
  import * as path from 'path';
23
24
  import { setHelpSections } from '../lib/help.js';
24
- import { getDaemonStatus, isDaemonRunning, isDaemonWedged, readDaemonLog, startDaemon, stopDaemon, signalDaemonReload, findSurvivingStateDirDaemons, getDaemonLogPath, isDaemonAutostartCircuitOpen, RedirectedHomeDaemonError, } from '../lib/daemon/daemon.js';
25
+ import { getDaemonStatus, isDaemonRunning, readDaemonLog, startDaemon, stopDaemon, signalDaemonReload, findSurvivingStateDirDaemons, getDaemonLogPath, isDaemonAutostartCircuitOpen, RedirectedHomeDaemonError, } from '../lib/daemon/daemon.js';
25
26
  import { listDaemonRunProcesses } from '../lib/daemon/leaked-daemons.js';
26
27
  import { getConfigValue, setConfigValue, isDaemonEnabled } from '../lib/device-config.js';
27
- import { readSubsystemHealth, readAllSubsystemHealth, SUBSYSTEM_BROWSER_IPC, SUBSYSTEM_DAEMON_START, } from '../lib/daemon-health.js';
28
+ import { readSubsystemHealth, readAllSubsystemHealth, SUBSYSTEM_DAEMON_START, } from '../lib/daemon-health.js';
28
29
  import { DAEMON_SERVICE_IDS, listDaemonServiceStates, setDaemonServiceEnabled, getDaemonServicesConfigPath, queueDaemonServiceRestart, } from '../lib/daemon-services.js';
29
30
  import { listJobs, getLatestRun } from '../lib/scheduling/routines.js';
30
31
  import { JobScheduler } from '../lib/scheduler.js';
@@ -293,25 +294,24 @@ function humanDuration(seconds) {
293
294
  }
294
295
  /** Reachability probe only — the daemon does not host, supervise, or take over the broker (OWN-1). */
295
296
  async function probeSecretsBroker() {
297
+ const { agentPing, agentStatus, keychainUsesFileFallback } = await import('../lib/secrets-client.js');
296
298
  try {
297
- const { agentPing, agentStatus } = await import('../lib/secrets-client.js');
298
299
  const ping = await agentPing();
299
- if (!ping.reachable)
300
- return { reachable: false, socketPath: null, heldBundles: null, record: null };
301
- const entries = await agentStatus();
302
- return { reachable: true, socketPath: null, heldBundles: entries.length, record: null };
300
+ if (ping.reachable) {
301
+ const entries = await agentStatus();
302
+ return { reachable: true, fileBacked: false, socketPath: null, heldBundles: entries.length, record: null };
303
+ }
303
304
  }
304
- catch {
305
- return { reachable: false, socketPath: null, heldBundles: null, record: null };
305
+ catch { /* fall through to the unreachable classification below */ }
306
+ // Unreachable: distinguish "broker needed and absent" (a keychain-backed box)
307
+ // from "no broker needed" (a file-backed worker reads secrets one-shot). An
308
+ // unknown store defaults to broker-needed, the louder verdict.
309
+ let fileBacked = false;
310
+ try {
311
+ fileBacked = await keychainUsesFileFallback();
306
312
  }
307
- }
308
- async function probeBrowserIPC() {
309
- const record = readSubsystemHealth(SUBSYSTEM_BROWSER_IPC);
310
- const { isBrowserServiceReachable, getSocketPath } = await import('../lib/browser/ipc.js');
311
- const { listAllProfileSnapshots } = await import('../lib/browser/runtime-state.js');
312
- const bound = await isBrowserServiceReachable();
313
- const sessionCount = listAllProfileSnapshots().filter((s) => s.pidAlive && s.daemonAlive).length;
314
- return { bound, socketPath: getSocketPath(), sessionCount, record };
313
+ catch { /* unknown → not file-backed */ }
314
+ return { reachable: false, fileBacked, socketPath: null, heldBundles: null, record: null };
315
315
  }
316
316
  function schedulerSummary() {
317
317
  const jobs = listJobs();
@@ -357,6 +357,23 @@ function healthLine(label, live, record) {
357
357
  : '';
358
358
  return ` ${chalk.red('down')} ${label} ${detail}`;
359
359
  }
360
+ /**
361
+ * The `secrets broker` health line. Reachable → healthy. Unreachable is a FAULT
362
+ * (`down`) only when the store is keychain-backed — there the broker is what
363
+ * holds the unlocked keychain across reads. On a file-backed box (every
364
+ * headless worker) no broker is needed: secrets read one-shot, so an absent
365
+ * broker is expected and reads as INFO, not `down` (PHNX-4116). Pure, so the
366
+ * three cases are unit-tested with no live daemon.
367
+ */
368
+ export function secretsBrokerHealthLine(secrets) {
369
+ if (secrets.reachable) {
370
+ return healthLine(`secrets broker (${secrets.socketPath}, ${secrets.heldBundles} bundle(s) held)`, true, secrets.record);
371
+ }
372
+ if (secrets.fileBacked) {
373
+ return ` ${chalk.cyan('info')} secrets agent not running (file-backed stores)`;
374
+ }
375
+ return healthLine('secrets broker (unreachable)', false, secrets.record);
376
+ }
360
377
  async function runStatus(opts) {
361
378
  const status = getDaemonStatus();
362
379
  const enabled = isDaemonEnabled();
@@ -378,7 +395,7 @@ async function runStatus(opts) {
378
395
  const staleTiers = staleDaemons(processes, pid ?? null, registeredPids);
379
396
  const stale = staleTiers.visible;
380
397
  const ownerEntryGone = owner ? entryIsGone(owner) : false;
381
- const [secrets, browserIpc] = await Promise.all([probeSecretsBroker(), probeBrowserIPC()]);
398
+ const secrets = await probeSecretsBroker();
382
399
  const scheduler = schedulerSummary();
383
400
  if (opts.json) {
384
401
  console.log(JSON.stringify({
@@ -386,6 +403,9 @@ async function runStatus(opts) {
386
403
  pid,
387
404
  uptimeSeconds: uptime,
388
405
  heartbeatAgeMs,
406
+ restarts24h: status.restarts24h,
407
+ lastRestartCause: status.lastRestartCause,
408
+ lastRestartAt: status.lastRestartAt,
389
409
  logPath: status.logPath,
390
410
  binaryPath: owner?.entry ?? status.binaryPath,
391
411
  binaryVersion: owner?.version ?? null,
@@ -409,12 +429,6 @@ async function runStatus(opts) {
409
429
  heldBundles: secrets.heldBundles,
410
430
  health: secrets.record,
411
431
  },
412
- browserIpc: {
413
- bound: browserIpc.bound,
414
- socketPath: browserIpc.socketPath,
415
- sessionCount: browserIpc.sessionCount,
416
- health: browserIpc.record,
417
- },
418
432
  },
419
433
  scheduler: {
420
434
  enabled: getConfigValue('scheduler.enabled').value !== false,
@@ -427,9 +441,8 @@ async function runStatus(opts) {
427
441
  return;
428
442
  }
429
443
  const stateLabel = state === 'running' ? chalk.green('running')
430
- : state === 'wedged' ? chalk.red('wedged')
431
- : state === 'disabled' ? chalk.yellow('disabled')
432
- : chalk.gray('stopped');
444
+ : state === 'disabled' ? chalk.yellow('disabled')
445
+ : chalk.gray('stopped');
433
446
  console.log(chalk.bold('Identity\n'));
434
447
  console.log(` State: ${stateLabel}`);
435
448
  if (pid)
@@ -438,6 +451,12 @@ async function runStatus(opts) {
438
451
  console.log(` Uptime: ${humanDuration(uptime)}`);
439
452
  if (heartbeatAgeMs !== null)
440
453
  console.log(` Heartbeat: ${Math.round(heartbeatAgeMs / 1000)}s ago`);
454
+ if (status.restarts24h > 0) {
455
+ const when = status.lastRestartAt ? ` (last ${new Date(status.lastRestartAt).toLocaleString()})` : '';
456
+ console.log(` Restarts: ${status.restarts24h} in the last 24h${when}`);
457
+ if (status.lastRestartCause)
458
+ console.log(` Last cause: ${chalk.gray(status.lastRestartCause)}`);
459
+ }
441
460
  const binaryLabel = owner?.entry ?? status.binaryPath ?? 'unknown';
442
461
  console.log(` Binary: ${ownerEntryGone ? chalk.red(`${binaryLabel} (MISSING from disk)`) : chalk.gray(binaryLabel)}`);
443
462
  console.log(` Version: ${chalk.gray(owner?.version ?? 'unknown')}`);
@@ -476,17 +495,13 @@ async function runStatus(opts) {
476
495
  console.log(chalk.gray('\n Only one install should own the daemon. Stop the stray(s): kill <pid>'));
477
496
  }
478
497
  console.log(chalk.bold('\nHealth\n'));
479
- console.log(healthLine(`secrets broker ${secrets.reachable ? `(${secrets.socketPath}, ${secrets.heldBundles} bundle(s) held)` : '(unreachable)'}`, secrets.reachable, secrets.record));
480
- console.log(healthLine(`browser IPC ${browserIpc.bound ? `(${browserIpc.socketPath}, ${browserIpc.sessionCount} session(s))` : '(unbound)'}`, browserIpc.bound, browserIpc.record));
498
+ console.log(secretsBrokerHealthLine(secrets));
481
499
  const schedulerEnabled = getConfigValue('scheduler.enabled').value !== false;
482
500
  console.log(` ${schedulerEnabled ? chalk.green('enabled') : chalk.yellow('disabled')} scheduler — ${scheduler.enabledCount}/${scheduler.routineCount} routine(s) enabled` +
483
501
  (scheduler.nextFire ? `, next ${scheduler.nextFire.toLocaleString()}` : ''));
484
502
  if (scheduler.failingCount > 0) {
485
503
  console.log(chalk.red(` ${scheduler.failingCount} routine(s) failing their last run — see: agents routines stats`));
486
504
  }
487
- if (state === 'wedged') {
488
- console.log(chalk.red('\nThe daemon is wedged (heartbeat stale). Restart: agents daemon restart'));
489
- }
490
505
  }
491
506
  /**
492
507
  * Combine the persisted enable/disable toggles with whatever health the
@@ -503,8 +518,8 @@ function buildServiceRows(daemonRunning) {
503
518
  // A persisted supervisor state (health.json) is only trustworthy while the
504
519
  // daemon is actually up. If it is not running (crash / kill -9 / never
505
520
  // started), every service is stopped no matter what the last-written record
506
- // says — trusting a stale 'running'/'parked'/'idle' here would print a
507
- // report that contradicts the live-probed socket rows below (RUSH-2368).
521
+ // says — trusting a stale 'running'/'idle' here would print a report that
522
+ // contradicts the live-probed socket rows below (RUSH-2368).
508
523
  const state = daemonRunning
509
524
  ? (h?.state ?? (s.enabled ? 'running (unsupervised)' : 'stopped'))
510
525
  : 'stopped';
@@ -524,8 +539,6 @@ function buildServiceRows(daemonRunning) {
524
539
  function serviceStateLabel(state) {
525
540
  if (state === 'running')
526
541
  return chalk.green('running');
527
- if (state === 'parked')
528
- return chalk.red('parked');
529
542
  if (state === 'stopped')
530
543
  return chalk.gray('stopped');
531
544
  if (state === 'idle')
@@ -533,13 +546,12 @@ function serviceStateLabel(state) {
533
546
  return chalk.yellow(state); // 'running (unsupervised)' or any future label
534
547
  }
535
548
  async function runServices(opts) {
536
- const [secrets, browserIpc] = await Promise.all([probeSecretsBroker(), probeBrowserIPC()]);
549
+ const secrets = await probeSecretsBroker();
537
550
  const rows = buildServiceRows(isDaemonRunning());
538
551
  if (opts.json) {
539
552
  console.log(JSON.stringify({
540
553
  // Existing fields — unchanged shape, agents/CI consume these directly.
541
554
  secretsBroker: { reachable: secrets.reachable, socketPath: secrets.socketPath, heldBundles: secrets.heldBundles, health: secrets.record },
542
- browserIpc: { bound: browserIpc.bound, socketPath: browserIpc.socketPath, sessionCount: browserIpc.sessionCount, health: browserIpc.record },
543
555
  // Additive: every registered service, supervised or legacy.
544
556
  services: rows,
545
557
  }, null, 2));
@@ -554,8 +566,7 @@ async function runServices(opts) {
554
566
  console.log(chalk.gray(` last-error: ${row.lastError}`));
555
567
  }
556
568
  console.log(chalk.bold('\nHosted sockets\n'));
557
- console.log(healthLine(`secrets broker ${secrets.reachable ? `(${secrets.socketPath}, ${secrets.heldBundles} bundle(s) held)` : '(unreachable)'}`, secrets.reachable, secrets.record));
558
- console.log(healthLine(`browser IPC ${browserIpc.bound ? `(${browserIpc.socketPath}, ${browserIpc.sessionCount} session(s))` : '(unbound)'}`, browserIpc.bound, browserIpc.record));
569
+ console.log(secretsBrokerHealthLine(secrets));
559
570
  console.log(chalk.gray('\nScheduled routines run through `agents routines` — see: agents routines stats'));
560
571
  console.log(chalk.gray('agents daemon services enable|disable|restart <id> apply live for supervised services.'));
561
572
  }
@@ -633,8 +644,6 @@ async function runDoctor(opts) {
633
644
  const problems = [];
634
645
  if (!status.running && enabled)
635
646
  problems.push('Daemon is not running. Start it: agents daemon start');
636
- if (status.running && isDaemonWedged())
637
- problems.push('Daemon is wedged (heartbeat stale). Restart: agents daemon restart');
638
647
  // RUSH-2418: an open auto-start circuit breaker is the FIRST thing to report
639
648
  // for a stopped daemon — otherwise the only advice is "agents daemon start",
640
649
  // which is the exact action the breaker just refused, with no hint that a
@@ -672,12 +681,6 @@ async function runDoctor(opts) {
672
681
  if (secrets.record && secrets.record.consecutiveFailures > 0) {
673
682
  problems.push(`Secrets broker has ${secrets.record.consecutiveFailures} consecutive failure(s): ${secrets.record.lastError}`);
674
683
  }
675
- const browserIpc = await probeBrowserIPC();
676
- if (!browserIpc.bound)
677
- problems.push('Browser IPC is unbound.');
678
- if (browserIpc.record && browserIpc.record.consecutiveFailures > 0) {
679
- problems.push(`Browser IPC has ${browserIpc.record.consecutiveFailures} consecutive failure(s): ${browserIpc.record.lastError}`);
680
- }
681
684
  const scheduler = schedulerSummary();
682
685
  if (scheduler.failingCount > 0) {
683
686
  problems.push(`${scheduler.failingCount} routine(s) failing their last run. See: agents routines stats`);
@@ -841,7 +844,7 @@ function runWebhooksList(json) {
841
844
  export function registerDaemonCommand(program) {
842
845
  const cmd = program
843
846
  .command('daemon')
844
- .description('The always-on daemon: browser IPC, watchdog, and the routines scheduler. Bare `agents daemon` shows status.')
847
+ .description('The always-on daemon: watchdog, session/usage sync, and the routines scheduler. Bare `agents daemon` shows status.')
845
848
  .option('--json', 'Emit as JSON')
846
849
  .action(async (opts, command) => {
847
850
  await runStatus({ json: command.optsWithGlobals().json === true });
@@ -870,12 +873,12 @@ export function registerDaemonCommand(program) {
870
873
  agents daemon services
871
874
 
872
875
  # Toggle or restart a service live — applies without a daemon restart
873
- # for supervisor-managed services (browser-ipc, account-state,
874
- # session-index, monitors' off-transition, watchdog, device-probe,
875
- # self-heal, state-dir-check)
876
- agents daemon services disable browser-ipc
877
- agents daemon services enable browser-ipc
878
- agents daemon services restart browser-ipc
876
+ # for supervisor-managed services (account-state, session-index,
877
+ # monitors' off-transition, watchdog, device-probe, self-heal,
878
+ # state-dir-check)
879
+ agents daemon services disable account-state
880
+ agents daemon services enable account-state
881
+ agents daemon services restart account-state
879
882
 
880
883
  # Host a signed webhook receiver here, supervised and restarted on crash
881
884
  agents daemon webhooks add --secrets-bundle linear-webhook
@@ -912,12 +915,10 @@ export function registerDaemonCommand(program) {
912
915
 
913
916
  'agents daemon services enable|disable|restart <id>' applies live (no
914
917
  daemon restart) for supervisor-managed services that were registered at
915
- boot. browser-ipc is also registered while disabled, specifically so a
916
- browser client can enable it live. The inline scheduler re-evaluates its
917
- toggle on every reload as well. A boot-disabled service other than
918
- browser-ipc, the monitor on-transition, and webhook-receiver still need an
919
- operator 'agents daemon restart'. 'agents daemon services' names which
920
- case you're in per row.
918
+ boot. The inline scheduler re-evaluates its toggle on every reload as
919
+ well. A boot-disabled service other than the monitor on-transition and
920
+ webhook-receiver still needs an operator 'agents daemon restart'. 'agents
921
+ daemon services' names which case you're in per row.
921
922
  `,
922
923
  });
923
924
  cmd.command('status')
@@ -1033,16 +1034,15 @@ export function registerDaemonCommand(program) {
1033
1034
  agents daemon services list
1034
1035
 
1035
1036
  # Toggle or restart live — no daemon restart for a supervisor-managed
1036
- # service (browser-ipc, account-state, session-index)
1037
- agents daemon services disable browser-ipc
1038
- agents daemon services enable browser-ipc
1039
- agents daemon services restart browser-ipc
1037
+ # service (account-state, session-index)
1038
+ agents daemon services disable account-state
1039
+ agents daemon services enable account-state
1040
+ agents daemon services restart account-state
1040
1041
  `,
1041
1042
  notes: `
1042
1043
  A service disabled at daemon boot is normally not registered on the
1043
- supervisor, so enabling it needs an operator restart. browser-ipc is the
1044
- exception: it is registered stopped and can be enabled live. The inline
1045
- scheduler also applies enable/disable on reload; webhook-receiver and the
1044
+ supervisor, so enabling it needs an operator restart. The inline
1045
+ scheduler applies enable/disable on reload; webhook-receiver and the
1046
1046
  monitor on-transition still require 'agents daemon restart'. Each row in
1047
1047
  the plain-text view names which case it is; 'supervised: true/false' does
1048
1048
  the same in --json.
@@ -1077,9 +1077,8 @@ export function registerDaemonCommand(program) {
1077
1077
  * Apply an enable/disable toggle live (RUSH-3193 P4): persist it, then signal
1078
1078
  * the running daemon to reload — its handler diffs the toggle and drives
1079
1079
  * `supervisor.start/stop(id)` for a supervised service, so no restart is
1080
- * needed for supervisor-managed services registered at boot. browser-ipc is
1081
- * registered even when disabled so it can be enabled live; the inline
1082
- * scheduler also re-evaluates its toggle on reload. webhook-receiver,
1080
+ * needed for supervisor-managed services registered at boot. The inline
1081
+ * scheduler re-evaluates its toggle on reload. webhook-receiver,
1083
1082
  * monitors' on-transition, and other boot-disabled services still need a
1084
1083
  * deliberate operator restart — the daemon's own reload log says so.
1085
1084
  */
@@ -203,7 +203,7 @@ export function addEventsReadOptions(command) {
203
203
  .option('--command <path>', 'Only this command path — prefix match (e.g. "teams create")')
204
204
  .option('--event <type>', 'Only this typed event (repeatable, e.g. secrets.get, run.dispatched, pr.opened)', collect, [])
205
205
  .option('--agent <name>', 'Only events tagged with this agent')
206
- .option('--caller <kind>', 'Only this caller kind (claude-code, codex, gemini, cursor, terminal, script)')
206
+ .option('--caller <kind>', 'Only this caller kind (claude-code, codex, cursor, terminal, script)')
207
207
  .option('--level <level>', 'Only this level: audit, warn, info, debug')
208
208
  .option('--session <id>', 'Only events from this session (the provenance sessionId) — e.g. trace which session read a secret')
209
209
  .option('--bundle <name>', 'Only events carrying this bundle in their payload — e.g. `--module secrets --bundle share` for every read of the share bundle')
@@ -17,6 +17,7 @@ import { parseLoopInterval } from '../lib/loop.js';
17
17
  import { AGENTS, resolveAgentName, isAgentHardDeprecated, hardDeprecationError } from '../lib/agents.js';
18
18
  import { parseAgentVersionSpec } from '../lib/agent-spec/agents.js';
19
19
  import { recordDispatchedRun } from '../lib/audit/log.js';
20
+ import { recordRunAuthOutcome } from '../lib/auth-health.js';
20
21
  import { maybeShowStarNudge } from '../lib/star-nudge.js';
21
22
  import { warnUnpushedWork, shouldWarnUnpushed } from '../lib/warn-unpushed.js';
22
23
  import { warnOrphanedOpenPr } from '../lib/pr-land-detach.js';
@@ -2681,6 +2682,37 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
2681
2682
  // the only `continue` is the preflight exhaustion handoff below, which
2682
2683
  // consumes one entry of the `--fallback` spec before retrying as that
2683
2684
  // alternate. The spec is finite, so this terminates (PHNX-3999 F19).
2685
+ // A balanced pick or the no-fresh-usage picker returns a CANDIDATE. Since
2686
+ // PHNX-3940 a native candidate is a registered account SLOT, not the
2687
+ // version home the executable lives in, so the spawn must resolve that
2688
+ // slot (home + durable env) through the canonical local launch resolver.
2689
+ // Without this the run launched the version home and whichever login it
2690
+ // held: on yosemite-m0 (2026-09-23) the picker chose trp and Claude
2691
+ // started as a different account in its first-run wizard. A remote run
2692
+ // re-resolves on the peer, which runs this same code locally.
2693
+ const applyPickedCandidate = async (candidate) => {
2694
+ if (!candidate.nativeAccount || options.host || options.device)
2695
+ return;
2696
+ const { resolveLocalAccountLaunch } = await import('../lib/accounting/account-launch.js');
2697
+ try {
2698
+ launchAccount = await resolveLocalAccountLaunch({
2699
+ agent,
2700
+ executableVersion: version ?? candidate.version,
2701
+ candidate,
2702
+ useDefault: false,
2703
+ });
2704
+ }
2705
+ catch (err) {
2706
+ console.error(chalk.red(err.message));
2707
+ process.exit(1);
2708
+ }
2709
+ if (launchAccount.execHome)
2710
+ execHome = launchAccount.execHome;
2711
+ if (launchAccount.configVersion)
2712
+ accountConfigVersion = launchAccount.configVersion;
2713
+ if (Object.keys(launchAccount.env).length > 0)
2714
+ accountEnv = { ...accountEnv, ...launchAccount.env };
2715
+ };
2684
2716
  preflight: for (;;) {
2685
2717
  if (!accountPickerRequested && !configuredAccount && (!version || strategy !== 'pinned' || options.balanced || explicitStrategy)) {
2686
2718
  if (version) {
@@ -2818,6 +2850,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
2818
2850
  // Keep the rotation so mid-run failover can still cascade across
2819
2851
  // the other (stale) healthy accounts after a real rejection.
2820
2852
  rotationResult = resolved.rotation;
2853
+ await applyPickedCandidate(selected);
2821
2854
  if (!options.quiet) {
2822
2855
  const identity = selected.accountLabel || 'signed-in account';
2823
2856
  process.stderr.write(chalk.gray(`[agents] no fresh usage for any ${agent} account — you picked ${identity} · ${agent}@${selected.version}\n`));
@@ -2836,12 +2869,13 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
2836
2869
  if (resolved.rotation) {
2837
2870
  launchSignedIn = resolved.rotation.picked.signedIn;
2838
2871
  launchEmail = resolved.rotation.picked.email;
2872
+ await applyPickedCandidate(resolved.rotation.picked);
2839
2873
  }
2840
2874
  // A balanced/available pick of a PROVIDER account (setup-token /
2841
2875
  // API-key) carries `providerAccount`. Resolve its env through the
2842
2876
  // same `resolveSpawnAccount` path an explicit `--account` uses, so
2843
2877
  // exec injects the credential; a native pick has no providerAccount
2844
- // and runs from its own version home unchanged (RUSH-3182).
2878
+ // and resolved its slot home in applyPickedCandidate above.
2845
2879
  const pickedProviderAccount = resolved.rotation?.picked.providerAccount;
2846
2880
  if (pickedProviderAccount) {
2847
2881
  try {
@@ -3561,6 +3595,24 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
3561
3595
  // Governance chokepoint (#347): every dispatched run finalizes here.
3562
3596
  // ONE tamper-evident audit record per run — non-fatal by contract.
3563
3597
  recordDispatchedRun({ agent: ranAgent, version: ranVersion ?? 'unknown', mode, cwd, exitCode });
3598
+ // A clean headless exit is real auth evidence — the account authenticated
3599
+ // and produced a result. Record it as the per-account FACT `agents view`
3600
+ // renders (`last used ok`), keyed to the slot the run used so it lands on
3601
+ // the right row and clears any stale failure fact (PHNX-4116). A non-zero
3602
+ // exit is NOT recorded as an auth failure: the exit code alone does not
3603
+ // prove the token was rejected (a task can fail for a hundred reasons), and
3604
+ // a lying "auth failure" fact is worse than none. Real auth failures are
3605
+ // recorded where they are actually detected (the routine runner's
3606
+ // isAuthFailureFromLog sites) and by the daemon probe.
3607
+ if (exitCode === 0) {
3608
+ recordRunAuthOutcome({
3609
+ agent: ranAgent,
3610
+ accountId: execOptions.accountId ?? null,
3611
+ version: ranVersion ?? null,
3612
+ home: execHome ?? null,
3613
+ outcome: { ok: true },
3614
+ });
3615
+ }
3564
3616
  // First-successful-run star nudge (one-time, non-nagging). Only on a
3565
3617
  // clean run, and never when output is machine-readable/quiet.
3566
3618
  if (exitCode === 0)
@@ -55,34 +55,13 @@ function syncMarketplacesForDefaults() {
55
55
  catch { /* best-effort */ }
56
56
  }
57
57
  }
58
- /** Write this device's account metadata before the user repo is committed. */
59
- async function publishUserRepoAccountState(provisionAuth) {
60
- const { publishUsageSnapshotToSharedStore } = await import('../lib/accounting/usage-sync.js');
61
- const usage = await publishUsageSnapshotToSharedStore();
62
- if (usage.error)
63
- console.error(chalk.yellow(`Usage snapshot: ${usage.error}`));
64
- if (provisionAuth) {
65
- const { syncReservedAuthBundle } = await import('../lib/secrets-policy.js');
66
- const auth = await syncReservedAuthBundle();
67
- if (auth.pushed.length > 0)
68
- console.log(chalk.gray(`Auth bundle: pushed to ${auth.pushed.join(', ')}`));
69
- for (const err of auth.errors)
70
- console.error(chalk.yellow(`Auth bundle: ${err.device}: ${err.message}`));
71
- return;
72
- }
73
- const { publishReservedAuthVerdict } = await import('../lib/secrets-policy.js');
74
- const auth = await publishReservedAuthVerdict();
75
- if (auth.error)
76
- console.error(chalk.yellow(`Auth verdict: ${auth.device}: ${auth.error}`));
77
- }
78
- /** Consume freshly pulled usage/auth metadata from the local user-repo checkout. */
79
- async function consumeUserRepoAccountState() {
80
- const { consumeUsageSnapshotsFromSharedStore } = await import('../lib/accounting/usage-sync.js');
81
- const usage = consumeUsageSnapshotsFromSharedStore();
82
- if (usage.merged > 0)
83
- console.log(chalk.gray(`Usage snapshot: merged ${usage.merged} row(s) from ${usage.sources.join(', ')}`));
84
- for (const err of usage.errors)
85
- console.error(chalk.yellow(`Usage snapshot: ${err.device}: ${err.message}`));
58
+ /**
59
+ * Push the reserved auth bundle to peers whose last-received verdict says
60
+ * `missing`. Daemon state (usage, verdicts, session digests) no longer rides the
61
+ * user repo — it moves over SSH on the usage-sync tick (PHNX-4116) — so a repo
62
+ * push/sync of human-authored resources only runs this credential half.
63
+ */
64
+ async function syncUserRepoAuthBundle() {
86
65
  const { syncReservedAuthBundle } = await import('../lib/secrets-policy.js');
87
66
  const auth = await syncReservedAuthBundle();
88
67
  if (auth.pushed.length > 0)
@@ -1085,7 +1064,7 @@ export function registerRepoCommands(program) {
1085
1064
  ].filter(Boolean);
1086
1065
  if (parts.length > 0)
1087
1066
  console.log(chalk.gray(`Device policy: ${parts.join(' · ')}`));
1088
- await consumeUserRepoAccountState();
1067
+ await syncUserRepoAuthBundle();
1089
1068
  }
1090
1069
  });
1091
1070
  repoCmd
@@ -1121,10 +1100,8 @@ export function registerRepoCommands(program) {
1121
1100
  return;
1122
1101
  }
1123
1102
  for (const t of pushable) {
1124
- // Account snapshots must exist BEFORE commitAndPush or this push ships
1125
- // the previous tick's state and delays propagation by one repo cycle.
1126
1103
  if (t.alias === 'user')
1127
- await publishUserRepoAccountState(true);
1104
+ await syncUserRepoAuthBundle();
1128
1105
  const spinner = interruptibleSpinner(`Pushing ${formatRepoTarget(t.alias, t.dir)}...`).start();
1129
1106
  const result = await commitAndPush(t.dir, options.message);
1130
1107
  if (result.success) {
@@ -1193,23 +1170,10 @@ Examples:
1193
1170
  }
1194
1171
  }
1195
1172
  const push = t.alias !== 'system';
1196
- // syncRepoGit pulls then pushes. Seed our conflict-free owner file first
1197
- // so the same transaction carries it; consume peers after the pull.
1198
- if (t.alias === 'user')
1199
- await publishUserRepoAccountState(false);
1173
+ // A plain pull/rebase (+ push) of human-authored resources for every
1174
+ // alias, the user repo included: daemon state left git (PHNX-4116).
1200
1175
  const spinner = interruptibleSpinner(`Syncing ${formatRepoTarget(t.alias, t.dir)}...`).start();
1201
- const result = t.alias === 'user'
1202
- ? await (async () => {
1203
- const { syncFleetSharedStateRepo } = await import('../lib/fleet-shared-repo-sync.js');
1204
- const synced = await syncFleetSharedStateRepo({ userAgentsDir: t.dir });
1205
- return {
1206
- success: synced.success,
1207
- commit: synced.commit ?? '',
1208
- pushed: synced.pushed,
1209
- error: synced.error ?? synced.skipped ?? undefined,
1210
- };
1211
- })()
1212
- : await syncRepoGit(t.dir, { push });
1176
+ const result = await syncRepoGit(t.dir, { push });
1213
1177
  if (result.success) {
1214
1178
  const pushed = result.pushed ? ' (pushed)' : '';
1215
1179
  spinner.succeed(`${formatRepoTarget(t.alias, t.dir)}: ${result.commit}${pushed}`);
@@ -1219,7 +1183,7 @@ Examples:
1219
1183
  recordUserRepoRemote(t.dir, u);
1220
1184
  const { reconcileDeviceDiscoveryPolicies } = await import('../lib/devices/discovery-policy.js');
1221
1185
  await reconcileDeviceDiscoveryPolicies();
1222
- await consumeUserRepoAccountState();
1186
+ await syncUserRepoAuthBundle();
1223
1187
  }
1224
1188
  }
1225
1189
  else {
@@ -95,13 +95,13 @@ export function registerRouteCommands(program) {
95
95
  setHelpSections(routeCmd, {
96
96
  examples: `
97
97
  # Create a router scoped to two harnesses, capped at a tier
98
- agents route add research --harness gemini,kimi --tier cheap,default
98
+ agents route add research --harness grok,kimi --tier cheap,default
99
99
 
100
100
  # Narrow one harness's model set
101
101
  agents route allow research kimi kimi-k2
102
102
 
103
103
  # Link accounts so routing under this router only ever picks them
104
- agents route link-account research gemini personal
104
+ agents route link-account research grok personal
105
105
  agents route link-account research kimi work
106
106
 
107
107
  # Inspect, rename, remove it
@@ -126,7 +126,7 @@ export function registerRouteCommands(program) {
126
126
  .command('add <name>')
127
127
  .alias('create')
128
128
  .description('Create a named router with an initial harness + tier allowlist.')
129
- .option('--harness <list>', 'Comma-separated harness ids to allow, e.g. gemini,kimi')
129
+ .option('--harness <list>', 'Comma-separated harness ids to allow, e.g. grok,kimi')
130
130
  .option('--tier <list>', 'Comma-separated tier tokens applied to every listed harness, e.g. cheap,default')
131
131
  .option('--task <type>', 'Free-text task type this router serves, e.g. research')
132
132
  .action((name, opts) => {
@@ -134,7 +134,7 @@ export function registerRouteCommands(program) {
134
134
  die(`Router '${name}' already exists. Use 'agents route allow ${name} <harness> <model|tier>...' to edit it.`);
135
135
  }
136
136
  if (!opts.harness) {
137
- die("Missing --harness. Example: agents route add research --harness gemini,kimi --tier cheap,default");
137
+ die("Missing --harness. Example: agents route add research --harness grok,kimi --tier cheap,default");
138
138
  }
139
139
  const harnesses = opts.harness.split(',').map((h) => h.trim()).filter(Boolean);
140
140
  const tiers = (opts.tier ?? '').split(',').map((t) => t.trim()).filter(Boolean);
@@ -170,7 +170,7 @@ export function registerRouteCommands(program) {
170
170
  }
171
171
  if (routers.length === 0) {
172
172
  console.log(chalk.gray('No routers configured.'));
173
- console.log(chalk.gray('Try: agents route add research --harness gemini,kimi --tier cheap,default'));
173
+ console.log(chalk.gray('Try: agents route add research --harness grok,kimi --tier cheap,default'));
174
174
  return;
175
175
  }
176
176
  for (const router of routers)