@phnx-labs/agents-cli 1.22.47 → 1.22.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/CHANGELOG.md +401 -17
  2. package/README.md +66 -32
  3. package/dist/bootstrap.js +4 -2
  4. package/dist/cli/command-registry.d.ts +1 -0
  5. package/dist/cli/command-registry.js +2 -0
  6. package/dist/commands/accounts.d.ts +58 -0
  7. package/dist/commands/accounts.js +206 -32
  8. package/dist/commands/artifacts.js +7 -6
  9. package/dist/commands/attach.js +1 -1
  10. package/dist/commands/browser.js +501 -103
  11. package/dist/commands/config.d.ts +0 -1
  12. package/dist/commands/config.js +0 -20
  13. package/dist/commands/daemon.d.ts +21 -0
  14. package/dist/commands/daemon.js +147 -12
  15. package/dist/commands/exec.js +52 -15
  16. package/dist/commands/focus.d.ts +14 -0
  17. package/dist/commands/focus.js +48 -5
  18. package/dist/commands/go.d.ts +3 -0
  19. package/dist/commands/go.js +16 -3
  20. package/dist/commands/inspect.js +2 -2
  21. package/dist/commands/lease.d.ts +1 -1
  22. package/dist/commands/lease.js +4 -3
  23. package/dist/commands/mailboxes.js +4 -3
  24. package/dist/commands/menubar.js +11 -4
  25. package/dist/commands/projects.d.ts +16 -1
  26. package/dist/commands/projects.js +58 -64
  27. package/dist/commands/resume.js +1 -1
  28. package/dist/commands/route.js +4 -4
  29. package/dist/commands/routines.js +3 -2
  30. package/dist/commands/run-broadcast.d.ts +1 -1
  31. package/dist/commands/run-broadcast.js +1 -1
  32. package/dist/commands/sessions-picker.d.ts +22 -0
  33. package/dist/commands/sessions-picker.js +48 -9
  34. package/dist/commands/sessions-trace.js +1 -1
  35. package/dist/commands/sessions.js +1 -0
  36. package/dist/commands/setup-secrets.js +23 -0
  37. package/dist/commands/share.d.ts +20 -5
  38. package/dist/commands/share.js +152 -52
  39. package/dist/commands/ssh.js +54 -7
  40. package/dist/commands/traces.d.ts +2 -0
  41. package/dist/commands/traces.js +184 -0
  42. package/dist/commands/view.d.ts +1 -1
  43. package/dist/commands/view.js +17 -34
  44. package/dist/index.d.ts +1 -0
  45. package/dist/index.js +5 -0
  46. package/dist/lib/accounting/usage.d.ts +14 -3
  47. package/dist/lib/accounting/usage.js +64 -18
  48. package/dist/lib/agent-spec/agents.js +11 -9
  49. package/dist/lib/browser/drivers/ssh.d.ts +16 -2
  50. package/dist/lib/browser/drivers/ssh.js +36 -9
  51. package/dist/lib/browser/ipc.js +8 -3
  52. package/dist/lib/browser/profiles.d.ts +35 -101
  53. package/dist/lib/browser/profiles.js +130 -301
  54. package/dist/lib/browser/registry.d.ts +39 -0
  55. package/dist/lib/browser/registry.js +149 -0
  56. package/dist/lib/browser/remote-control.d.ts +8 -6
  57. package/dist/lib/browser/remote-control.js +8 -6
  58. package/dist/lib/browser/resolve-target.d.ts +68 -0
  59. package/dist/lib/browser/resolve-target.js +250 -0
  60. package/dist/lib/browser/runtime-state.d.ts +40 -16
  61. package/dist/lib/browser/runtime-state.js +92 -25
  62. package/dist/lib/browser/service.d.ts +28 -9
  63. package/dist/lib/browser/service.js +139 -62
  64. package/dist/lib/browser/task-index.d.ts +70 -0
  65. package/dist/lib/browser/task-index.js +180 -0
  66. package/dist/lib/byok-usage.js +2 -2
  67. package/dist/lib/claude-statusline.d.ts +29 -0
  68. package/dist/lib/claude-statusline.js +139 -0
  69. package/dist/lib/cloud/host.d.ts +1 -1
  70. package/dist/lib/cloud/host.js +1 -1
  71. package/dist/lib/cloud/rush.js +1 -1
  72. package/dist/lib/computer/computer-rpc.js +2 -2
  73. package/dist/lib/computer/download.d.ts +15 -15
  74. package/dist/lib/computer/download.js +35 -98
  75. package/dist/lib/computer/ssh-tunnel.d.ts +0 -3
  76. package/dist/lib/computer/ssh-tunnel.js +12 -22
  77. package/dist/lib/config-keys.d.ts +2 -8
  78. package/dist/lib/config-keys.js +2 -13
  79. package/dist/lib/crabbox/cli.d.ts +1 -1
  80. package/dist/lib/crabbox/cli.js +2 -2
  81. package/dist/lib/crabbox/lease.d.ts +9 -0
  82. package/dist/lib/crabbox/lease.js +70 -15
  83. package/dist/lib/crabbox/runtimes.js +1 -1
  84. package/dist/lib/daemon/account-state-daemon-service.d.ts +16 -0
  85. package/dist/lib/daemon/account-state-daemon-service.js +26 -0
  86. package/dist/lib/daemon/browser-ipc-service.d.ts +20 -0
  87. package/dist/lib/daemon/browser-ipc-service.js +46 -0
  88. package/dist/lib/daemon/daemon.d.ts +3 -0
  89. package/dist/lib/daemon/daemon.js +212 -444
  90. package/dist/lib/daemon/device-probe-service.d.ts +26 -0
  91. package/dist/lib/daemon/device-probe-service.js +47 -0
  92. package/dist/lib/daemon/keychain-reap-service.d.ts +17 -0
  93. package/dist/lib/daemon/keychain-reap-service.js +32 -0
  94. package/dist/lib/daemon/monitor-engine-service.d.ts +22 -0
  95. package/dist/lib/daemon/monitor-engine-service.js +29 -0
  96. package/dist/lib/daemon/secrets-broker-service.d.ts +20 -0
  97. package/dist/lib/daemon/secrets-broker-service.js +66 -0
  98. package/dist/lib/daemon/self-heal-service.d.ts +27 -0
  99. package/dist/lib/daemon/self-heal-service.js +48 -0
  100. package/dist/lib/daemon/service.d.ts +102 -0
  101. package/dist/lib/daemon/service.js +98 -0
  102. package/dist/lib/daemon/session-index-service.d.ts +22 -0
  103. package/dist/lib/daemon/session-index-service.js +40 -0
  104. package/dist/lib/daemon/state-dir-check-service.d.ts +43 -0
  105. package/dist/lib/daemon/state-dir-check-service.js +63 -0
  106. package/dist/lib/daemon/supervisor.d.ts +87 -0
  107. package/dist/lib/daemon/supervisor.js +298 -0
  108. package/dist/lib/daemon/watchdog-service.d.ts +19 -0
  109. package/dist/lib/daemon/watchdog-service.js +40 -0
  110. package/dist/lib/daemon-health.d.ts +15 -0
  111. package/dist/lib/daemon-health.js +29 -5
  112. package/dist/lib/daemon-services.d.ts +8 -1
  113. package/dist/lib/daemon-services.js +55 -0
  114. package/dist/lib/daemon-ticks.d.ts +13 -2
  115. package/dist/lib/daemon-ticks.js +20 -18
  116. package/dist/lib/device-config.d.ts +0 -2
  117. package/dist/lib/device-config.js +1 -23
  118. package/dist/lib/devices/registry.d.ts +1 -1
  119. package/dist/lib/devices/registry.js +1 -1
  120. package/dist/lib/devices/stats-cache.d.ts +20 -0
  121. package/dist/lib/devices/stats-cache.js +49 -5
  122. package/dist/lib/devices/worker-pick.d.ts +48 -0
  123. package/dist/lib/devices/worker-pick.js +113 -0
  124. package/dist/lib/exec.d.ts +67 -13
  125. package/dist/lib/exec.js +85 -29
  126. package/dist/lib/harness/adapters/cursor.js +8 -11
  127. package/dist/lib/helper-download.d.ts +108 -0
  128. package/dist/lib/helper-download.js +194 -0
  129. package/dist/lib/helper-versions.d.ts +52 -0
  130. package/dist/lib/helper-versions.js +58 -0
  131. package/dist/lib/hooks/cache.js +11 -0
  132. package/dist/lib/hooks/install.js +2 -2
  133. package/dist/lib/hosts/credentials.d.ts +1 -1
  134. package/dist/lib/hosts/credentials.js +1 -1
  135. package/dist/lib/hosts/dispatch.d.ts +7 -1
  136. package/dist/lib/hosts/dispatch.js +24 -5
  137. package/dist/lib/hosts/passthrough.d.ts +8 -15
  138. package/dist/lib/hosts/passthrough.js +19 -16
  139. package/dist/lib/hosts/providers/devices.d.ts +1 -1
  140. package/dist/lib/hosts/providers/devices.js +1 -1
  141. package/dist/lib/hosts/reconnect.d.ts +179 -21
  142. package/dist/lib/hosts/reconnect.js +266 -45
  143. package/dist/lib/installations/migrate.d.ts +4 -5
  144. package/dist/lib/installations/migrate.js +6 -7
  145. package/dist/lib/installations/shims.d.ts +1 -1
  146. package/dist/lib/installations/shims.js +23 -13
  147. package/dist/lib/installations/versions.js +25 -2
  148. package/dist/lib/linear-autoclose.d.ts +1 -1
  149. package/dist/lib/linear-autoclose.js +1 -1
  150. package/dist/lib/loop.d.ts +2 -2
  151. package/dist/lib/loop.js +1 -1
  152. package/dist/lib/menubar/download-menubar.d.ts +47 -0
  153. package/dist/lib/menubar/download-menubar.js +60 -0
  154. package/dist/lib/menubar/install-menubar.d.ts +31 -7
  155. package/dist/lib/menubar/install-menubar.js +79 -21
  156. package/dist/lib/menubar/notify-desktop.d.ts +3 -3
  157. package/dist/lib/menubar/notify-desktop.js +6 -6
  158. package/dist/lib/model-tiers.d.ts +1 -1
  159. package/dist/lib/project-probe.d.ts +1 -1
  160. package/dist/lib/project-probe.js +1 -1
  161. package/dist/lib/project-pull.d.ts +1 -1
  162. package/dist/lib/project-pull.js +2 -2
  163. package/dist/lib/projects.d.ts +1 -1
  164. package/dist/lib/projects.js +1 -1
  165. package/dist/lib/sandbox.js +6 -7
  166. package/dist/lib/secrets/download-keychain.d.ts +47 -0
  167. package/dist/lib/secrets/download-keychain.js +70 -0
  168. package/dist/lib/secrets/install-helper.d.ts +20 -1
  169. package/dist/lib/secrets/install-helper.js +44 -3
  170. package/dist/lib/session/active.js +17 -1
  171. package/dist/lib/session/db.d.ts +11 -1
  172. package/dist/lib/session/db.js +138 -6
  173. package/dist/lib/session/discover.d.ts +21 -1
  174. package/dist/lib/session/discover.js +33 -5
  175. package/dist/lib/session/highlights.d.ts +49 -1
  176. package/dist/lib/session/highlights.js +95 -0
  177. package/dist/lib/session/host-link.d.ts +16 -2
  178. package/dist/lib/session/host-link.js +7 -1
  179. package/dist/lib/session/parse.js +33 -1
  180. package/dist/lib/session/remote/remote-list.d.ts +9 -0
  181. package/dist/lib/session/remote/remote-list.js +19 -2
  182. package/dist/lib/session/remote/watch.js +11 -2
  183. package/dist/lib/session/session-cache.d.ts +53 -0
  184. package/dist/lib/session/session-cache.js +88 -0
  185. package/dist/lib/session/state.js +1 -1
  186. package/dist/lib/session/tool-calls.d.ts +2 -0
  187. package/dist/lib/session/tool-calls.js +1 -1
  188. package/dist/lib/session/trajectory-html.d.ts +5 -1
  189. package/dist/lib/session/trajectory-html.js +42 -22
  190. package/dist/lib/session/trajectory-text.js +9 -2
  191. package/dist/lib/session/trajectory.js +53 -4
  192. package/dist/lib/session/types.d.ts +15 -0
  193. package/dist/lib/sha256-asset.d.ts +26 -0
  194. package/dist/lib/sha256-asset.js +41 -0
  195. package/dist/lib/share/backend.d.ts +95 -0
  196. package/dist/lib/share/backend.js +139 -0
  197. package/dist/lib/share/delete.d.ts +6 -2
  198. package/dist/lib/share/delete.js +8 -9
  199. package/dist/lib/share/html.d.ts +22 -0
  200. package/dist/lib/share/html.js +88 -0
  201. package/dist/lib/share/provision.d.ts +18 -4
  202. package/dist/lib/share/provision.js +38 -4
  203. package/dist/lib/share/publish.d.ts +39 -15
  204. package/dist/lib/share/publish.js +55 -26
  205. package/dist/lib/share/worker-template.js +171 -11
  206. package/dist/lib/sqlite.js +16 -3
  207. package/dist/lib/ssh-exec.d.ts +65 -0
  208. package/dist/lib/ssh-exec.js +120 -4
  209. package/dist/lib/startup/command-registry.js +1 -1
  210. package/dist/lib/startup/dev-build.js +3 -3
  211. package/dist/lib/state.d.ts +1 -1
  212. package/dist/lib/state.js +7 -3
  213. package/dist/lib/subagents-registry.js +1 -1
  214. package/dist/lib/terminal/index.d.ts +1 -1
  215. package/dist/lib/terminal/types.d.ts +1 -1
  216. package/dist/lib/terminal/types.js +1 -1
  217. package/dist/lib/tmux/orphan-reap.js +1 -1
  218. package/dist/lib/tmux/session.js +31 -0
  219. package/dist/lib/traces/backend.d.ts +29 -0
  220. package/dist/lib/traces/backend.js +46 -0
  221. package/dist/lib/traces/classify.d.ts +52 -0
  222. package/dist/lib/traces/classify.js +72 -0
  223. package/dist/lib/traces/config.d.ts +10 -0
  224. package/dist/lib/traces/config.js +2 -0
  225. package/dist/lib/traces/provision.d.ts +19 -0
  226. package/dist/lib/traces/provision.js +30 -0
  227. package/dist/lib/traces/sync.d.ts +150 -0
  228. package/dist/lib/traces/sync.js +447 -0
  229. package/dist/lib/traces/worker-template.d.ts +4 -0
  230. package/dist/lib/traces/worker-template.js +159 -0
  231. package/dist/lib/types.d.ts +18 -19
  232. package/dist/lib/types.js +14 -0
  233. package/dist/lib/usage-refresh.d.ts +1 -1
  234. package/dist/lib/usage-refresh.js +11 -8
  235. package/dist/lib/workflows.d.ts +1 -1
  236. package/package.json +4 -6
  237. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  238. package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +0 -24
  239. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  240. package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
  241. package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +0 -128
  242. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  243. package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -26
  244. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  245. package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
  246. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +0 -135
  247. package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
  248. package/dist/lib/usage-fleet.d.ts +0 -32
  249. package/dist/lib/usage-fleet.js +0 -125
