@phnx-labs/agents-cli 1.20.74 → 1.20.77

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 (231) hide show
  1. package/CHANGELOG.md +1055 -0
  2. package/README.md +36 -10
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/apply.js +21 -3
  5. package/dist/commands/attach.d.ts +10 -0
  6. package/dist/commands/attach.js +41 -0
  7. package/dist/commands/computer.js +3 -3
  8. package/dist/commands/detach-core.d.ts +46 -0
  9. package/dist/commands/detach-core.js +61 -0
  10. package/dist/commands/detach.d.ts +5 -0
  11. package/dist/commands/detach.js +171 -0
  12. package/dist/commands/doctor.js +18 -0
  13. package/dist/commands/exec.js +83 -13
  14. package/dist/commands/feed.d.ts +7 -1
  15. package/dist/commands/feed.js +47 -4
  16. package/dist/commands/go.d.ts +9 -2
  17. package/dist/commands/go.js +16 -6
  18. package/dist/commands/harness.d.ts +14 -0
  19. package/dist/commands/harness.js +145 -0
  20. package/dist/commands/import.js +38 -8
  21. package/dist/commands/inspect.js +5 -2
  22. package/dist/commands/profiles.d.ts +20 -0
  23. package/dist/commands/profiles.js +68 -21
  24. package/dist/commands/repo.js +14 -0
  25. package/dist/commands/routines.js +286 -15
  26. package/dist/commands/secrets.js +102 -37
  27. package/dist/commands/sessions-browser.js +12 -2
  28. package/dist/commands/sessions-export.js +19 -5
  29. package/dist/commands/sessions-migrate.d.ts +29 -0
  30. package/dist/commands/sessions-migrate.js +596 -0
  31. package/dist/commands/sessions-picker.d.ts +14 -1
  32. package/dist/commands/sessions-picker.js +184 -16
  33. package/dist/commands/sessions.d.ts +56 -14
  34. package/dist/commands/sessions.js +322 -62
  35. package/dist/commands/setup-computer.js +2 -2
  36. package/dist/commands/ssh.d.ts +13 -0
  37. package/dist/commands/ssh.js +110 -37
  38. package/dist/commands/status.js +10 -2
  39. package/dist/commands/versions.js +15 -6
  40. package/dist/commands/view.d.ts +5 -0
  41. package/dist/commands/view.js +24 -4
  42. package/dist/commands/watchdog.d.ts +7 -2
  43. package/dist/commands/watchdog.js +73 -57
  44. package/dist/index.js +59 -7
  45. package/dist/lib/activity.d.ts +17 -6
  46. package/dist/lib/activity.js +466 -40
  47. package/dist/lib/actor.d.ts +50 -0
  48. package/dist/lib/actor.js +166 -0
  49. package/dist/lib/agent-spec/provider.js +2 -1
  50. package/dist/lib/agent-spec/resolve.js +19 -5
  51. package/dist/lib/agent-spec/types.d.ts +9 -1
  52. package/dist/lib/agents.d.ts +21 -0
  53. package/dist/lib/agents.js +70 -6
  54. package/dist/lib/cloud/codex.d.ts +2 -0
  55. package/dist/lib/cloud/codex.js +14 -3
  56. package/dist/lib/cloud/session-index.d.ts +32 -0
  57. package/dist/lib/cloud/session-index.js +58 -0
  58. package/dist/lib/cloud/store.d.ts +7 -0
  59. package/dist/lib/cloud/store.js +25 -0
  60. package/dist/lib/config-transfer.js +4 -0
  61. package/dist/lib/daemon.d.ts +10 -1
  62. package/dist/lib/daemon.js +114 -11
  63. package/dist/lib/devices/connect.d.ts +15 -1
  64. package/dist/lib/devices/connect.js +15 -1
  65. package/dist/lib/devices/fleet.d.ts +37 -1
  66. package/dist/lib/devices/fleet.js +36 -2
  67. package/dist/lib/devices/health-report.d.ts +38 -0
  68. package/dist/lib/devices/health-report.js +214 -0
  69. package/dist/lib/devices/health.js +4 -1
  70. package/dist/lib/devices/reachability.d.ts +31 -0
  71. package/dist/lib/devices/reachability.js +40 -0
  72. package/dist/lib/devices/registry.d.ts +33 -0
  73. package/dist/lib/devices/registry.js +37 -0
  74. package/dist/lib/devices/resolve-target.d.ts +15 -27
  75. package/dist/lib/devices/resolve-target.js +63 -102
  76. package/dist/lib/devices/sync.d.ts +18 -0
  77. package/dist/lib/devices/sync.js +23 -1
  78. package/dist/lib/devices/tailscale.d.ts +3 -0
  79. package/dist/lib/devices/tailscale.js +1 -0
  80. package/dist/lib/events.d.ts +1 -1
  81. package/dist/lib/exec.d.ts +82 -0
  82. package/dist/lib/exec.js +218 -6
  83. package/dist/lib/feed-post.d.ts +63 -0
  84. package/dist/lib/feed-post.js +204 -0
  85. package/dist/lib/feed.d.ts +7 -1
  86. package/dist/lib/feed.js +96 -6
  87. package/dist/lib/fleet/apply.d.ts +32 -2
  88. package/dist/lib/fleet/apply.js +97 -10
  89. package/dist/lib/fleet/types.d.ts +11 -0
  90. package/dist/lib/fs-walk.d.ts +13 -0
  91. package/dist/lib/fs-walk.js +16 -7
  92. package/dist/lib/heal.d.ts +7 -4
  93. package/dist/lib/heal.js +10 -22
  94. package/dist/lib/hooks.d.ts +1 -0
  95. package/dist/lib/hooks.js +156 -0
  96. package/dist/lib/hosts/dispatch.d.ts +16 -0
  97. package/dist/lib/hosts/dispatch.js +26 -4
  98. package/dist/lib/hosts/passthrough.js +9 -2
  99. package/dist/lib/hosts/reconnect.d.ts +78 -0
  100. package/dist/lib/hosts/reconnect.js +127 -0
  101. package/dist/lib/hosts/registry.d.ts +75 -14
  102. package/dist/lib/hosts/registry.js +205 -30
  103. package/dist/lib/hosts/remote-cmd.d.ts +11 -0
  104. package/dist/lib/hosts/remote-cmd.js +32 -8
  105. package/dist/lib/hosts/remote-session-id.d.ts +45 -0
  106. package/dist/lib/hosts/remote-session-id.js +84 -0
  107. package/dist/lib/hosts/run-target.d.ts +17 -5
  108. package/dist/lib/hosts/run-target.js +30 -10
  109. package/dist/lib/hosts/session-index.d.ts +18 -1
  110. package/dist/lib/hosts/session-index.js +40 -5
  111. package/dist/lib/hosts/session-marker.d.ts +33 -0
  112. package/dist/lib/hosts/session-marker.js +51 -0
  113. package/dist/lib/hosts/tasks.d.ts +8 -4
  114. package/dist/lib/import.d.ts +2 -0
  115. package/dist/lib/import.js +35 -2
  116. package/dist/lib/menubar/install-menubar.d.ts +29 -0
  117. package/dist/lib/menubar/install-menubar.js +59 -1
  118. package/dist/lib/menubar/notify-desktop.d.ts +44 -0
  119. package/dist/lib/menubar/notify-desktop.js +78 -0
  120. package/dist/lib/migrate.d.ts +16 -0
  121. package/dist/lib/migrate.js +36 -0
  122. package/dist/lib/models.d.ts +27 -0
  123. package/dist/lib/models.js +54 -1
  124. package/dist/lib/overdue.d.ts +6 -2
  125. package/dist/lib/overdue.js +10 -36
  126. package/dist/lib/picker.js +4 -1
  127. package/dist/lib/platform/process.d.ts +17 -0
  128. package/dist/lib/platform/process.js +70 -0
  129. package/dist/lib/profiles-presets.js +9 -7
  130. package/dist/lib/profiles.d.ts +31 -0
  131. package/dist/lib/profiles.js +70 -0
  132. package/dist/lib/pty-server.d.ts +2 -10
  133. package/dist/lib/pty-server.js +4 -38
  134. package/dist/lib/registry.d.ts +1 -1
  135. package/dist/lib/registry.js +48 -8
  136. package/dist/lib/rotate.d.ts +18 -0
  137. package/dist/lib/rotate.js +28 -0
  138. package/dist/lib/routine-notify.d.ts +76 -0
  139. package/dist/lib/routine-notify.js +190 -0
  140. package/dist/lib/routines-placement.d.ts +38 -0
  141. package/dist/lib/routines-placement.js +79 -0
  142. package/dist/lib/routines-project.d.ts +97 -0
  143. package/dist/lib/routines-project.js +349 -0
  144. package/dist/lib/routines.d.ts +94 -0
  145. package/dist/lib/routines.js +93 -9
  146. package/dist/lib/runner.d.ts +12 -2
  147. package/dist/lib/runner.js +242 -20
  148. package/dist/lib/sandbox.js +10 -0
  149. package/dist/lib/secrets/account-token.d.ts +20 -0
  150. package/dist/lib/secrets/account-token.js +64 -0
  151. package/dist/lib/secrets/agent.d.ts +74 -4
  152. package/dist/lib/secrets/agent.js +181 -107
  153. package/dist/lib/secrets/bundles.d.ts +54 -6
  154. package/dist/lib/secrets/bundles.js +230 -34
  155. package/dist/lib/secrets/index.d.ts +26 -3
  156. package/dist/lib/secrets/index.js +42 -12
  157. package/dist/lib/secrets/rc-hygiene.d.ts +55 -0
  158. package/dist/lib/secrets/rc-hygiene.js +154 -0
  159. package/dist/lib/secrets/remote.d.ts +7 -4
  160. package/dist/lib/secrets/remote.js +7 -4
  161. package/dist/lib/secrets/session-store.d.ts +6 -2
  162. package/dist/lib/secrets/session-store.js +65 -15
  163. package/dist/lib/secrets/sync-commands.d.ts +21 -0
  164. package/dist/lib/secrets/sync-commands.js +21 -0
  165. package/dist/lib/secrets/unlock-hints.d.ts +27 -0
  166. package/dist/lib/secrets/unlock-hints.js +36 -0
  167. package/dist/lib/self-update.d.ts +23 -2
  168. package/dist/lib/self-update.js +86 -5
  169. package/dist/lib/session/active.d.ts +124 -7
  170. package/dist/lib/session/active.js +390 -56
  171. package/dist/lib/session/cloud.js +2 -1
  172. package/dist/lib/session/db.d.ts +54 -0
  173. package/dist/lib/session/db.js +320 -19
  174. package/dist/lib/session/detached.d.ts +30 -0
  175. package/dist/lib/session/detached.js +92 -0
  176. package/dist/lib/session/discover.d.ts +478 -3
  177. package/dist/lib/session/discover.js +1740 -472
  178. package/dist/lib/session/fork.js +2 -1
  179. package/dist/lib/session/hook-sessions.d.ts +43 -0
  180. package/dist/lib/session/hook-sessions.js +135 -0
  181. package/dist/lib/session/linear.d.ts +6 -0
  182. package/dist/lib/session/linear.js +55 -0
  183. package/dist/lib/session/migrate-targets.d.ts +65 -0
  184. package/dist/lib/session/migrate-targets.js +94 -0
  185. package/dist/lib/session/migrations.d.ts +37 -0
  186. package/dist/lib/session/migrations.js +60 -0
  187. package/dist/lib/session/parse.d.ts +18 -0
  188. package/dist/lib/session/parse.js +141 -32
  189. package/dist/lib/session/pid-registry.d.ts +34 -2
  190. package/dist/lib/session/pid-registry.js +49 -2
  191. package/dist/lib/session/prompt.d.ts +7 -0
  192. package/dist/lib/session/prompt.js +37 -0
  193. package/dist/lib/session/provenance.d.ts +7 -0
  194. package/dist/lib/session/render.d.ts +7 -2
  195. package/dist/lib/session/render.js +40 -26
  196. package/dist/lib/session/short-id.d.ts +17 -0
  197. package/dist/lib/session/short-id.js +20 -0
  198. package/dist/lib/session/state.d.ts +4 -0
  199. package/dist/lib/session/state.js +99 -13
  200. package/dist/lib/session/types.d.ts +25 -1
  201. package/dist/lib/session/types.js +8 -0
  202. package/dist/lib/shims.d.ts +1 -1
  203. package/dist/lib/shims.js +20 -6
  204. package/dist/lib/sqlite.d.ts +3 -2
  205. package/dist/lib/sqlite.js +27 -4
  206. package/dist/lib/staleness/detectors/commands.js +1 -1
  207. package/dist/lib/staleness/detectors/workflows.js +13 -2
  208. package/dist/lib/staleness/writers/commands.js +7 -7
  209. package/dist/lib/staleness/writers/workflows.d.ts +4 -2
  210. package/dist/lib/startup/command-registry.d.ts +1 -0
  211. package/dist/lib/startup/command-registry.js +3 -0
  212. package/dist/lib/state.d.ts +8 -4
  213. package/dist/lib/state.js +21 -37
  214. package/dist/lib/teams/agents.d.ts +19 -10
  215. package/dist/lib/teams/agents.js +40 -39
  216. package/dist/lib/types.d.ts +47 -2
  217. package/dist/lib/usage.d.ts +33 -1
  218. package/dist/lib/usage.js +172 -12
  219. package/dist/lib/versions.d.ts +9 -2
  220. package/dist/lib/versions.js +37 -8
  221. package/dist/lib/watchdog/log.d.ts +43 -0
  222. package/dist/lib/watchdog/log.js +69 -0
  223. package/dist/lib/watchdog/routine.d.ts +44 -0
  224. package/dist/lib/watchdog/routine.js +69 -0
  225. package/dist/lib/watchdog/runner.d.ts +51 -7
  226. package/dist/lib/watchdog/runner.js +239 -64
  227. package/dist/lib/watchdog/watchdog.d.ts +1 -1
  228. package/dist/lib/watchdog/watchdog.js +31 -16
  229. package/dist/lib/workflows.d.ts +16 -0
  230. package/dist/lib/workflows.js +110 -1
  231. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,7 +1,1049 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.20.77
