@phnx-labs/agents-cli 1.22.116 → 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 (254) hide show
  1. package/CHANGELOG.md +247 -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.d.ts +18 -0
  9. package/dist/commands/exec.js +104 -12
  10. package/dist/commands/repo.js +13 -49
  11. package/dist/commands/route.js +5 -5
  12. package/dist/commands/routines.js +39 -12
  13. package/dist/commands/sessions-render.d.ts +1 -1
  14. package/dist/commands/sessions-render.js +0 -1
  15. package/dist/commands/sessions.test-fixture.d.ts +0 -1
  16. package/dist/commands/sessions.test-fixture.js +0 -37
  17. package/dist/commands/setup-browser.d.ts +17 -12
  18. package/dist/commands/setup-browser.js +52 -87
  19. package/dist/commands/setup-preferences.d.ts +0 -17
  20. package/dist/commands/setup-preferences.js +18 -49
  21. package/dist/commands/setup-term.d.ts +4 -3
  22. package/dist/commands/setup-term.js +5 -4
  23. package/dist/commands/setup.js +11 -11
  24. package/dist/commands/ssh.js +10 -1
  25. package/dist/commands/sync.js +6 -44
  26. package/dist/commands/teams.js +0 -1
  27. package/dist/commands/update.d.ts +19 -0
  28. package/dist/commands/update.js +34 -6
  29. package/dist/commands/view.d.ts +12 -1
  30. package/dist/commands/view.js +47 -2
  31. package/dist/index.js +107 -26
  32. package/dist/lib/account-capabilities.js +0 -2
  33. package/dist/lib/account-catalog.d.ts +38 -0
  34. package/dist/lib/account-catalog.js +110 -26
  35. package/dist/lib/account-provider-registry.js +1 -1
  36. package/dist/lib/account-registry.d.ts +1 -1
  37. package/dist/lib/account-registry.js +36 -2
  38. package/dist/lib/accounting/rotate.js +22 -8
  39. package/dist/lib/accounting/usage-ingest.d.ts +6 -1
  40. package/dist/lib/accounting/usage-ingest.js +115 -45
  41. package/dist/lib/accounting/usage-sync.d.ts +134 -31
  42. package/dist/lib/accounting/usage-sync.js +227 -66
  43. package/dist/lib/accounting/usage.d.ts +16 -3
  44. package/dist/lib/accounting/usage.js +26 -8
  45. package/dist/lib/accounts/slots.d.ts +8 -0
  46. package/dist/lib/accounts/slots.js +17 -0
  47. package/dist/lib/add-dir.js +0 -1
  48. package/dist/lib/agent-cli-commands.js +0 -1
  49. package/dist/lib/agent-spec/agents.d.ts +6 -7
  50. package/dist/lib/agent-spec/agents.js +6 -60
  51. package/dist/lib/auth-health.d.ts +69 -1
  52. package/dist/lib/auth-health.js +133 -4
  53. package/dist/lib/browser/context.d.ts +82 -0
  54. package/dist/lib/browser/context.js +88 -0
  55. package/dist/lib/browser/paths.d.ts +25 -0
  56. package/dist/lib/browser/paths.js +51 -0
  57. package/dist/lib/browser/record.d.ts +38 -0
  58. package/dist/lib/browser/record.js +57 -0
  59. package/dist/lib/browser/sessions-list.js +1 -2
  60. package/dist/lib/browser-client.d.ts +158 -0
  61. package/dist/lib/browser-client.js +234 -0
  62. package/dist/lib/channels/owner-sink.d.ts +1 -18
  63. package/dist/lib/channels/owner-sink.js +29 -82
  64. package/dist/lib/channels/providers/rush.d.ts +7 -7
  65. package/dist/lib/channels/providers/rush.js +127 -57
  66. package/dist/lib/claude-statusline.d.ts +1 -1
  67. package/dist/lib/claude-statusline.js +15 -3
  68. package/dist/lib/commands.js +1 -1
  69. package/dist/lib/crabbox/lease.js +1 -1
  70. package/dist/lib/crabbox/runtimes.js +1 -3
  71. package/dist/lib/daemon/account-state-daemon-service.js +1 -0
  72. package/dist/lib/daemon/auth-sync-service.d.ts +21 -18
  73. package/dist/lib/daemon/auth-sync-service.js +37 -43
  74. package/dist/lib/daemon/daemon.d.ts +25 -14
  75. package/dist/lib/daemon/daemon.js +92 -154
  76. package/dist/lib/daemon/runner.d.ts +2 -2
  77. package/dist/lib/daemon/runner.js +32 -9
  78. package/dist/lib/daemon/self-update-service.d.ts +35 -34
  79. package/dist/lib/daemon/self-update-service.js +40 -36
  80. package/dist/lib/daemon/service.d.ts +12 -8
  81. package/dist/lib/daemon/supervisor.d.ts +65 -54
  82. package/dist/lib/daemon/supervisor.js +136 -171
  83. package/dist/lib/daemon/testdata/process-view-start.js +5 -2
  84. package/dist/lib/daemon/usage-sync-service.d.ts +21 -30
  85. package/dist/lib/daemon/usage-sync-service.js +46 -78
  86. package/dist/lib/daemon-health.d.ts +22 -5
  87. package/dist/lib/daemon-health.js +47 -3
  88. package/dist/lib/daemon-services.d.ts +1 -1
  89. package/dist/lib/daemon-services.js +0 -10
  90. package/dist/lib/daemon-ticks.js +4 -2
  91. package/dist/lib/devices/doctor-findings.js +8 -11
  92. package/dist/lib/devices/harness-inventory.js +14 -5
  93. package/dist/lib/exec-account-home.js +5 -0
  94. package/dist/lib/exec.js +0 -11
  95. package/dist/lib/feed/events.js +0 -1
  96. package/dist/lib/feed/tool-activity.js +5 -2
  97. package/dist/lib/fleet-shared-state.d.ts +45 -0
  98. package/dist/lib/fleet-shared-state.js +78 -9
  99. package/dist/lib/fs-atomic.js +25 -0
  100. package/dist/lib/harness-auth-capabilities.js +0 -1
  101. package/dist/lib/helper-versions.js +1 -1
  102. package/dist/lib/hooks/cache.d.ts +7 -0
  103. package/dist/lib/hooks/cache.js +27 -2
  104. package/dist/lib/hooks/install.js +7 -1
  105. package/dist/lib/hosts/ready.d.ts +38 -8
  106. package/dist/lib/hosts/ready.js +62 -14
  107. package/dist/lib/hosts/remote-cmd.js +1 -0
  108. package/dist/lib/installations/active-check.d.ts +27 -0
  109. package/dist/lib/installations/active-check.js +61 -0
  110. package/dist/lib/installations/migrate.js +6 -3
  111. package/dist/lib/installations/shims.js +9 -7
  112. package/dist/lib/installations/update.js +21 -15
  113. package/dist/lib/installations/versions.js +12 -1
  114. package/dist/lib/manifest.js +1 -1
  115. package/dist/lib/models.js +0 -2
  116. package/dist/lib/open-url.js +28 -54
  117. package/dist/lib/placement.d.ts +2 -0
  118. package/dist/lib/placement.js +5 -0
  119. package/dist/lib/profiles.js +0 -2
  120. package/dist/lib/refresh.js +1 -1
  121. package/dist/lib/routine-readiness.d.ts +35 -0
  122. package/dist/lib/routine-readiness.js +74 -16
  123. package/dist/lib/rules/compile.js +6 -0
  124. package/dist/lib/sandbox.js +0 -2
  125. package/dist/lib/secrets-policy.d.ts +89 -11
  126. package/dist/lib/secrets-policy.js +175 -28
  127. package/dist/lib/session/db.d.ts +1 -1
  128. package/dist/lib/session/db.js +65 -18
  129. package/dist/lib/session/discover.js +1 -283
  130. package/dist/lib/session/mirror.d.ts +4 -3
  131. package/dist/lib/session/mirror.js +4 -3
  132. package/dist/lib/session/parse.d.ts +0 -2
  133. package/dist/lib/session/parse.js +1 -138
  134. package/dist/lib/session/throughput.d.ts +1 -3
  135. package/dist/lib/session/throughput.js +0 -20
  136. package/dist/lib/session/types.d.ts +2 -2
  137. package/dist/lib/session/types.js +2 -2
  138. package/dist/lib/sessions-client.d.ts +60 -1
  139. package/dist/lib/sessions-client.js +227 -4
  140. package/dist/lib/setup-tool-status.js +8 -12
  141. package/dist/lib/signin-badge.d.ts +3 -2
  142. package/dist/lib/signin-badge.js +6 -3
  143. package/dist/lib/smart-launch.js +4 -1
  144. package/dist/lib/staleness/detectors/commands.d.ts +1 -1
  145. package/dist/lib/staleness/detectors/hooks.d.ts +1 -1
  146. package/dist/lib/staleness/detectors/mcp.d.ts +1 -1
  147. package/dist/lib/staleness/detectors/permissions.d.ts +1 -1
  148. package/dist/lib/staleness/detectors/plugins.d.ts +1 -1
  149. package/dist/lib/staleness/detectors/rules.d.ts +1 -1
  150. package/dist/lib/staleness/detectors/skills.d.ts +1 -1
  151. package/dist/lib/staleness/detectors/subagents.d.ts +1 -1
  152. package/dist/lib/staleness/detectors/workflows.d.ts +1 -1
  153. package/dist/lib/staleness/writers/commands.d.ts +1 -1
  154. package/dist/lib/staleness/writers/hooks.d.ts +1 -1
  155. package/dist/lib/staleness/writers/mcp.d.ts +1 -1
  156. package/dist/lib/staleness/writers/permissions.d.ts +1 -1
  157. package/dist/lib/staleness/writers/plugins.d.ts +1 -1
  158. package/dist/lib/staleness/writers/rules.d.ts +1 -1
  159. package/dist/lib/staleness/writers/skills.d.ts +1 -1
  160. package/dist/lib/staleness/writers/sources.js +5 -0
  161. package/dist/lib/staleness/writers/subagents.d.ts +1 -1
  162. package/dist/lib/staleness/writers/workflows.d.ts +1 -1
  163. package/dist/lib/state.d.ts +6 -4
  164. package/dist/lib/state.js +7 -17
  165. package/dist/lib/teams/parsers.d.ts +1 -1
  166. package/dist/lib/teams/parsers.js +1 -136
  167. package/dist/lib/teams/placement-probe.js +50 -15
  168. package/dist/lib/teams/scheduler.d.ts +7 -0
  169. package/dist/lib/types.d.ts +10 -1
  170. package/dist/lib/types.js +1 -1
  171. package/dist/lib/view-types.d.ts +32 -0
  172. package/dist/lib/watchdog/read.js +0 -1
  173. package/dist/lib/watchdog/rotate.js +1 -2
  174. package/dist/lib/watchdog/runner.js +2 -2
  175. package/dist/lib/watchdog/watchdog.d.ts +1 -1
  176. package/dist/lib/watchdog/watchdogTail.js +0 -31
  177. package/package.json +1 -1
  178. package/scripts/postinstall.js +8 -8
  179. package/dist/browser.d.ts +0 -2
  180. package/dist/browser.js +0 -17
  181. package/dist/commands/browser-picker.d.ts +0 -18
  182. package/dist/commands/browser-picker.js +0 -97
  183. package/dist/lib/browser/arc-discovery.d.ts +0 -52
  184. package/dist/lib/browser/arc-discovery.js +0 -197
  185. package/dist/lib/browser/arc-dom.d.ts +0 -14
  186. package/dist/lib/browser/arc-dom.js +0 -121
  187. package/dist/lib/browser/caller-identity.d.ts +0 -40
  188. package/dist/lib/browser/caller-identity.js +0 -175
  189. package/dist/lib/browser/cdp.d.ts +0 -51
  190. package/dist/lib/browser/cdp.js +0 -254
  191. package/dist/lib/browser/chrome.d.ts +0 -141
  192. package/dist/lib/browser/chrome.js +0 -704
  193. package/dist/lib/browser/chromium-discovery.d.ts +0 -28
  194. package/dist/lib/browser/chromium-discovery.js +0 -96
  195. package/dist/lib/browser/devices.d.ts +0 -34
  196. package/dist/lib/browser/devices.js +0 -61
  197. package/dist/lib/browser/domain-skills.d.ts +0 -71
  198. package/dist/lib/browser/domain-skills.js +0 -195
  199. package/dist/lib/browser/drivers/arc.d.ts +0 -40
  200. package/dist/lib/browser/drivers/arc.js +0 -276
  201. package/dist/lib/browser/drivers/firefox.d.ts +0 -99
  202. package/dist/lib/browser/drivers/firefox.js +0 -377
  203. package/dist/lib/browser/drivers/local.d.ts +0 -55
  204. package/dist/lib/browser/drivers/local.js +0 -266
  205. package/dist/lib/browser/drivers/ssh.d.ts +0 -120
  206. package/dist/lib/browser/drivers/ssh.js +0 -467
  207. package/dist/lib/browser/editor.d.ts +0 -3
  208. package/dist/lib/browser/editor.js +0 -50
  209. package/dist/lib/browser/ffmpeg.d.ts +0 -12
  210. package/dist/lib/browser/ffmpeg.js +0 -184
  211. package/dist/lib/browser/firefox-discovery.d.ts +0 -69
  212. package/dist/lib/browser/firefox-discovery.js +0 -162
  213. package/dist/lib/browser/har.d.ts +0 -85
  214. package/dist/lib/browser/har.js +0 -77
  215. package/dist/lib/browser/hygiene.d.ts +0 -97
  216. package/dist/lib/browser/hygiene.js +0 -153
  217. package/dist/lib/browser/index.d.ts +0 -5
  218. package/dist/lib/browser/index.js +0 -5
  219. package/dist/lib/browser/input.d.ts +0 -7
  220. package/dist/lib/browser/input.js +0 -92
  221. package/dist/lib/browser/ipc.d.ts +0 -204
  222. package/dist/lib/browser/ipc.js +0 -1453
  223. package/dist/lib/browser/login-detection.d.ts +0 -87
  224. package/dist/lib/browser/login-detection.js +0 -274
  225. package/dist/lib/browser/profiles.d.ts +0 -422
  226. package/dist/lib/browser/profiles.js +0 -1157
  227. package/dist/lib/browser/refs.d.ts +0 -89
  228. package/dist/lib/browser/refs.js +0 -191
  229. package/dist/lib/browser/registry.d.ts +0 -70
  230. package/dist/lib/browser/registry.js +0 -252
  231. package/dist/lib/browser/remote-control.d.ts +0 -59
  232. package/dist/lib/browser/remote-control.js +0 -85
  233. package/dist/lib/browser/resolve-target.d.ts +0 -66
  234. package/dist/lib/browser/resolve-target.js +0 -264
  235. package/dist/lib/browser/runtime-state.d.ts +0 -291
  236. package/dist/lib/browser/runtime-state.js +0 -584
  237. package/dist/lib/browser/secret-ref.d.ts +0 -10
  238. package/dist/lib/browser/secret-ref.js +0 -14
  239. package/dist/lib/browser/service.d.ts +0 -812
  240. package/dist/lib/browser/service.js +0 -4693
  241. package/dist/lib/browser/stream.d.ts +0 -17
  242. package/dist/lib/browser/stream.js +0 -72
  243. package/dist/lib/browser/task-index.d.ts +0 -78
  244. package/dist/lib/browser/task-index.js +0 -199
  245. package/dist/lib/browser/types.d.ts +0 -647
  246. package/dist/lib/browser/types.js +0 -61
  247. package/dist/lib/browser/upload.d.ts +0 -29
  248. package/dist/lib/browser/upload.js +0 -298
  249. package/dist/lib/daemon/browser-ipc-service.d.ts +0 -20
  250. package/dist/lib/daemon/browser-ipc-service.js +0 -46
  251. package/dist/lib/daemon/browser-task-reap-service.d.ts +0 -14
  252. package/dist/lib/daemon/browser-task-reap-service.js +0 -26
  253. package/dist/lib/fleet-shared-repo-sync.d.ts +0 -55
  254. package/dist/lib/fleet-shared-repo-sync.js +0 -490