package/CHANGELOG.md CHANGED
@@ -1,6 +1,223 @@
1
1
  # Changelog
2
2
 
3
- ## 1.22.47
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
4
221
 
5
222
  - **On your own machine, every Claude run uses your normal login — not the
6
223
  worker setup-token (RUSH-2395).** The credential now follows **device role**,
@@ -89,8 +306,39 @@ type: fix
89
306
 
90
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.
91
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
+
92
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`.
93
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
+
94
342
  - **Security: the remote browser-control consent gate now covers the whole launch path.**
95
343
  `agents browser remote-control off` (the default) was only enforced on the
96
344
  `browser start` command, but `navigate`, `click`, `screenshot`, `evaluate` and
@@ -110,23 +358,22 @@ Make opt-in tmux-wrapped runs default to mouse interaction, OSC 52 system clipbo
110
358
  Source: `src/lib/browser/service.ts`, `src/lib/browser/remote-control.ts`,
111
359
  `src/lib/browser/ipc.ts`, `src/lib/browser/types.ts`.
112
360
 
113
- - **`agents browser profiles edit` and `profiles scope`.** An existing profile's
114
- description, endpoints, secrets, viewport, and binary could not be changed
115
- without deleting and recreating it — `-d/--description` existed only on
116
- `create`, and `updateProfile()` had no CLI caller at all. `profiles edit <name>`
117
- reuses `create`'s flag spellings (minus `-b/--browser` and `--fleet`, which key
118
- the on-disk profile cache and the store) and validates the merged record, so a
119
- binary edit re-resolves the browser path and a `--target-filter` edit re-checks
120
- the `--electron` gate. `profiles scope <name> <local|fleet>` moves a profile
121
- between the synced store and this machine.
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.
122
368
  Source: `src/lib/browser/profiles.ts`, `src/commands/browser.ts`.
123
- - **`profiles doctor` flags a fleet profile with a loopback endpoint.** A
124
- `--fleet` profile whose endpoint is `cdp://localhost:PORT` is evaluated on the
125
- machine running the command, so the name resolved to a different browser on
126
- every box — silently handing an agent a logged-out stranger instead of the
127
- credentialed profile it asked for. Now a failing `scope` check naming the
128
- repair. `ssh://` profiles are unaffected: they address a host, so fleet scope is
129
- correct for them. Source: `src/lib/browser/profiles.ts` (`misfiledFleetProfile`).
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`).
130
377
  - **Fixed: editing a profile's endpoint could collide with itself.** The local
131
378
  port scan `createProfile` runs was never applied on update, and applying it
132
379
  naively would have failed every edit against the profile's own stored port.
@@ -208,6 +455,48 @@ Make opt-in tmux-wrapped runs default to mouse interaction, OSC 52 system clipbo
208
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`.
209
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`.
210
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
+
211
500
  ---
212
501
  type: fix
213
502
  ---
@@ -218,6 +507,28 @@ type: fix
218
507
  type: feat
219
508
  ---
220
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
+
221
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`.
222
533
 
