@phnx-labs/agents-cli 1.20.73 → 1.20.76

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