@@ -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';
@@ -90,6 +91,24 @@ export function parseRunPickerMarkers(agentSpec) {
90
91
  export function hostTargetGiven(options) {
91
92
  return [options.host, options.device, options.on, options.computer].filter((v) => !!v);
92
93
  }
94
+ /**
95
+ * Turn a host target that names THIS machine into an explicit local pin: the
96
+ * host-family flags are cleared and `local` is set, so no dispatch path
97
+ * self-SSHes and the bare-interactive default (`bareInteractiveRunDefaultsToDeviceAuto`)
98
+ * sees a decided placement. Returns true when the pin was applied. Pure over
99
+ * the `isSelf` predicate so the rule is testable without a device registry.
100
+ */
101
+ export function pinLocalWhenTargetIsSelf(options, isSelf) {
102
+ const targets = hostTargetGiven(options);
103
+ if (targets.length === 0 || !targets.every(isSelf))
104
+ return false;
105
+ options.host = undefined;
106
+ options.device = undefined;
107
+ options.on = undefined;
108
+ options.computer = undefined;
109
+ options.local = true;
110
+ return true;
111
+ }
93
112
  /**
94
113
  * Return every option whose selection semantics conflict with an account
95
114
  * choice (`agent#`). Device routing is deliberately absent: the marker rides
@@ -118,6 +137,8 @@ export function runAccountPickerConflicts(options) {
118
137
  */
119
138
  export function runDevicePickerConflicts(options) {
120
139
  const conflicts = hostTargetGiven(options).map((h) => `--device ${h}`);
140
+ if (options.local)
141
+ conflicts.push('--local');
121
142
  if (options.lease)
122
143
  conflicts.push('--lease');
123
144
  if (options.box)
@@ -145,6 +166,11 @@ export { RUN_AUTO_KEYWORD };
145
166
  * fleet. Pure so the pinning matrix is unit-testable.
146
167
  */
147
168
  export function runAutoDefaultsToAffinity(options, env = process.env) {
169
+ // An explicit local pin (--local, --where local, --device <this machine>)
170
+ // is a decided host layer with an empty host set; read it here so the
171
+ // `run auto` and bare-interactive defaults share one rule.
172
+ if (options.local)
173
+ return false;
148
174
  if (hostTargetGiven(options).length > 0)
149
175
  return false;
150
176
  if (env.AGENTS_RUN_AUTO_HOST_RESOLVED === '1')
@@ -639,7 +665,8 @@ export function registerRunCommand(program) {
639
665
  .option('--until <signal>', 'Loop stop condition. `signal` reads <runDir>/loop-signal.json {continue,reason} each iteration; absent or continue:false stops (fail-closed). Loop only.')
640
666
  .option('--interval <dur>', 'Loop delay between iterations ("0" back-to-back, "30m" paces). Loop only.')
641
667
  .option('--where <spec>', 'Where this run\'s body executes (one placement door): local | device:<name> | auto | lease[:backend] | cloud[:provider]. Expands to --device/--lease/--cloud. Do not combine with those flags. See docs/00-concepts.md#placement.')
642
- .option('-D, --device <name>', 'Offload this run onto another machine over SSH — a registered device, or user@host. Pass "auto" to pick the least-loaded reachable device where the requested agent is installed and signed in, keeping the run local when no remote is better, or "interactive" for the machine pinned as interactive.host (the box a human is sitting at). Same as --where device:<name>. See `agents devices`.')
668
+ .option('--local', 'Run on this machine. A bare interactive run otherwise places itself like --device auto; --local pins it here. Same as --where local or --device <this machine>.')
669
+ .option('-D, --device <name>', 'Offload this run onto another machine over SSH — a registered device, or user@host. Pass "auto" to pick the least-loaded reachable device where the requested agent is installed and signed in, keeping the run local when no remote is better, or "interactive" for the machine pinned as interactive.host (the box a human is sitting at). Naming this machine runs locally, no SSH. Same as --where device:<name>. See `agents devices`.')
643
670
  .option('--remote-cwd <dir>', "Explicit device working directory for --device runs, used VERBATIM (overrides --cwd; usually --cwd suffices — it re-roots a local-home path onto the remote home). Pass a single-quoted '$HOME/…' or a valid remote absolute path; a local ~ expands here and won't exist there (/Users/you vs /home/you).")
644
671
  .option('--no-follow', 'With --device, dispatch detached and return immediately (track via `agents hosts ps/logs`).')
645
672
  .option('--any', 'With --device <cap> (a capability tag), pick any matching device instead of erroring when several match.')
@@ -680,9 +707,9 @@ export function registerRunCommand(program) {
680
707
  agents run claude "fix lint errors in src/" --mode edit
681
708
 
682
709
  # Interactive (TUI): a bare run places itself like --device auto (a fleet
683
- # worker, TUI forwarded over SSH); pin the device to stay on this machine
710
+ # worker, TUI forwarded over SSH); --local keeps it on this machine
684
711
  agents run claude
685
- agents run claude --device zion # stay local (or pick this machine in claude@)
712
+ agents run claude --local # stay local (or pick this machine in claude@)
686
713
 
687
714
  # Pick a signed-in account/version for only this run (# = account picker)
688
715
  agents run claude#
@@ -790,10 +817,10 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
790
817
  Interactive placement: a bare 'agents run <harness>' (no prompt, real TTY)
791
818
  places itself like --device auto — a fleet worker runs it, with the TUI
792
819
  forwarded over SSH. Headless runs (any prompt, --json, no TTY) are
793
- unchanged: they run in place. To stay on this machine, pass
794
- --device <this machine>, or pick this machine (listed first) in the
795
- '<harness>@' device picker. When placement finds no healthy device the
796
- run fails loud and names the local spellings.
820
+ unchanged: they run in place. To stay on this machine, pass --local
821
+ (--device <this machine> means the same), or pick this machine (listed
822
+ first) in the '<harness>@' device picker. When placement finds no
823
+ healthy device the run fails loud and names the local spelling.
797
824
 
798
825
  Fallback: --fallback codex,antigravity retries on rate-limit failure via /continue handoff. Each entry accepts @version.
799
826
 
@@ -891,6 +918,11 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
891
918
  const { placementFromRunFlags, expandPlacementToRunFlags, PlacementError } = await import('../lib/placement.js');
892
919
  try {
893
920
  const placement = placementFromRunFlags(options);
921
+ // An explicit local placement (--local, --where local) must outlive
922
+ // this block: expansion yields no host flag, and the bare-interactive
923
+ // default below reads an empty host set as "decide for me".
924
+ if (placement.kind === 'local' && placement.source !== 'default')
925
+ options.local = true;
894
926
  if (options.where) {
895
927
  const expanded = expandPlacementToRunFlags(placement);
896
928
  if (expanded.host !== undefined)
@@ -918,6 +950,16 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
918
950
  throw err;
919
951
  }
920
952
  }
953
+ // --device naming this machine (short id, MagicDNS name, loopback, or the
954
+ // `interactive` sentinel pinned here) is a local run, not a self-SSH: the
955
+ // hop probes its own login shell and times out under load. Pinned here,
956
+ // ahead of every placement gate, so the bare-interactive default sees an
957
+ // explicit local choice rather than an empty host set.
958
+ {
959
+ const { isSelfHost } = await import('../lib/devices/self-host.js');
960
+ const { isDeviceInteractive, resolveInteractiveDevice } = await import('../lib/devices/interactive-host.js');
961
+ pinLocalWhenTargetIsSelf(options, (name) => isSelfHost(isDeviceInteractive(name) ? resolveInteractiveDevice() ?? name : name));
962
+ }
921
963
  // Cloud refinement flags without the placement are a typo, not a no-op.
922
964
  if (!options.cloud) {
923
965
  const { cloudFlagsWithoutCloud } = await import('./run-cloud.js');
@@ -1101,13 +1143,13 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
1101
1143
  const { sessionRecoveryDestinationMatches, sessionRecoveryPeer, } = await import('../lib/session/recovery.js');
1102
1144
  const sourceMachine = resolvedResumeSource.machine;
1103
1145
  const sourcePeer = sessionRecoveryPeer(resolvedResumeSource);
1104
- const explicitPlacement = hostTargetGiven(options).length > 0;
1146
+ const explicitPlacement = hostTargetGiven(options).length > 0 || options.local === true;
1105
1147
  if (sourcePeer && !explicitPlacement) {
1106
1148
  options.host = sourcePeer;
1107
1149
  }
1108
1150
  else if (sourcePeer && explicitPlacement && !hostTargetGiven(options).some((host) => sessionRecoveryDestinationMatches(resolvedResumeSource, host))) {
1109
1151
  console.error(chalk.red(`Session ${resolvedResumeSource.shortId} must recover on ${sourcePeer}, where its conversation state is stored; ` +
1110
- `the requested device was ${hostTargetGiven(options).join(', ')}.`));
1152
+ `the requested device was ${hostTargetGiven(options).join(', ') || 'this machine'}.`));
1111
1153
  process.exit(1);
1112
1154
  }
1113
1155
  // Recovery is resolved on the device that owns the transcript. A remote
@@ -1246,8 +1288,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
1246
1288
  if (!defaultPlacement)
1247
1289
  throw err;
1248
1290
  console.error(chalk.red(err.message));
1249
- const { machineId } = await import('../lib/machine-id.js');
1250
- console.error(chalk.gray(`Run here instead: agents run ${runBaseAgentName} --device ${machineId()}`));
1291
+ console.error(chalk.gray(`Run here instead: agents run ${runBaseAgentName} --local`));
1251
1292
  process.exit(1);
1252
1293
  }
1253
1294
  if (!options.quiet && result.banner) {
@@ -2641,6 +2682,37 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
2641
2682
  // the only `continue` is the preflight exhaustion handoff below, which
2642
2683
  // consumes one entry of the `--fallback` spec before retrying as that
2643
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
+ };
2644
2716
  preflight: for (;;) {
2645
2717
  if (!accountPickerRequested && !configuredAccount && (!version || strategy !== 'pinned' || options.balanced || explicitStrategy)) {
2646
2718
  if (version) {
@@ -2778,6 +2850,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
2778
2850
  // Keep the rotation so mid-run failover can still cascade across
2779
2851
  // the other (stale) healthy accounts after a real rejection.
2780
2852
  rotationResult = resolved.rotation;
2853
+ await applyPickedCandidate(selected);
2781
2854
  if (!options.quiet) {
2782
2855
  const identity = selected.accountLabel || 'signed-in account';
2783
2856
  process.stderr.write(chalk.gray(`[agents] no fresh usage for any ${agent} account — you picked ${identity} · ${agent}@${selected.version}\n`));
@@ -2796,12 +2869,13 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
2796
2869
  if (resolved.rotation) {
2797
2870
  launchSignedIn = resolved.rotation.picked.signedIn;
2798
2871
  launchEmail = resolved.rotation.picked.email;
2872
+ await applyPickedCandidate(resolved.rotation.picked);
2799
2873
  }
2800
2874
  // A balanced/available pick of a PROVIDER account (setup-token /
2801
2875
  // API-key) carries `providerAccount`. Resolve its env through the
2802
2876
  // same `resolveSpawnAccount` path an explicit `--account` uses, so
2803
2877
  // exec injects the credential; a native pick has no providerAccount
2804
- // and runs from its own version home unchanged (RUSH-3182).
2878
+ // and resolved its slot home in applyPickedCandidate above.
2805
2879
  const pickedProviderAccount = resolved.rotation?.picked.providerAccount;
2806
2880
  if (pickedProviderAccount) {
2807
2881
  try {
@@ -3521,6 +3595,24 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
3521
3595
  // Governance chokepoint (#347): every dispatched run finalizes here.
3522
3596
  // ONE tamper-evident audit record per run — non-fatal by contract.
3523
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
+ }
3524
3616
  // First-successful-run star nudge (one-time, non-nagging). Only on a
3525
3617
  // clean run, and never when output is machine-readable/quiet.
3526
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)
@@ -246,7 +246,7 @@ function renderRoutineRows({ jobs, scheduler, overdueSet, link, now, local = tru
246
246
  : lastStatus === 'missed' ? chalk.magenta
247
247
  : chalk.gray;
248
248
  const overdueTag = overdueSet.has(job.name) ? chalk.yellow(' (overdue)') : '';
249
- // Append the concrete reason a routine did not complete (auth_failed, wedged,
249
+ // Append the concrete reason a routine did not complete (auth_failed,
250
250
  // blocked, missed) right in the Last Status cell, so the list answers "why"
251
251
  // without a drill-in. Only for non-completed local runs; peer rows stay blank.
252
252
  const reason = latestRun ? runFailureReason(latestRun) : null;
@@ -650,12 +650,29 @@ function routineMatchesQuery(job, q) {
650
650
  .toLowerCase()
651
651
  .includes(q);
652
652
  }
653
- /** Friendly one-liners for the claim a `skipped` run lost. */
653
+ /**
654
+ * Friendly one-liners for the claim a `skipped` run lost — the reasons whose
655
+ * message is static. `active_run` is dynamic (it names the live run that owns
656
+ * the slot and when it started), built in {@link runFailureReason}: since
657
+ * PHNX-4116 there is no "wedged" daemon state, so an overlapping routine reads
658
+ * as `blocked` on the live run, not "wedged".
659
+ */
654
660
  const SKIP_REASON_LABEL = {
655
- active_run: 'wedged: a prior run is still active',
656
661
  duplicate_slot: 'duplicate slot (already fired)',
657
662
  wrong_owner: 'pinned to another device',
658
663
  };
664
+ /**
665
+ * A run id is an ISO timestamp with `:`/`.` replaced by `-` (`slotRunId` /
666
+ * `generateRunId`), so it reverses losslessly to the run's start time. Returns
667
+ * the ISO string, or null when the id is not that shape (a hand-set id).
668
+ */
669
+ function runIdToStartIso(runId) {
670
+ const m = /^(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d{3})Z$/.exec(runId);
671
+ if (!m)
672
+ return null;
673
+ const iso = `${m[1]}T${m[2]}:${m[3]}:${m[4]}.${m[5]}Z`;
674
+ return Number.isFinite(Date.parse(iso)) ? iso : null;
675
+ }
659
676
  /**
660
677
  * The short, human reason a run did not simply complete — for inline display in
661
678
  * the list/detail so "why did it fail" needs no dig into the run dir. Prefers the
@@ -670,6 +687,21 @@ export function runFailureReason(run) {
670
687
  const one = s.replace(/\s+/g, ' ').trim();
671
688
  return one.length > 80 ? one.slice(0, 79) + '…' : one;
672
689
  };
690
+ // An overlap skip names the live run it lost to (PHNX-4116): `blocked: run
691
+ // <id> active since <t>`, not the removed "wedged" wording. The structured
692
+ // `activeRunId` is preferred; a legacy record without it falls back to the
693
+ // errorMessage (which embeds the same id) so nothing regresses to vaguer text.
694
+ if (run.skipReason === 'active_run') {
695
+ if (run.activeRunId) {
696
+ const since = runIdToStartIso(run.activeRunId);
697
+ return since
698
+ ? `blocked: run ${run.activeRunId} active since ${since}`
699
+ : `blocked: run ${run.activeRunId} still active`;
700
+ }
701
+ if (run.errorMessage)
702
+ return compact(run.errorMessage);
703
+ return 'blocked: a prior run is still active';
704
+ }
673
705
  if (run.errorMessage)
674
706
  return compact(run.errorMessage);
675
707
  if (run.status === 'blocked' && run.readiness) {
@@ -751,8 +783,8 @@ function buildRoutineDetail(job, scheduler, now) {
751
783
  : chalk.yellow(run.status);
752
784
  const dur = run.completedAt ? ` ${formatRunDuration(run.startedAt, run.completedAt)}` : '';
753
785
  // Surface WHY a run did not complete, inline, so "looking at status" does not
754
- // require digging into the run dir. auth_failed / OAuth-revoked, a wedged
755
- // active-run skip, or a readiness block all live on the RunMeta already.
786
+ // require digging into the run dir. auth_failed / OAuth-revoked, an
787
+ // active-run overlap skip, or a readiness block all live on the RunMeta already.
756
788
  const reason = runFailureReason(run);
757
789
  const why = reason ? chalk.gray(` — ${reason}`) : '';
758
790
  lines.push(` ${run.startedAt} ${status}${dur}${why}`);
@@ -1941,7 +1973,7 @@ export function registerRoutinesCommands(program) {
1941
1973
  chalk.gray(` ${run.startedAt}`) +
1942
1974
  chalk.gray(formatRunDuration(run.startedAt, run.completedAt)) +
1943
1975
  (run.exitCode !== null && run.exitCode !== undefined ? chalk.gray(` exit ${run.exitCode}`) : ''));
1944
- // The structured reason (auth_failed, blocked readiness, wedged skip) — the
1976
+ // The structured reason (auth_failed, blocked readiness, overlap skip) — the
1945
1977
  // report/stdout tail below often buries or omits it, so name it up front.
1946
1978
  const logsReason = runFailureReason(run);
1947
1979
  if (logsReason)
@@ -2300,9 +2332,7 @@ export function registerRoutinesCommands(program) {
2300
2332
  console.log(chalk.bold('Scheduler\n'));
2301
2333
  const stateLabel = schedulerState === 'running'
2302
2334
  ? chalk.green('running')
2303
- : schedulerState === 'wedged'
2304
- ? chalk.red('wedged')
2305
- : chalk.gray('stopped');
2335
+ : chalk.gray('stopped');
2306
2336
  console.log(` Status: ${stateLabel}`);
2307
2337
  console.log(` Daemon: ${status.running ? chalk.green('running') : chalk.gray('stopped')}`);
2308
2338
  if (status.pid)
@@ -2316,9 +2346,6 @@ export function registerRoutinesCommands(program) {
2316
2346
  const jobs = listAllJobs();
2317
2347
  const enabled = jobs.filter((j) => j.enabled);
2318
2348
  console.log(` Routines: ${enabled.length} enabled / ${jobs.length} total`);
2319
- if (status.state === 'wedged') {
2320
- console.log(chalk.red('\n The shared daemon is wedged (heartbeat stale). Restart deliberately with: agents daemon restart'));
2321
- }
2322
2349
  if (schedulerState === 'running' && enabled.length > 0) {
2323
2350
  const scheduler = new JobScheduler(async () => { });
2324
2351
  scheduler.loadAll();
@@ -1,6 +1,6 @@
1
1
  import type { Command } from 'commander';
2
2
  import type { SessionMeta } from '../lib/session/types.js';
3
- export declare const MARKDOWN_RENDER_AGENTS: readonly ["claude", "codex", "gemini", "antigravity", "opencode", "grok", "rush", "hermes", "kimi", "droid", "cursor"];
3
+ export declare const MARKDOWN_RENDER_AGENTS: readonly ["claude", "codex", "antigravity", "opencode", "grok", "rush", "hermes", "kimi", "droid", "cursor"];
4
4
  export type ReasoningMode = 'omit' | 'fold' | 'include';
5
5
  /** Build one shareable Markdown document from the canonical preview and event model. */
6
6
  export declare function renderSessionMarkdownDocument(session: SessionMeta, options?: {
@@ -14,7 +14,6 @@ import { selectSessions } from './sessions-export.js';
14
14
  export const MARKDOWN_RENDER_AGENTS = [
15
15
  'claude',
16
16
  'codex',
17
- 'gemini',
18
17
  'antigravity',
19
18
  'opencode',
20
19
  'grok',
@@ -15,7 +15,6 @@ export declare const tsxLoaderUrl: string;
15
15
  export declare function writeUpdateCache(tempHome: string): void;
16
16
  export declare function writeClaudeSession(tempHome: string, projectKey: string, sessionId: string, cwd: string, content: string, timestamp: string): void;
17
17
  export declare function writeCodexSession(tempHome: string, sessionId: string, cwd: string, prompt: string, timestamp: string): void;
18
- export declare function writeGeminiSession(tempHome: string, sessionId: string, cwd: string, prompt: string, timestamp: string, version?: string): void;
19
18
  export declare function writeOpenClawSetup(tempHome: string, version?: string): string;
20
19
  export declare function runAgents(args: string[], cwd: string, home: string, envOverrides?: Record<string, string>): import("node:child_process").SpawnSyncReturns<string>;
21
20
  export declare function outputOf(result: {
@@ -1,7 +1,6 @@
1
1
  import { describe } from 'vitest';
2
2
  import * as fs from 'fs';
3
3
  import * as path from 'path';
4
- import * as crypto from 'crypto';
5
4
  import { spawnSync } from 'child_process';
6
5
  import { createRequire } from 'node:module';
7
6
  import { pathToFileURL } from 'node:url';
@@ -121,42 +120,6 @@ export function writeCodexSession(tempHome, sessionId, cwd, prompt, timestamp) {
121
120
  ];
122
121
  fs.writeFileSync(filePath, lines.join('\n') + '\n', 'utf-8');
123
122
  }
124
- export function writeGeminiSession(tempHome, sessionId, cwd, prompt, timestamp, version = '0.29.5') {
125
- const versionHome = path.join(tempHome, '.agents', '.history', 'versions', 'gemini', version, 'home');
126
- const geminiHome = path.join(versionHome, '.gemini');
127
- const projectHash = crypto.createHash('sha256').update(cwd).digest('hex');
128
- const chatsDir = path.join(geminiHome, 'tmp', projectHash, 'chats');
129
- fs.mkdirSync(cwd, { recursive: true });
130
- fs.mkdirSync(chatsDir, { recursive: true });
131
- fs.mkdirSync(path.dirname(path.join(tempHome, '.gemini')), { recursive: true });
132
- const activeGeminiHome = path.join(tempHome, '.gemini');
133
- if (!fs.existsSync(activeGeminiHome)) {
134
- fs.symlinkSync(geminiHome, activeGeminiHome);
135
- }
136
- fs.writeFileSync(path.join(geminiHome, 'projects.json'), JSON.stringify({ projects: [cwd] }), 'utf-8');
137
- fs.writeFileSync(path.join(chatsDir, `session-${timestamp.replace(/[:.]/g, '-')}-${sessionId.slice(0, 8)}.json`), JSON.stringify({
138
- sessionId,
139
- projectHash,
140
- startTime: timestamp,
141
- lastUpdated: timestamp,
142
- messages: [
143
- {
144
- id: `${sessionId}-user`,
145
- timestamp,
146
- type: 'user',
147
- content: [{ text: prompt }],
148
- },
149
- {
150
- id: `${sessionId}-assistant`,
151
- timestamp,
152
- type: 'gemini',
153
- content: 'Investigating now.',
154
- model: 'gemini-3-flash-preview',
155
- tokens: { total: 1234 },
156
- },
157
- ],
158
- }, null, 2), 'utf-8');
159
- }
160
123
  export function writeOpenClawSetup(tempHome, version = '2026.3.8') {
161
124
  const managedHome = path.join(tempHome, '.agents', '.history', 'versions', 'openclaw', version, 'home', '.openclaw');
162
125
  fs.mkdirSync(managedHome, { recursive: true });
@@ -1,20 +1,25 @@
1
1
  /**
2
- * `agents setup browser` — interactive wizard to get `agents browser` working on
3
- * a fresh machine: detect an installed Chromium-family browser, create the
4
- * `default` profile pinned to it, optionally make it this machine's default, and
5
- * point the user at the one manual step we can't automate (first-run + sign-in).
2
+ * `agents setup browser` — get `agents browser` working on a fresh machine:
3
+ * install the standalone `browser` CLI (@phnx-labs/browser-cli), let it detect
4
+ * installed browsers and create machine-local profiles (`browser profiles seed`),
5
+ * then pick this machine's default (`browser use`).
6
6
  *
7
- * Idempotent: re-running shows the current default profile and offers to change
8
- * the pinned browser or re-point the device default.
7
+ * The engine owns profile declarations and browser detection now (PHNX-4101), so
8
+ * this wizard DELEGATES to it rather than crafting profiles itself. What stays
9
+ * agents-cli's is the onboarding flow and the readiness check over the shared
10
+ * `browser.profile` config key both CLIs read/write.
11
+ *
12
+ * Idempotent: `profiles seed` leaves existing profiles untouched, and `use`
13
+ * re-points the device default.
9
14
  */
10
15
  import type { Command } from 'commander';
11
16
  /**
12
- * Interactive browser setup. Returns true if a usable default profile exists
13
- * afterwards, false if none exists / the user backed out. Non-interactively it
14
- * only RECOGNIZES an existing profile — it never creates one (PHNX-3296), since
15
- * silently minting a logged-out `auto-chrome` on a headless box is the exact bug
16
- * that removed; a headless box gets its browser from the fleet hub. Never throws
17
- * on cancel — the `agents setup` hub relies on that.
17
+ * Interactive browser setup. Returns true if a default profile is configured
18
+ * afterwards, false if none is / the user backed out. Non-interactively it only
19
+ * RECOGNIZES an existing default — it never creates one (PHNX-3296), since
20
+ * silently minting a logged-out profile on a headless box is the exact bug that
21
+ * removed; a headless box gets its browser from the fleet hub. Never throws on
22
+ * cancel — the `agents setup` hub relies on that.
18
23
  */
19
24
  export declare function runBrowserWizard(): Promise<boolean>;
20
25
  /** Register `agents setup browser` under the parent `setup` command. */