223
534
  - **Stream and answer the canonical operator feed.** `agents feed watch --json`
@@ -235,6 +546,12 @@ Feed attention is now modeled as an explicit lifecycle the CLI reconciles, the f
235
546
  READMEs and docs updated to match, and license references now name
236
547
  FSL-1.1-Apache-2.0. Source: `apps/cli/src/commands/auth.ts`.
237
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
+
238
555
  - **New hook predicate `matches.permission_mode_not` — gate a hook OFF in one
239
556
  mode without enumerating every other one.** The existing `permission_mode` is
240
557
  an allowlist, so "run everywhere except plan mode" had to be spelled as a list
@@ -253,6 +570,14 @@ Feed attention is now modeled as an explicit lifecycle the CLI reconciles, the f
253
570
  during a planning turn. Source: `apps/cli/src/lib/hooks/match.ts`,
254
571
  `apps/cli/src/lib/types.ts`, `apps/cli/docs/hooks.md`.
255
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
+
256
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`.
257
582
 
258
583
  ---
@@ -261,8 +586,35 @@ type: fix
261
586
 
262
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.
263
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
+
264
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`.
265
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
+
266
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`.
267
619
 
268
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`.
@@ -271,6 +623,26 @@ AGI Menu no longer re-prompts for Accessibility on dev machines, and the clip-pa
271
623
 
272
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`.
273
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
+
274
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).
275
647
 
276
648
  ---
@@ -278,6 +650,18 @@ type: feat
278
650
  ---
279
651
 
280
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`.
281
665
 
282
666
  ## 1.22.46
283
667