@phnx-labs/agents-cli 1.22.46 → 1.22.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. package/CHANGELOG.md +663 -0
  2. package/LICENSE +102 -182
  3. package/README.md +96 -49
  4. package/dist/bootstrap.js +4 -2
  5. package/dist/cli/command-registry.d.ts +2 -1
  6. package/dist/cli/command-registry.js +6 -2
  7. package/dist/commands/accounts.d.ts +58 -0
  8. package/dist/commands/accounts.js +243 -25
  9. package/dist/commands/artifacts.js +7 -6
  10. package/dist/commands/attach.js +1 -1
  11. package/dist/commands/auth.js +7 -5
  12. package/dist/commands/browser-sessions-picker.js +9 -7
  13. package/dist/commands/browser.js +711 -62
  14. package/dist/commands/config.d.ts +0 -1
  15. package/dist/commands/config.js +26 -24
  16. package/dist/commands/cost.d.ts +1 -1
  17. package/dist/commands/daemon.d.ts +21 -0
  18. package/dist/commands/daemon.js +147 -12
  19. package/dist/commands/detach.d.ts +13 -0
  20. package/dist/commands/detach.js +11 -8
  21. package/dist/commands/exec.js +107 -53
  22. package/dist/commands/feed-watch.d.ts +2 -0
  23. package/dist/commands/feed-watch.js +28 -0
  24. package/dist/commands/feed.js +27 -0
  25. package/dist/commands/feedback.js +3 -19
  26. package/dist/commands/focus.d.ts +14 -0
  27. package/dist/commands/focus.js +48 -5
  28. package/dist/commands/go.d.ts +3 -0
  29. package/dist/commands/go.js +16 -3
  30. package/dist/commands/insights.d.ts +1 -1
  31. package/dist/commands/insights.js +2 -2
  32. package/dist/commands/inspect.js +2 -2
  33. package/dist/commands/lease.d.ts +1 -1
  34. package/dist/commands/lease.js +7 -6
  35. package/dist/commands/mailboxes.js +4 -3
  36. package/dist/commands/menubar.js +11 -4
  37. package/dist/commands/projects.d.ts +16 -1
  38. package/dist/commands/projects.js +58 -64
  39. package/dist/commands/resume.js +1 -1
  40. package/dist/commands/route.js +4 -4
  41. package/dist/commands/routines.js +4 -3
  42. package/dist/commands/run-broadcast.d.ts +1 -1
  43. package/dist/commands/run-broadcast.js +1 -1
  44. package/dist/commands/sessions-picker-factory.d.ts +2 -1
  45. package/dist/commands/sessions-picker-factory.js +1 -1
  46. package/dist/commands/sessions-picker.d.ts +22 -0
  47. package/dist/commands/sessions-picker.js +48 -9
  48. package/dist/commands/sessions-stop.d.ts +24 -0
  49. package/dist/commands/sessions-stop.js +66 -0
  50. package/dist/commands/sessions-trace.d.ts +82 -0
  51. package/dist/commands/sessions-trace.js +387 -0
  52. package/dist/commands/sessions.js +5 -0
  53. package/dist/commands/setup-secrets.js +23 -0
  54. package/dist/commands/share.d.ts +20 -5
  55. package/dist/commands/share.js +152 -52
  56. package/dist/commands/ssh.d.ts +15 -0
  57. package/dist/commands/ssh.js +243 -18
  58. package/dist/commands/teams.js +34 -2
  59. package/dist/commands/traces.d.ts +2 -0
  60. package/dist/commands/traces.js +184 -0
  61. package/dist/commands/view.d.ts +2 -0
  62. package/dist/commands/view.js +32 -62
  63. package/dist/commands/watchdog.js +9 -14
  64. package/dist/index.d.ts +1 -0
  65. package/dist/index.js +5 -0
  66. package/dist/lib/account-capabilities.d.ts +4 -0
  67. package/dist/lib/account-capabilities.js +13 -9
  68. package/dist/lib/account-registry.d.ts +2 -0
  69. package/dist/lib/account-registry.js +30 -2
  70. package/dist/lib/accounting/rotate.d.ts +9 -0
  71. package/dist/lib/accounting/rotate.js +12 -2
  72. package/dist/lib/accounting/usage.d.ts +84 -6
  73. package/dist/lib/accounting/usage.js +293 -75
  74. package/dist/lib/agent-modes.js +29 -1
  75. package/dist/lib/agent-spec/agents.d.ts +4 -3
  76. package/dist/lib/agent-spec/agents.js +25 -15
  77. package/dist/lib/analytics/mix-commands.d.ts +1 -1
  78. package/dist/lib/analytics/mix-commands.js +2 -2
  79. package/dist/lib/auth-health.d.ts +13 -3
  80. package/dist/lib/auth-health.js +50 -8
  81. package/dist/lib/browser/devices.d.ts +19 -0
  82. package/dist/lib/browser/devices.js +23 -0
  83. package/dist/lib/browser/drivers/ssh.d.ts +16 -2
  84. package/dist/lib/browser/drivers/ssh.js +36 -9
  85. package/dist/lib/browser/ipc.d.ts +1 -1
  86. package/dist/lib/browser/ipc.js +40 -9
  87. package/dist/lib/browser/profiles.d.ts +104 -65
  88. package/dist/lib/browser/profiles.js +277 -165
  89. package/dist/lib/browser/registry.d.ts +39 -0
  90. package/dist/lib/browser/registry.js +149 -0
  91. package/dist/lib/browser/remote-control.d.ts +28 -2
  92. package/dist/lib/browser/remote-control.js +35 -3
  93. package/dist/lib/browser/resolve-target.d.ts +68 -0
  94. package/dist/lib/browser/resolve-target.js +250 -0
  95. package/dist/lib/browser/runtime-state.d.ts +44 -11
  96. package/dist/lib/browser/runtime-state.js +98 -15
  97. package/dist/lib/browser/service.d.ts +65 -9
  98. package/dist/lib/browser/service.js +196 -61
  99. package/dist/lib/browser/sessions-list.d.ts +1 -3
  100. package/dist/lib/browser/sessions-list.js +3 -16
  101. package/dist/lib/browser/task-index.d.ts +70 -0
  102. package/dist/lib/browser/task-index.js +180 -0
  103. package/dist/lib/browser/types.d.ts +12 -1
  104. package/dist/lib/byok-usage.js +2 -2
  105. package/dist/lib/claude-statusline.d.ts +29 -0
  106. package/dist/lib/claude-statusline.js +139 -0
  107. package/dist/lib/cloud/host.d.ts +1 -1
  108. package/dist/lib/cloud/host.js +1 -1
  109. package/dist/lib/cloud/rush.js +1 -1
  110. package/dist/lib/codex-policy.d.ts +11 -1
  111. package/dist/lib/codex-policy.js +20 -5
  112. package/dist/lib/computer/computer-rpc.js +2 -2
  113. package/dist/lib/computer/download.d.ts +15 -15
  114. package/dist/lib/computer/download.js +35 -98
  115. package/dist/lib/computer/ssh-tunnel.d.ts +0 -3
  116. package/dist/lib/computer/ssh-tunnel.js +12 -22
  117. package/dist/lib/config-keys.d.ts +6 -11
  118. package/dist/lib/config-keys.js +18 -19
  119. package/dist/lib/crabbox/cli.d.ts +1 -1
  120. package/dist/lib/crabbox/cli.js +2 -2
  121. package/dist/lib/crabbox/lease.d.ts +9 -0
  122. package/dist/lib/crabbox/lease.js +70 -15
  123. package/dist/lib/crabbox/runtimes.js +1 -1
  124. package/dist/lib/daemon/account-state-daemon-service.d.ts +16 -0
  125. package/dist/lib/daemon/account-state-daemon-service.js +26 -0
  126. package/dist/lib/daemon/browser-ipc-service.d.ts +20 -0
  127. package/dist/lib/daemon/browser-ipc-service.js +46 -0
  128. package/dist/lib/daemon/daemon.d.ts +3 -0
  129. package/dist/lib/daemon/daemon.js +212 -444
  130. package/dist/lib/daemon/device-probe-service.d.ts +26 -0
  131. package/dist/lib/daemon/device-probe-service.js +47 -0
  132. package/dist/lib/daemon/keychain-reap-service.d.ts +17 -0
  133. package/dist/lib/daemon/keychain-reap-service.js +32 -0
  134. package/dist/lib/daemon/monitor-engine-service.d.ts +22 -0
  135. package/dist/lib/daemon/monitor-engine-service.js +29 -0
  136. package/dist/lib/daemon/runner.js +15 -2
  137. package/dist/lib/daemon/secrets-broker-service.d.ts +20 -0
  138. package/dist/lib/daemon/secrets-broker-service.js +66 -0
  139. package/dist/lib/daemon/self-heal-service.d.ts +27 -0
  140. package/dist/lib/daemon/self-heal-service.js +48 -0
  141. package/dist/lib/daemon/service.d.ts +102 -0
  142. package/dist/lib/daemon/service.js +98 -0
  143. package/dist/lib/daemon/session-index-service.d.ts +22 -0
  144. package/dist/lib/daemon/session-index-service.js +40 -0
  145. package/dist/lib/daemon/state-dir-check-service.d.ts +43 -0
  146. package/dist/lib/daemon/state-dir-check-service.js +63 -0
  147. package/dist/lib/daemon/supervisor.d.ts +87 -0
  148. package/dist/lib/daemon/supervisor.js +298 -0
  149. package/dist/lib/daemon/watchdog-service.d.ts +19 -0
  150. package/dist/lib/daemon/watchdog-service.js +40 -0
  151. package/dist/lib/daemon-health.d.ts +15 -0
  152. package/dist/lib/daemon-health.js +29 -5
  153. package/dist/lib/daemon-services.d.ts +8 -1
  154. package/dist/lib/daemon-services.js +55 -0
  155. package/dist/lib/daemon-ticks.d.ts +13 -2
  156. package/dist/lib/daemon-ticks.js +22 -20
  157. package/dist/lib/device-config.d.ts +25 -10
  158. package/dist/lib/device-config.js +90 -39
  159. package/dist/lib/devices/config-migration.d.ts +7 -0
  160. package/dist/lib/devices/config-migration.js +67 -3
  161. package/dist/lib/devices/health.d.ts +27 -2
  162. package/dist/lib/devices/health.js +39 -9
  163. package/dist/lib/devices/interactive-host.d.ts +15 -0
  164. package/dist/lib/devices/interactive-host.js +53 -0
  165. package/dist/lib/devices/registry.d.ts +61 -4
  166. package/dist/lib/devices/registry.js +94 -38
  167. package/dist/lib/devices/stats-cache.d.ts +26 -0
  168. package/dist/lib/devices/stats-cache.js +59 -7
  169. package/dist/lib/devices/worker-pick.d.ts +48 -0
  170. package/dist/lib/devices/worker-pick.js +113 -0
  171. package/dist/lib/exec.d.ts +70 -14
  172. package/dist/lib/exec.js +110 -38
  173. package/dist/lib/feed/answer.d.ts +29 -0
  174. package/dist/lib/feed/answer.js +145 -0
  175. package/dist/lib/feed/attention.d.ts +83 -0
  176. package/dist/lib/feed/attention.js +0 -0
  177. package/dist/lib/feed/feed.d.ts +109 -0
  178. package/dist/lib/feed/feed.js +154 -3
  179. package/dist/lib/feed/pr-status.d.ts +14 -0
  180. package/dist/lib/feed/pr-status.js +47 -0
  181. package/dist/lib/feed/watch.d.ts +59 -0
  182. package/dist/lib/feed/watch.js +180 -0
  183. package/dist/lib/fleet/capture.js +6 -0
  184. package/dist/lib/fleet/remote-login.js +8 -3
  185. package/dist/lib/fleet/types.d.ts +17 -0
  186. package/dist/lib/harness/adapter.d.ts +11 -0
  187. package/dist/lib/harness/adapters/claude.js +38 -24
  188. package/dist/lib/harness/adapters/codex.js +2 -4
  189. package/dist/lib/harness/adapters/cursor.js +8 -11
  190. package/dist/lib/helper-download.d.ts +108 -0
  191. package/dist/lib/helper-download.js +194 -0
  192. package/dist/lib/helper-versions.d.ts +52 -0
  193. package/dist/lib/helper-versions.js +58 -0
  194. package/dist/lib/hooks/cache.js +19 -0
  195. package/dist/lib/hooks/install.js +2 -2
  196. package/dist/lib/hooks/match.d.ts +3 -0
  197. package/dist/lib/hooks/match.js +25 -0
  198. package/dist/lib/hosts/credentials.d.ts +1 -1
  199. package/dist/lib/hosts/credentials.js +1 -1
  200. package/dist/lib/hosts/dispatch.d.ts +7 -1
  201. package/dist/lib/hosts/dispatch.js +24 -5
  202. package/dist/lib/hosts/passthrough.d.ts +8 -15
  203. package/dist/lib/hosts/passthrough.js +35 -16
  204. package/dist/lib/hosts/providers/devices.d.ts +1 -1
  205. package/dist/lib/hosts/providers/devices.js +1 -1
  206. package/dist/lib/hosts/reconnect.d.ts +179 -21
  207. package/dist/lib/hosts/reconnect.js +266 -45
  208. package/dist/lib/hosts/registry.js +7 -0
  209. package/dist/lib/installations/migrate.d.ts +4 -5
  210. package/dist/lib/installations/migrate.js +6 -7
  211. package/dist/lib/installations/shims.d.ts +1 -1
  212. package/dist/lib/installations/shims.js +23 -13
  213. package/dist/lib/installations/versions.js +25 -2
  214. package/dist/lib/linear-autoclose.d.ts +1 -1
  215. package/dist/lib/linear-autoclose.js +1 -1
  216. package/dist/lib/loop.d.ts +2 -2
  217. package/dist/lib/loop.js +1 -1
  218. package/dist/lib/menubar/download-menubar.d.ts +47 -0
  219. package/dist/lib/menubar/download-menubar.js +60 -0
  220. package/dist/lib/menubar/install-menubar.d.ts +44 -7
  221. package/dist/lib/menubar/install-menubar.js +105 -23
  222. package/dist/lib/menubar/notify-desktop.d.ts +3 -3
  223. package/dist/lib/menubar/notify-desktop.js +6 -6
  224. package/dist/lib/model-tiers.d.ts +1 -1
  225. package/dist/lib/open-url.d.ts +60 -2
  226. package/dist/lib/open-url.js +167 -12
  227. package/dist/lib/project-probe.d.ts +1 -1
  228. package/dist/lib/project-probe.js +1 -1
  229. package/dist/lib/project-pull.d.ts +1 -1
  230. package/dist/lib/project-pull.js +2 -2
  231. package/dist/lib/projects.d.ts +1 -1
  232. package/dist/lib/projects.js +1 -1
  233. package/dist/lib/refresh-coordinator.d.ts +1 -1
  234. package/dist/lib/refresh-coordinator.js +1 -1
  235. package/dist/lib/sandbox.js +6 -7
  236. package/dist/lib/secrets/download-keychain.d.ts +47 -0
  237. package/dist/lib/secrets/download-keychain.js +70 -0
  238. package/dist/lib/secrets/install-helper.d.ts +20 -1
  239. package/dist/lib/secrets/install-helper.js +44 -3
  240. package/dist/lib/session/active.d.ts +67 -0
  241. package/dist/lib/session/active.js +86 -2
  242. package/dist/lib/session/db.d.ts +11 -1
  243. package/dist/lib/session/db.js +142 -8
  244. package/dist/lib/session/discover.d.ts +23 -1
  245. package/dist/lib/session/discover.js +44 -10
  246. package/dist/lib/session/highlights.d.ts +49 -1
  247. package/dist/lib/session/highlights.js +95 -0
  248. package/dist/lib/session/host-link.d.ts +16 -2
  249. package/dist/lib/session/host-link.js +7 -1
  250. package/dist/lib/session/parse.js +33 -1
  251. package/dist/lib/session/prompt.d.ts +35 -0
  252. package/dist/lib/session/prompt.js +56 -0
  253. package/dist/lib/session/remote/remote-list.d.ts +9 -0
  254. package/dist/lib/session/remote/remote-list.js +19 -2
  255. package/dist/lib/session/remote/watch.d.ts +1 -1
  256. package/dist/lib/session/remote/watch.js +17 -3
  257. package/dist/lib/session/render.js +7 -3
  258. package/dist/lib/session/session-cache.d.ts +53 -0
  259. package/dist/lib/session/session-cache.js +93 -1
  260. package/dist/lib/session/state.js +1 -1
  261. package/dist/lib/session/tool-calls.d.ts +2 -0
  262. package/dist/lib/session/tool-calls.js +1 -1
  263. package/dist/lib/session/trajectory-compare.d.ts +47 -0
  264. package/dist/lib/session/trajectory-compare.js +116 -0
  265. package/dist/lib/session/trajectory-html.d.ts +19 -0
  266. package/dist/lib/session/trajectory-html.js +770 -0
  267. package/dist/lib/session/trajectory-lineage.d.ts +92 -0
  268. package/dist/lib/session/trajectory-lineage.js +195 -0
  269. package/dist/lib/session/trajectory-text.d.ts +37 -0
  270. package/dist/lib/session/trajectory-text.js +284 -0
  271. package/dist/lib/session/trajectory.d.ts +102 -0
  272. package/dist/lib/session/trajectory.js +384 -0
  273. package/dist/lib/session/types.d.ts +15 -0
  274. package/dist/lib/sha256-asset.d.ts +26 -0
  275. package/dist/lib/sha256-asset.js +41 -0
  276. package/dist/lib/share/backend.d.ts +95 -0
  277. package/dist/lib/share/backend.js +139 -0
  278. package/dist/lib/share/delete.d.ts +6 -2
  279. package/dist/lib/share/delete.js +8 -9
  280. package/dist/lib/share/html.d.ts +22 -0
  281. package/dist/lib/share/html.js +88 -0
  282. package/dist/lib/share/provision.d.ts +18 -4
  283. package/dist/lib/share/provision.js +38 -4
  284. package/dist/lib/share/publish.d.ts +39 -15
  285. package/dist/lib/share/publish.js +55 -26
  286. package/dist/lib/share/worker-template.js +171 -11
  287. package/dist/lib/sqlite.js +16 -3
  288. package/dist/lib/ssh-exec.d.ts +65 -0
  289. package/dist/lib/ssh-exec.js +120 -4
  290. package/dist/lib/startup/command-registry.d.ts +3 -1
  291. package/dist/lib/startup/command-registry.js +6 -3
  292. package/dist/lib/startup/dev-build.js +3 -3
  293. package/dist/lib/state.d.ts +2 -2
  294. package/dist/lib/state.js +11 -5
  295. package/dist/lib/subagents-registry.js +1 -1
  296. package/dist/lib/teams/agents.js +9 -0
  297. package/dist/lib/terminal/index.d.ts +1 -1
  298. package/dist/lib/terminal/inject.d.ts +19 -0
  299. package/dist/lib/terminal/inject.js +19 -9
  300. package/dist/lib/terminal/types.d.ts +1 -1
  301. package/dist/lib/terminal/types.js +1 -1
  302. package/dist/lib/tmux/orphan-reap.js +1 -1
  303. package/dist/lib/tmux/session.d.ts +46 -13
  304. package/dist/lib/tmux/session.js +204 -27
  305. package/dist/lib/traces/backend.d.ts +29 -0
  306. package/dist/lib/traces/backend.js +46 -0
  307. package/dist/lib/traces/classify.d.ts +52 -0
  308. package/dist/lib/traces/classify.js +72 -0
  309. package/dist/lib/traces/config.d.ts +10 -0
  310. package/dist/lib/traces/config.js +2 -0
  311. package/dist/lib/traces/provision.d.ts +19 -0
  312. package/dist/lib/traces/provision.js +30 -0
  313. package/dist/lib/traces/sync.d.ts +150 -0
  314. package/dist/lib/traces/sync.js +447 -0
  315. package/dist/lib/traces/worker-template.d.ts +4 -0
  316. package/dist/lib/traces/worker-template.js +159 -0
  317. package/dist/lib/types.d.ts +34 -19
  318. package/dist/lib/types.js +14 -0
  319. package/dist/lib/usage-backoff.d.ts +2 -1
  320. package/dist/lib/usage-backoff.js +60 -19
  321. package/dist/lib/usage-refresh.d.ts +18 -4
  322. package/dist/lib/usage-refresh.js +68 -19
  323. package/dist/lib/watchdog/log.d.ts +1 -1
  324. package/dist/lib/watchdog/log.js +1 -1
  325. package/dist/lib/watchdog/runner.d.ts +36 -45
  326. package/dist/lib/watchdog/runner.js +150 -119
  327. package/dist/lib/watchdog/service.d.ts +0 -1
  328. package/dist/lib/watchdog/service.js +0 -1
  329. package/dist/lib/watchdog/watchdog-agent.d.ts +38 -0
  330. package/dist/lib/watchdog/watchdog-agent.js +59 -0
  331. package/dist/lib/watchdog/watchdog.d.ts +11 -2
  332. package/dist/lib/watchdog/watchdog.js +37 -104
  333. package/dist/lib/workflows.d.ts +1 -1
  334. package/package.json +5 -7
  335. package/dist/commands/usage.d.ts +0 -27
  336. package/dist/commands/usage.js +0 -96
  337. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  338. package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +0 -24
  339. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  340. package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
  341. package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +0 -128
  342. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  343. package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -24
  344. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  345. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +0 -123
  346. package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
  347. package/dist/lib/usage-fleet.d.ts +0 -32
  348. package/dist/lib/usage-fleet.js +0 -125