4
+
5
+ - **Interactive `agents run --host` now tracks the real session for every agent,
6
+ not just Claude.** Codex, Kimi, Grok, and Gemini coin their own session id and
7
+ reject a caller-supplied one, so an interactive host run of any of them showed a
8
+ stale/absent id locally — `agents sessions` couldn't surface it and a dropped
9
+ link couldn't auto-reconnect it (RUSH-2033 fixed only the Claude `--session-id`
10
+ path). The launcher now forwards one correlation key it controls
11
+ (`AGENT_LAUNCH_ID`); the remote `agents run` adopts that key
12
+ (`resolveLaunchId`), so its SessionStart hook records the agent's real session id
13
+ under it. After the stream the launcher does one ssh read of the remote hook
14
+ record, resolves the real id by launch id (`resolveRemoteSessionId` /
15
+ `pickRemoteSessionId`), registers it in the local session index, and reconnects
16
+ against it on a dropped link. Claude still forces its own id up front and is
17
+ unchanged. Source: `apps/cli/src/lib/hosts/remote-session-id.ts`,
18
+ `resolveLaunchId` in `apps/cli/src/lib/exec.ts`, and the interactive `--host`
19
+ branch in `apps/cli/src/commands/exec.ts`. (RUSH-2034)
20
+
21
+ - **Project routines can opt into daemon firing with source tracking, sync, and
22
+ host placement (RUSH-2035).** Project YAML under `<project>/.agents/routines/*.yml`
23
+ stays inspection-only until `agents routines enable-project` (with interactive /
24
+ `--yes` approval) records the project on `meta.routines.projects` and materialises
25
+ copies into `~/.agents/routines/` with a `source:` block (`projectPath`, git
26
+ `repo`/`branch`/`commit`). `agents routines sync` (and daemon start/SIGHUP reload)
27
+ refreshes those copies when project YAML changes; `disable-project` / `projects`
28
+ manage the allowlist. New `hostStrategy: local|host|fleet|cloud` (CLI `--placement`)
29
+ chooses where the job body runs: local, a named `--run-on` host, one online fleet
30
+ device per fire (no cross-device double-fire — off-box strategies auto-pin
31
+ `devices`), or the agent's native cloud provider. `--host` remains the remote-
32
+ management passthrough. List/JSON surfaces source repo/branch and strategy. Source:
33
+ `apps/cli/src/lib/routines.ts`, `routines-project.ts`, `routines-placement.ts`,
34
+ `runner.ts`, `daemon.ts`, `commands/routines.ts`, `docs/03-routines.md`.
35
+
36
+ - **Factory interactive launches default to `--mode auto` (RUSH-2038).** The Factory VS Code extension no longer inherits the CLI's `plan` default for interactive terminal launches. Codex, Claude, Gemini, Cursor, OpenCode, and Antigravity now start in `auto` (writable-but-gated) when opened from Factory without an explicit mode, so the agent can edit files instead of stalling in a read-only sandbox. Source: `apps/factory/src/core/agents.ts`.
37
+
38
+ - **Codex approval blocks now notify you.** A headless or terminal Codex agent
39
+ blocked on an approval prompt used to stall silently — the feed/notification path
40
+ only fired for Claude. Codex emits `PermissionRequest` (not Claude's
41
+ `Notification`), which the `feed-publish` hook now handles: it publishes an
42
+ approval-class block with a high cost-of-delay and a `deny` safe-default, so the
43
+ blocked agent surfaces on `agents feed` and `agents feed --dispatch` pages the
44
+ phone as urgent. A Codex approval card clears once the approved tool runs, via a
45
+ matcher-less `PostToolUse` clear hook registered **for Codex only** — so Claude's
46
+ card lifetime (its `permission_prompt`/`idle_prompt`/`elicitation_dialog`
47
+ notification blocks persist until `Stop`/`SessionEnd`) and per-tool overhead are
48
+ exactly as before. The other feed hooks are now registered for Codex too, not
49
+ Claude only. The Factory extension bridges the same waiting state to an
50
+ edge-triggered VS Code notification with a "Focus terminal" action. Claude's path
51
+ is unchanged. Source: `FEED_PUBLISH_HOOK_SCRIPT` / `ensureFeedPublishHook` in
52
+ `apps/cli/src/lib/feed.ts`, `apps/factory/src/core/waitingNotifier.ts`.
53
+ (RUSH-2039)
54
+
55
+ - **`agents fleet ping` now completes within ~15 s per device and ~30 s total, even when several fleet devices are offline or slow (RUSH-2041).** The per-device remote auth probe timeout was lowered from 60 s to 15 s (matching the `fleet status` version-probe budget, which is enough for the ~8 s provider-fetch inside the local auth probe). `fanOutDevices` gained an optional `perDeviceTimeoutMs` that races each probe against a deadline and records it as `failed: timed out` instead of hanging. `runFleetPing` now also wraps the entire fan-out in a 30 s hard cap so the command can never outlast a reasonable budget. Offline devices are now reported promptly as failed/timed-out rather than left hanging in the spinner. Source: `apps/cli/src/lib/devices/fleet.ts` (`fanOutDevices`, `FanOutDeviceOptions`), `apps/cli/src/commands/ssh.ts` (`probeRemoteAuth`, `runFleetPing`).
56
+
57
+ - **`agents sessions` surfaces checklist progress in every list/preview (RUSH-2045).** The picker preview, `--active` rows (local + cross-machine), flat `doing` cell, and metadata-only previews now show compact `✓done/total · current step` from `SessionMeta.todos` / `ActiveSession.todos`, plus the originating prompt and a directories-touched activity line. Active/cross-machine rows also show label + clickable project/ticket alongside the agent short id. Covers interactive, headless, teams, and sub-agent sessions that share the preview infra. Source: `apps/cli/src/commands/sessions-picker.ts`, `apps/cli/src/commands/sessions.ts`.
58
+
59
+ - **Checklist completions emit a feed event (RUSH-2046).** When an agent marks a
60
+ task-checklist item done, the `11-activity-log.py` hook now appends a
61
+ `task.completed` milestone to the session activity log (and `checklist.created`
62
+ the first time a checklist appears), so completions show in `agents feed` and the
63
+ unified `agents events` stream with the item subject and running `N/M`. Detection
64
+ folds the transcript across harnesses — Claude `TaskUpdate`/`TodoWrite`, Grok
65
+ `todo_write`, Codex `update_plan` — so a completion is recognized regardless of
66
+ which agent produced it. Source: `apps/cli/src/lib/activity.ts` (incl. the embedded
67
+ hook), `apps/cli/src/lib/events.ts`, `apps/cli/src/commands/feed.ts`.
68
+
69
+ - **Actor provenance now survives the SSH hop.** A run dispatched to another host
70
+ (`agents run --host`, a remote `agents teams` supervisor, or any `--host`
71
+ passthrough) used to drop the resolved actor at the SSH boundary, so the remote
72
+ re-resolved it from the *originating* box's `SSH_CONNECTION` and mis-credited the
73
+ work to the shared machine or `UNRESOLVED@<host>`. The dispatch layer now forwards
74
+ `AGENTS_ACTOR*` / `GIT_*` across the wire (POSIX `export` and Windows `$env:`
75
+ alike), so the remote inherits the origin identity instead of re-resolving. A
76
+ caller-supplied env value still wins on collision (mirrors `buildExecEnv`).
77
+ Source: `withActorEnv` in `apps/cli/src/lib/hosts/dispatch.ts`, wired into
78
+ `launchDetached` / `runInteractiveOnHost` and the `--host` passthrough. (RUSH-2028)
79
+
80
+ - **A Linux-driven release now auto-discovers its macOS sign host instead of
81
+ hardcoding `mac-mini`.** `scripts/remote-sign-mac.sh` previously defaulted
82
+ `SIGN_HOST` to `mac-mini`, so a release from a Linux box failed outright whenever
83
+ that one appliance was offline — the recurring reason a release stalled and a
84
+ human had to finish it by hand. With `SIGN_HOST` unset the script now reads
85
+ `agents devices list --json`, keeps the reachable/online macOS devices, and picks
86
+ the first that answers `ssh` in preference order `mac-mini` → `zion` → any other
87
+ online Mac. `mac-mini` stays first because it signs headlessly (no Touch ID);
88
+ `zion` (the interactive Mac) is the fallback. An explicit `SIGN_HOST=<host>` still
89
+ pins one and skips discovery, and when no reachable Mac qualifies the script fails
90
+ with the ordered list it tried rather than hanging on a dead host. Source:
91
+ `apps/cli/scripts/remote-sign-mac.sh`.
92
+
93
+ - **An agent launch never raises a Touch ID sheet.** On macOS, starting an agent
94
+ terminal or firing a routine could pop several biometric prompts in a row, because
95
+ each keychain read runs in its own helper process and the biometric assertion never
96
+ reuses across processes. Two causes: `interactiveUnlock` defaulted to true whenever
97
+ an agent name was present, which let an agent-initiated read fall through the
98
+ `agentOnly` guard; and `isHeadlessSecretsContext` recognized the `headless` and
99
+ `teams` runtimes but not `terminal`, which is what an interactive run sets. Agent
100
+ launches now resolve broker-only and a locked bundle fails fast naming
101
+ `agents secrets unlock <bundle>`; `AGENTS_SECRETS_NO_PROMPT=1` is no longer needed
102
+ as a workaround. `agents secrets get/export/exec` typed in a **plain shell** still
103
+ prompts — it carries no `AGENTS_RUNTIME`, so the guard does not apply. Run beneath
104
+ an agent it refuses, because there the agent is the caller. This narrows the
105
+ agent-triggered approval added in RUSH-2032, which is unreleased.
106
+
107
+ - **`release.sh` now borrows the npm token from a primary device when the local box
108
+ has none, so a Linux-driven release stops asking a human to approve a token.**
109
+ Token resolution was env → local `npmjs.com` bundle → *die*. On a fleet box whose
110
+ own keychain holds no npm token, that dead end pushed agents to hand-move a
111
+ credential between machines (and correctly get gated on it). A third step now
112
+ resolves the bundle **ephemerally from a primary device over SSH** —
113
+ `agents secrets exec npmjs.com --host <host>`, which resolves on the remote and
114
+ injects into the run only, never storing the token locally. It tries `SECRET_HOST`
115
+ first, then `zion`, then `mac-mini`, and fails with the list it tried if none
116
+ answer. Combined with the sign-host auto-discovery, a Linux box can now cut a full
117
+ release end-to-end given a reachable Mac for signing and any reachable device that
118
+ holds the npm token. Source: `apps/cli/scripts/release.sh`.
119
+
120
+ - **Branded, actionable daemon notifications on the routine lifecycle (RUSH-2030).**
121
+ Daemon desktop notifications (overdue routines, config heal, the no-credential
122
+ warning) now route through the `MenubarHelper.app` companion instead of raw
123
+ AppleScript, so they carry the agents-cli mark rather than the generic Script
124
+ Editor icon; they degrade to `osascript`/`notify-send` only when the helper is
125
+ not installed. The daemon also notifies when a routine **starts** and
126
+ **finishes** (success/failure, with the report's first line or the error reason
127
+ folded in), suppressing command-housekeeping start/success pings to avoid spam.
128
+ Clicking a finish notification opens the run report/log; start/overdue open the
129
+ runs folder. Source: `apps/cli/src/lib/menubar/notify-desktop.ts`,
130
+ `apps/cli/src/lib/routine-notify.ts`, `apps/cli/src/lib/daemon.ts`,
131
+ `apps/cli/menubar/Sources/MenubarHelper/PromptPanel.swift`.
132
+
133
+ - **`agents secrets status` now suggests which bundles to unlock.** It reads the existing `secrets.get` audit events and surfaces bundles you keep getting a Touch ID prompt for — read from the keychain (not served silently by the broker/session) 3+ times in the last 7 days and not currently held — with a ready `agents secrets unlock <name>` command. `never`/no-ACL bundles (which never prompt) are excluded, and the hint is best-effort so it never breaks `status`. Source: `apps/cli/src/lib/secrets/unlock-hints.ts`, `apps/cli/src/commands/secrets.ts`.
134
+
135
+ - **`agents sessions <id>` with a short/partial id resolves by id only — no more
136
+ "Multiple sessions match" from fuzzy content.** A complete UUID already resolved
137
+ by id, but a bare hex short-id like `d3470b57` was not caught by
138
+ `isCompleteSessionId`, so it fell through to the ranked content search and
139
+ surfaced every transcript that merely MENTIONED the string (a resume prompt
140
+ echoes the parent id into the body of many later sessions) — a real view id
141
+ returned a list of unrelated sessions. Any id-shaped query — complete id OR hex
142
+ short-id/prefix (`looksLikeSessionId`) — now resolves through the index by id in
143
+ both `resolveSessionQuery` and the `renderOneSession` content-widen gate, and
144
+ reports "no session found" when nothing matches instead of content-searching.
145
+ Free-text phrases keep the ranked search path. Source:
146
+ `apps/cli/src/commands/sessions.ts`.
147
+
148
+ - **`agents sessions --active` attributes the initiating device for SSH-launched
149
+ sessions.** A session started by ssh'ing into a box (common for tmux-hosted
150
+ runs) used to render as `local` with no origin, because the tmux discovery path
151
+ stamped a `transport:'local'` placeholder that made provenance enrichment skip
152
+ it. Enrichment now probes the pane process's env and upgrades the row to `ssh`
153
+ with the real origin, then resolves the SSH client IP against the device
154
+ registry into `provenance.origin` (`{ device, user? }`). Both the flat listing
155
+ and the interactive browser read `ssh←<device>` (e.g. `ssh←zion`); an
156
+ unregistered IP stays bare `ssh`. Answers "which box launched this session"
157
+ without scraping `ps`/`who`/`tailscale`. Source:
158
+ `apps/cli/src/lib/session/active.ts`, `apps/cli/src/lib/session/provenance.ts`,
159
+ `apps/cli/src/commands/sessions.ts`, `apps/cli/src/commands/sessions-browser.ts`.
160
+
161
+ - **`agents sessions --include user` / `--first` / `--last` now count genuine user turns, not harness-injected scaffolding (#1550).** A Claude session opened with a `!`-prefix command (e.g. `j <dir>`) stores `<bash-input>`/`<bash-stdout>` as `role=user` records, and `<system-reminder>`/`<task-notification>`/`<command-*>`/hook-feedback/skill bodies land the same way — so `--include user --first 3` returned the jump command and its shell output before the real ask, and every consumer of `--include user` (the `verify-work-complete` Stop hook's "original request" self-audit, `session-recall`) inherited the noise. `parseSession` now flags these injected `role=user` events `_synthetic` at its central post-parse chokepoint via one shared classifier (`isSyntheticUserMessage`), so turn slicing (`applyTurnSlice`) and role filtering (`roleOfEvent`) skip them; they stay in the default/`--markdown` stream for full fidelity. Claude-specific in practice — Codex/Gemini/OpenCode/Grok/Kimi/Rush route shell output to `tool_result`, never to `role=user`, and Droid's `<system-reminder>` was already dropped — but the classifier is cross-harness by construction. Source: `apps/cli/src/lib/session/{prompt,parse,render,types}.ts`.
162
+
163
+ - **Routine/daemon Claude runs authenticate the rotation-pinned account via its own long-lived setup-token — fixes fleet-wide daily logout.** Claude Code's interactive OAuth session uses single-use *rotating* refresh tokens: when one fleet machine refreshes, the server invalidates that account's token on every other machine, so unattended boxes 401 and drop (Claude Code #25609/#56339). A `claude setup-token` is a 1-year, non-rotating token that sidesteps this. The daemon now injects every `CLAUDE_CODE_OAUTH_TOKEN_<account>` present in the `claude` bundle (not just the one ambient token), and a routine spawn selects the token matching the account its version-home is pinned to (`runner.ts` `buildRoutineSpawnEnv` → `resolveAccountSetupToken`), so each unattended account authenticates with its own setup-token instead of the rotating interactive session. Works on macOS too, where the prior drop-based path was inert. Inert (no behavior change) until per-account setup-tokens are stored in the no-ACL `claude` bundle. Interactive `agents run` and remote `--host` dispatch are unchanged (out of scope; noted for follow-up). Source: `apps/cli/src/lib/secrets/account-token.ts`, `apps/cli/src/lib/runner.ts`, `apps/cli/src/lib/daemon.ts`, `apps/cli/src/lib/sandbox.ts`.
164
+
165
+ ## 1.20.76
166
+
167
+ - **The routines daemon can read a `never`/no-ACL secrets bundle headlessly again — fixes a false "no Claude credential" alert.** The headless secrets guard (`readAndResolveBundleEnv`'s `agentOnly` branch) threw for every keychain-backed bundle absent from the broker, but a `never`/no-ACL bundle carries no biometry ACL — its reads raise no Touch ID sheet, so blocking it served no purpose. That wrongly blocked the automation-only `claude` bundle the routines daemon reads at startup (`readDaemonClaudeOAuthToken`), leaving every scheduled Claude routine token-less, and — on the new auth-failure alert path — firing "no Claude credential" on each daemon start even when the bundle was configured correctly. The guard now exempts `never`/no-ACL bundles (policy learned via a prompt-less metadata read), matching the existing file-backend exemption. Source: `apps/cli/src/lib/secrets/bundles.ts`.
168
+
169
+ - **`release.sh` can now cut the next patch when main is ahead of an unpublishable
170
+ version.** The catch-up guard refuses to publish a merged release PR whose squash pulled
171
+ in concurrent main commits — correctly, since the tree that would ship is not the tree CI
172
+ tested (the hole that let 1.20.58 publish before its Windows matrix failed). Its refusal
173
+ advises cutting the next patch through the normal release PR flow, but the version
174
+ validator measured patch+1 from the REGISTRY, so with main at 1.20.75 and npm at 1.20.74
175
+ both 1.20.75 (blocked) and 1.20.76 (read as a skipped version) were rejected — leaving no
176
+ patch-level path forward and a minor bump as the only escape. A new `patch-from-main`
177
+ case accepts the version one patch above `package.json` when main is ahead of the
178
+ registry; it grants no bypass, and the release still earns its own release PR, full
179
+ cross-platform matrix, merge, tag, and publish. The decision moved out of `release.sh`
180
+ into `scripts/validate-bump.sh` so it can be tested directly — `release.sh` itself
181
+ cannot be run in a test, since it demands a clean main plus npm and gh auth long before
182
+ it reaches the bump decision, which is why this arithmetic had no coverage at all. The
183
+ rejection message now also lists the main-ahead options only when main really is ahead,
184
+ instead of advising a version the script would then refuse. Source:
185
+ `apps/cli/scripts/validate-bump.sh`, `apps/cli/scripts/release.sh`.
186
+
187
+ - **Teams now run local teammates under one frozen actor.** The orchestrator was
188
+ spawning each local teammate through a raw shell without the actor env, so every
189
+ teammate's inner `agents run` re-resolved the actor independently instead of
190
+ inheriting the orchestrator's — contradicting the "resolve once, whole tree
191
+ shares one actor" contract. The local spawn env now carries `actorEnv(resolveActor())`
192
+ (process env < actor < `--env` overrides), so all teammates inherit the single
193
+ frozen actor. Teammate records also carry an `actor` field, persisted to
194
+ `meta.json` and emitted in the status dict. Remote teammates inherit the fix at
195
+ the dispatch layer. Source: `apps/cli/src/lib/teams/agents.ts`.
196
+
197
+ - **`agents sessions <full-session-id>` no longer answers with an unrelated
198
+ session.** A complete id that was not in the local index fell through to the
199
+ FTS content search, which tokenizes the UUID and matches every transcript that
200
+ merely *mentions* it. The miss surfaced as up to ten unrelated sessions under
201
+ `Multiple sessions match "<id>"` plus the advice `Pass a longer ID to narrow it
202
+ down` — impossible to follow, since a full id is already the longest form. The
203
+ same fallthrough made `--preview` render a different session's transcript, let
204
+ an 8-char short id lose to a content hit, and made `agents sessions export
205
+ <id>` bundle every transcript that mentions the id (14 unrelated sessions
206
+ written into an archive meant to be handed to someone else). A query that is a
207
+ whole session id now resolves by id alone: it reports `No session with id <id>
208
+ on this machine.` and points at `--device <host>` for the fleet. Short-id
209
+ prefixes and text searches are unchanged. The recognized shapes are the ones
210
+ the index actually holds — a bare UUID, `session_` + UUID (kimi, rush), and
211
+ `ses_` + 26-char ULID (opencode); routine run ids and cloud execution ids stay
212
+ out of scope and keep today's search behavior. Source:
213
+ `apps/cli/src/lib/session/discover.ts` (`isCompleteSessionId`),
214
+ `apps/cli/src/commands/sessions.ts` (`resolveSessionQuery`), and
215
+ `apps/cli/src/commands/sessions-export.ts` (`selectSessions`).
216
+
217
+ ## 1.20.75
218
+
219
+ - **Wire native file-based slash commands for Grok (RUSH-1851).** Grok >= 0.2.111
220
+ discovers commands from the cross-agent `~/.agents/commands/` dir (plus the
221
+ legacy `~/.claude/commands/` symlink). `agents sync grok` now writes native
222
+ `.md` command files there instead of converting commands to skills, so `agents
223
+ view grok` and `agents commands list grok` report Grok as commands-capable.
224
+ Source: `apps/cli/src/lib/agents.ts`.
225
+
226
+ - **Document that Droid Factory Missions are invoke-only (RUSH-1864).** Probed
227
+ droid v0.177.0 (self-updating; ticket cited v0.161.0) and Factory docs:
228
+ Missions run via `/missions` or `droid exec --mission` (optional `-f` is a
229
+ prompt file, not a named template). `~/.factory/missions/<sessionId>/` is
230
+ runtime state only — no auto-discovery dir agents-cli can populate — so
231
+ `workflows` stays `false` with an evidence comment rather than inventing a
232
+ writer target. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/tests/agents.test.ts`.
233
+
234
+ - **Reading state no longer writes `agents.yaml`, which silently deadlocked
235
+ `agents repo pull` (RUSH-1925).** Registry presets from `SEEDED_REGISTRIES` (today
236
+ `skill.hermes`) were seeded on the state **read** path, which wrote the registry entry
237
+ plus a `seededPresets` marker into `agents.yaml`. That file is git-tracked in the user's
238
+ DotAgents repo, so the write left the working tree dirty and every subsequent
239
+ `agents repo pull` aborted with `Working tree has uncommitted changes` — naming neither
240
+ the file nor the cause. Because *every* `agents` invocation reads state, the dirt
241
+ reappeared the instant it was cleared: `git checkout -- agents.yaml && agents repo pull`
242
+ re-seeded before the pull ran, so the loop could not be escaped through the CLI at all.
243
+ On a host with several live agent sessions even a raw `git pull --rebase` lost the race,
244
+ and one machine sat 27 commits behind for weeks as a result. Seeded presets are now
245
+ resolved in memory by `getRegistries` — the same way `DEFAULT_REGISTRIES` has always
246
+ worked — so nothing is written and no later write can flush them into the file.
247
+ `seededPresets` becomes a removal tombstone: `agents registry remove skill hermes`
248
+ records the key and the preset stops being offered, which is the behaviour the marker
249
+ existed to protect. Files seeded by the old code carry both the tombstone and an explicit
250
+ entry in their own `registries:` block, and the explicit entry still wins, so upgrading
251
+ changes nothing for them. `setRegistry` also falls back to
252
+ `SEEDED_REGISTRIES` when merging a partial update, so `registry disable/enable/config`
253
+ on a never-materialized preset can no longer persist a stripped entry that drops `url`.
254
+ Source: `apps/cli/src/lib/registry.ts`,
255
+ `apps/cli/src/lib/state.ts`, `apps/cli/src/lib/registry.seeds.test.ts`,
256
+ `apps/cli/src/lib/state.test.ts`.
257
+
258
+ - **`agents fleet status` no longer hangs on an unreachable box (RUSH-1964).** The cheap
259
+ stats probe (~2.5s) already learns whether each box is reachable, but the dead-box skip
260
+ that spares the expensive `agents --version` (15s) + `agents doctor --json` (30s) dials
261
+ was gated behind `--refresh` — so a default run still spent up to 45s per genuinely
262
+ unreachable box, and one down box could stall the whole matrix. The default path now
263
+ gates those dials on the same reachability verdict: a box the stats probe found
264
+ unreachable short-circuits straight to an `unreachable` row with zero further SSH
265
+ round-trips. Measured on a single blackholed target, `fleet status` dropped from 22.8s
266
+ to 2.8s. (VPN-first transport and SSH key provisioning remain deferred.) Source:
267
+ `apps/cli/src/lib/devices/fleet.ts` (`fleetHealthSkip`), `apps/cli/src/commands/ssh.ts`
268
+ (`runFleetStatus`).
269
+
270
+ - **Fleet reachability reflects the live probe (RUSH-1965).** `agents devices` and `agents fleet status` now persist the live SSH probe's `{reachable, via, checkedAt}` verdict to the registry and read the online/offline word from it — freshest signal wins: a live stat this run, then the written-back verdict, then the cached `tailscale.online` snapshot. A reachable box (including a `via:"manual"` device with no Tailscale peer) no longer renders "offline" while its live load/mem sit one column over. Source: `apps/cli/src/lib/devices/reachability.ts`, `apps/cli/src/lib/devices/registry.ts`.
271
+
272
+ - **`agents fleet status` output redesigned — rollup + NEEDS ATTENTION, quiet when healthy
273
+ (RUSH-1966).** The old grid buried "is my fleet OK?" under duplicated columns and glyph
274
+ soup (a "Health" column that just repeated Load/Mem, `stale · cold` counts across every
275
+ orphan version, an `●5 ·8 ◐3` auth cell, and warnings that re-listed all 12 devices three
276
+ times). The default view now leads with a one-line rollup (`● N online · ○ M offline`),
277
+ then a short **NEEDS ATTENTION** list where every item names its fix command — offline →
278
+ `check the box`, config drift or a stark CLI gap → `agents apply <box>`, version skew →
279
+ `agents upgrade --fleet` — then quiet per-device rows grouped by OS (macOS / Linux /
280
+ Windows) showing `name · capacity · load/mem · version`, with this machine flagged
281
+ `▸ … ← this machine`. Drift is reported on the active version only (not orphan versions);
282
+ orphaned versions are demoted to a one-line `agents prune` nudge in the footer; the
283
+ freshness footer names the cache age and what `--live` / `--verbose` add. A healthy fleet
284
+ reads in a few lines. The full per-device auth/CLI/sync/version grid moves behind the new
285
+ `--verbose` flag; `--json` is unchanged. Source: `apps/cli/src/lib/devices/health-report.ts`
286
+ (`renderFleetSummary`, `buildFleetAttentionItems`), `apps/cli/src/commands/ssh.ts`
287
+ (`runFleetStatus`).
288
+
289
+ - **`agents doctor` now flags credentials exported from shell rc files (RUSH-1968).** A
290
+ secret exported from `~/.zshenv`/`~/.zshrc`/`~/.bashrc`/`~/.profile` is inherited by every
291
+ process the login shell spawns and is readable from `/proc/<pid>/environ` by any same-user
292
+ process — `.zshenv` is sourced even by non-interactive `ssh host 'cmd'`, so the value lands
293
+ in essentially everything the box runs. The doctor overview now scans the current user's rc
294
+ files and prints a `Secrets in shell config` warning that names each credential-shaped
295
+ export by `file:line` and variable name (never the value), with the file-store master key
296
+ `AGENTS_SECRETS_PASSPHRASE` called out separately — its off-env home is
297
+ `~/.agents/.secrets-key/passphrase` (chmod 600), and other credentials should move to
298
+ `agents secrets` and inject via `agents secrets exec`. The scanner reads only the variable
299
+ name and line number, so a finding is safe to print or log. Source:
300
+ `apps/cli/src/lib/secrets/rc-hygiene.ts` (`scanUserRcFiles`, `scanRcExports`,
301
+ `rcSecretWarningLines`), wired into `apps/cli/src/commands/doctor.ts`
302
+ (`renderRcHygieneAdvisory`).
303
+
304
+ - **`agents devices` no longer forces a Touch ID prompt on a password-auth box
305
+ (RUSH-1970).** The read-only stats probe's live SSH to an uncached
306
+ `auth.method === 'password'` device used to drive the askpass shim to resolve
307
+ the SSH password through the biometry-gated Keychain sheet under a TTY, popping
308
+ Touch ID during what should be a silent probe. The probe now threads a
309
+ broker-only signal (`AGENTS_SSH_AGENT_ONLY`) so it resolves from an
310
+ already-unlocked broker or degrades to an unreachable row — never a biometric
311
+ prompt. Source: `apps/cli/src/commands/ssh.ts`,
312
+ `apps/cli/src/lib/devices/health.ts`, `apps/cli/src/lib/devices/connect.ts`.
313
+
314
+ - **`agents sessions migrate` (alias `relocate`) relocates a RUNNING session onto
315
+ another machine, then stops the source here (RUSH-1977).** `--auto` scores the
316
+ fleet and picks a target, `--host <name>` names one explicitly, and `--lease`
317
+ provisions a fresh ephemeral box; `--mode resume|rehydrate` chooses whether the
318
+ target resumes the native transcript or replays it via `/continue`. Every
319
+ migration is written to an append-only ledger, viewable with `agents sessions
320
+ migrations`. Load-bearing invariant: the source session is never stopped until
321
+ the transcript is confirmed live on the target, so a failed hop leaves the
322
+ original running. (Not to be confused with `agents sessions detach`/`attach`,
323
+ the unrelated background/foreground pair.) Source:
324
+ `apps/cli/src/commands/sessions-migrate.ts`,
325
+ `apps/cli/src/lib/session/migrate-targets.ts`,
326
+ `apps/cli/src/lib/session/migrations.ts`.
327
+
328
+ - **`agents sessions --active --json` now emits flat `ticketId` and `project`
329
+ keys on every row.** A supervising watcher joins active sessions on ticket +
330
+ project, but the raw row nested the ticket under `ticket.id` and carried no
331
+ `project` at all, so a naive join silently dropped every session. Each row now
332
+ carries top-level `ticketId` (from the detected ticket) and `project` (the
333
+ basename of the session's cwd — the same derivation the historical `--json`
334
+ listing uses), both always present and `null` when unknown. The existing raw
335
+ fields are unchanged. Source: `apps/cli/src/commands/sessions.ts`
336
+ (`serializeActiveSessionsForJson`).
337
+
338
+ - **Actor provenance — agent git commits are now credited to the human who
339
+ started the run, not the shared account.** One account across a shared fleet
340
+ meant every commit, from anyone who SSH'd into a box, showed up as the same
341
+ author. `resolveActor()` now identifies who is behind a run: over SSH it
342
+ `tailscale whois`es the client IP to the connecting tailnet identity (name +
343
+ login email); locally it stays honest with `UNRESOLVED@<host>` and claims no
344
+ identity. The resolved actor rides the agent's process env as `AGENTS_ACTOR` /
345
+ `AGENTS_ACTOR_KIND` (inherited by the whole spawn tree, so it resolves once),
346
+ and for a resolved human it also injects `GIT_AUTHOR_*` / `GIT_COMMITTER_*` — so
347
+ the agent's own `git commit` is attributed to the person. An unresolved actor
348
+ injects no git identity, so local runs keep their ambient git config unchanged.
349
+ Source: `apps/cli/src/lib/actor.ts`, wired into `buildExecEnv`
350
+ (`apps/cli/src/lib/exec.ts`).
351
+
352
+ - **New optional `actors:` map in `agents.yaml`.** Keyed by a short slug, each
353
+ entry (`kind` / `name` / `email` / `github` / `login`) enriches or overrides
354
+ what `tailscale whois` resolves — pin a preferred git email, add a GitHub
355
+ handle, override the display name, or mark an entry as an agent rather than a
356
+ human. Entirely optional: a tailnet SSH identity already resolves without it.
357
+ Source: `apps/cli/src/lib/types.ts` (`ActorConfig`, `Meta.actors`).
358
+
359
+ - **`agents run antigravity "prompt"` now works headless without an explicit
360
+ `--headless`.** Antigravity's `--print` flag was gated on the raw `--headless`
361
+ flag, which defaults to `false` at the CLI layer — but headless is inferred from
362
+ prompt presence. So a bare `agents run antigravity "do X"` built `agy <prompt>`
363
+ with no `--print`, launching the interactive TUI and dying with
364
+ `bubbletea: could not open TTY: /dev/tty` in any non-terminal shell (headless
365
+ runs, teams, routines, `--host`). Print flags are now gated on the resolved
366
+ headless state, matching the documented "`--headless` auto-enabled when a prompt
367
+ is provided" contract and the behavior of every other agent. Antigravity was the
368
+ only agent affected — it is the sole harness whose prompt is a bare positional
369
+ with no headless subcommand and no `-p` print alias. Source:
370
+ `apps/cli/src/lib/exec.ts`.
371
+
372
+ - **Routine auth-failures are now detected, not silent.** When a routine's agent is logged
373
+ out or its token is revoked, the run is classified `failed` with an `auth_failed:` /
374
+ `auth_preflight:` reason instead of a generic non-zero exit. The login-error text is no
375
+ longer written into `report.md`, and `{last_report}` now only injects the last *completed*
376
+ run's report — so a single logged-out run can no longer poison every subsequent run's
377
+ prompt. Classification uses the Claude stream-json markers (`error:"authentication_failed"`
378
+ and a `result` event with `is_error:true`), which is the reliable signal — `terminal_reason`
379
+ is `"completed"` on a logged-out run. Rate-limit still classifies first, so a 429 keeps
380
+ triggering failover rather than being mistaken for an auth failure. Source:
381
+ `apps/cli/src/lib/exec.ts`, `apps/cli/src/lib/runner.ts`, `apps/cli/src/lib/routines.ts`.
382
+ - **`agents routines run` now exits non-zero when a run doesn't complete.** A run that ends
383
+ in `failed`, `timeout`, or an auth failure returns exit code 1 and `--json { "ok": false, … }`
384
+ with the reason, instead of exiting 0 with `ok:true` — so cron wrappers, `&&` chains, and
385
+ `--json` consumers actually see the failure. (Exit code is set via `process.exitCode` so the
386
+ JSON payload flushes fully to a pipe first.) Source: `apps/cli/src/commands/routines.ts`.
387
+ - **Auth preflight before dispatch.** A routine whose (agent, version) has a cached
388
+ `revoked` auth verdict fails fast with `auth_preflight: revoked` without spawning a
389
+ doomed agent. Fails open on any other verdict, so a stale/absent probe or a network blip
390
+ never blocks a run, and agents with no live probe (codex/gemini/grok) are never blocked.
391
+ Source: `apps/cli/src/lib/runner.ts`, reusing `apps/cli/src/lib/auth-health.ts`.
392
+ - **The routines daemon no longer starts silently token-less.** When no Claude OAuth token
393
+ is available (e.g. a headless macOS daemon whose keychain was locked at start), the daemon
394
+ now logs a `WARN` and fires a desktop notification instead of quietly spawning Claude
395
+ routines that all fail auth. Source: `apps/cli/src/lib/daemon.ts`.
396
+
397
+ - **`agents sessions` indexes again from the standalone binary.** Every session
398
+ write went through a named-parameter bind (`INSERT ... VALUES (@id, @short_id,
399
+ ...)` in `apps/cli/src/lib/session/db.ts`), and `bun:sqlite` matches such an
400
+ object only when its keys carry the SQL sigil — bare keys bound nothing, so all
401
+ columns landed NULL and `sessions.short_id` (NOT NULL) rejected the row. The
402
+ shims exec the Bun-compiled `dist/bin/agents`, so no session reached the index
403
+ from the CLI: `agents sessions` printed `Warning: skipped unindexable session
404
+ <id>: NOT NULL constraint failed: sessions.short_id` per session and then
405
+ listed only rows indexed earlier by the Node entrypoint. The suite runs under
406
+ Node (vitest), where `node:sqlite` accepts bare keys, which is why CI stayed
407
+ green. `apps/cli/src/lib/sqlite.ts` now opens the DB with `strict: true` under
408
+ Bun, so the bare-key call shape this codebase uses works on both runtimes (the
409
+ edges still differ — the module doc lists what strict changes). `sqlite.test.ts`
410
+ covers both the bind and a full `agents sessions` scan in a real `bun`
411
+ subprocess.
412
+
413
+ - **The compiled binary no longer reports itself as a phantom `/$bunfs` install, and can self-upgrade again.**
414
+ Since the standalone executable started shipping (1.20.53), the running copy located
415
+ its own package root as `<__dirname>/..`. Under a Bun standalone binary `__dirname`
416
+ is the embedded virtual filesystem, so that resolved to `/$bunfs` — a path that
417
+ exists nowhere. Two symptoms followed on every machine running the compiled binary:
418
+ the multi-install check reported one install as two (`/$bunfs (running)` alongside
419
+ the real npm root, with the misleading advice to uninstall a stale copy that did not
420
+ exist), and `agents upgrade` failed closed with `/$bunfs is not an npm-managed
421
+ install` because no global prefix can be derived from a virtual path. A new
422
+ `resolveRunningPackageRoot()` resolves the real on-disk root by walking up from
423
+ `process.execPath` to the directory whose `package.json` names this package, and
424
+ both sites use it. The PATH scan also recognizes `<root>/dist/bin/agents` as an
425
+ entrypoint, so a shim pointing at the compiled binary — typically first on PATH, and
426
+ the copy that actually runs — resolves to the same root as its sibling npm bin
427
+ instead of being invisible. Genuine multi-install warnings still fire, now naming a
428
+ real, actionable path. Source: `apps/cli/src/lib/self-update.ts`,
429
+ `apps/cli/src/index.ts`, `apps/cli/src/lib/self-update.test.ts`.
430
+
431
+ - **Reading Claude usage no longer rotates the token and logs your fleet out.**
432
+ `getClaudeUsageInfo` refreshed the OAuth token just to read the usage endpoint
433
+ (`getClaudeAccessToken`, `usage.ts`) — and Claude's refresh token is single-use
434
+ and rotates server-side, so with one account signed into several machines that
435
+ background refresh (fired by the stale-while-revalidate usage cache and by
436
+ `agents run`'s default "balanced" rotation on every unpinned run) invalidated
437
+ every other box's copy, dropping the fleet to "run /login". This is the
438
+ RUSH-1822 stampede, which was fixed for the 3-minute health probe but left live
439
+ in the usage/run hot path. Usage reads are now strictly read-only: a new pure
440
+ `claudeUsageAccessTokenNoRefresh` uses the stored access token and, when it is
441
+ within the refresh leeway, reports "no usage right now" instead of rotating —
442
+ exactly mirroring `probeClaudeStatus`. The single legitimate refresh stays on
443
+ the real `claude` run, never a usage read. Source: `apps/cli/src/lib/usage.ts`,
444
+ `apps/cli/src/lib/usage.test.ts`.
445
+
446
+ - **No more Touch ID storm from the usage view.** On macOS, the usage-bar fetch
447
+ (`agents view`, the Factory watchdog that polls `agents view --json` every 60s
448
+ per agent) and the daemon's every-3-min auth-health probe each read Claude's own
449
+ ACL-bound `Claude Code-credentials-<hash>` keychain item on every refresh — each
450
+ read popping a Touch ID prompt, so several running Claude agents meant a
451
+ biometric prompt every couple of minutes. Those two access-token-only, high-
452
+ frequency callers now opt into a device-local **no-ACL** access-token cache (the
453
+ prompt-free mechanism `secrets/session-store.ts` uses for unlocked bundles),
454
+ bounded by the token's own expiry, so the ACL-gated read happens at most once per
455
+ token lifetime and every agent process reads the cache silently. The cache is
456
+ opt-in and caches only the short-lived access token — callers that need the full
457
+ credential (`isClaudeAuthValid`'s refresh, `readClaudeCredentialsBlob`'s Rush
458
+ Cloud export) still take the ACL read with the refresh token intact. Source:
459
+ `apps/cli/src/lib/usage.ts`.
460
+
461
+ - **The daemon no longer silently repoints your default agent version.** The unattended
462
+ 6-hourly launch-health pass (`healBrokenDefaultLaunches` → `ensureAgentRunnable`) now runs
463
+ with `allowDefaultSwitch: false`: it still repairs the *current* default in place, but if
464
+ that default can't be repaired it no longer adopts another installed version or installs
465
+ `latest` and pins it. A background default switch installs a fresh version home, which for
466
+ Claude is a fresh, empty credential scope (macOS keychain keyed off `CLAUDE_CONFIG_DIR`;
467
+ Linux per-version token file) — i.e. an "unprovoked logout" at a time uncorrelated with
468
+ anything you did, and a leading cause of routine auth-failures on unattended machines. The
469
+ daemon now logs a `WARN` naming the version to pick instead; interactive callers
470
+ (`agents run`, `agents add`) are unchanged and still repoint as before. Source:
471
+ `apps/cli/src/lib/versions.ts`, `apps/cli/src/lib/daemon.ts`.
472
+
473
+ - **`agents sessions detach` / `agents sessions attach` — send a running agent to the
474
+ background and back.** `agents sessions detach <id>` stops a live session's
475
+ interactive process (killing the tmux session when tmux-hosted, else SIGTERM'ing the
476
+ pid) and continues it **headless**, detached, via the existing version-pinned
477
+ `agents run --resume` path — so it drives its task to completion without holding a
478
+ terminal. The resumed run carries a nudge that tells the now-unwatched agent it is
479
+ headless and to make the call rather than stall on a confirmation nobody can answer.
480
+ `agents sessions attach <id>` stops that headless continuation and **resumes the
481
+ session interactively** in the current terminal (`resumeSessionInPlace`) — the same
482
+ session and full history, including whatever the background run did. They sit under
483
+ `sessions` next to `focus`/`resume` (the session-lifecycle verbs); the Factory
484
+ extension exposes them as **Agents: Detach** (`Cmd/Ctrl+K B`) and **Agents: Attach**
485
+ (`Cmd/Ctrl+K A`). Both verbs are agent-agnostic (native resume for Claude/Codex,
486
+ `/continue` replay for the rest). A session on **another host** is detached there over SSH rather than
487
+ killed locally; **cloud and team sessions are refused** (they have their own
488
+ lifecycles); the interactive process is fully awaited before the headless resume
489
+ starts (no transcript race); and the background run's output is captured to
490
+ `~/.agents/.cache/logs/detach-<shortid>.log` so a crash after detach is
491
+ debuggable. `agents sessions --active --json` now carries a `presence` field
492
+ (`attached` / `background` / `parked`), folded onto every row from a per-session
493
+ detach record, so the menu bar and Factory show where each agent is. Source:
494
+ `apps/cli/src/commands/detach.ts`, `apps/cli/src/commands/attach.ts`,
495
+ `apps/cli/src/lib/session/detached.ts`.
496
+ (`agents sessions migrate`'s old `detach` alias is renamed to `relocate` to free
497
+ the `detach` name for the background/foreground verb — `migrate` and `relocate`
498
+ both still work.)
499
+
500
+ - **`agents devices sync` no longer auto-registers tailnet nodes another user
501
+ shared into the tailnet.** `tailscale status` includes ShareeNode peers (for
502
+ example a tagged relay shared in by a teammate); the parser ignored that flag,
503
+ so bootstrap registered them as your own boxes and they surfaced in `agents
504
+ fleet ls`. Parsing now carries a `sharee` flag, `runDeviceSync` filters those
505
+ peers out of auto-registration and suggestions, and the interactive picker
506
+ leaves them unchecked (labeled `shared`). Deliberate paths — `devices
507
+ register`/`add` and a `fleet:` manifest — still reach shared nodes when you name
508
+ them. Source: `apps/cli/src/lib/devices/sync.ts`,
509
+ `apps/cli/src/lib/devices/tailscale.ts`.
510
+
511
+ - **Faster `agents sessions` on large / unchanged session trees.** Session
512
+ discovery re-walked and re-`stat`'d every transcript directory on every
513
+ `agents sessions` / `output` / `view` / `teams` call — for a heavy user the
514
+ immutable version-home and backup roots dominated the cost yet never changed. A
515
+ new `dir_ledger` (SQLite, schema v14) caches each leaf transcript directory's
516
+ `(mtime, entry_count)`; when both match, the per-file `stat` of that directory
517
+ is skipped and its unchanged files are served straight from the DB, so those
518
+ immutable roots cost one dir stat each instead of hundreds of per-file stats.
519
+ Append safety is preserved: a file under the agent's live `~/.<agent>` root, or
520
+ scanned within the last 10 minutes, is always re-`stat`'d (a parent-dir mtime
521
+ bumps on create/delete/rename but NOT on an in-place append), so a growing live
522
+ session is never missed; a create / delete / rename bumps the dir mtime and
523
+ forces a full re-walk of that dir exactly as before. Wired into the Claude and
524
+ Gemini scanners (the biggest win); the other scanners keep the existing
525
+ per-file path. Set `AGENTS_SESSIONS_NO_DIR_LEDGER=1` to disable the
526
+ short-circuit and force the old full per-file walk. Source:
527
+ `apps/cli/src/lib/session/db.ts`, `apps/cli/src/lib/session/discover.ts`.
528
+
529
+ - **`agents feed post` — agents announce progress without opening a “needs you”
530
+ block.** Free-text status posts append a `status.posted` milestone to the
531
+ per-session activity log (same stream as `agents activity` and the feed’s
532
+ recent-activity lane). Session/agent/host/runtime/pid/launch identity is
533
+ auto-stamped from the process env and the per-pid launch registry — no
534
+ domain-specific flags (tickets, URLs). Managed runs export `AGENT_SESSION_ID`
535
+ / `AGENTS_AGENT_NAME` / `AGENTS_CWD` so a Bash tool call needs no extra
536
+ wiring. Source: `apps/cli/src/lib/feed-post.ts`, `apps/cli/src/commands/feed.ts`,
537
+ `apps/cli/src/lib/activity.ts`, `apps/cli/src/lib/exec.ts`.
538
+
539
+ - **PID-reuse protection now works on Windows, from one implementation.**
540
+ `captureProcessStartTime()` existed twice — in `pty-server.ts` and `teams/agents.ts` —
541
+ and neither copy had a Windows branch: both fell through to `ps`, which does not exist
542
+ there, so the function always returned `null` and every caller silently skipped the
543
+ guard. A dead session whose PID the OS had recycled read as alive, and
544
+ `agents teams stop` could signal an unrelated process group. Both copies now delegate to
545
+ a single implementation in `platform/process.ts` that reads `CreationDate` from
546
+ `Win32_Process` as a culture-independent FILETIME, memoizes per PID (the listing path
547
+ probes one PID per row), and bounds the spawn with a timeout. Source:
548
+ `apps/cli/src/lib/platform/process.ts` (`captureProcessStartTime`).
549
+
550
+ - **A session's recorded working directory is no longer rebased onto the local drive.**
551
+ `normalizeCwd()` ran `path.resolve()` over a cwd read out of a transcript, which may name
552
+ a directory on another machine. On Windows that grafted the current drive onto a POSIX
553
+ path (`/Users/me` became `D:\Users\me`), inventing a location that never existed. A
554
+ foreign path is now normalized with POSIX rules and never realpath'd; local paths still
555
+ normalize and resolve symlinks as before. Source:
556
+ `apps/cli/src/lib/session/discover.ts` (`normalizeCwd`).
557
+
558
+ - **`agents cloud`'s task database can now be closed.** `cloud/store.ts` opened `tasks.db`
559
+ and exported no closer, so nothing could release the handle — on Windows that leaves the
560
+ file un-unlinkable. Adds `closeStore()`, the mirror of `closeDB()` in `session/db.ts`.
561
+ Source: `apps/cli/src/lib/cloud/store.ts` (`closeStore`).
562
+
563
+ - **Collapse indistinguishable worker processes into one active-session row.** A daemon
564
+ that spawns many agent binaries (an OpenClaw gateway running `codex app-server`) produced
565
+ one `sessions --active` row per process, because a row with no session id and no
566
+ transcript file skipped dedupe entirely — the Factory Floor showed ~40 identical
567
+ `.openclaw · bg · 0s ago` rows that buried every real session. Dedupe now falls back to
568
+ the cloud/run handle and then to the worker's identity (agent binary + context + working
569
+ directory), so N indistinguishable workers become one row carrying `pidCount: N`.
570
+ Source: `apps/cli/src/lib/session/active.ts`.
571
+
572
+ - **`sessions --active` now stamps a start and last-activity time on every
573
+ interactive session.** Terminal, tmux, and headless agents discovered by the
574
+ process scan carried no `startedAtMs` — so the Factory Floor rendered every
575
+ running agent as "0s ago" even when its transcript, topic, and progress had
576
+ resolved. The scan now stamps `startedAtMs` (the SessionStart hook's own
577
+ timestamp, else the transcript's creation time) and a new `lastActivityMs` (the
578
+ transcript's last-write) on each row, and the Floor renders "Xs ago" off the
579
+ real last-activity instead of the session's age. Source:
580
+ `apps/cli/src/lib/session/active.ts`, `apps/cli/src/lib/session/hook-sessions.ts`.
581
+
582
+ - **`agents sessions` now classifies Grok transcripts into real events, not a
583
+ one-line stub.** Grok sessions were indexed (title, timestamps, message count)
584
+ but opening one showed a single placeholder `session_start` event — `parseGrok`
585
+ was a stub. It now reads the session's `chat_history.jsonl` and normalizes every
586
+ line into the shared `SessionEvent` shape: `user`/`assistant` messages,
587
+ `reasoning` → thinking, `assistant.tool_calls[]` → tool_use (with `path` and
588
+ `command` surfaced), and `tool_result` correlated back to its call by
589
+ `tool_call_id` (an `Error:`-prefixed result becomes an error event). The scanner
590
+ records `summary.json` as the session path, so the parser resolves
591
+ `chat_history.jsonl` beside it; per-line timestamps aren't stored, so each event
592
+ carries the session's `created_at` (falling back to the transcript mtime).
593
+ Verified end-to-end against a real Grok session (30 events: messages + thinking +
594
+ tool_use + tool_result). Source: `apps/cli/src/lib/session/parse.ts`.
595
+
596
+ - **`agents harness` — name a (host CLI + model) combo and run it like a native agent
597
+ type.** `agents harness add spark --host opencode --model meta/muse-spark-1.1` writes
598
+ `~/.agents/profiles/spark.yml`, and `agents run spark` then dispatches OpenCode pinned to
599
+ that model; `--model` at run time still overrides it. A harness is a profile under the
600
+ hood (same YAML, same run resolution, same `agents repo push user` device sync), so
601
+ `agents profiles` is unchanged; `harness` adds the host+model one-shot (no preset
602
+ required), owns its own `--host` (never remote-routed, unlike `profiles --host`), and
603
+ `agents harness list` shows custom harnesses, addable presets, and the native harness
604
+ registry in one view. The model lands on the host's model env var
605
+ (`OPENCODE_MODEL`/`ANTHROPIC_MODEL`/`GROK_MODEL`/`GEMINI_MODEL`). Source:
606
+ `apps/cli/src/commands/harness.ts`, `apps/cli/src/lib/profiles.ts`,
607
+ `apps/cli/src/lib/hosts/passthrough.ts`.
608
+ - **Fixed the Spark presets, which never ran.** `claude-spark`, `opencode-spark`, and the
609
+ `opencode` preset help all named `meta/claude-spark-1.1` — a model neither OpenRouter nor
610
+ OpenCode serves; the live id is `meta/muse-spark-1.1`. Separately, an `authOptional`
611
+ preset (opencode) still wrote a keychain `auth` block that `resolveProfileEnv` always
612
+ read, so `agents run opencode-spark` died with "Keychain item not found" even though
613
+ OpenCode uses its own login. `resolveProfileEnv` now skips optional auth when no token is
614
+ stored, so those presets run on the host's own credentials. Source:
615
+ `apps/cli/src/lib/profiles-presets.ts`, `apps/cli/src/lib/profiles.ts`.
616
+
617
+ - **`agents sessions` now heals any pre-existing empty-`shortId` rows on upgrade.**
618
+ The prior fix stopped *producing* empty shortIds (bare-prefix ids like a `session_`
619
+ directory stripped to `''`), but a row already poisoned in the index did not
620
+ self-heal — an empty shortId is not re-parsed unless its transcript changes, and an
621
+ orphaned row whose file is gone never re-parses at all, so it stayed unaddressable in
622
+ the `short_id LIKE ?` picker lookups. A one-time schema migration (v16) repairs every
623
+ such row in place (`short_id = substr(id, 1, 8)`), so upgrading users get a clean
624
+ index without a full rescan. Source: `apps/cli/src/lib/session/db.ts`.
625
+
626
+ - **Host and cloud runs are now mappable in `agents sessions` for every agent, not
627
+ just Claude.** A `--host` dispatch forced a session id only for Claude (the sole
628
+ agent that accepts `--session-id`); every other agent's remote run coined its own
629
+ id that the launcher never learned, so the run was orphaned in `agents sessions`
630
+ and couldn't be resumed by id. The remote run now prints its resolved session id
631
+ as a one-line stdout sentinel (via a new internal `--emit-session-id` flag the
632
+ dispatch forwards); the launcher parses it out of the followed log and stamps it
633
+ on the host task, so `agents sessions`/resume-by-id work for Codex, Gemini, and
634
+ the rest. Source: `apps/cli/src/lib/hosts/session-marker.ts`,
635
+ `apps/cli/src/lib/hosts/session-index.ts`, `apps/cli/src/lib/hosts/run-target.ts`,
636
+ `apps/cli/src/lib/exec.ts`.
637
+
638
+ - **`agents cloud run` reconciles into the session index at dispatch.** The cloud
639
+ task store (`tasks.db`) and the session index were disjoint: a cloud run wrote only
640
+ the store, and `agents sessions` learned of it only later, via a proxy discovery.
641
+ Now every cloud dispatch (and every status poll) registers a session row keyed by
642
+ the real execution id with a `[cloud/<status>]` label, so a launch is mappable to a
643
+ session immediately. Source: `apps/cli/src/lib/cloud/session-index.ts`,
644
+ `apps/cli/src/lib/cloud/store.ts`.
645
+
646
+ - **Codex Cloud dispatch no longer fabricates a task id.** When `codex cloud exec`
647
+ didn't print a parseable id, the provider minted a synthetic `codex-<timestamp>` —
648
+ an id that could never match the real execution, silently breaking status, list,
649
+ and session reconcile. It now also scans stderr for the id and, on a genuine miss,
650
+ fails loud pointing at `agents cloud list` rather than persisting a bogus id.
651
+ Source: `apps/cli/src/lib/cloud/codex.ts`.
652
+
653
+ - **`agents import <agent> --as <version>` — the version flag now actually works.** The
654
+ option was declared as `--version <version>`, which the program-level `.version(VERSION)`
655
+ claims globally: `agents import codex --version 1.2.3` printed the CLI's own version and
656
+ exited without importing. It had been unreachable since it was introduced, and the
657
+ "could not determine version" error advised passing it. Renamed to `--as`, which reaches
658
+ the command. This also makes `agents import <agent> --isolated --as <version>` re-seed an
659
+ *existing* isolated copy from your current local config, instead of only ever creating a
660
+ new copy at whatever version happens to be installed locally.
661
+ - **Fixed a silent no-op in config copying under the compiled binary.** `fs.cpSync` defaults
662
+ to `force: true`, but Bun drops that default when a `filter` is supplied — so copies that
663
+ strip symlinks left existing destination files untouched. `dist/bin/agents` is
664
+ bun-compiled, so this affected a shipped path, not just tests. Now passed explicitly in
665
+ `config-transfer.ts` and `import.ts`.
666
+
667
+ - **`agents import --isolated` no longer misdescribes itself, chokes on codex, or copies
668
+ your session history.** Three defects found by using it: (1) the confirmation summary
669
+ printed `config: ~/.codex (will be moved into version home)` even under `--isolated` —
670
+ announcing the exact adoption the flag exists to prevent, though the code correctly
671
+ copied; it now reads `will be COPIED — your original stays put`. (2) Seeding failed
672
+ outright for codex with `Cannot overwrite non-directory`, because its version home is a
673
+ SUN_LEN-safe symlink to `~/.agents/.codex-homes/<version>/.codex` rather than a real
674
+ directory; the seeder now follows the link and writes the home the agent actually reads.
675
+ (3) The seed copied the whole config dir including sessions, logs, caches and sqlite —
676
+ 757MB on a real machine, 349MB of it `sessions` — so runtime state is now skipped and
677
+ reported (33MB for the same install), with `--all` to include it. Also skips the config
678
+ copy when `~/.<agent>` is itself a managed symlink, which is another version's home
679
+ rather than the user's real settings. Source: `apps/cli/src/lib/import.ts`,
680
+ `apps/cli/src/commands/import.ts`.
681
+
682
+ - **Incremental Claude transcript parsing on the live scan path.** When an active
683
+ Claude session grows, `agents sessions` (and every consumer that scans:
684
+ `output` / `view` / `teams` / the watcher) now re-parses only the newly-appended
685
+ bytes instead of re-reading the whole transcript from the top. The scan persists
686
+ a resumable continuation (`parser_state` + `content_text`, schema v15) in the
687
+ `scan_ledger`; the next scan resumes from the saved byte offset when the file
688
+ merely grew and its mtime did not go backwards, and falls back to a full reparse
689
+ from byte 0 on a cold start, a truncation / rewrite (size shrank), or a clock
690
+ rewind. Both paths run through one shared reducer, so the indexed row an append
691
+ produces is identical, field for field, to a from-scratch full reparse — token
692
+ counts, cost, duration, topic/title, PR + ticket refs, and FTS content all match
693
+ even when a signal straddles two scans (a `gh pr create` in one write and its URL
694
+ in the next). Only the Claude scanner is wired for now (Codex / Kimi are
695
+ follow-ups); the other scanners are unchanged. Source:
696
+ `apps/cli/src/lib/session/discover.ts`, `apps/cli/src/lib/session/db.ts`.
697
+
698
+ - **Incremental Codex + Kimi transcript parsing on the live scan path.** Following
699
+ the Claude incremental parse, the Codex rollout scanner and the Kimi wire.jsonl
700
+ scanner now re-parse only the newly-appended bytes when an active session grows,
701
+ instead of re-reading the whole file from the top every scan (`agents sessions`
702
+ and every consumer that scans: `output` / `view` / `teams` / the watcher). Each
703
+ persists a resumable continuation in the `scan_ledger` (`parser_state`, reusing
704
+ the schema v15 columns): the next scan resumes from the saved byte offset when
705
+ the file merely grew and its mtime did not go backwards, and falls back to a full
706
+ reparse from byte 0 on a cold start, a truncation / rewrite (size shrank), or a
707
+ clock rewind. Both branches run through one shared reducer per scanner, so the
708
+ indexed row an append produces is identical, field for field, to a from-scratch
709
+ full reparse. For Codex that covers messageCount, the last-wins cumulative token
710
+ snapshot (tokenCount / outputTokens / cost), duration, topic, and PR + ticket +
711
+ team signals that straddle two scans (a `gh pr create` function_call in one write
712
+ and its URL in the next). For Kimi it covers the additive message + token
713
+ counters. Both incremental paths apply only newline-terminated lines and defer a
714
+ complete-but-unterminated trailing record to the next pass, so a record written
715
+ before its `'\n'` is flushed is never double-counted. Grok is out of scope (it
716
+ reads a whole `summary.json`, not an append-only JSONL); Claude / Gemini and the
717
+ shared helpers are unchanged. Source: `apps/cli/src/lib/session/discover.ts`.
718
+
719
+ - **Isolated installs now resume sessions and resolve `@default` like any other install.**
720
+ Two places still assumed a managed version is reachable on PATH — which an isolated
721
+ install deliberately is not. (1) `agents sessions` resume looked up
722
+ `<cli>@<version>` with a plain PATH lookup, never found it (the shims dir is
723
+ intentionally off PATH under `--isolated`), concluded the version was uninstalled, and
724
+ fell back to spawning `<cli> "/continue <id>"` — a slash command neither CLI has, so
725
+ the session simply never resumed. It now resolves the versioned alias by absolute path,
726
+ the way `agents run` already did, and the fallback is the agent's real resume verb
727
+ against the current version rather than `/continue`. (2) The agent-spec resolver behind
728
+ `--agents` / `@default` / `@pinned` read only the global default, so an isolated-only
729
+ agent threw "No default version set" even after an explicit `agents use` —
730
+ `resolveVersion` had gained the isolated-default fallback but this resolver had not.
731
+ Both now consult it, and report `isolated-default` as the source rather than claiming a
732
+ global default. `opencode` resume stays deliberately un-pinned, since its sessions are
733
+ shared across versions. Source: `apps/cli/src/commands/sessions.ts`,
734
+ `apps/cli/src/lib/agent-spec/`.
735
+
736
+ - **Menu bar: prune orphan attention sentinels; group `NEEDS YOU`; end silent
737
+ truncation.** `LocalState.attentionMarks` now takes the caller's live-session
738
+ set and unlinks sentinels whose `sessionId` is not alive — the
739
+ `06-attention-sentinel.sh` hook already clears on `Stop`/`UserPromptSubmit`,
740
+ but leaks when a terminal is killed hard, a Claude version has no hook, or the
741
+ `sessionId` doesn't round-trip; the reader is the only layer with `pidAlive`
742
+ ground truth. Verified on mac-mini: 6 stale sentinels aged 1–22 days pruned to
743
+ 0 on one dump run. `addNeedsAttention` groups blocked sessions by
744
+ `(agent, repo)` and collapses groups of 2+ into a single
745
+ `<Agent> · <repo> · N waiting · oldest <t> ›` row + submenu, dropping the
746
+ generic `— Claude is waiting for your input` filler when the Notification
747
+ message is empty. `addActive` collapses the `"other"` bucket to a single
748
+ clickable `ACTIVE · other · N idle ›` row when idle-only, and replaces the
749
+ silent 3-cap on idle rows with an explicit `+ N more idle ›` row + submenu so
750
+ the header count always matches visible + explicit-hidden. No new session
751
+ state — closed = hidden, as before.
752
+
753
+ - **The menu-bar helper no longer crash-loops on macOS 26.** npm's pack/extract
754
+ strips the ad-hoc signature the release bakes into `MenubarHelper.app`, leaving
755
+ it `code object is not signed at all`. macOS 26's code-signing monitor SIGKILLs
756
+ an unsigned binary at launch (`SIGKILL (Code Signature Invalid)`), so under the
757
+ launchd `KeepAlive` service it restarted forever, and its unstable identity made
758
+ the Accessibility grant (needed for the clip→paste keystroke in `Clip.swift`)
759
+ re-prompt every time. The install path now re-signs the copied bundle ad-hoc and
760
+ verifies it before bootstrapping the service, so every machine gets a valid
761
+ signature the kernel accepts — and a bundle that can't be made valid is skipped
762
+ instead of spun in a crash loop. A Developer-ID-signed helper (which survives
763
+ npm) is left untouched. Source: `apps/cli/src/lib/menubar/install-menubar.ts`,
764
+ `apps/cli/menubar/scripts/build.sh`.
765
+
766
+ - **The configured model now shows wherever an agent is displayed.** `agents
767
+ view`, `use`, `add`, `status`, and `inspect` surface the model an agent+version
768
+ actually runs with, beside the version (the identity cluster reads `agent ·
769
+ version · model · account`). The model is resolved agents.yaml `run.defaults` →
770
+ the native `settings.json` → the built-in default, and `agents view --json`
771
+ gains a `configuredModel { model, source }` field so downstream tools can read
772
+ both the value and where it came from. Source: `apps/cli/src/lib/models.ts`,
773
+ `apps/cli/src/commands/view.ts`.
774
+
775
+ - **`agents repo pull` now reloads the routines daemon so device pins refresh.**
776
+ The scheduler froze each routine's config — device pins included — in memory at
777
+ daemon start. A `repo pull` rewrites the synced routine YAML on disk (a routine
778
+ re-pinned to another host, say), but without a reload the daemon kept firing the
779
+ pre-pull pins, so a routine moved to another device still fired on the old host
780
+ too — a phantom double-fire across the fleet. A successful pull now SIGHUPs the
781
+ running daemon (`scheduler.reloadAll()`), re-reading the YAML so pins refresh. A
782
+ no-op when the daemon isn't running or on Windows (no SIGHUP). Source:
783
+ `apps/cli/src/commands/repo.ts`.
784
+
785
+ - **Routines can pin a Claude account by identity to stop the OAuth-rotation
786
+ logout storm.** Unpinned `claude` routines pick an account with the default
787
+ `balanced` (stateless weighted-random) strategy, so two concurrent unattended
788
+ runs — on one box or across the fleet — can land on the same account; Claude's
789
+ refresh token is single-use and rotates server-side, so the second refresh
790
+ revokes the first run's token mid-flight (`401 OAuth access token has been
791
+ revoked`). Across ~20 routines waking in one morning window that is a
792
+ self-inflicted logout storm (RUSH-1957). A routine may now set `account:` (a
793
+ login email or account key) to pin the run to the version slot holding that
794
+ account — no rotation, no usage-read refresh, no failover onto other accounts —
795
+ so each routine (or each device's routines) refreshes one credential nobody else
796
+ touches. Prefer it over `version:`, which pins a version *number* that is GC'd on
797
+ the next upgrade, silently dropping the routine back to `balanced`. An account
798
+ that is not signed in on the box warns and falls back to the strategy rather than
799
+ refusing to run. Source: `apps/cli/src/lib/routines.ts`,
800
+ `apps/cli/src/lib/rotate.ts`, `apps/cli/src/lib/runner.ts`.
801
+
802
+ - **Balanced account rotation now works for scheduled Claude routines.** The
803
+ routines daemon injects one `CLAUDE_CODE_OAUTH_TOKEN` into its environment so a
804
+ token-less default account still authenticates (RUSH-1759). But Claude — and the
805
+ Linux shim's own `-z CLAUDE_CODE_OAUTH_TOKEN` guard — both prefer that env var
806
+ over a pinned account's `CLAUDE_CONFIG_DIR`, so once balanced rotation pinned a
807
+ specific account the injected token shadowed it: the whole pool was inert and
808
+ every fire authenticated as (and eventually 401'd on) the one token. A routine
809
+ spawn now drops the injected token when the rotated account holds its own on-disk
810
+ credential, so it authenticates as that account; when the account has no on-disk
811
+ credential (the RUSH-1759 default) the injected token is kept. Source:
812
+ `apps/cli/src/lib/runner.ts` (`buildRoutineSpawnEnv`),
813
+ `apps/cli/src/lib/agents.ts` (`claudeHomeHasOwnCredential`).
814
+
815
+ - **No more Touch ID prompt on every new agent session.** Bundle metadata (names,
816
+ descriptions, variable names + references, and non-sensitive `--value` literals)
817
+ is now stored WITHOUT the biometry ACL at every prompt-policy tier, not just
818
+ `never`. Metadata is non-sensitive by contract — real secret values live in
819
+ separate `agents-cli.secrets.*` items that keep the bundle's policy ACL — so
820
+ enumerating bundles no longer needs a keychain unlock. This kills the recurring
821
+ Touch ID prompt that fired on every new Claude/agent terminal: a SessionStart
822
+ hook runs `agents devices list`, which scans bundle metadata through crabbox, and
823
+ that scan used to pop Touch ID once per broker window (~7 days) on every cold
824
+ launch. `agents secrets list` is now silent too. Reading a bundle's actual
825
+ values (run injection, `view --reveal`) still prompts. Existing bundles are
826
+ migrated automatically and once: the first metadata scan after upgrade re-homes
827
+ each bundle's metadata item no-ACL (reusing the read it already did, so it adds
828
+ no extra prompt), and every scan after that is prompt-free. Source:
829
+ `apps/cli/src/lib/secrets/bundles.ts`, `apps/cli/src/lib/secrets/index.ts`.
830
+
831
+ - **Wire workflows support for Grok (RUSH-1863).** Grok Build ships native Workflows as of v0.2.111 (on by default): file-defined Rhai orchestration scripts in `~/.grok/workflows/<name>.rhai` (user-global, under `GROK_HOME`) and `.grok/workflows/` (repo-level). agents-cli now declares `workflows: { since: 0.2.111 }` for grok, projects central `WORKFLOW.md` bundles into managed Rhai scripts via `transformWorkflowForGrok` (with an `// agents_workflow: <name>` marker so user-authored scripts are never overwritten), and registers the writer + detector so `agents workflows add --agents grok@…` / sync land files Grok can invoke as `/<name>`. Distinct from the grok *commands* gap (RUSH-1851). Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/workflows.ts`, `apps/cli/src/lib/staleness/{writers,detectors}/workflows.ts`, `apps/cli/src/lib/versions.ts`.
832
+
833
+ - **Dispatch-bar screenshots now upload via `linear create --image` instead of
834
+ landing as dead local paths.** The menu-bar helper previously injected
835
+ screenshot paths into the ticket-agent prompt, and the model echoed them into
836
+ the issue description as `/Users/…` text. The agent now returns ticket fields
837
+ as JSON, and the helper itself runs `linear create` with `--image <path>` for
838
+ each selected screenshot, so paths pass through Swift argv and survive spaces
839
+ or `@` in CleanShot filenames. Coordinates with the `linear create --image`
840
+ support added in `phnx-labs/linear-cli#28`. Source:
841
+ `apps/cli/menubar/Sources/MenubarHelper/AgentsCLI.swift`,
842
+ `apps/cli/menubar/Sources/MenubarHelper/IssueSelfTest.swift`.
843
+
844
+ - **Native helper bundles now ship the agents-cli icon and the computer helper is
845
+ branded "Agents Computer".** MenubarHelper.app, ComputerHelper.app, and the
846
+ keychain `Agents CLI.app` previously had no `CFBundleIconFile`/`.icns`, so
847
+ Notification Center and System Settings → Privacy & Security showed a blank
848
+ square. Each build script now generates `AppIcon.icns` from `assets/logo.png`
849
+ and adds `CFBundleIconFile` to the bundle `Info.plist`. The computer helper
850
+ display name changed from "Computer Helper" to "Agents Computer" while keeping
851
+ its bundle id and on-disk path, so existing Accessibility/Screen Recording
852
+ grants remain valid. Source: `apps/cli/menubar/scripts/build.sh`,
853
+ `native/computer-mac/scripts/build.sh`, `apps/cli/scripts/build-keychain-helper.sh`,
854
+ `apps/cli/src/commands/setup-computer.ts`, `apps/cli/menubar/Sources/MenubarHelper/PromptPanel.swift`.
855
+
856
+ - **One resolver for `--host` / `--device` — every subcommand now dials the same box
857
+ (RUSH-1967).** A host token used to resolve through two disagreeing code paths:
858
+ `run --host` (and the generic passthrough, teams placement, doctor, funnel, remote
859
+ secrets) let a `~/.ssh/config` stanza win and dialed its bare name, while
860
+ `sessions --host`, session bundles, and `agents ssh` dialed the device Tailscale
861
+ `user@dnsName`. The same name could reach two different machines, and because the two
862
+ emitted different target strings they never shared a multiplexed SSH connection.
863
+ Resolution is now a single merged lookup (`matchHost`): the live devices registry
864
+ supplies address/OS/presence, the agents.yaml overlay supplies capability tags, and
865
+ ssh_config supplies hosts Tailscale has never seen — merged per-field, not one
866
+ shadowing another. Fallout fixed with it: an enrolled device address always comes from
867
+ the live registry (so `agents devices sync` takes effect without re-enrolling, no more
868
+ frozen route), an enrolled device keeps its presence and `dispatchable` flags, a
869
+ password-auth device cannot be made dispatchable by shadowing it with an inline entry,
870
+ and a host present only in `~/.ssh/config` is now visible to the `sessions --host`
871
+ fan-out.
872
+
873
+ - **`agents run --device`/`--host` now auto-reconnects when the network drops.** A
874
+ remote interactive agent runs in a detached tmux session on the peer, so an SSH
875
+ blink kills only the local client — the agent keeps running. Previously the local
876
+ side exited with ssh's connection-layer code (255) and you had to notice, find the
877
+ session id, and `agents sessions focus` by hand. Now, when a tmux-hosted run with a
878
+ known session id drops (exit 255), the client re-attaches the live remote pane
879
+ automatically over SSH — reusing the peer's own `agents sessions focus <id> --local
880
+ --attach-only` (a live join, not a resumed copy) — with bounded exponential backoff
881
+ (2s→30s, up to 6 attempts, and the budget refills after a genuinely live
882
+ reconnection). A clean detach (Ctrl-b d, exit 0) or a real agent exit (any non-255
883
+ code) is left alone; `--raw`/no-tmux runs, which don't survive a drop, are not
884
+ retried. This covers Claude and resumed runs today; capturing a resumable id for
885
+ other agents on the `--device` path is tracked in RUSH-2007. Source:
886
+ `apps/cli/src/lib/hosts/reconnect.ts`, `apps/cli/src/commands/exec.ts`.
887
+
888
+ - **The secrets broker cache now actually works on the shipped macOS binary — one Touch ID per bundle per hold window, not one per read.** The three synchronous broker clients (`agentGetSync`, `agentReachableSync`, `agentEvictSync`) spawned `process.execPath -e <inline node program>`, which is only correct when `process.execPath` is node. Since 1.20.53 the macOS `agents` is a bun-compiled Mach-O, so `process.execPath` is the CLI itself and the spawn became `agents -e …` — rejected with `error: unknown option '-e'` and a non-zero exit. Each client then took its own failure path (`null` / `false` / no-op), which the caller reads as "broker down" and falls through to a real keychain read. Net effect: on every standalone install the hot cache was never hit, so the `daily` policy's one-prompt-per-7d never applied and **every bundle read re-popped Touch ID** — `agents secrets status` would report the broker running while holding nothing but explicitly `unlock`ed bundles (the durable session-store path, the only client that never spawned). Same defect class as the broker launch fixed in 1.20.56 and the PTY sidecar in 1.20.72; these three sites were simply never converted. They now spawn top-level `__secrets-get` / `__secrets-ping` / `__secrets-lock` tokens built by the shared `getCliLaunch` primitive and intercepted in `index.ts` before commander — alongside `__daemon-run` and `__vault-age-helper`, and deliberately above the line where every normal command runs `checkForUpdates()` and forks a detached background sync, which would otherwise fire on every cache hit. Source: `apps/cli/src/lib/secrets/agent.ts`, `apps/cli/src/index.ts`.
889
+
890
+ - **Let a bundle whose passphrase is lost be deleted, so the name can be recovered.**
891
+ A file-backed bundle that no longer decrypts bricked its own name: `view`, `add`,
892
+ `delete`, and both `import --from icloud` and `import --from 1password` all called
893
+ `readBundle()` first, so none of them could touch it — including the two commands that
894
+ exist to restore it from a valid iCloud Keychain or 1Password copy. `delete` now uses
895
+ the new `readBundleIfDecryptable()` and proceeds without the plaintext, reporting that
896
+ the bundle's keychain items cannot be enumerated for purging instead of claiming a
897
+ clean purge. The `view` hint no longer points at `import --from icloud` for a bundle
898
+ that is still on disk — that command fails identically — and names the delete-then-import
899
+ sequence that actually works. Only a genuine decrypt failure counts as deletable: a
900
+ bundle that is merely locked for the run (headless macOS with no
901
+ `AGENTS_SECRETS_PASSPHRASE`) still fails loudly and is left in place, so
902
+ `secrets delete <name> --yes` from a cron/launchd run that forgot to export the
903
+ passphrase can't silently destroy a healthy bundle. Source:
904
+ `apps/cli/src/lib/secrets/bundles.ts`, `apps/cli/src/commands/secrets.ts`.
905
+
906
+ - **Session tab titles no longer go missing or stale.** A rescan that carried an
907
+ empty or whitespace-only label used to clobber a good stored label, because
908
+ `upsertSession`/`upsertSessionsBatch` wrote `label = excluded.label`
909
+ unconditionally on conflict. The `ON CONFLICT` clause now preserves an existing
910
+ non-empty label and only overwrites when a real label arrives, so a `/rename`,
911
+ agent-generated title, or `--name` handle survives later rescans. Headless runs
912
+ launched with `--name` now also surface that name as the session label (matching
913
+ the terminal path) instead of showing only the topic. Source:
914
+ `apps/cli/src/lib/session/db.ts`, `apps/cli/src/lib/session/active.ts`.
915
+
916
+ - **`agents sessions --active` no longer shows zombie sessions from recycled pids.**
917
+ Liveness was a bare `process.kill(pid, 0)` existence check, so once the OS handed a
918
+ dead session's pid to an unrelated process, that session kept showing as alive — and
919
+ the registry GC never pruned it. `isPidAlive` now takes the session's recorded
920
+ `startedAtMs` and, when a start time is available, verifies the process at that pid did
921
+ not begin meaningfully after the session started (a 60s window): a process that started
922
+ later is a reused pid, so the session is dead. The start time is read once via
923
+ `ps -o lstart=` (macOS + Linux); Windows and any unreadable start time fall back to the
924
+ existence check, never worse than before. Applied to every registry-backed liveness
925
+ path — the live-terminals filter, the terminal listing, the tmux-pane resolver, and the
926
+ pid-registry prune. Source: `apps/cli/src/lib/session/active.ts`.
927
+
928
+ - **`agents sessions` no longer corrupts the index with empty `shortId` rows.**
929
+ Session ids that are only a known prefix — a bare `session_` Rush directory, an
930
+ id of exactly `api-` (Hermes) or `ses_` (OpenCode) — used to strip to `''`
931
+ (`'session_'.replace(/^session_/, '').slice(0, 8) === ''`). An empty `shortId`
932
+ passes the `short_id TEXT NOT NULL` constraint (empty string is not NULL) yet
933
+ matches nothing in the `short_id LIKE ?` picker lookups, so the row was silently
934
+ unaddressable. All shortId derivation is now routed through one helper,
935
+ `deriveShortId`, that guarantees a non-empty result by falling back to the
936
+ unstripped id when the strip empties it. Every producer — the twelve parsers in
937
+ `discover.ts`, `session/cloud.ts`, `cloud/session-index.ts`, `hosts/session-index.ts`,
938
+ `session/fork.ts`, and `commands/go.ts` — uses it, replacing the duplicated inline
939
+ `.slice(0, 8)` (some with a `.replace(prefix, '')`). Source:
940
+ `apps/cli/src/lib/session/short-id.ts`.
941
+
942
+ - **Honest live status: report `unknown` instead of a fake `idle` (RUSH-1976).** `agents
943
+ sessions --active` now reports an explicit `unknown` status (`◌`) for a live agent whose
944
+ activity it cannot introspect — a running gemini/droid/cursor/opencode whose transcript
945
+ format is not parsed — instead of the misleading `idle` it showed before. Status resolution
946
+ is standardized in one place (`resolveFallbackStatus`): a vanished transcript file no longer
947
+ flips to a false `running`, an unanswered prose question with no mtime signal no longer
948
+ sticks as "waiting on you" forever (the RUSH-1522 null-mtime hole), and the `ps`/`lsof`
949
+ probes behind the scan now have hard timeouts so a hung syscall can't silently drop live
950
+ sessions. Source: `apps/cli/src/lib/session/active.ts` (`resolveFallbackStatus`),
951
+ `apps/cli/src/lib/session/state.ts`, `apps/cli/src/commands/sessions.ts`.
952
+
953
+ - **Interactive session browser: preview-by-default with clickable ticket + PR
954
+ links.** In `agents sessions` / `agents sessions --active`, the highlighted
955
+ row's preview is now open by default (`tab` toggles it off), and the preview's
956
+ links line renders the ticket and PR as OSC 8 terminal hyperlinks — the ticket
957
+ resolves to its Linear URL (workspace slug resolved config-first) and `PR#`
958
+ resolves to its GitHub URL — so they are click-through in terminals that support
959
+ them. Source: `apps/cli/src/lib/picker.ts`,
960
+ `apps/cli/src/commands/sessions-browser.ts`,
961
+ `apps/cli/src/lib/session/render.ts`.
962
+
963
+ - **`agents sessions` now lists what agents-cli manages, not your own installs.** Discovery
964
+ scans the union of your real `~/.<agent>` and every managed version home, so once you had
965
+ managed versions the listing mixed both — most visibly after `agents add --isolated`, where
966
+ keeping the two apart was the whole point. Listing is now scoped to managed versions
967
+ (isolated or not); `--unmanaged` brings your own installs back, and every render path prints
968
+ what it hid (`N sessions from your own unmanaged installs hidden`) so nothing disappears
969
+ silently. A user who has never run `agents add` sees exactly what they saw before — with
970
+ nothing managed there is nothing to scope to. Scoping happens at query time rather than by
971
+ narrowing the scan, so the index stays complete, `--unmanaged` needs no re-scan, and
972
+ watchdog / `--roots` / the Factory watcher are unaffected. Source:
973
+ `apps/cli/src/lib/session/discover.ts`, `apps/cli/src/commands/sessions.ts`.
974
+
975
+ - **Attribute split-spawned agents on the authoritative tmux source (RUSH-1976).** `agents
976
+ sessions --active` now attributes each tmux pane to the agent actually running in it: an
977
+ agent bare-spawned into a split of an existing shared-socket session (where `$TMUX` is
978
+ already set, so no new session meta is stamped) is surfaced by the authoritative tmux
979
+ source with its own exact identity and pane, instead of being dropped there and left to the
980
+ weaker `ps`-scan fallback. Attribution reads the per-pane launch registry (the id recorded
981
+ at launch, or the SessionStart-hook join by launchId for a non-Claude agent), gated on the
982
+ pid still being alive so a dead agent's pane can't linger. Source:
983
+ `apps/cli/src/lib/session/active.ts` (`resolvePaneIdentity`, `listTmuxAgentSessions`),
984
+ `apps/cli/src/lib/session/pid-registry.ts` (`listPidSessionEntries`).
985
+
986
+ - **Watchdog v2 — the always-on watchdog now has judgment and delivers correctly
987
+ into VS Codium.** The 2-minute `agents watchdog --nudge` routine no longer
988
+ hard-skips a session that stopped to ask a question. The deterministic pass is
989
+ now a cheap pre-filter (clearly-complete → skip, clear promise-without-toolcall →
990
+ nudge) that ESCALATES the judgment-heavy cases — a session parked on a question,
991
+ or an ambiguous stall — to a smart brain. The brain drives the agent to finish
992
+ end-to-end when it asked a needless / already-authorized question or paused with
993
+ work left, and leaves it for the human only for genuine cases (credentials/auth,
994
+ an irreversible or outward-facing action, a real ambiguous product decision, or a
995
+ finished task). Nudge messages restate the goal, tell the agent to use best
996
+ judgment, and give one concrete next step. The brain is a customizable
997
+ `watchdog` workflow: drop a `watchdog` WORKFLOW.md in your project or user
998
+ `workflows/` to override the prompt and pick the `model:`; absent one, the
999
+ improved built-in prompt runs via `agents run … --mode plan`. Source:
1000
+ `apps/cli/src/lib/watchdog/watchdog.ts`, `apps/cli/src/lib/watchdog/runner.ts`.
1001
+ - **Watchdog delivery routes through the answer-router with the VS Codium rail
1002
+ working.** A running agent is steered via its mailbox; a parked-on-question agent
1003
+ is answered into its EXACT split — including a VS Codium / Cursor / VS Code
1004
+ integrated terminal, which the answer-router's own resolver could not address —
1005
+ or, when headless, re-entered via resume; a parked agent with no addressable rail
1006
+ is flagged, never a guessed target.
1007
+ - **Watchdog precedence + concurrency fixes.** A long-idle (>15m) open question is
1008
+ no longer blindly force-nudged — waiting-on-user and completion now win over the
1009
+ 15-minute force-review short-circuit. The per-session cooldown ledger is written
1010
+ under a file lock (fresh-read + merge + atomic write), closing a lost-update race
1011
+ between concurrent ticks.
1012
+ - **Watchdog decisions are logged to `~/.agents/.cache/logs/watchdog.log`** in the
1013
+ JSONL shape the Factory Floor watchdog card reads, so it keeps working after the
1014
+ extension-side watchdog is retired.
1015
+
1016
+ - **The always-on watchdog is now a daemon-fired routine, not a hand-rolled loop + sentinel.**
1017
+ `agents watchdog enable` used to flip a private `~/.agents/.cache/state/watchdog/enabled`
1018
+ sentinel that only meant anything to a manually-launched `agents watchdog --watch` loop —
1019
+ so the auto-nudge only ran while some shell was babysitting it. It now creates and enables a
1020
+ plain `watchdog` command routine (`agents watchdog --nudge`, every 2 minutes) and reloads
1021
+ the daemon, so the always-on watchdog is fired by the same scheduler that runs every other
1022
+ routine: it survives reboots, catches up if the daemon was down, and shows up in
1023
+ `agents routines list`. `disable` pauses that routine; `status` reports whether it is
1024
+ enabled. The Swift menu-bar toggle and `watchdog status --json` are unchanged. Bare
1025
+ `agents watchdog` (dry) and `agents watchdog --watch` (now dry unless `--nudge`) still work
1026
+ for ad-hoc runs. If you had already opted in under the old build, a one-shot migration
1027
+ folds that state forward — you stay enabled, now as the routine. Source:
1028
+ `apps/cli/src/lib/watchdog/routine.ts`, `apps/cli/src/commands/watchdog.ts`,
1029
+ `apps/cli/src/lib/migrate.ts`.
1030
+
1031
+ - **Wire hooks support for OpenCode through generated plugins (RUSH-1850).** `hooks.yaml` entries now compile into `~/.config/opencode/plugins/agents-cli-hooks.ts`, mapping tool, prompt, and session lifecycle events to OpenCode's native plugin API and executing managed scripts with Bun's `$` shell primitive. OpenCode hooks are capability-gated to v0.3.130 and newer.
1032
+
3
1033
  ## 1.20.74
4
1034
 
1035
+ - **`agents apply --agent claude@all --device <box>` — replicate this machine's
1036
+ exact version set.** The fleet roster is agent-granular, so a fresh box only ever
1037
+ got one `claude@latest` — losing a multi-version setup (e.g. several claude
1038
+ versions, one per Max account, to spread rate-limit quota). The new `--agent
1039
+ <specs...>` flag overrides the roster for the targeted device(s): `claude@all`
1040
+ expands source-side to every version installed here (`claude@2.1.170`,
1041
+ `claude@2.1.207`, …) and installs each missing one on the target; a pinned
1042
+ `claude@2.1.207` installs that exact version even if another claude is present.
1043
+ Version-pinned specs diff against a per-device `agents view --json` probe, so the
1044
+ plan installs only what's missing and login still propagates once per agent.
1045
+ Source: `apps/cli/src/commands/apply.ts`, `apps/cli/src/lib/fleet/apply.ts`.
1046
+
5
1047
  - **Project resource manifests are now portable across Windows and POSIX.** The
6
1048
  managed-resource manifest `.agents-managed.json` recorded its paths with the
7
1049
  host's native separator, so a sync run on Windows wrote entries like
@@ -96,6 +1138,19 @@
96
1138
  dirs, which could otherwise report an isolated copy — deliberately unreachable from
97
1139
  PATH — as `(global)`. Source: `apps/cli/src/commands/view.ts`, `apps/cli/src/lib/agents.ts`.
98
1140
 
1141
+ - **`agents sessions --active` now resolves the exact session id for non-Claude and
1142
+ user-typed agents.** Previously only Claude (launched with a known `--session-id`) got an
1143
+ exact id; every other agent fell back to "newest `.jsonl` in the cwd", which collapses
1144
+ co-located agents onto one row. `ag run` now mints a launch id and exports it as
1145
+ `AGENT_LAUNCH_ID` on every launch path (bare spawn, tmux, and the Windows shim); the
1146
+ agent's own SessionStart hook already records that id, so the active-scan reconciles a
1147
+ `ps`-discovered process to the hook's authoritative session id by `launchId` (robust even
1148
+ when the hook runs under a different pid — a tmux pane leaf or `cmd.exe` wrapper), falling
1149
+ back to `terminalId` and pid. This also attributes agents `ag run` never launched (you
1150
+ typing `claude` in a terminal). No on-disk directory moved — the CLI reads the existing
1151
+ hook state files read-only, so old installed hooks and a new CLI coexist safely. Source:
1152
+ `apps/cli/src/lib/exec.ts`, `apps/cli/src/lib/session/{pid-registry,hook-sessions,active}.ts`.
1153
+
99
1154
  ## 1.20.73
100
1155
 
101
1156
  - **`agents cli install <binary-cli>` no longer hardcodes `/usr/local/bin` (#1103).**