package/CHANGELOG.md CHANGED
@@ -1,5 +1,668 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.22.49
4
+
5
+ - **Check CLI benchmark numbers into `docs/benchmarks.md` (RUSH-2385).** Commander-bootstrap and audit-hook means from the yosemite-s1 vitest bench (PR #2349) plus pointers to OPT-01/OPT-02 live in git. Linear is not the ledger. Source: `cli/docs/benchmarks.md`.
6
+
7
+ fix(view): keep Claude session/week quota slots aligned, render omitted windows as unavailable, and show only the unlabeled last-active timestamp
8
+
9
+ - **No native binary ships in the npm tarball (RUSH-3100).** `Agents CLI.app` (2.6 MB) and
10
+ `MenubarHelper.app` (3.3 MB) were copied into `dist/` by `build`, listed in `files`, and
11
+ hard-gated by two `prepack` checks — so every Linux and Windows user downloaded 5.9 MB of
12
+ signed macOS bundles, and the tarball could only be packed on a Mac holding them. All
13
+ three are gone; helpers are fetched on demand from their own release tags and verified
14
+ exactly as before (sha256 + Developer ID team + notarization + designated-requirement
15
+ pin). Unpacked tarball: 21,337,724 → 15,629,419 bytes. `build` still clears any stale
16
+ bundle from `dist/` so an upgrade cannot leave one behind. This is what lets an ordinary
17
+ release be produced without a signing Mac: the prepack gates required the signed `.app`s
18
+ to be present at pack time, which is why the attestation was macOS-only. Both gate
19
+ scripts are retained — they remain the right check for cutting a *helper* release.
20
+ Source: `cli/package.json`.
21
+ - **The attestation producer no longer seeds signed helper apps into its worktree
22
+ (RUSH-3100).** That seeding existed only because `prepack` refused to pack without them,
23
+ so it was the workaround for the coupling above; its comments claimed "the prepack gates
24
+ still decide … fails the pack exactly as before", which stopped being true the moment
25
+ those gates were removed. Source: `cli/scripts/release-attestation-produce.sh`.
26
+
27
+ - **Trace storage can now be deployed from the CLI (RUSH-3140).** `agents traces setup` provisions the isolated `agents-traces` R2 bucket and Worker, uploads the canonical private trace Worker, binds its write and Phoenix identity secrets, enables its workers.dev route, and maps `traces.agents-cli.sh`.
28
+
29
+ - **`agents traces sync` emits the rich Phoenix Evals index shard (RUSH-3142 M3).** The per-device `index.json` now carries duration/error stats, ranked sessions needing attention, derived topic counts, and structured tool-failure counts split into real failures, repository guards, and auto-mode permission denials recorded on failed tool calls. Topic classification uses only session metadata plus tool mix and is lazily cached in a self-healing `session_topics` table keyed by transcript mtime + size; per-session trajectory JSON and the incremental upload ledger are unchanged. Source: `apps/cli/src/lib/traces/{classify,sync}.ts`, `apps/cli/src/lib/session/db.ts`.
30
+
31
+ - **The offloaded suite no longer unpacks inside your DotAgents repo.** `scripts/test.sh --device` shipped the tree to `~/.agents/test-runs/agents-cli` — but `~/.agents` is itself a git repo, so anything in the suite calling `git rev-parse --show-toplevel` resolved to `~/.agents` instead of the shipped tree (`release-manifest.test.ts` went looking for `~/.agents/native/computer-mac/Sources` and four tests failed with a confusing "helper input missing"), and the tree showed up as `?? test-runs/` in the operator's own repo status on every worker. The tree now lands in `~/.cache/agents-cli/test-runs/tree`, which has no git ancestor — the same reason `sandbox.sh`'s `~/workspaces` choice never hit this. The shipped tree is still given its own blank git repo, because parts of the suite resolve paths from a repo root and would otherwise hard-fail. Source: `apps/cli/scripts/test.sh`, `apps/cli/scripts/bound-repo-root.sh`.
32
+
33
+ - **`scripts/test.sh` — the suite is offloaded by default and can no longer land on your machine by accident.** The full vitest suite pins a box for several minutes, and the only offloaded path used to be the `test:remote` package.json alias. Offloading was opt-in *at each call site* because `scripts/sandbox.sh` took a hand-composed command string rather than a verb, so every call site opted out: `scripts/build.sh` and `scripts/release-attestation-produce.sh` both ran `bun run test` on whatever machine invoked them. New `scripts/test.sh` is the one entry point — it offloads to a crabbox by default, takes `--device <box>` to run on any fleet Linux box, and runs locally only under an explicit, loudly-warned `--here`. When the offload target is unavailable it **fails naming the exact `--device` command** instead of silently falling back. `build.sh` and the attestation producer now both call it (the producer gains `--test-device` / `--test-here`), so a macOS signing box no longer doubles as the test runner just because a native helper needs notarizing. `sandbox.sh` gains the sibling projects' verb vocabulary (`sandbox.sh test`), which also fixes its bare default running `bun install && bun run test` at the monorepo root, where no test script exists. Source: `apps/cli/scripts/test.sh`, `apps/cli/scripts/sandbox.sh`, `apps/cli/scripts/build.sh`, `apps/cli/scripts/release-attestation-produce.sh`.
34
+
35
+ - **A helper-only GitHub release no longer blocks the CLI release train.** `release-attestation-produce.sh` seeded its helper manifest from `gh release list --limit 1` — unconditionally the newest release. But not every release is a CLI release: the Windows computer-helper workflow publishes helper-only releases (assets `computer-helper-win.exe` + `.sha256`) into the same `v<version>` tag namespace. One of those shadows the last real CLI release, the manifest seed silently misses, every helper then reads as "changed", and the producer hard-fails on `computer-mac` — a helper it never rebuilds — with `helper computer-mac input changed but this producer never rebuilds it`. Observed live: `v1.22.48` (helper-only, published 09:54Z) shadowed `v1.22.47` and blocked the release. The seed now walks back through recent releases to the newest one that actually carries a `release-manifest.json`. Source: `apps/cli/scripts/release-attestation-produce.sh`.
36
+
37
+ - **Watchdog, device-probe, self-heal, keychain-reap, and state-dir-check are now supervised daemon services (RUSH-3193 #3).** These five bare `setInterval` timers in `runDaemon()` are now `PeriodicService`s registered on `ServiceSupervisor`, alongside secrets-broker, browser-ipc, account-state, session-index, and monitors — each gets the same per-tick deadline, error boundary, and park/backoff circuit breaker, and now reports measured health through `agents daemon services` instead of an inferred `running (unsupervised)` label. `scheduler` and `webhook-receiver` remain outside the supervisor (neither fits the `PeriodicService` shape — see `cli/src/lib/daemon/AGENTS.md`). Source: `cli/src/lib/daemon/watchdog-service.ts`, `device-probe-service.ts`, `self-heal-service.ts`, `keychain-reap-service.ts`, `state-dir-check-service.ts`, `cli/src/lib/daemon/daemon.ts`.
38
+
39
+ - **`agents daemon services` shows every service's live health, with live enable/disable/restart (RUSH-3193 #4).** The daemon `services` view now reports state / last-run / consecutive-failures / last-error for every supervised service (secrets-broker, browser-ipc, monitor-engine, account-state, session-index), not just the two socket services; `--json` gains the full service list while keeping the existing `secretsBroker`/`browserIpc` fields. `agents daemon services enable`/`disable`/`restart <id>` now take effect live through the supervisor instead of requiring a daemon restart. Source: `cli/src/commands/daemon.ts`, `cli/src/lib/daemon-services.ts`, `cli/src/lib/daemon/supervisor.ts`.
40
+
41
+ - **Self-heal's staggered boot-time tick is restored (RUSH-3193 #17).** The `ServiceSupervisor` migration (RUSH-3193 #3) fired every periodic service's first tick immediately at daemon start, including self-heal — dropping the ~30s post-boot stagger the old inline timer used so shims/PATH could settle before self-heal's first sweep, without making daemon launch itself busy. `PeriodicService` gains an optional `startupDelayMs` (default 0, no change for other services); `SelfHealService` sets it to 30s. Also fixes a stale comment on `watchdog-service.ts` claiming an SSH fan-out that `runWatchdogPass` does not do (it's host-local). Source: `cli/src/lib/daemon/service.ts`, `cli/src/lib/daemon/supervisor.ts`, `cli/src/lib/daemon/self-heal-service.ts`, `cli/src/lib/daemon/watchdog-service.ts`.
42
+
43
+ - **Usage refresh is per-device — the `usage.primary-host` broadcast is gone (RUSH-3193 #15).** Every host now reads its own usage directly from the provider APIs, so the old model where one primary device fetched fleet usage and SSH-broadcast a token-free derived envelope for subscribers to import is removed. The `usage.primary-host` config key (and its `interactive.host` fallback for usage) is deleted, along with the publisher/subscriber role split; each daemon runs the local usage refresh on every host. Source: `cli/src/lib/daemon-ticks.ts`, `cli/src/lib/usage-fleet.ts`, `cli/src/lib/usage-refresh.ts`, `cli/src/lib/device-config.ts`, `cli/src/lib/config-keys.ts`.
44
+
45
+ - **Daemon service supervisor: per-service contract, error boundaries, deadlines, health (RUSH-3193 P1).** The daemon's background services used to be bare `setInterval` closures sharing one event loop — a throw escaping a tick's local try/catch killed the whole daemon, and a hung tick (an unbounded SSH/keychain await) latched its overlap guard forever, silently freezing that one service for the daemon's life (observed ~51h). A new `DaemonService`/`PeriodicService` contract plus a `ServiceSupervisor` give every registered service its own timer, a per-service error boundary that can never propagate to `process.exit`, and a hard per-tick deadline that always releases the overlap guard even when the tick itself hangs; repeated failures park the service and retry it with exponential backoff while every sibling service keeps ticking, and `supervisor.health()` reports state/last-run/consecutive-failures for every registered service. The session-index warm service is migrated onto it as the first proof of concept — `agents daemon services list` now shows it as `session-index`. Other background services are unchanged; they migrate in follow-up PRs. Source: `cli/src/lib/daemon/{service,supervisor,session-index-service}.ts`, `cli/src/lib/daemon/daemon.ts`, `cli/src/lib/daemon-services.ts`.
46
+
47
+ - **Managed Cursor accounts now use the account they display (RUSH-3196).** Cursor runs and direct version aliases force Cursor's HOME-relative file credential store (`~/.cursor/auth.json`) inside the selected version home instead of silently falling through to one macOS Keychain login. Stale `cli-config.json` metadata no longer counts as signed in; existing Keychain credentials are left untouched and an unseeded managed version asks for Cursor's normal login flow.
48
+
49
+ - **Grok billing events now reach `agents view` (RUSH-3197).** The daemon publishes freshly collected local-event snapshots instead of rejecting every `last_seen` source as a failed refresh. Current Grok weekly meters cache normally; an expired or missing event tells the operator which `grok@version` to run once rather than leaving a silent plan-only gap.
50
+
51
+ - Rebuilt the internal documentation as a compact architecture and decisions corpus, with command syntax remaining generated from the CLI.
52
+
53
+ ### Added
54
+
55
+ - `agents devices pick` — prints the device automatic placement would choose for
56
+ offloaded machine work: the least-loaded reachable POSIX box from the same auto
57
+ pool `agents run --device auto` draws from, so `role=worker` / `role=personal`
58
+ marks govern it. The name alone goes to stdout so scripts can consume it
59
+ (`box="$(agents devices pick)"`); candidates and load go to stderr. `--json`
60
+ adds every candidate and each exclusion reason. Fails loud with the excluded
61
+ devices named when no worker is eligible — it never answers "run it locally".
62
+
63
+ ### Changed
64
+
65
+ - `scripts/test.sh` now **auto-picks a fleet worker by default** instead of
66
+ requiring crabbox. `--device auto` says the same thing explicitly, `--crabbox`
67
+ selects the previous disposable-crabbox path, and `--device <box>` / `--here`
68
+ are unchanged. The old default needed the crabbox binary plus provider
69
+ credentials, so the no-argument invocation failed on any box without them. An
70
+ auto-pick that lands on the local machine runs in place without the `--here`
71
+ warning when that machine is itself a pool worker.
72
+ - `scripts/release-attestation-produce.sh` gains `--test-crabbox`, so all three of
73
+ `test.sh`'s lanes are reachable from a release. It previously exposed only
74
+ `--test-device` and `--test-here`, leaving a release on a box with no fleet worker
75
+ in reach unable to ask for the disposable crabbox it can still use.
76
+
77
+ ### Changed
78
+
79
+ - **Native helper downloads are decoupled from the CLI release.** `helper-download.ts`
80
+ used to build `releases/download/v${cliVersion}/<asset>`, keying every helper to the
81
+ CLI's own tag. That was coupled in both directions: a CLI release had to re-stage every
82
+ helper asset onto its new tag or the download 404'd, and a helper fix could not reach
83
+ anyone without cutting a CLI release. Helpers now publish to their own tags
84
+ (`menubar/v1.0.0`, `keychain/v1.0.0`, `computer-mac/v…`) and the CLI records a
85
+ per-helper **floor** in `helper-versions.ts` — the build it was tested against, and the
86
+ immutable tag it falls back to offline.
87
+
88
+ ### Fixed
89
+
90
+ - **The keychain helper download could never have worked.** GitHub rewrites a space in a
91
+ release-asset name to a dot on upload, so an asset staged as `Agents CLI.app.zip` is
92
+ served as `Agents.CLI.app.zip` while the CLI requested the spaced name — a permanent
93
+ 404. The asset is now published as **`Agents_CLI.app.zip`**: a name GitHub preserves
94
+ verbatim, chosen deliberately rather than mirroring GitHub's rewrite (matching
95
+ `Agents.CLI.app.zip` would work only for as long as that normalization rule holds).
96
+ `helperAssetUrls` additionally refuses any spec whose asset name contains a space,
97
+ rather than minting a URL that cannot resolve. The extracted bundle directory keeps its
98
+ space (`Agents CLI.app`) — that is the on-disk bundle name macOS and the TCC grant key
99
+ on, and it is unchanged.
100
+
101
+ - **The Windows helper exe is decoupled too.** `ssh-tunnel.ts` is a separate
102
+ implementation that never routed through `helper-download.ts`, and it still keyed
103
+ `computer-helper-win.exe` to `v${cliVersion}` — the identical coupling, meaning every
104
+ CLI release had to re-stage a ~165 MB binary or the download 404'd. It now resolves
105
+ `computer-win/v<x.y.z>` from the same floor table.
106
+
107
+ ### Removed
108
+
109
+ - **`release.sh` no longer stages helper fallback assets onto the CLI's own tag, and no
110
+ longer gates a release on one.** With helpers resolving from their own tags, those
111
+ assets were work nothing reads — and the keychain one was republished on every release
112
+ under `Agents CLI.app.zip`, a name GitHub rewrites to a dot and no client can fetch.
113
+ The hard `die` on a missing `ComputerHelper.app.zip` is gone with them: it would have
114
+ failed an otherwise-good release over an asset the runtime no longer consults.
115
+
116
+ ### Fixed
117
+
118
+ - **The menu-bar helper build could emit a Developer-ID-signed bundle that was never
119
+ notarized, and say nothing.** Notarization was gated on
120
+ `[ "$MODE" = "release" ] && [ "$SIGN_ID" != "-" ]`, so any invocation without the
121
+ `release` argument on a machine holding a Developer ID produced a real signed bundle,
122
+ passed every self-test, and exited 0 — while `spctl` reported
123
+ `rejected / source=Unnotarized Developer ID` with no stapled ticket. Gatekeeper on
124
+ macOS 26+ rejects such a bundle as "damaged" and crashes AppKit at launch. The three
125
+ credential guards meant to fail loud on missing Apple creds also lived *inside* that
126
+ branch, so they were unreachable in exactly the case that needed them. The gate is now
127
+ the signature (`[ "$SIGN_ID" != "-" ]`): a Developer-ID signature always implies
128
+ notarization, whatever the mode. Ad-hoc builds are unchanged — they cannot be notarized
129
+ by construction and `prepack` already stops them shipping.
130
+
131
+ A credential-less **debug** build on a machine that holds a Developer ID now signs
132
+ ad-hoc (with the `.dev` bundle id) instead of failing: `SIGN_ID` is auto-detected from
133
+ the keychain, so demanding notary creds unconditionally would have broken the local
134
+ build loop on every signing-capable Mac. The downgrade is resolved before `Info.plist`
135
+ is written — a late one would emit an ad-hoc bundle carrying the *production* bundle id
136
+ and poison the user's Accessibility grant. `MENUBAR_HELPER_SIGN_ID=-` remains the
137
+ explicit opt-out and is now documented in the script.
138
+
139
+ ### Fixed
140
+
141
+ - **The full test suite was red on `main`, and pull-request CI could not see it.** A
142
+ top-level `await import('bun:sqlite')` in `src/lib/sqlite.ts` made that module
143
+ un-lowerable to CJS, so every test that spawns a subprocess through `tsx` died at
144
+ transform time with `Top-level await is currently not supported with the "cjs" output
145
+ format` — 49 failures across 7 files, none of which touch SQLite. Both runtime arms now
146
+ load through `createRequire`, so there is no top-level await to lower. The specifier is
147
+ held in a variable rather than written inline, keeping the off-Bun collector from trying
148
+ to resolve a module that only exists under Bun (what the old `as string` cast did for
149
+ the dynamic import).
150
+
151
+ This mattered beyond the suite: `release-attestation-produce.sh` runs the **full** suite
152
+ fail-closed, while PR CI runs only the tests selected for a diff. A failure outside every
153
+ recent diff's selection is therefore invisible until release time, where it blocks the
154
+ attestation and with it any publish.
155
+
156
+ ### Fixed
157
+
158
+ - **Restored operator-facing master-key custody guidance to `docs/secrets.md`.** The
159
+ docs reset (`2d96d05d6`) rewrote the page from a manual into an architecture summary and
160
+ dropped the section that names the machine-local key path
161
+ (`~/.agents/.secrets-key/passphrase`), states that resolution is non-interactive, points
162
+ headless sync at `AGENTS_SYNC_PASSPHRASE` rather than the master key, and **forbids
163
+ exporting the master key from a shell rc file** — the exact advice that caused RUSH-1968
164
+ on seven machines. Four `docs-hygiene` guards had been failing ever since, which is the
165
+ alarm working as designed rather than test staleness.
166
+ - **Restored the `**Severity rubric**` section to `docs/observability.md`**, removed by the
167
+ same reset. `doctor-findings.test.ts` pins this prose copy against `FINDING_SEVERITY`, so
168
+ its absence meant the docs-cannot-drift guard was simply not running.
169
+ - **`add-targets-user-repo.test.ts` no longer enumerates the `state.js` surface.** Its
170
+ partial mock hand-listed every export, so adding `getCliVersionCachePath` to `state.ts`
171
+ took the whole file down with `No "getCliVersionCachePath" export is defined on the mock`.
172
+ It now spreads `importActual` and overrides only the directory resolvers it redirects, so
173
+ a new export cannot break it again.
174
+
175
+ - **`release.sh --with-helpers` — an ordinary release publishes the CLI and nothing else
176
+ (RUSH-3216).** Two couplings survived helpers moving to their own tags, and both are now
177
+ behind this opt-in (default off): staging `ComputerHelper.app.zip` onto the CLI's
178
+ `v<version>` tag, which publishes an asset no client requests; and
179
+ `release-manifest.sh require`, which re-derives every helper's input digest and aborts
180
+ when one moved without a rebuild — failing a good CLI release over Swift the CLI does not
181
+ ship. Source: `cli/scripts/release.sh`.
182
+ - **A new `release.sh` flag is inert until it is merged (RUSH-3216).** `release.sh` execs
183
+ `release-worktree.sh`, which checks out `origin/<default>` and re-runs the script from
184
+ there, so the second parse is the merged copy and an unmerged flag dies as `unknown
185
+ flag` even though the local parser handles it. Pass `--orchestration-phase` to skip the
186
+ re-exec when exercising a new flag pre-merge. Source: `cli/scripts/release.sh`.
187
+
188
+ - **Managed share URLs use your Phoenix handle, not a UUID (RUSH-3224).** `agents artifacts share` now publishes to `share.agents-cli.sh/<handle>/<slug>` where `<handle>` is the local-part of the signed-in email (`muqsitnawaz@gmail.com` → `muqsitnawaz`), and the default slug is `<readable>-<16hex>` (a short view-id). Owner metadata is still the Phoenix userId; the Worker 403s a PUT to someone else's handle and 409s if two accounts collide on the same local-part. HTML publishes rewrite Chrome-saved `file://…#section` TOC links to in-page hashes and inline local sibling images as data URIs, so a self-contained plan (including a 1 MB page with embedded screenshots) stays viewable after upload. Source: `cli/src/lib/share/{backend,worker-template,html,publish}.ts`.
189
+
190
+ - **A remote interactive `--device` run now prints its session id as it connects (RUSH-3227).** When Claude (or a resume) already has a real id before the TTY is taken, stderr shows `Session <uuid> on <host>` and `agents sessions resume <uuid>` so the id exists while the connection is live, not only after OpenSSH closes. A launch id is not printed as a session id. Source: `cli/src/lib/hosts/reconnect.ts`, `cli/src/commands/exec.ts`.
191
+
192
+ - **A remote interactive session that closes now prints its id (RUSH-3227).** After `agents run --device` (including `--raw`), `sessions focus`/`resume` over SSH, or a remote tmux attach ends, the CLI writes the full session id and `agents sessions resume <id>` under OpenSSH's `Shared connection … closed.` line, so a dropped tab is not a bare shell. Auto-reconnect still swallows the 255 it will retry; this fires only when the user is actually back locally. Source: `cli/src/lib/hosts/reconnect.ts`, `cli/src/commands/{exec,go,focus,resume,attach,sessions}.ts`, `cli/src/lib/session/remote/remote-list.ts`.
193
+
194
+ - **Re-pin the keychain helper to the binary that actually shipped in 1.22.47.** `scripts/verify-keychain-helper.sh` compared `bin/Agents CLI.app` to `beb02d…`, but `@phnx-labs/agents-cli@1.22.47` and `keychain/v1.0.0` both contain `a49080…`. Linux `npm pack` therefore died at prepack and blocked every release attestation. Same class of fix as #835 / #912. Source: `cli/scripts/Agents CLI.app.sha256`.
195
+
196
+ - **A session with no observer no longer reports as healthy (RUSH-3125).** `classifyHostLink` decides a session's host link from two local signals — the owning IDE window's heartbeat and tmux's attached-client count — and when it had *neither* it fell through to `connected`. That is the wrong default for a detector: the rows it silently blessed were exactly the ones with no observer (a bare terminal, a team spawn, a cloud task, or any `--device` session whose pane lives on another machine, since every signal it reads is local). There is now an `unknown` link, which says the question is open rather than answering it; `connected` is returned only on positive evidence — a counted client or a republishing window. `unknown` is not a loss signal: it never promotes a status, and it no longer clears a derived `attached` presence, so a plain terminal session you are sitting in keeps its marker. Everything else is unchanged and deliberately so — a deliberate `agents sessions detach` is still excluded first, `tmuxClients === 0` is still authoritative, an absent client count still never reads as zero, and per SES-18a a `running` session still keeps its status. Source: `cli/src/lib/session/host-link.ts`, `cli/src/lib/session/active.ts`, spec SES-18a.
197
+
198
+ - **A tmux status bar can show which agent session a pane is running — and never a fabricated one.** `agents run` wraps an interactive agent in a tmux session named `ag-<agent>-<8 chars>`, built from `(options.sessionId ?? randomUUID()).slice(0, 8)` (`cli/src/lib/exec.ts`). Reading those 8 characters as a session id is quietly wrong: the launcher pre-assigns an id only for Claude, so on a box running two harnesses `ag-claude-c8c4a2c8` carries a real, resumable short id while `ag-codex-364cd550` carries a throwaway that resolves to nothing — and the two are indistinguishable on sight. `createSession` now stamps two session-scoped tmux user options, `@ag_session_id` and `@ag_agent`, so a format like `#{?#{@ag_session_id},#{@ag_session_id},#{@ag_agent}}` renders a real handle where one exists and falls back to the harness name where none does, instead of printing something `agents sessions <id>` would reject. The id is published only when the harness actually *received* it — a new `isHarnessKnownSessionId` mirrors the two branches in `buildExecCommand` that put an id on the command line (a native resume on any harness with a `resume` spec, and Claude's create-with-`--session-id`), so `agents run codex --session-id X` without `--resume`, where X never reaches codex, correctly publishes nothing. This is the in-tmux counterpart to the existing name-parsing recovery tier (`shortIdFromName` + `resolveNamesToSessionIds` in `cli/src/lib/session/active.ts`), which validates a short id against the session DB; a tmux format cannot do a DB lookup, so the option carries the answer instead. Best-effort throughout: a failed stamp never fails a launch, and an unset option renders empty. Source: `cli/src/lib/tmux/session.ts`, `cli/src/lib/exec.ts`.
199
+
200
+ ### traces: drift signal for topic buckets
201
+
202
+ `agents traces sync` now computes a **drift signal** for each topic bucket by comparing today's error and stall rates against a 14-day rolling history stored inside the shard. Buckets that cross a 0.20 absolute-delta threshold are marked `degrading` or `improving`; the rest are `stable`. Buckets with fewer than 3 historical days are skipped to avoid noise on fresh deployments.
203
+
204
+ The shard now carries two new fields:
205
+
206
+ - `bucketHistory` — rolling 14-day array of per-bucket `BucketStats` (errorRate, stallRate)
207
+ - `driftSignals` — `DriftSignal[]` for the current sync, sorted by errorDelta descending
208
+
209
+ `--dry-run --out <dir>` seeds history from the previously written `index.json` in the output directory, so successive local runs accumulate signal without hitting the network.
210
+
211
+ - `agents traces sync --dry-run --out <dir>` computes the derived trace shards
212
+ from your local `sessions.db` and writes them to a directory — no Phoenix
213
+ sign-in, no worker, no upload — so you can verify your real trajectories
214
+ before the hosted path is wired.
215
+ - The per-session drill-down (`sessions/<id>.json`) now emits a `SessionDetail`
216
+ (a `meta` summary — spanMs/turns/tools/errorCount/tokens/cost/outcome/repo —
217
+ plus a plain-language `whereItWentWrong`) that the Phoenix Evals console
218
+ consumes directly, instead of the raw internal trajectory shape.
219
+
220
+ ## 1.22.48
221
+
222
+ - **On your own machine, every Claude run uses your normal login — not the
223
+ worker setup-token (RUSH-2395).** The credential now follows **device role**,
224
+ not run mode. A device marked `config.role: personal` (your interactive box —
225
+ set it with `agents devices role <name> personal`) authenticates every Claude
226
+ run from its per-version login, whether it opens a TUI or is a headless
227
+ one-shot like `agents run claude "fix the bug"`. Before, the choice keyed only
228
+ on whether a prompt was present ("this opens a TUI", not "a human is present"),
229
+ so a headless run on your laptop grabbed the reserved `auth`-bundle setup-token
230
+ and took the session off your login — surfacing as `/status` reporting
231
+ `Auth token: CLAUDE_CODE_OAUTH_TOKEN`. The setup-token stays the credential for
232
+ headless runs on **worker** devices (and unmarked boxes), which have no
233
+ keychain login to defer to. Routines follow the same role gate. Source:
234
+ `apps/cli/src/lib/harness/adapters/claude.ts`,
235
+ `apps/cli/src/lib/exec.ts`, `apps/cli/src/lib/daemon/runner.ts`,
236
+ `apps/cli/src/lib/device-config.ts`.
237
+
238
+ - **A hand-cut release no longer dead-ends on a fresh attestation store or a lost `RELEASE_ATTESTATION_DIR` (RUSH-2970).** Three traps that cost roughly eight failed release attempts across 1.22.43-1.22.46 are fixed at the source. (1) `release-attestation-produce.sh` now seeds `release-manifest.json` from the last published GitHub release before falling back to an empty one — a fresh store had no recorded `computer-mac` inputDigest, so the helper loop read "input changed" and told the operator to run `publish-computer-helper-mac.sh`, which does not write a manifest, so the instruction looped forever on a byte-identical helper. (2) `release-worktree.sh` now exports `RELEASE_ATTESTATION_DIR` to the caller's store when it is unset: the throwaway release worktree resolved its own empty `.release-attestations`, so `require` reported `missing exact attestation key` with `?` for every key component — which reads like a key mismatch rather than a wrong directory. An explicit export still wins. (3) `publish-computer-helper-mac.sh` now sources `headless-sign-context.sh` itself: its own documented invocation (`agents secrets exec apple.com -- ...`) injected the notary creds but left the Developer ID signing keychain locked, so a headless publish died in `codesign` with `errSecInternalComponent` unless the operator knew to source that context first — which nothing said. On a Mac without the release-box pass files it is a no-op. Source: `apps/cli/scripts/{release-attestation-produce,release-worktree,publish-computer-helper-mac}.sh`.
239
+
240
+ - **Session rows show what the agent DID, and dead crash-orphans stop piling up (RUSH-3011).**
241
+ The `agents sessions watch --json` row (which the AGI EXT Fleet reads) now carries a
242
+ **recap** so a row reads as the agent's work, not its stale first prompt: `title` follows
243
+ a best-source-wins ladder — a `/rename`/harness `label` (which also holds an agent-generated
244
+ title) → the last agent line → the first-prompt topic — and `recapSource`
245
+ (`'label'|'last'|'prompt'`) names which rung won, so a session that produced work shows an
246
+ agent-derived line. The first
247
+ user turn is cleaned into `userPromptClean`/`userPromptKind` (with `lastAgentLine` exposed)
248
+ so a screenshot path folds to `[image]`, a pasted `$ cmd` to the command, and a `/skill`
249
+ install path to `/<name>` — path noise never shows on the "You" line — and the recap card's
250
+ `Prompt:` line uses the same cleaning. Separately, a crash-leaked `--device` tunnel session
251
+ that is genuinely dead and days-stale (`abandoned` + dead pid) is folded OUT of the
252
+ reconnectable set (`resumable: false`, `recovery: null`), so the "Needs reconnecting" list
253
+ stops ballooning; a live pid (idle-but-unfinished) or a recently-closed session is never
254
+ reaped. Source: `apps/cli/src/lib/session/{prompt,active,render}.ts`,
255
+ `apps/cli/src/lib/session/remote/watch.ts`, `apps/cli/docs/sessions.md`.
256
+
257
+ - **Keep generated session names in `label`, separate from the first-prompt
258
+ `topic` (RUSH-3011).** Claude `ai-title` and `/rename` events now populate the
259
+ canonical name field consumed by fleet clients, and an empty live metadata
260
+ record can no longer erase an existing generated title or launch handle.
261
+ Source: `apps/cli/src/lib/session/discover.ts`,
262
+ `apps/cli/src/lib/session/db.ts`.
263
+
264
+ - fix(teams): observe AgentManager's fire-and-forget init rejection so a lost init race (dir removed mid-mkdir) fails the awaiting caller instead of crashing the process as an unhandled rejection — the flake that failed two fully-green release attestation runs
265
+
266
+ - **Per-account 429 backoff + auth verdicts derived from fresh usage fetches — the last four 'usage unavailable' accounts recover (RUSH-3036).** After the 20-min probe throttle shipped, four of eight Claude accounts still showed `usage unavailable`: the 429 backoff was per-PROVIDER, so the first account to hit its quota in a refresh pass parked `claude` entirely and every account after it in the loop's fixed iteration order was never fetched — the same accounts starved every pass. The backoff is now scoped per account (`usage-backoff.ts` records `claude@<usageKey>.<deadline>`; a provider-wide penalty still parks everyone, and callers with no account identity keep the old behavior). Second, the auth-health probe no longer spends a second request on the same endpoint: a fresh (≤20 min) successful usage snapshot is the same authenticated request with the same shared setup-token, so `probeAuthHealth` derives a `live` verdict from it and only fires a network probe when no fresh evidence exists — roughly halving fleet endpoint load. `agents devices ping --strict` passes `forceLive`, which skips the derivation entirely — its contract stays a genuinely live request that surfaces `revoked` immediately — and derivation additionally requires a local credential (`signedIn`), so a fleet-imported snapshot on an unsigned home never reads as `live`. The auth probes themselves are account-scoped too, so a probe 429 parks only its account. Source: `apps/cli/src/lib/usage-backoff.ts`, `apps/cli/src/lib/accounting/usage.ts` (`UsageOptions.usageScope`), `apps/cli/src/lib/auth-health.ts` (`verdictFromFreshUsage`), `apps/cli/src/lib/usage-refresh.ts`, `apps/cli/src/lib/daemon-ticks.ts`.
267
+
268
+ - **`agents menubar doctor` no longer claims a healthy just-upgraded helper is "running the OLD binary" (RUSH-3038).** The stale-process check compared a pid start time parsed from `ps -o lstart`, which prints whole **seconds**, against the installed bundle's sub-second mtime. `restartMenubarHelperAfterSwap` restarts the helper within the same second as the swap that triggered it, so a correct upgrade always tripped the strict `<`: on zion at 1.22.46 the pid started at `…353000` and the bundle was written at `…353700` — 700 ms apart, one second — and doctor reported the helper stale and told the user their Accessibility grant "may not be trusted", after an upgrade that had already restarted it onto the new binary. The bundle mtime is now truncated to the same whole second before comparing, so a pid that started in the swap's own second reads fresh while a pid a full second or more older is still caught. Source: `apps/cli/src/lib/menubar/install-menubar.ts`.
269
+
270
+ - **`agents run codex --mode auto` stops prompting for approval.** Codex declared only `plan`/`edit`/`skip`, so `--mode auto` silently degraded to `edit` — and `edit` is `approval_policy="on-request"`, which asks before every command the sandbox denies. Every unattended caller that passes `--mode auto` (AGI EXT's agent launches, teams teammates, routines) therefore sat on an approval dialog nobody was there to answer. Codex now has a real `auto`: the `agents-auto` permission profile, identical sandbox to `agents-edit` (workspace, `~/.agents`, the regenerable toolchain cache roots, network on), with `approval_policy="never"` — a sandbox-denied command surfaces to the model as a plain command failure it can work around instead of stopping the run. Autonomy is the approval axis only: `auto` does not widen the sandbox, and `--mode skip` remains the only mode that removes it. Interactive shim launches (a bare `codex` at your own terminal) still pin `edit`, where an approval prompt is the useful outcome. Source: `apps/cli/src/lib/codex-policy.ts`, `apps/cli/src/lib/harness/adapters/codex.ts`, `apps/cli/src/lib/agent-spec/agents.ts`, `apps/cli/src/lib/exec.ts`.
271
+
272
+ - **Hooks can be scoped to a permission mode: new `matches.permission_mode` predicate (RUSH-3050).** A `hooks.yaml` / subrule `hooks.yaml` entry may declare `matches: { permission_mode: plan }` (string or array) so the hook fires only when the harness reports that mode in the hook's stdin JSON (`permission_mode`, or Grok-style `permissionMode`). The predicate is deliberately fail-open on absence: a harness that never reports a mode (Codex, and most others — Claude Code is the one that does) keeps firing the hook, so declaring it never silently disables a hook fleet-wide. Enforced in both the TS reference evaluator (`shouldFire()`) and the generated shim gate, pinned together by the existing conformance suite. Motivating case: the plan-presentation Stop hook fired on every stop in every session and false-positived on ordinary answers that mentioned plans; it can now declare `permission_mode: plan`. Source: `apps/cli/src/lib/types.ts` (`HookMatches`), `apps/cli/src/lib/hooks/match.ts`, `apps/cli/src/lib/hooks/cache.ts`, `apps/cli/docs/hooks.md`.
273
+
274
+ - **New device config key `description` — a one-line, fleet-synced answer to
275
+ "what is this box FOR".** `agents devices config <name> description "gpu box
276
+ — cuda 12.4"` stores a single-line summary in the device's tracked
277
+ `~/.agents/devices/<name>/agents.yaml` under `config.description`, so it
278
+ syncs to every machine via `agents repo push/pull` exactly as `role` does,
279
+ and any box may set it for any device (`shared` visibility). Because it
280
+ will be shown by the device-list renderer it is validated: a newline is rejected outright and
281
+ the value is capped at 80 characters — over-long input fails with a readable
282
+ error naming the cap, never a silent truncation. `notes` is unchanged: it
283
+ stays the appended list of long-form operator scratch, and both key
284
+ descriptions now state the distinction. Source:
285
+ `apps/cli/src/lib/device-config.ts`.
286
+
287
+ - **Dismissing a discovered device is now a fleet-wide fact (RUSH-3062).** The ignore-list lived at `~/.agents/.history/devices/ignored.json` — a gitignored, per-machine path — so a node dismissed on one box kept re-surfacing as an auto-discovery suggestion on every other box. It now lives in the tracked, synced central `~/.agents/agents.yaml` under `fleet.ignored`, a list of `{ name, ignoredAt, ignoredOn }` entries (who dismissed it, when, on which box — the read side for a future `agents devices ignored`). An ignored node is deliberately not a device, so it has no per-device doc; central agents.yaml is the established home (it already carries `fleet.defaults`). Writes go through the existing `updateMeta` path (`withMetaLock` + atomic write), and a malformed `fleet.ignored` block is still a hard error rather than a silently-emptied set. A one-shot migration folds any existing `ignored.json` into `fleet.ignored` (union by name, legacy `updatedAt` kept as `ignoredAt`) and removes the legacy file only after the central write lands; running it twice is a no-op. The `loadIgnored` / `isIgnored` / `addIgnored` / `removeIgnored` signatures are unchanged, so no call site churn. Source: `apps/cli/src/lib/devices/registry.ts`, `apps/cli/src/lib/devices/config-migration.ts`, `apps/cli/src/lib/state.ts`.
288
+
289
+ ---
290
+ type: feat
291
+ ---
292
+
293
+ Collect root-disk capacity in the existing device health probe and cache static hardware specifications for seven days.
294
+
295
+ ---
296
+ type: feat
297
+ ---
298
+
299
+ Put real capacity in the default `agents devices list` — a `spec` cell (cores / total RAM / total disk), a `disk` used column beside load and mem, and the per-device `description` as the tail column (truncated first on narrow terminals, then role; the numbers never truncate). Add `agents devices describe <name> <text>` (task-shaped sugar over the `description` config key) and `agents devices ignored` (dismissed nodes — when, and which machine dismissed them). `devices list --json` gains `description` and disk totals inside `health` (additive only).
300
+
301
+ - **Interactive `agents run` now spawns directly by default (RUSH-3066).** `tmux.enabled` is an explicit per-device opt-in for addressable panes instead of the default wrapper: the wrap's benefit (a unique `%pane` so `agents sessions --active` can tell co-located agents apart, and `agents focus` re-attaching without forking) accrues to the control plane, while the cost — mouse, scrollback and clipboard behavior — is paid by whoever is at the terminal. Turn it back on per device with `agents config set devices.<name>.tmux on`. `--no-tmux` and `--disable-tmux` remain compatible no-ops while wrapping is off.
302
+
303
+ ---
304
+ type: fix
305
+ ---
306
+
307
+ Make opt-in tmux-wrapped runs default to mouse interaction, OSC 52 system clipboard integration, and 20,000 lines of scrollback while preserving the user's tmux configuration.
308
+
309
+ - **`agents sessions trace --no-redact` reads honestly in compare and lineage too (RUSH-3077).** The single-trajectory footer already said `Unredacted (local only)` under `--no-redact`, but the compare (`--compare`) and lineage (`--tree`) renderers hardcoded a `Secret-redacted` footer regardless of the flag — claiming a redaction that never happened. Both now share the single-trajectory labelling logic (`redactionLabel`): compare derives it from the two compared trajectories' own `redacted` state (a mixed pair still reads redacted, never a false safe-to-share claim), and lineage takes the trace's redaction flag. Redacted-by-default output is unchanged. Source: `apps/cli/src/lib/session/trajectory-html.ts`, `apps/cli/src/commands/sessions-trace.ts`.
310
+
311
+ - **BREAKING: the top-level `agents usage` command is removed (RUSH-3079).** It was a second, worse surface for the same data `agents view` already renders — per-account quota/rate-limit usage with the account, version, and auth state beside it — and in practice most harnesses printed `stale` or "does not publish usage data" there. Use `agents view` (optionally `agents view <agent>`, `--refresh` for an explicit collection, `--json` for scripts). The name is retired: `agents usage` fails as an unknown command and never auto-corrects into a live command. The shared usage library behind `agents view` and rotation/backoff is untouched. Source: `apps/cli/src/commands/usage.ts` (deleted), `apps/cli/src/cli/command-registry.ts`, `apps/cli/src/lib/startup/command-registry.ts`.
312
+
313
+ - **`agents browser` binds `--device` at start (RUSH-3086, RUSH-3087).** `--device` stays on `agents browser start` and is rejected on later verbs; the starting machine records a local `task → device` index so `type`/`click`/`screenshot --task post` resolve the device without repeating the flag. A verb naming an unknown or killed task exits non-zero and lists the open tasks (real URL + device), and never opens a second browser. `screenshot -o /path/x.png` now writes exactly that path — the daemon still sandboxes its own cache, and the CLI copies out. Source: `apps/cli/src/lib/browser/task-index.ts`, `apps/cli/src/commands/browser.ts`.
314
+
315
+ ---
316
+ type: fix
317
+ ---
318
+
319
+ - **An offline device keeps its spec cell (RUSH-3096).** `agents devices list`
320
+ rendered a down box as a bare `ci-runner-fsn1 linux offline`, because a failed
321
+ probe wrote a row with no hardware facts over the cached one. Cores, total RAM,
322
+ and root-disk capacity now survive an unreachable probe and render beside the
323
+ offline marker — `ci-runner-fsn1 linux 8c 16G 500G offline`. Load, memory, and
324
+ disk-used stay blank (there is no current reading for a box that did not answer),
325
+ and the Fleet capacity footer still counts only reachable devices.
326
+ Source: `apps/cli/src/lib/devices/stats-cache.ts`, `apps/cli/src/commands/ssh.ts`.
327
+
328
+ - **The macOS keychain broker (`Agents CLI.app`) now has a download-on-demand fallback (RUSH-3100).** The signed + notarized keychain helper still ships inside the npm tarball, but a release now also publishes an `Agents CLI.app.zip` GitHub release asset per tagged version. A machine whose tarball lacks the bundle fetches that asset for its exact CLI version on the explicit `agents setup secrets` path (and the upgrade self-heal) — verified by sha256 + codesign + Developer ID Team + notarization before install — reusing the shared helper-download machinery already behind the computer and menu-bar helpers. The secrets hot path (`getKeychainHelperPath`) stays synchronous and network-free: when no bundled helper exists it now fails loud pointing at `agents setup secrets` rather than a bare "reinstall". Unlike the menu-bar helper it pins no designated requirement, since keychain items are gated by the access-group entitlement + biometry, not a DR-keyed grant. Source: `apps/cli/src/lib/secrets/download-keychain.ts`, `apps/cli/src/lib/secrets/install-helper.ts`, `apps/cli/scripts/release.sh`.
329
+
330
+ - **The menu-bar helper can be fetched on demand, mirroring the computer helper (RUSH-3100 Stage N).** `MenubarHelper.app` is now ALSO published as a signed + notarized `MenubarHelper.app.zip` GitHub release asset, so a machine whose npm tarball lacks the bundle can fetch + verify it from the `v<version>` release. `agents menubar enable` / `agents menubar setup` download it when no bundled or local `.app` is present; a downloaded bundle is verified (sha256 + `codesign --verify --deep --strict` + Developer ID Team `2HTP252L87` + a **designated-requirement pin** on bundle id `com.phnx-labs.agents-menubar` + `spctl` notarization) before it is ever installed — the DR pin is what keeps the Accessibility grant alive across upgrades, so a substituted bundle is refused loud, never silently accepted. The `.app` STILL ships inside the npm tarball this release (bundled-first, download-fallback); the download runs only on the explicit enable/setup path, never on the every-invocation startup self-heal, which stays synchronous and no-ops when no local source exists. The download and verify machinery is shared with the computer helper in a new `src/lib/helper-download.ts` (no duplicated code path). Source: `apps/cli/src/lib/helper-download.ts`, `apps/cli/src/lib/menubar/download-menubar.ts`, `apps/cli/src/lib/menubar/install-menubar.ts`, `apps/cli/src/lib/computer/download.ts`, `apps/cli/scripts/release.sh`.
331
+
332
+ - **Fix: the menu-bar helper's Accessibility row now reads "AGI Menu", not "MenubarHelper" (RUSH-3101).** Since the RUSH-3076 grant-persistence fix, the compiled executable was still named `MenubarHelper`, and launchd execs that Mach-O directly — bypassing LaunchServices name resolution — so macOS fell back to `CFBundleExecutable` for both the Accessibility list row (blank icon) and the "would like to control this computer" prompt. The executable inside the bundle is now named `AGI Menu`; the bundle folder (`MenubarHelper.app`), bundle id (`com.phnx-labs.agents-menubar`), and designated requirement are all unchanged, so the existing Accessibility grant is unaffected by the upgrade. Every basename-matching check moved with the rename: `classifyMenubarProcesses`/`installedExecutablePath` (`install-menubar.ts`), the `orphan-reap.ts` protected-service regex, and the Swift side's `SingleInstance.swift`/`ChildProcessSelfTest.swift`, now reading a shared `HelperIdentity.executableName` constant. Source: `apps/cli/menubar/Package.swift`, `apps/cli/menubar/scripts/build.sh`, `apps/cli/menubar/Sources/MenubarHelper/HelperIdentity.swift`, `apps/cli/src/lib/menubar/install-menubar.ts`, `apps/cli/src/lib/tmux/orphan-reap.ts`.
333
+
334
+ - **Fixed a module-initialization cycle that crashed any entry point reaching `helper-download` first.** `helper-download.ts` imported two pure sha256 helpers (`parseSha256Asset`, `sha256File`) from `computer/ssh-tunnel.ts`, whose import graph runs `browser/drivers/ssh` → `browser/chrome` → `secrets/*` → `secrets/download-keychain`, which imports back into `helper-download` while it is still evaluating — before `EXPECTED_TEAM_ID` is bound. The result was a hard `ReferenceError: Cannot access 'EXPECTED_TEAM_ID' before initialization` thrown from `secrets/download-keychain.ts:45`, which took down the `drift-sync` apply path and the `self-heal` isolated-home sweep (both run real subprocesses) and blocked release attestation, since the producer runs the full suite fail-closed. The two helpers now live in a dependency-free leaf module, `lib/sha256-asset.ts`, so nothing in the download path pulls the ssh/browser/secrets graph in at module-init time. Source: `apps/cli/src/lib/sha256-asset.ts`, `apps/cli/src/lib/helper-download.ts`, `apps/cli/src/lib/computer/ssh-tunnel.ts`.
335
+
336
+ - **`agents accounts label` reads like the task: label by `<harness>@<version>`, or pick from a list (RUSH-3126).** Labeling the account behind a specific install used to require knowing its email — `agents accounts label codex work --account you@example.com` — even though the user is looking at `agents view codex`, which is organized by version. The first argument now also accepts `<harness>@<version>`: `agents accounts label codex@0.146.0 work` labels whatever account is signed in there, and the label stays bound to the account identity, so `codex#work` keeps selecting it after that account moves to a newer install. A bare-harness call with several signed-in logins opens an interactive picker (identity · default marker · versions) instead of erroring; the same account signed into multiple versions counts as ONE login, so it never demands a selector. Non-interactive callers keep the loud error, now naming both script-safe forms, and `--account <email|id>` still works. Passing both `@<version>` and `--account` is refused as contradictory. Source: `apps/cli/src/commands/accounts.ts` (`groupLabelIdentities`).
337
+
338
+ - **Managed artifact sharing: sign in and publish with zero Cloudflare setup (RUSH-3135, Phase 1).** Signed-in users (`agents auth login`) publish to the already-live managed endpoint at `share.agents-cli.sh` — the Phoenix access_token from `phoenix-session.json` is the bearer, so there is no R2 bucket, Worker, or write token to provision. `share status`, `share list`, `share revisions`, and `unshare`/`share delete` resolve through the same `resolveShareBackend` seam, so a managed-only user can see and take down what they just published instead of being told to run `agents artifacts setup`. `share analytics` is a scoped P1 exclusion (Cloudflare Web Analytics is BYO): a managed user gets a message naming that, not the generic "Not configured" error. The Worker verifies `Authorization: Bearer` against Phoenix ID (`GET ${PHOENIX_ID_BASE}/api/v1/auth/me`), stamps `customMetadata.owner` with the verified `userId`, and namespaces the R2 key by that userId so one user cannot write another's prefix. `--visibility public|unlisted` (default `public`) replaces the `--unlisted`/`--private` booleans; those flags stay as hidden aliases mapping to `--visibility unlisted`. Unlisted GET now sends `X-Robots-Tag: noindex` (it was already hidden from the gallery/list). `org` visibility is 400 — that is Phase 2. `--meta owner=` / `visibility=` / `expires-at=` is rejected client-side (those keys are Worker-stamped). BYO Cloudflare is unchanged: a static `WRITE_TOKEN` is still honored when the bearer matches it (checked first, so the platform endpoint may set both principals); `AGENTS_SHARE_BACKEND=byo` or a caller-supplied write token forces the BYO path while signed in. Already-provisioned BYO endpoints need `agents artifacts share update` to pick up the new Worker (template hash changed). Source: `apps/cli/src/lib/share/{backend,worker-template,publish,delete,config}.ts`, `apps/cli/src/commands/share.ts`.
339
+
340
+ - **Managed share deploy wires `PHOENIX_ID_BASE` so Phoenix-bearer auth survives `agents artifacts share update` (RUSH-3138).** Cloudflare's script-upload API clears Worker bindings/secrets wholesale; the managed Worker reads `env.PHOENIX_ID_BASE` to verify bearers at `${PHOENIX_ID_BASE}/api/v1/auth/me`, so every update wiped it and Phoenix PUTs 401'd while BYO `WRITE_TOKEN` kept working. The deploy path now re-applies `PHOENIX_ID_BASE` as a `secret_text` after every script upload (same Secrets API as `WRITE_TOKEN`), sourced from the CLI's `PHOENIX_ID_BASE` (env, else the deployed Phoenix ID service). Pure-BYO deploys do not set it. Source: `apps/cli/src/lib/share/{provision,backend}.ts`, `apps/cli/src/commands/share.ts`.
341
+
342
+ - **Security: the remote browser-control consent gate now covers the whole launch path.**
343
+ `agents browser remote-control off` (the default) was only enforced on the
344
+ `browser start` command, but `navigate`, `click`, `screenshot`, `evaluate` and
345
+ the other page verbs open a browser implicitly when the caller has no live
346
+ task. A `browser navigate --device <box>` therefore opened a browser on a
347
+ machine whose owner never opted in, while `browser start --device <box>` was
348
+ correctly refused. The gate now sits in the daemon at the two points that can
349
+ launch a browser (`BrowserService.start` and the create branch of
350
+ `resolveOrCreateTask`), so every implicit-LAUNCH verb is covered. It does not
351
+ cover *attaching*: a request naming an existing task (`--task`, or the
352
+ single-match-by-caller path) returns before the gate and can drive that task's
353
+ tabs. That is pre-existing and tracked separately — `remote-control off` means
354
+ "no new browser", not "no access". The consent
355
+ marker rides the IPC request rather than the daemon's environment: a daemon
356
+ auto-started by a fleet-remote CLI inherits `AGENTS_FLEET_REMOTE=1`
357
+ permanently, and reading that would have refused every later local drive.
358
+ Source: `src/lib/browser/service.ts`, `src/lib/browser/remote-control.ts`,
359
+ `src/lib/browser/ipc.ts`, `src/lib/browser/types.ts`.
360
+
361
+ - **`agents browser profiles edit`.** An existing profile's description,
362
+ endpoints, secrets, viewport, and binary could not be changed without deleting
363
+ and recreating it — `-d/--description` existed only on `create`, and
364
+ `updateProfile()` had no CLI caller at all. `profiles edit <name>` reuses
365
+ `create`'s flag spellings (minus `-b/--browser`, which keys the on-disk profile
366
+ cache) and validates the merged record, so a binary edit re-resolves the
367
+ browser path and a `--target-filter` edit re-checks the `--electron` gate.
368
+ Source: `src/lib/browser/profiles.ts`, `src/commands/browser.ts`.
369
+ - **`profiles doctor` flags a profile that resolves to the wrong machine's
370
+ browser.** A profile declared by one device but bound to `cdp://localhost:PORT`
371
+ is evaluated on the machine running the command, so the name meant a different
372
+ browser on every box — silently handing an agent a logged-out stranger instead
373
+ of the credentialed profile it asked for. The check now names both the
374
+ declaring device and this one. `ssh://` profiles are unaffected: they address a
375
+ host, so they mean the same browser from anywhere.
376
+ Source: `src/lib/browser/runtime-state.ts` (`identityLoopbackMismatch`).
377
+ - **Fixed: editing a profile's endpoint could collide with itself.** The local
378
+ port scan `createProfile` runs was never applied on update, and applying it
379
+ naively would have failed every edit against the profile's own stored port.
380
+ Extracted as `assertLocalPortFree(profile, { ignore })` and now used by both.
381
+ Source: `src/lib/browser/profiles.ts`.
382
+
383
+ - **`agents browser profiles rename <from> <to>`.** A profile could not be
384
+ renamed at all: `profiles edit` refuses a name change because the name keys the
385
+ on-disk runtime dir, every endpoint/fork dir derived from it, and the
386
+ `browser.profile` pointer. The only route was delete-and-recreate, which
387
+ silently abandons the browser's `--user-data-dir` — where a profile's logins
388
+ live. On a real agent browser that is gigabytes of session state and every
389
+ account it has ever signed into. `rename` moves the config (staying in whichever
390
+ store it already lives in), moves every cache dir belonging to the old name, and
391
+ repoints both `browser.profile` and `browser.viewer` when either pointed there — a dangling `browser.viewer` sends every artifact back to the OS default handler, which is the exact bug the viewer seam was built to fix. Refuses while the profile is in
392
+ use, because moving a `--user-data-dir` out from under a running browser
393
+ corrupts it; refuses when the name exists in BOTH stores, since rewriting one
394
+ would leave the other listed under the old name with its data already moved
395
+ away; and validates every destination BEFORE moving any of them, so a
396
+ collision on the second endpoint cannot strand the first one's logins under a
397
+ name with no config entry. `os` joins `default` as a name a profile may not
398
+ take — it is the reserved `browser.viewer` value meaning the OS handler. Source: `src/lib/browser/profiles.ts`, `src/commands/browser.ts`.
399
+ - **Profile-name validation is shared between `create` and `rename`.** The shape
400
+ rule lived inline in `profiles create`, so a second caller would have accepted
401
+ names `create` rejects. Now `assertRegistrableProfileName`, which also refuses
402
+ `default` — the reserved alias meaning "this machine's configured profile"
403
+ (RUSH-2709), not a name. Source: `src/lib/browser/profiles.ts`.
404
+
405
+ - **User-facing pages open in your configured browser profile, not the OS
406
+ default.** `agents browser navigate` honoured `browser.profile` and nothing
407
+ else did: `agents fleet login`, `agents devices lease`, `agents feedback`,
408
+ `agents sessions trace --open`, and `agents browser sessions --open` each
409
+ shelled straight to `open`/`xdg-open`, so every one of them landed in whatever
410
+ the OS handler happened to be — on a Mac with Arc set as default, all of them
411
+ opened in Arc while the configured Comet profile sat unused. They now route
412
+ through one seam (`showUrl`/`showFile`), which resolves the viewer once.
413
+ This matters beyond tidiness: the configured profile is where the fleet's
414
+ logins accumulate, so a page opened there is one you are already signed in for,
415
+ and a login it acquires is inherited by every later agent.
416
+ The seam does not auto-start the browser daemon: showing a page is a side
417
+ errand, so blocking it on a cold start would be a surprising stall. Daemon
418
+ already running -> the viewer; not running -> the OS handler.
419
+ Source: `src/lib/open-url.ts`.
420
+ - **New `browser.viewer` config key** (device scope) — a profile name, or `os`
421
+ to keep using the OS default handler. Unset follows `browser.profile`.
422
+ Deliberately distinct from `browser.profile`: one is the profile agents drive,
423
+ the other is the browser that shows you a page. Source: `src/lib/device-config.ts`.
424
+ - **New `show` IPC action** — opens a tab bound to no task, so the abandoned-task
425
+ reaper never closes a page you are reading. That is the whole reason it is not
426
+ `navigate`. Screenshots, PDFs and recordings still go to the OS app, where
427
+ Preview and QuickTime are the better viewer.
428
+ Source: `src/lib/browser/service.ts`, `src/lib/browser/ipc.ts`.
429
+ - **New `agents browser show <url|file>`** — the CLI entry point to that seam, so
430
+ external tools (a renderer's `--open`, a script) can show a page in the
431
+ configured profile instead of shelling to `open`. Use it instead of `navigate`
432
+ for anything a person will read: `navigate` binds a task and the reaper closes
433
+ a task's tabs. `--os-browser` forces the OS handler; `--json` reports where it
434
+ landed. Source: `src/commands/browser.ts`.
435
+
436
+ - **`--device interactive` resolves to the machine the human is at.** `--device
437
+ auto` picks a box by load; this picks the one box someone is actually looking
438
+ at, pinned as `interactive.host`. It resolves in the shared host
439
+ matcher rather than per command, so `browser`, `run`, `sessions` and `secrets`
440
+ inherit it; `teams` and `ssh` resolve it explicitly because they leave the
441
+ fleet passthrough before the matcher runs. A few narrower `--device` surfaces
442
+ do not consult the matcher. None of them mis-routes: most fail loud, and
443
+ `devices harnesses` filters to an empty result. Wiring them up is a follow-up.
444
+ It exists because a skill cannot teach a host name: guidance that says
445
+ "deliver it to <box>" is wrong on every other fleet and stale the moment the
446
+ pin changes, so agents were left inferring the target or skipping the step. A
447
+ fixed token is something documentation can state literally and have be correct
448
+ everywhere.
449
+ When no host is pinned it refuses and names the command that fixes it, rather
450
+ than falling back to the local machine — running on a headless worker with
451
+ nobody watching is the exact failure the sentinel prevents, and it would fail
452
+ invisibly. Source: `src/lib/devices/interactive-host.ts`.
453
+
454
+ - **`agents sessions stop <id>` ends a live agent outright, and a tmux-wrapped agent no longer leaves an orphaned dead session behind (graceful shutdown, #5a/#5b).** Two halves of one gap — a single close should tear down every layer (agent → tmux mux → tab):
455
+ - New verb `agents sessions stop <id>` on the session-lifecycle axis (beside `detach`/`attach`): it stops the interactive process and tears down its tmux/mux session — reusing `detach`'s exact `stopInteractive` teardown (`kill-session` when tmux-hosted, else SIGTERM→SIGKILL the pid) and resolution (remote sessions stopped over SSH, cloud/team refused) — but does NOT resume it headless. Use `detach` to keep an agent working unattended, `stop` when the work is over. AGI EXT calls it when a user genuinely closes an agent tab so the agent + mux shut down instead of lingering idle. Source: `apps/cli/src/commands/sessions-stop.ts`.
456
+ - The tmux-wrap `pane-died` hook is now `session_attached`-aware (`AGENT_HOOK_SCHEMA` v6): with a client attached it `detach-client`s as before (so `resolveAfterAttach` reads the exit status, EXEC-23b unchanged); with **no** client attached it `kill-session`s outright. A wrapped agent that exits unattended — a closed terminal, or a `/exit` after the user detached — previously left a dead `remain-on-exit` husk on the socket until the daemon's periodic reap, showing as an orphaned idle session. Source: `apps/cli/src/lib/tmux/session.ts`, `apps/cli/src/lib/exec.ts`.
457
+
458
+ - **`agents accounts list` is grouped, aligned, and label-first (RUSH-3053).** The native-logins section was a ragged, ungrouped wall — 15 logins in one flat list, no columns, no labels, opaque ids dumped raw. It now groups logins **by harness** (printed once per group), aligns **label · identity · version** into columns, marks the **default account per harness** with `*`, and prints the `run <harness>#<label>` / `run <harness> --account <name>` selector hints inline. Provider bundles keep their own aligned section. `--json` output is unchanged. This completes RUSH-3053's list-redesign track — the `<harness>#<label>` account selector itself shipped in 1.22.47. The renderer is extracted as the pure, unit-tested `renderAccountList`. Source: `apps/cli/src/commands/accounts.ts`.
459
+
460
+ ---
461
+ type: breaking
462
+ ---
463
+
464
+ Browser profiles now live only in each declaring machine's `devices/<machine>/agents.yaml`. The fleet registry is the read-time union of those files: a name declared once is identity-bearing, while the same name declared by several devices is fungible. Leftover central `browser:` entries are not claimed on first read — run `agents browser profiles claim` on the machine that hosts the browser. Only profiles that machine can actually launch are moved into its device file; the rest stay central until that machine claims them. A configured default that no device declares is now an error on `agents browser start`, not a silent fallback to a logged-out `auto-chrome`. `profiles prune` only considers profiles this device declares (`--fleet` is gone; deleting a peer's declaration is not possible).
465
+
466
+ ---
467
+ type: feat
468
+ ---
469
+
470
+ - **`agents browser profiles list` shows WHERE, not a stored scope.** The
471
+ column is the devices whose own `devices/<machine>/agents.yaml` declares the
472
+ name — true by construction, no field to drift. `--json` adds `devices` and
473
+ `kind` (`identity` when exactly one device declares the name, `fungible` when
474
+ several do). `profiles add` is an alias of `create` and prints
475
+ `Added "<name>" on <device> (port N).`
476
+ Source: `src/commands/browser.ts`.
477
+ - **`profiles doctor` fails `where` on the original comet-local shape.** An
478
+ identity-bearing name whose endpoint is loopback, viewed from a box that is
479
+ not the declaring device, exits non-zero and names both machines. Local
480
+ binary/port/onboarding checks are skipped so they cannot paint a green local
481
+ chromium over someone else's logins. `ssh://` endpoints and fungible names
482
+ are unaffected.
483
+ Source: `src/lib/browser/runtime-state.ts` (`identityLoopbackMismatch`),
484
+ `src/commands/browser.ts`.
485
+
486
+ ---
487
+ type: breaking
488
+ ---
489
+
490
+ `agents browser` no longer asks the caller which machine a profile lives on. The daemon reads the device-declaration registry: a name this machine declares connects locally; a name only other machines declare is tunnelled to a reachable declaring device (and the command output names which one); a name nobody declares fails loudly, listing similar names, and never auto-creates a logged-out local browser. Identity-bearing profiles share one connection (no Electron fork, no second chrome-data). Runtime keys are `<profile>@<device>` instead of `<profile>@endpoint-N`; leftover `@endpoint-N` dirs are renamed onto the new key.
491
+
492
+ - **`browser remote-control off` now gates *attaching* to a running browser, not just *launching* one (RUSH-3064).** PR #2932 moved the consent gate to `BrowserService.start` and the create branch of `resolveOrCreateTask`, closing the implicit-launch bypass — but the two early-return attach paths (an explicit `--task`, or a lone caller-identity match) returned before it, and `tabAdd` reached `createPageTarget` unconditionally. So on a machine whose browser was already running with live tasks, a fleet-remote caller could act in the owner's *authenticated* profile with consent off (`agents browser tab-add --device <box> --task <name>` — and task names are discoverable through the ungated `status`). The gate now sits at the top of `resolveOrCreateTask`, the one chokepoint every page/close verb resolves through, so a fleet-remote attach is refused exactly as a fleet-remote create already was. It stays per-request (`IPCRequest.fleetRemote`), never the daemon's env, so an auto-started daemon that inherited `AGENTS_FLEET_REMOTE=1` never refuses a subsequent local drive. Local drives are unaffected. Source: `apps/cli/src/lib/browser/service.ts`.
493
+
494
+ ---
495
+ type: fixed
496
+ ---
497
+
498
+ A leftover local `comet-local@endpoint-N` chrome-data on a worker is no longer renamed onto the declaring device's key and attached over localhost when the daemon should tunnel. Identity-bearing browsers stay on the declaring machine.
499
+
500
+ ---
501
+ type: fix
502
+ ---
503
+
504
+ `agents devices capture` no longer wipes `fleet.ignored` — the captured manifest carries device dismissals forward instead of rebuilding the fleet block without them.
505
+
506
+ ---
507
+ type: feat
508
+ ---
509
+
510
+ - **Deletion verbs normalize on `remove` (specific item) and `prune` (bulk
511
+ stale), old spellings kept as hidden aliases.** The CLI had drifted into seven
512
+ different words for "delete" across groups — `remove`, `rm`, `delete`, `gc`,
513
+ `cleanup`, plus `prune`. The canonical pair `remove <name>` / `prune` (already
514
+ the pattern in `commands`, `hooks`, `skills`, `versions`) is now applied to the
515
+ stragglers: `agents route rm` / `agents devices rm` / `agents projects rm` make
516
+ `remove` the primary spelling (`rm` stays an alias); `agents browser profiles
517
+ delete` → `remove` (alias `delete`); and the bulk-stale sweeps `agents browser
518
+ gc`, `agents lease gc`, `agents mailboxes gc`, and `agents routines cleanup`
519
+ become `prune` (each old verb stays a hidden alias). No invocation breaks — the
520
+ old verbs still resolve; they just no longer appear in `--help`. `agents
521
+ secrets remove`/`delete` (key vs whole-bundle) and `agents artifacts share
522
+ delete`/`unshare` are deliberately left as-is: those pairs encode a real
523
+ distinction, not drift. Source: `apps/cli/src/commands/{route,ssh,projects,
524
+ browser,lease,mailboxes,routines}.ts`.
525
+
526
+ - **An interactive `--device` agent now survives a dropped connection, and one blink no longer takes out every tab (RUSH-3125).** A remote interactive agent was a direct child of the sshd session holding its TTY, so a brief network disruption SIGHUPed it and the in-flight turn was lost — while the auto-reconnect layer re-attached on the stated premise that "the agent is still running there." Durability had been gated on the peer's `tmux.enabled`, which defaults **off** (and was off across the whole fleet), even though that toggle is an ergonomics preference about tmux's mouse, clipboard, and scrollback at the local operator's keyboard. The two are now separate: the interactive dispatch exports `AGENTS_REMOTE_INTERACTIVE=1` and the peer wraps the run in a detached tmux session regardless of the local toggle, so the agent outlives the link that carries it. `--raw`, `--no-tmux`, and `AGENTS_NO_TMUX=1` still win, so the escape hatch keeps working over `--device`; a remote run on a peer with **no tmux installed** is now refused with an install hint rather than started as something a blink would kill. Separately, the interactive stream no longer rides the shared `ControlMaster` — `ControlPath=cm-%C` hashes only host/port/user, so every agent tab pointed at a peer shared one master and OpenSSH closes all its channels when it dies (six tabs on one socket, observed live). Probes and fan-outs keep multiplexing. Source: `apps/cli/src/lib/exec.ts` (`shouldWrapInTmux` → `resolveTmuxWrap`), `apps/cli/src/lib/hosts/dispatch.ts`, `apps/cli/src/lib/types.ts`.
527
+
528
+ ---
529
+ type: feat
530
+ ---
531
+
532
+ Feed attention is now modeled as an explicit lifecycle the CLI reconciles, the foundation for a feed-driven Needs-You surface. `OpenBlock` carries `generation`/`source`/`state`/`sourceCursor` (back-compatible — derived for existing blocks), the answer/continue/clear paths write a resolution tombstone before the block clears so a resolved ask can no longer silently resurrect, and a new pure `reconcileAttention` merges the open-block ledger, session lifecycle, and a CLI-supplied PR signal into one canonical `AttentionItem`.
533
+
534
+ - **Stream and answer the canonical operator feed.** `agents feed watch --json`
535
+ now emits one versioned agents, attention, activity, and scope projection for
536
+ thin clients, including retained peer rows across disconnects. `agents feed
537
+ answer <attention-key>` atomically claims the first answer and routes it over
538
+ the recorded reply rail; concurrent losers return `already_answered` without
539
+ injecting twice. Pull-request attention is refreshed by the CLI on a bounded
540
+ TTL. Source: `apps/cli/src/lib/feed/{watch,answer,pr-status}.ts`.
541
+
542
+ - **`agents auth` help no longer claims plan tiers.** The command's description
543
+ now reads "the account layer behind team spaces" — there are no plan tiers —
544
+ and its help notes state that signing in is optional: every local feature
545
+ works with no account, and team spaces are the one thing an account unlocks.
546
+ READMEs and docs updated to match, and license references now name
547
+ FSL-1.1-Apache-2.0. Source: `apps/cli/src/commands/auth.ts`.
548
+
549
+ - **The `git-output` tests no longer fail on any box that exports a git identity (unblocks the release attestation).** `git-output.test.ts`'s fixture authored its commits with `-c user.email=…`, but git's `GIT_AUTHOR_EMAIL` / `GIT_COMMITTER_EMAIL` environment variables **outrank** `-c` config — so on a machine that exports them (developer laptops and the agent fleet do) every fixture commit was silently re-authored to the ambient identity, `collectCommits` matched none of them, and four tests failed with a bare `expected +0 to be 2`. CI never caught it because its runners export no such vars, which made this a local-only failure that also wedged `release-attestation-produce.sh` — that script is fail-closed, so no attestation was written and no release could be cut from an affected box. The fixture now passes identity through the environment it builds for git, and always assigns those four vars so an ambient value can never decide fixture authorship. Verified by causation: the unchanged test is 4-failed with the vars set and 9-passed with them cleared. Source: `apps/cli/src/lib/output/__tests__/git-output.test.ts`.
550
+
551
+ - **`permission_mode_not` was typed, documented, and silently inert — every gated hook fired anyway (RUSH-3116).** The predicate shipped in `shouldFire()` (`src/lib/hooks/match.ts:156`), but nothing at runtime calls that function: the generated hook shim decides fire/skip with a **hand-mirrored Python copy** of the same logic embedded in `src/lib/hooks/cache.ts`, and the mirror was never updated. So `matches: {permission_mode_not: plan}` fell through to `return True` and the hook ran in plan mode regardless — fail-open, so nothing was left unguarded, but the predicate did nothing while still charging a Python gate subprocess per fire. Verified on published 1.22.47: `MATCHES_JSON='{"permission_mode_not":"plan"}'` against a `permission_mode: plan` payload printed `FIRE`, while the positive `permission_mode` control correctly printed `SKIP`. The Python gate now implements the negative form with the same fail-open-on-absence and camelCase (`permissionMode`) handling as the TS reference. Source: `apps/cli/src/lib/hooks/cache.ts`.
552
+
553
+ - **The shim/TS conformance suite now pins itself to the `HookMatches` surface, so a predicate cannot ship half-implemented again.** `cache-matches.test.ts` already cross-checked the shim's real bash+Python decision against `shouldFire()` "so the two implementations can't drift" — but only over a hand-written fixture list, and `permission_mode_not` had no fixture, so the suite stayed green through the drift. A new test reads the `HookMatches` interface out of `types.ts` and fails with the offending key name when any declared predicate has no conformance fixture; `permission_mode_not`, `git_dirty` and `project_has` were all uncovered and now have fixtures. Source: `apps/cli/src/lib/hooks/cache-matches.test.ts`.
554
+
555
+ - **New hook predicate `matches.permission_mode_not` — gate a hook OFF in one
556
+ mode without enumerating every other one.** The existing `permission_mode` is
557
+ an allowlist, so "run everywhere except plan mode" had to be spelled as a list
558
+ of every other mode — and that list silently stops matching the moment a
559
+ harness adds or renames one, which for a guard means it quietly stops
560
+ guarding. The new predicate names the mode to skip instead, so an unknown mode
561
+ still fires: the failure direction is "ran unnecessarily", never "did not
562
+ run". Same fail-open-on-absence rule as its positive twin (a harness that
563
+ reports no mode keeps firing), reads both `permission_mode` and Grok's
564
+ camelCase `permissionMode`, and ANDs with the positive form when both are
565
+ declared. Motivation, measured from `~/.agents/.cache/perf/perf.db`: nine
566
+ guards fire on **every** `Bash` tool call for a combined 292 ms before the
567
+ command runs, across 285,667 recorded fires of which 284 (0.099%) changed the
568
+ outcome — and four of them (`merge-guard`, `pr-description-reminder`,
569
+ `large-file-add-guard`, `git-require-clean-tree`) cannot fire meaningfully
570
+ during a planning turn. Source: `apps/cli/src/lib/hooks/match.ts`,
571
+ `apps/cli/src/lib/types.ts`, `apps/cli/docs/hooks.md`.
572
+
573
+ - **Interactive Claude on a keychain-less Linux worker now authenticates from an attached setup-token, instead of falling back to an empty per-version login and printing "looks logged out".** Two gaps combined to break it: (1) `generateVersionedAliasScript` — the `claude@<version>` alias shim `agents run` actually invokes for a pinned/balanced version — carried a **hand-copied subset** of the config env that had dropped the Linux `.oauth_token` fallback block the main shim's `claudeAdapter.shimConfigEnvBash` has (the alias env now reuses that adapter block verbatim via `VERSION_DIR`, so the two can't drift again); and (2) **nothing wrote the `.oauth_token` file** the shim reads — it was referenced only by the read in the shim, never created — so the fallback could never fire. `agents accounts attach <setup-token-account> claude@<version>` now writes the resolved setup-token to `<version-home>/.claude/.oauth_token` (mode 0600) on Linux, a no-op on macOS where the credential lives in the keychain. Headless dispatch was unaffected (it injects the token via `buildExecEnv`); this fixes the interactive path only. Source: `apps/cli/src/lib/installations/shims.ts`, `apps/cli/src/commands/accounts.ts`.
574
+
575
+ - **A regression test now pins the `claude@<version>` alias shim to the `.oauth_token` fallback**, so the alias env cannot silently drop it again. `generateVersionedAliasScript('claude', …)` is asserted to contain the block, alongside the existing main-shim assertion. Source: `apps/cli/src/lib/installations/shims.test.ts`.
576
+
577
+ - **`agents run claude --interactive` on a worker no longer prints a false "⚠ claude looks logged out" banner** when it authenticates from a setup-token. The pre-launch check probed only native credentials (empty on a keychain-less worker), so it warned even though the shim's `.oauth_token` fallback authenticates the run. It now also treats a resolvable per-version setup-token as signed in (no-op on macOS, where the credential is in the keychain). Source: `apps/cli/src/commands/exec.ts`.
578
+
579
+ - **A leased box that can't finish setup fails loud and is stopped, instead of provisioning a billed box that dies at "agents-cli is not set up".** `agents run … --lease` ran `agents setup` on the fresh box with `>/dev/null 2>&1 || true` — output and exit both discarded — so a setup that never left `~/.agents/.system` a git repo was invisible, and the agent then ran straight into the run-side gate (`ensureInitialized`) that refuses with *"agents-cli is not set up"*. The bootstrap now gates on that exact postcondition: it captures `agents setup`'s output, and if the system repo still isn't a git repo it prints the real cause and aborts (exit 97) rather than running the agent. A box **this run provisioned** whose bootstrap failed is stopped (even without `--fresh`/`--keep-box`) so unusable capacity doesn't bill until the idle-GC window; a **reused** box is never auto-stopped. A `~/.agents` config-copy failure is now surfaced instead of silently swallowed. Source: `apps/cli/src/lib/crabbox/lease.ts`.
580
+
581
+ - **New versions are licensed FSL-1.1-Apache-2.0.** Use, modification, and redistribution remain free for every user. Offering agents-cli as a competing commercial product or service is barred. Each version converts to Apache-2.0 two years after it is made available. Already-shipped Apache-2.0 versions are unchanged. Source: `LICENSE`, `apps/cli/LICENSE`.
582
+
583
+ ---
584
+ type: fix
585
+ ---
586
+
587
+ AGI Menu no longer re-prompts for Accessibility on dev machines, and the clip-paste hotkey (Cmd-Shift-V) no longer depends on the grant at all. A locally-built (ad-hoc) menu-bar helper now signs under a distinct `com.phnx-labs.agents-menubar.dev` bundle id, so it can never poison the shipped app's Accessibility grant (macOS keys the grant to the bundle id and revokes it when a same-id binary fails the stored Developer-ID code requirement). An ad-hoc build also can no longer overwrite a healthy Developer-ID install when the recorded owner path vanishes. A release now hard-fails if the shipped helper's designated requirement ever drops the pinned bundle id or Developer ID team, since that would silently revoke every user's grant on upgrade. And when the grant is missing, Cmd-Shift-V now silently copies the `host:path` reference to the clipboard (press Cmd-V) instead of re-showing the system permission modal on every paste — auto-type stays on only where Accessibility is already granted.
588
+
589
+ ---
590
+ type: fix
591
+ ---
592
+
593
+ Cmd-Shift-V once again pastes the clip reference in a single keystroke. 1.22.47 decoupled the paste from Accessibility so it never prompted — but that meant an ungranted machine silently fell back to copying the token to the clipboard, so you had to press Cmd-V yourself (two keystrokes). The helper now prompts for Accessibility **once** per launch to restore the one-keystroke auto-type, then falls back to the clipboard silently if you decline (no per-paste nagging). Because dev builds now use a distinct `.dev` bundle id and the release pins the helper's designated requirement, granting it once sticks across upgrades — so a single prompt is all it takes.
594
+
595
+ - **Auto-reconnect now works for every harness, not just Claude (RUSH-3125).** When an interactive `--device` run lost its link, only Claude ever reconnected. Claude is handed a `--session-id` before launch, so its id survives the drop; every other harness's real session id is coined on the peer and was read back over SSH *after* the interactive stream returned — that is, over the link that had just died. The read failed exactly when it was needed, so the run had no id to reconnect with and the process exited straight to a bare shell. A Grok tab therefore showed nothing at all while a Claude tab beside it counted down. Reconnect now falls back to the launcher-minted `AGENT_LAUNCH_ID`, which is known before the connection exists and so cannot be lost with it, and the peer maps it to the real session with a purely local lookup. Codex, Grok, Kimi, Droid, Cursor, OpenCode and the rest recover for the first time. Source: `apps/cli/src/lib/hosts/reconnect.ts` (`pickReconnectTarget`), `apps/cli/src/commands/exec.ts`.
596
+ - **`agents sessions focus --launch-id <id>`** targets a run by the launch id its dispatcher minted instead of a session id, resolving it from that machine's own hook records. This is what makes the reconnect above work with no network at the moment there is none; it is also usable directly when you know the launch id but not the session. An unknown launch id fails loudly rather than opening the picker, so an automated reattach can never strand itself at an interactive prompt. Source: `apps/cli/src/commands/focus.ts`.
597
+ - **The reconnect give-up notices name a command that still exists (RUSH-3125).** When the retry window closed, all three notices told you to run `agents reconnect <id>` — a command deprecated and hidden in favour of `agents sessions resume`, so the advice printed at the one moment you needed something that worked was stale. They now name `agents sessions resume <id>`, or for a launch-id target the peer-side resolver, since no local verb accepts a launch id. Source: `apps/cli/src/lib/hosts/reconnect.ts` (`recoveryHint`).
598
+
599
+ - **Reconnect waits minutes instead of 90 seconds, counts down, and Ctrl-C works (RUSH-3125).** The retry budget was 6 attempts over a 2/4/8/16/30/30 backoff — about **90 seconds**, shorter than a laptop lid close, a Wi-Fi handoff, a VPN or Tailscale re-auth, or a router reboot. Worse, timers are suspended across sleep, so on wake the whole backoff fired back-to-back before the network was up and the budget was gone in seconds. The bound is now a **15-minute wall-clock window** over an unproductive streak, and it still resets the moment a reattach reconnects and holds — so a session that blinks all day keeps reconnecting, exactly as before. The notice counts the window down (`Reconnecting in 30s · 12m14s left · attempt 7 · Ctrl-C to stop`) rather than printing an attempt fraction that no longer says when it stops. **Ctrl-C during the wait** previously hit node's default handler and killed the whole process mid-notice, dropping you at a bare shell with no hint the agent was still alive on the peer; it now exits the loop cleanly (130) and prints where the agent is and how to get back.
600
+ - **The terminal is restored after an interactive remote stream dies (RUSH-3125).** `ssh -tt` leaves the local tty in raw mode, and an agent TUI killed by a dropped link never sends its own exit sequences — so focus reporting and the mode/colour-scheme reports stayed armed and the terminal answered back at a shell that was not expecting it, littering the screen with `^[[?997;1n ^[[I ^[[O`. Those bytes were also still queued on the tty, so the next reattach handed them to the agent as if they had been typed. A `stty -g` snapshot is now taken before the spawn and restored after, the DEC modes a TUI arms are reset (focus, bracketed paste, alt screen, mouse tracking, cursor), and the input buffer is drained. Done in `sshStream` itself, so every caller that opens an interactive remote stream is covered. Source: `apps/cli/src/lib/ssh-exec.ts`, `apps/cli/src/lib/hosts/reconnect.ts`.
601
+
602
+ - **A release no longer needs `main` to hold still — publish is decoupled from live `main` (RUSH-2395 audit).** `release.sh` used to squash-merge the release PR into `origin/main` and then refuse to publish unless `main`'s tree byte-matched the attested release tree — so any commit that landed on `main` during the release (or a `CHANGELOG` merge conflict) killed it, forcing a ~15-minute quiet window that a busy fleet rarely offers. It now **tags and publishes the attested release commit itself** — the exact tree CI attested and the tarball was packed from — and merges the version-bump PR **asynchronously, after publish, best-effort**. The published bytes are the attested tree by construction, so `main` can churn freely and the bump-merge can be deferred or hand-resolved without ever wedging the release. The attestation of the release-commit tree remains the sole functional gate; the tag push and publish routing stay lease-gated. The catch-up recovery path (registry behind a `main` already at the target version) is unchanged. Source: `apps/cli/scripts/release.sh`.
603
+
604
+ - **Fixed: `resolve-target.test.ts` failed on any machine actually named `mac-mini` or `zion`.** The
605
+ fixtures wrote device declarations for two real fleet hostnames and then asserted the resolver
606
+ would tunnel to them. On a box with that `machineId()` the resolver correctly reports the profile
607
+ as locally declared, so the tunnel assertions failed — green on Linux CI, red on both Macs, which
608
+ is where releases run. The suite is now hermetic: fixtures use `peer-alpha`/`peer-zulu`, keeping
609
+ the sort order the "first reachable declaring device" assertions depend on.
610
+ Source: `src/lib/browser/resolve-target.test.ts`.
611
+
612
+ - **A session launched with a skill is no longer named after the skill's install path.** Claude derives its generated `ai-title` from the first turn, so a session opened with `/continue` (or any skill) was named `Base directory for this skill: /home/…/.claude/skills/continue` — the scaffolding line the skill injects, not the task. That name lands in `SessionMeta.label`, which wins on **every** surface for the session's whole life: the interactive `agents sessions` preview header, `--flat` / `--tree`, the `agents feed watch` / `sessions watch` streams, the AGI EXT Fleet row, and the editor tab title. The generated title now goes through `classifyUserPrompt` and collapses to `/<skill>` when — and only when — the classifier reports that injected line. A user's `/rename` (`custom-title`) is never rewritten, including one that merely names a `skills/…` path. Fixed at the one point where the label is composed, so every reader inherits it with no reader-side special-casing. **Not retroactive:** transcript rescans are `(mtime, size)`-gated with no scan-version invalidation, so a session already indexed under the old derivation keeps its stored label until that file next changes and triggers a rescan — tracked in RUSH-3122. Scoped to Claude's `ai-title`; a harness that supplies its own title verbatim (e.g. Cursor's `chatMeta.title`) is unaffected and untouched (RUSH-3123). Source: `apps/cli/src/lib/session/discover.ts` (`finalizeClaudeScan`).
613
+
614
+ - **`agents sessions preview` shows the fan-out a session left behind, on remote rows too (RUSH-3091, RUSH-3095).** The Doing line now carries `N sub-agents · N background shells`. Both counts are persisted at scan time (`sessions.sub_agent_count` / `background_shell_count`, schema v40) rather than only recomputed per render, which is what makes them visible on a **remote or unindexed row** — that path renders from `SessionMeta` alone through `formatMetaOnlyBody`, so it had no events to derive from and silently showed no fan-out at all. A freshly derived count still wins when the caller has parsed events, since the column lags the transcript by one scan. Background-shell detection is a per-harness registry probed against real transcripts, not assumed: claude/kimi flag `Bash` with `run_in_background`, grok flags `run_terminal_command` with `background`; codex and droid record no such concept and cursor persists no tool calls locally, so those render **nothing** rather than `0 background shells` — a zero would assert "none running" where the truth is "cannot know", and `NULL` (not scanned) stays distinct from `0` (scanned, none found) for the same reason. The counts mean "started / left behind", never "still running": a transcript records a start and never a death, the same trap `agents devices ps` documents, so live status stays with `sessions --active`. Source: `apps/cli/src/lib/session/highlights.ts`, `apps/cli/src/lib/session/{db,discover}.ts`, `apps/cli/src/commands/sessions-picker.ts`.
615
+
616
+ - **`agents sessions trace` now reads newer Codex sessions by program, not a wall of "exec".** Codex `gpt-5.6-sol` (codex ~0.145+) runs every shell command inside a JS cell — a `custom_tool_call` named `exec` whose code is `await tools.exec_command({cmd:"git status …"})`. The trace now unwraps that cell to the real shell command, so a Codex trajectory reads `agents 68% · git 15% · scp 13%` with `git fetch origin` / `sed -n …` steps and exit codes, exactly like Claude's `Bash` and Droid's `Execute` — instead of `exec 100%` with every step labeled `exec`. Genuine non-shell cells (`tools.view_image`, raw JS) stay labeled by their code. Source: `apps/cli/src/lib/session/parse.ts` (`extractCodexExecCommand`), `trajectory.ts` (`SHELL_TOOLS`).
617
+
618
+ - **`agents sessions trace` compares two sessions.** Pass exactly two selectors and the same command renders a **compare**: the two sessions' tool sequences aligned by tool name, the first divergence point (where the runs' tool order stops lining up), the steps each session ran that the other never did, and a per-session summary — in the same three renderings (HTML with stacked lanes on a shared time axis, compact text, and `--json` with `layout: 'compare'`). Three or more selectors, or `--tree`, still fail loud — lineage (a parent + its team) lands in a follow-up PR. Source: `apps/cli/src/lib/session/trajectory-compare.ts`, `apps/cli/src/commands/sessions-trace.ts`.
619
+
620
+ - **`agents sessions trace <id> --tree` renders a team's lineage.** The third layout of the trace surface, after the single trajectory and the two-session compare: the selected session and every session it spawned, drawn as a delegation graph. The edges are read from the session index, not inferred — a teammate's `meta.json` `parent_session_id` (`teamOrigin.parentSessionId`), with the team's agreed-on spawner (`groupSessionsByTeam().spawnerSessionId`) filling in for a teammate whose own record names none, bounded to that run's own spawn window so a second run of the same team name never adopts the first run's teammates; the edge carries which record established it. A node is always a real session: an inline `Task`/`Agent` sub-agent is a step inside one transcript and produces no session, so it is never drawn as a node. Each node carries its handle, harness, role, indexed tool count, span, PR number, and a recency class (`active`/`idle`/`stale`) — recency, not a success verdict, because nothing on a session row records whether the work landed. HTML draws a self-contained inline-SVG graph with clickable per-node summaries; `--text` prints an indented tree; `--json` emits `layout: 'lineage'` with a `lineage: { rootId, nodes, edges, teams, unresolvedParentIds }` block plus the root's trajectory. Selecting a child roots the graph at its topmost ancestor, so the whole team is always shown; a referenced parent outside the scanned pool is reported rather than dropped. Source: `apps/cli/src/lib/session/trajectory-lineage.ts`, `apps/cli/src/lib/session/trajectory-html.ts`, `apps/cli/src/lib/session/trajectory-text.ts`, `apps/cli/src/commands/sessions-trace.ts`.
621
+
622
+ - **`agents sessions trace` v2 — a real session debugger, program-aware.** The HTML view is rebuilt from a wall-clock waterfall (which crammed all activity into a sliver on long, idle-gappy sessions) into an **analysis hero** — where the time went, the slowest steps, the command/program mix, and error/idle KPIs — over a **readable, execution-ordered step list** with expandable output and clean idle-gap dividers. Every shell step is labeled by the **effective program** it ran (`git`, `gh`, `agents`, `bun`, `sed`… via the shared `extractShellPrograms` parser; `sudo`/`env`/`agents ssh` unwrapped, bare `cd`/`export` skipped) across **every harness's shell tool** — Claude's `Bash`, Codex's `exec_command`, `run_shell_command`, `shell`, `Execute` — so the mix reads `git 94 · agents 81 · gh 75` instead of "Bash 98%". Process **exit codes** show on failures. The "where the time went" share is now keyed by program in **both** the HTML and `--text` renderers off one model field — a Bash-heavy run reads `git 56% · gh 33% · agents 11%`, never `Bash 100%`. `--text` and the `--json` step model gain additive `program` + `exitCode` fields, and the trajectory's `toolTimeShare` is renamed `programTimeShare` (program-keyed) in the `--json` envelope. Source: `apps/cli/src/lib/session/trajectory.ts`, `trajectory-html.ts`, `trajectory-text.ts`.
623
+
624
+ - **`agents sessions trace` (alias `agents trace`) — visualize a session's trajectory.** A tool-call waterfall over a real time axis (durations, errors, idle stalls, delegations) instead of scrolling the Markdown wall. One `buildTrajectory()` model, rendered three ways and auto-selected by audience: an interactive HTML page on your interactive host for a person, a compact token-bounded text trajectory for an agent (`--text`, `--errors-only`), and the versioned `sessions-trace` JSON envelope (`--json`) for tools. Single-session in this release; multi-session compare and team lineage follow. Redacted by default, self-contained HTML (no CDN). Source: `apps/cli/src/commands/sessions-trace.ts`, `apps/cli/src/lib/session/trajectory.ts`.
625
+
626
+ ---
627
+ type: fix
628
+ ---
629
+
630
+ - **`agents sessions trace` steps read by what the command DID, not their `cd`
631
+ prefix.** A shell step's label now strips the leading throwaway statements —
632
+ `cd <repo> &&`, `export X=Y;`, `set -e`, `source …`, and bare `VAR=val`
633
+ assignments — so `cd /long/path && git fetch origin` renders as
634
+ `git fetch origin`, and a multi-line script whose first line is `cd <repo>`
635
+ shows its real command instead. Before this, most rows in a coding session
636
+ rendered an identical `cd [HOME]/…/<repo>` and the trajectory was unreadable;
637
+ now every row is distinct and agrees with its program badge. Pipelines are left
638
+ intact and a command that is nothing but `cd` is shown as-is. Fixed at the
639
+ model (`buildTrajectory`), so the HTML, text, and `--json` renderings all
640
+ benefit. Source: `apps/cli/src/lib/session/trajectory.ts`.
641
+ - **Trace durations roll into hours past 60 minutes.** An overnight idle gap now
642
+ reads `24h01m` instead of `1441m18s`, in both the HTML and the compact text
643
+ renderings. Source: `apps/cli/src/lib/session/trajectory-html.ts`,
644
+ `trajectory-text.ts`.
645
+
646
+ - **`agents view` stops printing "usage unavailable" for a harness that reports a plan and no meters.** Grok's collector writes a subscription tier with no usage windows (`{plan: 'SuperGrok Heavy', windows: []}`), and the cache deserializer treated "no fresh windows" as "nothing cached" — so `--refresh` rendered `SuperGrok Heavy`, the very next plain `agents view grok` rendered `usage unavailable`, and reading the row also **deleted** it. Both grok accounts were permanently stuck in the wrong state because the daemon's periodic refresh re-wrote a row that the next read destroyed. A cached row that carries a plan now survives with no windows; a row with no windows, no plan, and no refusal is still dropped, so an all-expired snapshot keeps pruning (RUSH-2858) and a meterless row can never read as a 0% bar or an `available` badge — `deriveUsageStatusFromSnapshot` still returns null for zero windows. Routing keeps the same guarantee from the other side: a windowless snapshot no longer counts as verified usage, so a meterless pool still spreads across its accounts instead of pinning to whichever one ran most recently. Source: `apps/cli/src/lib/accounting/usage.ts`, `apps/cli/src/lib/accounting/rotate.ts` (RUSH-3060).
647
+
648
+ ---
649
+ type: feat
650
+ ---
651
+
652
+ The watchdog decider is now an agent, not a heuristic script. Every idle session on the machine (its originating task + transcript tail) is handed to ONE `agents run --mode plan` call per tick, which judges each: idle-but-unfinished → nudge that drives it to finish; idle-and-done or genuinely-needs-human → skip. The deterministic pre-filter (`isLikelyTrulyBlocked`, completion/promise regex) and the per-session LLM spawn are gone — one bounded call per tick, only when something is actually idle. A nudge is booked in the cooldown ledger and logged `nudge` ONLY when delivery is confirmed; tmux/iterm/pty self-confirm, while vscodium's fire-and-forget `--open-url` is recorded `undelivered` until the swarm-ext extension acks the verb, ending the phantom-nudge ledger. `agents watchdog history` gains an `undelivered` row; the `--smart` flag is removed (the agent is always the decider). Defaults stay OFF.
653
+ - **Claude usage now comes from normal Claude Code sessions.** Managed Claude
654
+ homes install a status line that records Claude's native five-hour and
655
+ seven-day rate limits for the active account, so `agents view claude` gains
656
+ fresh bars without reading or copying OAuth credentials. The status line also
657
+ shows the hostname and active model and preserves any existing status-line
658
+ command. Compact usage bars now render at one-eighth-cell resolution instead
659
+ of exaggerating every nonzero value to at least 20%, and the trailing activity
660
+ and authentication-probe ages identify which event they measure.
661
+
662
+ ## 1.22.47
663
+
664
+ - **`agents traces sync` pushes derived, redacted trajectories to your Phoenix account (RUSH-3140).** A new `agents traces` command group increments over `sessions.db` via the `file_mtime_ms` gate (only sessions modified since the last sync are uploaded), computes a `SessionTrajectory` for each (steps + gaps + stats, no raw transcript text), applies `redactSecrets()` before PUT, and stores the result under `<userId>/<device>/sessions/<id>.json` in an R2 bucket guarded by Phoenix bearer auth — no public GET path exists anywhere. A per-device index shard (`index.json`) is updated on each run. Three subcommands: `agents traces sync` (incremental push), `agents traces status` (show last sync time), `agents traces open` (open the Phoenix Evals console). The traces Worker uses `cache-control: private, no-store` on every response. Source: `apps/cli/src/lib/traces/{backend,sync,worker-template}.ts`, `apps/cli/src/commands/traces.ts`.
665
+
3
666
  ## 1.22.46
4
667
 
5
668
  - **`agents auth` returns, against Phoenix ID instead of a sibling product's backend (RUSH-2581).** 1.22.45 removed the account layer that authenticated against Rush's `api.prix.dev`. It comes back pointed at **Phoenix ID** (`phnx-labs/phoenix-id`), agents-cli's own account service: `agents auth login` runs a device-code flow whose browser page is Phoenix-branded and Google-only, `agents auth whoami` reports the signed-in account, `agents auth logout` clears **this machine** and nothing else, and the team surface nests as `agents auth space` (`list`/`create`/`members`/`invite`/`role`/`remove`). Everything goes through one new seam, `lib/identity/` — one base URL (`PHOENIX_ID_BASE`), one session file, one HTTP funnel, one error type — replacing the shape that had the backend URL hardcoded in five files and the session token re-read by seven separate functions. agents-cli reads no other product's credentials: there is no `~/.rush/user.yaml` fallback. Source: `apps/cli/src/lib/identity/{client,index}.ts`, `apps/cli/src/commands/auth.ts`.