@phnx-labs/agents-cli 1.22.26 → 1.22.28

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 (183) hide show
  1. package/CHANGELOG.md +293 -0
  2. package/README.md +80 -27
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/bench.d.ts +2 -0
  5. package/dist/commands/bench.js +101 -0
  6. package/dist/commands/doctor.js +24 -10
  7. package/dist/commands/exec.js +71 -26
  8. package/dist/commands/hosts.js +4 -3
  9. package/dist/commands/insights.js +39 -5
  10. package/dist/commands/output.js +100 -25
  11. package/dist/commands/perf.d.ts +10 -0
  12. package/dist/commands/perf.js +14 -6
  13. package/dist/commands/projects.js +24 -10
  14. package/dist/commands/resume.d.ts +11 -0
  15. package/dist/commands/resume.js +51 -0
  16. package/dist/commands/routines.js +31 -4
  17. package/dist/commands/run-account-picker.d.ts +37 -0
  18. package/dist/commands/run-account-picker.js +101 -17
  19. package/dist/commands/secrets.js +86 -2
  20. package/dist/commands/sessions-picker.js +12 -7
  21. package/dist/commands/sessions.d.ts +73 -7
  22. package/dist/commands/sessions.js +391 -49
  23. package/dist/commands/ssh.js +184 -11
  24. package/dist/commands/teams.js +22 -11
  25. package/dist/index.js +16 -20
  26. package/dist/lib/activity.js +45 -99
  27. package/dist/lib/auth-health.d.ts +47 -3
  28. package/dist/lib/auth-health.js +74 -15
  29. package/dist/lib/bench/index.d.ts +4 -0
  30. package/dist/lib/bench/index.js +4 -0
  31. package/dist/lib/bench/runner.d.ts +16 -0
  32. package/dist/lib/bench/runner.js +111 -0
  33. package/dist/lib/bench/schema.d.ts +5 -0
  34. package/dist/lib/bench/schema.js +91 -0
  35. package/dist/lib/bench/storage.d.ts +5 -0
  36. package/dist/lib/bench/storage.js +32 -0
  37. package/dist/lib/bench/types.d.ts +40 -0
  38. package/dist/lib/bench/types.js +1 -0
  39. package/dist/lib/claude-account-token.d.ts +2 -0
  40. package/dist/lib/claude-account-token.js +56 -3
  41. package/dist/lib/cloud/host.js +1 -0
  42. package/dist/lib/crabbox/cli.d.ts +2 -0
  43. package/dist/lib/crabbox/cli.js +2 -0
  44. package/dist/lib/crabbox/lease.js +7 -1
  45. package/dist/lib/daemon.js +20 -0
  46. package/dist/lib/devices/connect.d.ts +2 -0
  47. package/dist/lib/devices/connect.js +7 -0
  48. package/dist/lib/devices/doctor-findings.d.ts +4 -1
  49. package/dist/lib/devices/doctor-findings.js +15 -1
  50. package/dist/lib/devices/harness-inventory.d.ts +97 -0
  51. package/dist/lib/devices/harness-inventory.js +0 -0
  52. package/dist/lib/devices/registry.d.ts +2 -0
  53. package/dist/lib/devices/resolve-target.d.ts +1 -0
  54. package/dist/lib/devices/resolve-target.js +9 -2
  55. package/dist/lib/devices/ssh-config.js +3 -0
  56. package/dist/lib/devices/windows-ssh-enrollment.d.ts +20 -0
  57. package/dist/lib/devices/windows-ssh-enrollment.js +98 -0
  58. package/dist/lib/events.d.ts +1 -1
  59. package/dist/lib/events.js +2 -2
  60. package/dist/lib/exec.js +18 -8
  61. package/dist/lib/fleet/apply.js +9 -7
  62. package/dist/lib/fleet/remote-login.d.ts +4 -3
  63. package/dist/lib/fleet/remote-login.js +11 -9
  64. package/dist/lib/gemini-settings.d.ts +0 -1
  65. package/dist/lib/gemini-settings.js +12 -7
  66. package/dist/lib/git.d.ts +9 -3
  67. package/dist/lib/git.js +39 -22
  68. package/dist/lib/hooks/profile.d.ts +12 -1
  69. package/dist/lib/hooks/profile.js +6 -1
  70. package/dist/lib/hooks.d.ts +8 -5
  71. package/dist/lib/hooks.js +14 -7
  72. package/dist/lib/hosts/dispatch.d.ts +10 -0
  73. package/dist/lib/hosts/dispatch.js +85 -25
  74. package/dist/lib/hosts/logs.js +11 -1
  75. package/dist/lib/hosts/passthrough.d.ts +27 -0
  76. package/dist/lib/hosts/passthrough.js +19 -10
  77. package/dist/lib/hosts/progress.d.ts +12 -1
  78. package/dist/lib/hosts/progress.js +41 -8
  79. package/dist/lib/hosts/providers/devices.js +1 -0
  80. package/dist/lib/hosts/ready.d.ts +45 -4
  81. package/dist/lib/hosts/ready.js +124 -20
  82. package/dist/lib/hosts/reconcile.d.ts +2 -1
  83. package/dist/lib/hosts/reconcile.js +24 -7
  84. package/dist/lib/hosts/reconnect.d.ts +45 -12
  85. package/dist/lib/hosts/reconnect.js +94 -36
  86. package/dist/lib/hosts/registry.d.ts +2 -2
  87. package/dist/lib/hosts/registry.js +4 -5
  88. package/dist/lib/hosts/remote-cmd.d.ts +17 -0
  89. package/dist/lib/hosts/remote-cmd.js +29 -0
  90. package/dist/lib/hosts/tasks.d.ts +3 -0
  91. package/dist/lib/hosts/types.d.ts +1 -0
  92. package/dist/lib/hosts/types.js +3 -0
  93. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  94. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  95. package/dist/lib/menubar/install-menubar.d.ts +9 -6
  96. package/dist/lib/menubar/install-menubar.js +20 -9
  97. package/dist/lib/menubar/snapshot.js +15 -2
  98. package/dist/lib/models.d.ts +42 -6
  99. package/dist/lib/models.js +55 -153
  100. package/dist/lib/perf/db.js +16 -3
  101. package/dist/lib/perf/types.d.ts +12 -1
  102. package/dist/lib/pricing/cost.d.ts +9 -0
  103. package/dist/lib/pricing/cost.js +24 -0
  104. package/dist/lib/pricing/index.d.ts +1 -1
  105. package/dist/lib/pricing/index.js +1 -1
  106. package/dist/lib/project-probe.d.ts +22 -3
  107. package/dist/lib/project-probe.js +105 -17
  108. package/dist/lib/project-status.d.ts +9 -0
  109. package/dist/lib/project-status.js +15 -0
  110. package/dist/lib/redact.js +8 -3
  111. package/dist/lib/refresh.js +22 -11
  112. package/dist/lib/remote-agents-json.d.ts +32 -0
  113. package/dist/lib/remote-agents-json.js +47 -16
  114. package/dist/lib/resource-profiles.js +1 -2
  115. package/dist/lib/rotate.d.ts +22 -0
  116. package/dist/lib/rotate.js +26 -0
  117. package/dist/lib/routine-notify-owner.d.ts +102 -0
  118. package/dist/lib/routine-notify-owner.js +232 -0
  119. package/dist/lib/routines.d.ts +10 -0
  120. package/dist/lib/runner.d.ts +9 -1
  121. package/dist/lib/runner.js +182 -17
  122. package/dist/lib/sandbox.d.ts +0 -2
  123. package/dist/lib/sandbox.js +2 -19
  124. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  125. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  126. package/dist/lib/secrets/agent.d.ts +19 -3
  127. package/dist/lib/secrets/agent.js +61 -22
  128. package/dist/lib/secrets/audit.d.ts +1 -1
  129. package/dist/lib/secrets/audit.js +2 -0
  130. package/dist/lib/secrets/bundles.d.ts +3 -3
  131. package/dist/lib/secrets/bundles.js +15 -5
  132. package/dist/lib/secrets/filestore.d.ts +2 -0
  133. package/dist/lib/secrets/filestore.js +4 -0
  134. package/dist/lib/secrets/lease.d.ts +25 -0
  135. package/dist/lib/secrets/lease.js +44 -0
  136. package/dist/lib/secrets/session-store.d.ts +7 -0
  137. package/dist/lib/secrets/session-store.js +21 -0
  138. package/dist/lib/self-update.d.ts +20 -5
  139. package/dist/lib/self-update.js +93 -16
  140. package/dist/lib/session/active.d.ts +49 -0
  141. package/dist/lib/session/active.js +175 -11
  142. package/dist/lib/session/bash-command.d.ts +16 -0
  143. package/dist/lib/session/bash-command.js +65 -0
  144. package/dist/lib/session/db.d.ts +23 -2
  145. package/dist/lib/session/db.js +86 -20
  146. package/dist/lib/session/discover.d.ts +21 -1
  147. package/dist/lib/session/discover.js +124 -32
  148. package/dist/lib/session/insights.d.ts +19 -0
  149. package/dist/lib/session/insights.js +78 -10
  150. package/dist/lib/session/recovery.d.ts +17 -1
  151. package/dist/lib/session/recovery.js +111 -4
  152. package/dist/lib/session/remote-bundle.js +1 -1
  153. package/dist/lib/session/remote-list.d.ts +10 -9
  154. package/dist/lib/session/remote-list.js +25 -23
  155. package/dist/lib/session/resume-owner.d.ts +55 -0
  156. package/dist/lib/session/resume-owner.js +69 -0
  157. package/dist/lib/session/team-filter.d.ts +65 -0
  158. package/dist/lib/session/team-filter.js +98 -3
  159. package/dist/lib/session/types.d.ts +33 -0
  160. package/dist/lib/smart-launch.d.ts +31 -5
  161. package/dist/lib/smart-launch.js +43 -7
  162. package/dist/lib/ssh-exec.d.ts +27 -0
  163. package/dist/lib/ssh-exec.js +34 -1
  164. package/dist/lib/ssh-tunnel.d.ts +3 -2
  165. package/dist/lib/ssh-tunnel.js +25 -16
  166. package/dist/lib/staleness/detectors/skills.d.ts +2 -0
  167. package/dist/lib/staleness/detectors/skills.js +17 -2
  168. package/dist/lib/staleness/index.d.ts +10 -1
  169. package/dist/lib/staleness/index.js +28 -3
  170. package/dist/lib/startup/command-registry.d.ts +1 -0
  171. package/dist/lib/startup/command-registry.js +2 -0
  172. package/dist/lib/teams/agents.d.ts +1 -0
  173. package/dist/lib/teams/agents.js +38 -11
  174. package/dist/lib/teams/placement-probe.d.ts +1 -1
  175. package/dist/lib/teams/placement-probe.js +24 -24
  176. package/dist/lib/teams/remoteWorktree.d.ts +11 -7
  177. package/dist/lib/teams/remoteWorktree.js +29 -27
  178. package/dist/lib/tmux/session.d.ts +8 -0
  179. package/dist/lib/tmux/session.js +22 -0
  180. package/dist/lib/types.d.ts +2 -0
  181. package/dist/lib/versions.d.ts +1 -0
  182. package/dist/lib/versions.js +40 -22
  183. package/package.json +3 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,298 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.22.28
4
+
5
+ - **`agents run` / `agents teams` `--device`/`--host`: fail loud when a pinned harness version is not installed on the target (RUSH-2313).** A concrete pin like `codex@0.145.0` is checked against the remote `agents view --json` listing during `ensureHostReady` *before* the run is marked dispatched. Missing pins exit non-zero naming the box, the pin, what is installed there, and `agents ssh <box> -- agents add <agent>@<ver>` — so detached fleet drains no longer print `Dispatched` and then die only in the remote log. Aliases (`@latest` / …) still resolve on the remote; a bare agent name still only warns. Source: `apps/cli/src/lib/hosts/ready.ts`, `dispatch.ts`, `teams/agents.ts`.
6
+
7
+ - **fix: Windows-host e2e suites can resolve `win-mini` again under the hermetic device registry.** `tests/setup.ts` (RUSH-2042 / #1572) redirects `AGENTS_DEVICES_DIR` to an empty fork-private directory so unit fixtures never leak into the real fleet registry. The live `ssh-tunnel.e2e` / `browser/drivers/ssh.e2e` suites still need a real `DeviceProfile` for `AGENTS_TEST_WIN_HOST`, so after #1572 every `tests-windows-host-e2e.yml` run failed immediately with `Unknown device 'win-mini'` — even on a tailnet-joined runner that could ssh to the box. The setup file now seeds the private registry from the real fleet entry (or synthesizes one from `ssh -G <host>`) when `AGENTS_TEST_WIN_HOST` is set; the real registry is never written. Source: `apps/cli/tests/seed-e2e-win-host.ts`, `apps/cli/tests/setup.ts`.
8
+
9
+ - **OpenCode timeout-sample spool dir is Windows-safe (#1869).** The generated
10
+ `agents-cli-hooks.ts` plugin used `PERF_SPOOL.slice(0, PERF_SPOOL.lastIndexOf("/"))`
11
+ to mkdir the perf spool parent. On Windows `getPerfDir()` is backslash-separated,
12
+ so `lastIndexOf("/")` returned -1 and the slice dropped one character — the
13
+ sample write was fail-silent and never landed. The plugin now imports
14
+ `node:path` and uses `path.dirname(PERF_SPOOL)`. Source: `apps/cli/src/lib/hooks.ts`.
15
+
16
+ - **Bash-command classifier: single-source tool registry + broader unwrap (#1889).** The embedded Python activity-log hook (`ACTIVITY_LOG_HOOK_SCRIPT` in `activity.ts`) hand-duplicated `bash-command.ts`'s `TOOL_REGISTRY` and `VALUE_FLAGS` with a "keep them in sync" comment — the two had already drifted (Python was missing `rmdir`, and `agents`/`linear` two-level tools). Both tables are now generated from the TypeScript source (`pythonToolRegistryLiteral` / `pythonValueFlagsLiteral`) at module load. Separately, `unwrapCommand` (and the matching Python `_unwrap_command`) now peels `export VAR=…`, `set -euo pipefail`, `for`/`until` loop bodies, `if`/`then` branches, and `(command)` subshell prefixes that previously classified as `other`. Source: `apps/cli/src/lib/session/bash-command.ts`, `apps/cli/src/lib/activity.ts`.
17
+
18
+ - **Active-session polls skip re-parsing quiet transcripts (#2047).** `computeLiveSignals` (the per-session tail/parse behind `agents sessions --active` and the menu-bar badge tick) now memoizes by transcript path + mtime + `pidAlive` inside the process, so a 30s poll no longer re-tails every live session whose file has not changed. Positive Claude transcript-path resolutions are also memoized while the file still exists, avoiding a full walk of every Claude version-home `projects/` tree per pid per tick. Source: `apps/cli/src/lib/session/active.ts`.
19
+
20
+ - perf(sessions): throttle headless `ps`/`lsof` scan + memoize process table across an active-session poll (#2047)
21
+
22
+ - **`agents routines devices --set` / `--clear` no longer abort when a fleet peer is offline (#2118).** Pinning a routine used to fan out pause/resume to every registered device and throw on the first unreachable one — often *after* the pin had already succeeded on the target — so a single asleep laptop made fleet pins unusable and the error looked like the pin failed. Offline peers are now skipped with a warning; the command exits non-zero only when a *selected* target device cannot be reached. Source: `apps/cli/src/commands/routines.ts`.
23
+
24
+ - **`agents models claude` no longer lists a per-cloud bare-minor id as a plain catalog entry (#2233).** The id-scan fallback (`scanClaudeCatalogIds`, used when the structured alias/perCloud maps yield fewer than two models) used to surface short forms like `claude-opus-4-1` that only appear as a `foundry:` / cloud-scoped field next to a real firstParty id (`claude-opus-4-1-20250805`). `dropBareLegacyIds` now drops **any** id that is a dash-boundary prefix of a more-specific sibling also present in the scan — covering both the bare-major `.includes("claude-opus-4")` artifacts (#1892) and these bare-minor cloud-metadata values — while keeping genuine bare currents with no sibling (`claude-sonnet-5`) and not collapsing `claude-opus-4-1` into `claude-opus-4-10`. Source: `apps/cli/src/lib/models.ts`.
25
+
26
+ - **Menu-bar dropdown density toggle removed.** The `Density: Auto/Rich/Compact` footer item and the whole rich/compact rendering fork are gone; the dropdown now always renders the rich rows (session/work titles, expanded Routines and Recent sections). Removes the `menubarDensity` UserDefaults key and `MENUBAR_DENSITY` env override, and updates the menu-bar docs to match. Source: `apps/cli/menubar/Sources/MenubarHelper/StatusItemController.swift`, `apps/cli/docs/menubar.md`.
27
+
28
+ - **The macOS secrets broker can now hold an explicit key subset under its own lease id and expiry (RUSH-2255).** `agents secrets lease <bundle> --keys K1,K2 --for 8h` authenticates once, stores only those resolved values in broker memory and the restart session, and reports lease metadata without exposing values. Unknown, missing, and expired keys fail closed. Source: `apps/cli/src/{commands/secrets.ts,lib/secrets/{agent,lease,session-store}.ts}`.
29
+
30
+ - **Scoped leases can now be listed and revoked by id (RUSH-2256).** `agents secrets leases` shows bundle, keys, and remaining lifetime; `agents secrets revoke <lease-id>` wipes exactly that broker grant and its restart session. Source: `apps/cli/src/{commands/secrets.ts,lib/secrets/{agent,session-store}.ts}`.
31
+
32
+ - **Fleet devices accept an explicit SSH private-key path (RUSH-2265).** `agents devices set <name> --auth key --identity-file <path>` stores the path on that device and every shared SSH invocation passes it to OpenSSH with `-i`, so devices no longer depend on whichever key the ambient ssh agent happens to offer. Switching a device from password to key auth also removes stale password-bundle metadata from the registry.
33
+
34
+ - **Detached agent dispatch now runs on Windows OpenSSH hosts (RUSH-2267).** Headless `agents run … --host <windows>` launches through a hidden PowerShell process, preserves actor/session/env context, and uses durable Windows-native log, follow, reconcile, stop, and cleanup operations.
35
+
36
+ - **`agents repo pull` fast-forwards a clean behind-only checkout instead of failing with "Cannot rebase onto multiple branches" (RUSH-2282).** After fetch, a 1-behind tree integrates via `merge --ff-only` against the tracking ref; only genuinely diverged histories enter rebase. Avoids re-running `git pull --rebase` on a multi-entry `FETCH_HEAD` (common after bare fetch when the remote has several branches, or under concurrent fleet fetch). Source: `apps/cli/src/lib/git.ts` `pullRepo`.
37
+
38
+ - **`agents perf` splits intentional deny (exit 2) from crashes (exit 1) — hook health is readable again (RUSH-2294).** Deny-by-design guards (`ask-user-question-guard`, `plan-html-reminder`, `git-guard`, …) exit 2 on purpose; the warehouse previously counted any nonzero exit as `errorCount`, so a working guard read as a "92%-error" hook. Exit 0 = allow, exit 2 = `blockCount`/`blockRate`, exit 1 / other = `errorCount`/`errorRate`. The hooks table column is now `ERR/BLOCK/TO` (`err:…% block:…% to:…%`). JSON rows gain `blockCount`/`blockRate`. Source: `apps/cli/src/lib/perf/db.ts`, `apps/cli/src/lib/hooks/profile.ts`, `apps/cli/src/commands/perf.ts`.
39
+
40
+ - **Claude launches reuse an unchanged file-backed setup-token instead of decrypting it on every exec (RUSH-2317).** The process-lifetime cache is isolated by version home and invalidates from the encrypted credential file's identity, ctime, mtime, and size; token rotation is picked up on the next launch, missing per-account tokens are negatively cached, and plaintext tokens remain memory-only. Source: `apps/cli/src/lib/claude-account-token.ts`, `apps/cli/src/lib/secrets/filestore.ts`.
41
+
42
+ - **Repeated full session listings reuse stable transcript membership (RUSH-2318).** The process briefly caches settled transcript directories by mtime+size, rereads recently changed directories, and expires every entry within the filesystem timestamp-precision window, avoiding burst-time repeat reads without hiding filesystem-only creates or deletes. Source: `apps/cli/src/lib/session/db.ts`.
43
+
44
+ - **`agents sync --yes` drops ~1s/agent of dead work and reuses still-fresh fingerprints (RUSH-2320).** Measured on a real install: `getActuallySyncedResources` was ~1055 ms/agent on the unattended path that never reads it; `buildManifest` re-hashed every file (~716 ms) after a no-op force sync; the guard-hit path spent ~12 ms building inventories it then discarded. Unattended sync now skips the interactive inventory, the skills detector is stat-first on size before any content read, `buildManifest` carries still-fresh fingerprints from the previous manifest, and `syncResourcesToVersion` runs its no-change guard before pattern expansion (accepting a caller-supplied `available` inventory so multi-version fan-out does not re-scan). Source: `apps/cli/src/lib/refresh.ts`, `apps/cli/src/lib/versions.ts`, `apps/cli/src/lib/staleness/`.
45
+
46
+ - **fix: `agents run <agent>` no longer dead-ends when every account is logged out — it launches so you can sign in.** A harness with one installed, signed-out version had no reachable login path at all: `agents run cursor` exited with `no healthy cursor account under strategy 'balanced' — excluded: 2026.07.23 (signed_out)`, the `agents run cursor@` account picker marked the logged-out row `disabled` and offered only `No usable accounts — cancel`, and `agents use cursor` only set a default. The zero-healthy guard (RUSH-2132) treated a missing login like an exhausted account, but they are opposites — a throttled account must not be launched, while a signed-out one is fixed precisely BY launching, since the harness's own TUI is the login surface. On a human-facing terminal run (a real TTY and no `--json`), a single sign-in-recoverable account now launches directly (naming the version and the login command) and several open the account picker with auth-blocked rows selectable and labelled `launch to sign in`; `rate_limited` / `out_of_credits` still fail loud, and off a TTY — or under `--json` — both classes keep the exact watchdog-parsed error, now with the harness's login command alongside `--strategy pinned`. Source: `apps/cli/src/lib/rotate.ts` (`isSignInRecoverable`, `signInRecoverableCandidates`), `apps/cli/src/commands/run-account-picker.ts` (`pickSignInLaunchVersion`), `apps/cli/src/commands/exec.ts`. (RUSH-2334)
47
+
48
+ - **`sessions --active` no longer shows retained dead/queued rows, and every process row now carries its PID (RUSH-2336).** Bare `--active` (CLI table/JSON, the interactive browser, `focus`, and the menu bar) previously kept a row alive as long as its pid wasn't *known* dead — so a queued-but-not-started row, or a process of genuinely unverified liveness, could still show up. The canonical selector (`isRunningLiveSession`) now excludes `queued`/`closed`/`crashed` outright (still reachable via `--queued`/`--closed`/`--crashed`) and requires a real process row to positively verify its machine, a positive pid, and `pidAlive === true`; a cloud row stays active on its provider + task id alone. Every process-backed `--active --json` row now guarantees `machine`/`pid`/`pidAlive: true`, and the human CLI row and the menu bar's session detail both show the matching `machine:pid` (or `provider · taskId` for cloud) locator. Source: `apps/cli/src/commands/sessions.ts`, `apps/cli/src/lib/menubar/snapshot.ts`, `apps/cli/menubar/Sources/MenubarHelper/`.
49
+
50
+ - **`agents projects status` card scans faster: grouped warnings, a fleet health summary, and a truthful `dead` label (RUSH-2337).** On a busy fleet the card printed each host's git drift twice — once in the inline `fleet` table, then again as one 2-line block per host in the warnings footer (18 lines for 3 facts) — and read `dead 41 finished or lost (41 crashed)`, which contradicts itself when every dead session is a crash. Now: (1) the warnings footer **groups by root cause** — all behind hosts collapse to one warning listing each with its count (`4 hosts behind origin/main — mac-mini ↓172, yosemite-m2 ↓217, …`) under one shared remediation, dirty/missing the same, a lone host keeps its full sentence, grouped per probed path so two repos never merge (mirrors doctor's `emitGroup`); (2) a one-line **fleet health summary** (`6/13 clean · 4 behind · 4 dirty · 1 missing`) sits above the per-host table, which keeps its branch/drift detail; (3) the `dead` row names the status directly when singular (`dead 41 crashed`); (4) the fleet-wide rollup carries a `fleet snapshot · as of HH:MM` stamp; (5) `linear` shows a completion percent (`468/547 done (86%)`). Source: apps/cli/src/lib/project-probe.ts, apps/cli/src/lib/project-status.ts, apps/cli/src/commands/projects.ts, apps/cli/docs/11-projects.md.
51
+
52
+ ## 1.22.27
53
+
54
+ - **`agents sessions --teams` groups sessions by team (RUSH-1997).** Instead of one flat list with a `[team/handle]` tag, `--teams` now prints a report grouped by team: each team names its spawner (the orchestrator session that created it) and spawn time, and every teammate row shows its mode + handle. Team-flagged spawns that carry no teammate `meta.json` — headless `agents run` sessions, or teammates whose team record aged out — sink into a trailing `(no team)` bucket, so a real `agents teams` teammate and a bare SDK spawn are never shown as the same thing. `--teams --flat`/`--tree` keep the plain inline table, and a search query keeps the interactive picker. `TeamOrigin` (also on `--teams --json`) now carries the teammate's `startedAt` spawn time and a `source` (`meta` = teammate, `entrypoint` = bare spawn). Source: `apps/cli/src/lib/session/team-filter.ts`, `apps/cli/src/commands/sessions.ts`.
55
+
56
+ - **`agents run --device auto` and `agents teams add --device auto` now choose from live fleet health instead of 14-day launch affinity (RUSH-2001).** The picker probes candidates in parallel, excludes unreachable, overloaded, and missing-agent machines, prefers signed-in candidates with the lowest normalized load, and keeps execution local when no remote is better. Remote readiness now uses `agents view --json`, preserving the installed/sign-in split instead of treating every remote sign-in state as unknown. Source: `apps/cli/src/lib/smart-launch.ts`, `apps/cli/src/lib/teams/placement-probe.ts`, `apps/cli/src/lib/hosts/ready.ts`, `apps/cli/src/commands/exec.ts`, `apps/cli/src/commands/teams.ts`.
57
+
58
+ - **`agents devices harnesses` / `agents devices accounts` — per-device harness + account readiness (RUSH-2003).** Two new fleet lenses: `harnesses` lists every installed `agent@version` across the fleet with its account, signed-in state, quota (highest usage-window utilization; `*` = cached snapshot), and a single `ready` verdict (signed in AND not rate-limited); `accounts` collapses that to one row per account, naming which harnesses share it. Both SSH-probe each online device (bounded like `fleet ping`, so one unreachable box can't stall the glance) and read the daemon-warmed usage cache — `--refresh` (`--live`) forces a live quota read. Scope with `--agents <csv>` / `--device <csv>`; `--json` emits per-host rows (harnesses) or account groups (accounts). Source: `apps/cli/src/lib/devices/harness-inventory.ts`, `apps/cli/src/commands/ssh.ts`.
59
+
60
+ - **A session that ran on another device now resumes ON that device, and a typo'd
61
+ command with `--host` says `unknown command` (RUSH-2022).** Two bugs found while
62
+ recovering ~15 sessions after a machine crash, both of which sent recovery down the
63
+ wrong path.
64
+
65
+ (1) **`agents resume <id>` restarted a remote session locally.** The harness keeps its
66
+ conversation state on the machine that produced the session, but nothing checked which
67
+ machine that was — so a peer-owned session started the agent here, against state this
68
+ box had never seen (`sessions-resume.ts` even swapped in `process.cwd()` when the
69
+ recorded directory did not exist locally). `agents resume` now re-runs itself on the
70
+ owning device over SSH; `--here` overrides. The bare `agents sessions` picker routes
71
+ the same way, and `agents sessions attach` hops as an **attach** (its detach record and
72
+ the headless process it stops both live on the owner). The multi-select
73
+ `agents sessions resume` inherits it: each tab it opens runs the canonical
74
+ `agents resume <id>`, which now routes itself, and its no-tab-backend path routes
75
+ explicitly. Root cause of
76
+ the population that made this common: a run dispatched with `agents run --device
77
+ <box>` was indexed with no origin machine at all, so the index claimed the dispatching
78
+ box; it now records `<box>`, which also means such a run finally shows up under
79
+ `agents sessions --host <box>`. Source: `apps/cli/src/lib/session/resume-owner.ts`,
80
+ `apps/cli/src/lib/hosts/session-index.ts`, `apps/cli/src/commands/resume.ts`.
81
+
82
+ The hop carries its "don't route again" pin as an exported env var, not a flag, so it
83
+ works against a peer still on an older CLI. Sessions indexed *before* this release keep
84
+ their old machine tag — re-dispatch or a fresh scan corrects them.
85
+
86
+ (2) **The `--host`/`--device` router answered for commands that do not exist.** It runs
87
+ before commander parses, so `agents session resume --host <box>` (one letter off
88
+ `sessions`, which *does* accept `--host`) reported `` `agents session` does not support
89
+ --host/--device `` — a true statement about a command nobody typed and the opposite of
90
+ the truth for the one they meant. Unknown names now fall through to `unknown command
91
+ '<name>'` with a did-you-mean, and the spellcheck can suggest the lazily-registered
92
+ groups (`sessions`/`teams`/`cloud`/…) it previously could not see. A real command with
93
+ no remote semantics still gets the flag-support error. Source:
94
+ `apps/cli/src/lib/hosts/passthrough.ts`, `apps/cli/src/lib/startup/command-registry.ts`.
95
+
96
+ - **`agents publish` is in the lazy command table.** `commands/packages.ts` registers it at
97
+ top level but the registry did not list it, so it only resolved through the
98
+ unknown-command fallback that loads the whole command tree. Found by the new test that
99
+ pins the command-name set against the real tree.
100
+
101
+ - **A mistyped command keeps its `--host`.** The distance-1 auto-correct now runs *before*
102
+ the router instead of after commander gave up, so `agents docto --host <box>` corrects to
103
+ `doctor` **and** runs on `<box>` — previously the corrected command re-parsed locally with
104
+ a `--host` it did not accept. Four routing-table entries naming commands that do not exist
105
+ (`cli`, `packages`, `versions`, `daemon`) were removed; a test now keeps both routing
106
+ tables to real command names.
107
+
108
+ - **Cross-machine `agents … --json` fan-out caps each peer's stdout at 16 MiB
109
+ instead of buffering it unbounded (RUSH-2065).** The shared fan-out
110
+ (`gatherRemoteAgentsJson`, behind `agents sessions --active`, `agents feed`, and
111
+ every other fleet-wide JSON sweep) streamed each peer's output into memory with
112
+ no ceiling, under one `Promise.all` — so a single peer returning a corrupt or
113
+ pathologically large payload could retain ~170 MB and OOM the whole sweep. Each
114
+ peer's capture now stops and SIGKILLs the connection once it would exceed the
115
+ ceiling, treating that box as unreachable (reported in `skipped`) so the rest of
116
+ the fleet still renders. The bound and the UTF-8-safe accumulator now live once
117
+ in `apps/cli/src/lib/ssh-exec.ts`, shared with the `agents sessions` browse
118
+ fan-out that already had the guard. Source: `apps/cli/src/lib/remote-agents-json.ts`,
119
+ `apps/cli/src/lib/ssh-exec.ts`, `apps/cli/src/lib/session/remote-list.ts`.
120
+
121
+ - **Auth-health probes once per account, not once per version home (RUSH-2111).**
122
+ The daemon's every-3-minute auth-health refresh fanned `probeLocalFleetAuth`
123
+ over *every* installed version home at once, so a box with several Claude homes
124
+ signed into one account fired that many concurrent requests at the same
125
+ provider OAuth endpoint — racing its rate limit into a `429` that then parked
126
+ the whole box's usage reads behind a `Retry-After` penalty (`usage-backoff.ts`
127
+ survives that penalty; this removes its cause). Installs are now grouped by
128
+ account and the live probe runs once per (agent, account), fanning the one
129
+ verdict out to each home's per-version cache row. Homes with no resolvable
130
+ account are still probed individually. Source: `apps/cli/src/lib/auth-health.ts`.
131
+
132
+ - **Fallback-chain agents now receive their own active rules preset before dispatch (RUSH-2129).** `runWithFallback` resolves each attempted entry's harness/version home and runs the same skip-fast preset synchronization as the primary `agents run` path, so a rate-limit handoff cannot launch against stale rules. Source: `apps/cli/src/lib/exec.ts`, `apps/cli/src/lib/rules/run-sync.ts`.
133
+
134
+ - **Gemini hard-deprecation: routines daemon can no longer execute a legacy Gemini routine (RUSH-2202).** RUSH-2060 gated `agents add`/`import`/`sync`/`run`/`routines add` against a hard-deprecated harness, but the routines daemon's own executor (`runner.ts`) had no equivalent gate — a Gemini routine written before RUSH-2060, or synced/edited on disk directly, would still fire and try to build a `gemini …` command against a backend Google retired. `executeJob`/`executeJobDetached` now reject a hard-deprecated agent up front, before any version/account resolution or sandbox prep, and record a `failed` run with the same deprecation message every other entry point already shows. Also removed the now-unreachable Gemini model-catalog extractor (`models.ts`), Factory's Gemini model-catalog fetch (`agentModels.ts`), and the dead Gemini sandbox-config writer (`sandbox.ts`/`gemini-settings.ts` — its generic JSON helpers stay, since Antigravity's permission writer reuses them), plus a stale `profiles.md` doc line still listing `gemini` as a live profile agent. Source: `apps/cli/src/lib/runner.ts`, `apps/cli/src/lib/models.ts`, `apps/cli/src/lib/sandbox.ts`, `apps/cli/src/lib/gemini-settings.ts`, `apps/factory/src/core/agentModels.ts`.
135
+
136
+ - **Routine transcripts now archive as `origin='routine'` sessions (RUSH-2271).** A
137
+ Claude (and Codex) routine writes its transcript to the per-version `CLAUDE_CONFIG_DIR`
138
+ / `CODEX_HOME` home, not the sandbox overlay the archiver scanned — so routine runs
139
+ were indexed as ordinary `origin='cli'` sessions and never linked to their routine or
140
+ run. `archiveRoutineTranscripts` now reads the same per-version home `buildExecEnv`
141
+ writes to (re-pointed to each failover attempt's account as the chain advances), scoped
142
+ by a pre-spawn baseline so it copies only that run's transcript out of the shared home,
143
+ and `agents sessions --routine` shows them again. Kimi relocates too but its
144
+ routine-archive discovery reader is a separate follow-up. Source:
145
+ `apps/cli/src/lib/runner.ts`, `apps/cli/src/lib/routines.ts`.
146
+
147
+ - **Release lease detects a holder killed from outside (RUSH-2274).** An externally
148
+ killed release (SIGKILL, a severed ssh, a rebooted box) left its lease on `origin`
149
+ and `scripts/release-lease.sh status` read `held` for up to the 30-minute TTL with
150
+ nothing actually releasing. The lease now records the holding `host`, `pid`, and
151
+ that pid's start time, and `status` reports `holder-alive=yes|no|unknown`. A holder
152
+ that is provably gone is reclaimed by the next `claim` immediately instead of
153
+ waiting out the TTL, and a new `release-lease.sh clear` drops such a lease without
154
+ starting a release. A live holder is never taken at any age, an unprobeable one
155
+ (another box, or a lease from an older release) still falls back to the TTL, and a
156
+ reused pid or an unreaped zombie counts as dead rather than as a live release.
157
+ Source: `apps/cli/scripts/release-lease.sh`, `apps/cli/scripts/release.sh`.
158
+
159
+ - **`agents output` and the session index no longer under-count Windows hosts
160
+ (RUSH-2286).** A Windows box could report zero token burn / zero sessions even
161
+ when it was actively used, because two per-harness scanners in
162
+ `session/discover.ts` failed on Windows: the OpenClaw scan gated on `which
163
+ openclaw`, which is POSIX-only (`which` throws ENOENT on Windows, so the whole
164
+ OpenClaw scan silently returned before indexing anything), and the Grok scanner
165
+ recovered a session's version from `summary.grok_home` with a `/`-only regex
166
+ that never matched a backslash-separated Windows path. The OpenClaw presence
167
+ check now uses the cross-platform `hasCommand`, its `openclaw` invocations route
168
+ through `execFileShellSpec` so a Windows `.cmd`/`.ps1` shim actually launches,
169
+ and the Grok version regex normalizes separators first. Separately, JSON relayed
170
+ from a Windows peer over SSH (`agents output --host <win> --json`,
171
+ `agents sessions … --json`) is now stripped of any PowerShell `#< CLIXML`
172
+ banner before parsing (`stripClixml` in `hosts/remote-cmd.ts`), so a fleet-wide
173
+ rollup that folds in a Windows box no longer drops it on a `JSON.parse` failure.
174
+ The banner strip is applied at every remote-`--json` boundary a Windows peer's
175
+ output flows through: the `remote-agents-json` fan-out, the session
176
+ `remote-list` list/payload/tool-search parsers, the `--host` fleet passthrough
177
+ (`agents view --host all`), and `agents output`'s per-device fetch.
178
+ Source: `apps/cli/src/lib/session/discover.ts`,
179
+ `apps/cli/src/lib/hosts/remote-cmd.ts`, `apps/cli/src/lib/hosts/passthrough.ts`,
180
+ `apps/cli/src/lib/remote-agents-json.ts`,
181
+ `apps/cli/src/lib/session/remote-list.ts`, `apps/cli/src/commands/output.ts`.
182
+
183
+ - **`agents output` now reports the burn split and a `--pricing no-cache` scenario (RUSH-2287).**
184
+ The productivity rollup collapsed token burn into a single counter. It now
185
+ breaks the burn into uncached **input** / **cache-read** / **cache-write** tokens
186
+ wherever the harness records a per-message cache split (Claude, Codex, Gemini,
187
+ Droid) — a `burn split:` line in the text report and the three counts on `burn`
188
+ and every `breakdown` row in `--json`. New `--pricing no-cache` reprices cached
189
+ tokens at the model's full input rate to model "what would this cost with caching
190
+ off?"; the text report leads with that figure (breakdown column `burn(nc)`) while
191
+ `--json` always carries **both** `costUsd` and `costUsdNoCache` so a dashboard can
192
+ choose. The saving is surfaced in actual mode too (`caching: actual $X vs no-cache
193
+ $Y`). Backed by four new session columns (`input_tokens`, `cache_read_tokens`,
194
+ `cache_write_tokens`, `cost_usd_nocache`, schema v37) populated at scan time;
195
+ pre-upgrade sessions show total-only until re-scanned. Source:
196
+ `apps/cli/src/commands/output.ts`, `apps/cli/src/lib/session/{db,discover}.ts`,
197
+ `apps/cli/src/lib/pricing/cost.ts`.
198
+
199
+ - **Routine failures now reach the owner's phone, not just the local desktop (RUSH-2288).**
200
+ A `failed`/`timeout` routine finish, or a pre-spawn failure such as `auth_failed`, now
201
+ also pings the owner over the same channel stack `agents notify` uses (the
202
+ `owner.channels` in `humans.yaml`, or the legacy `notify.owner`), delivered in-process by
203
+ the daemon — no shelling out to `ssh mac-mini agents notify`. This closes the gap where a
204
+ failed scheduled routine on a headless fleet box was invisible, and specifically covers
205
+ the `auth_failed` case the per-routine `agents notify` prompt can never send (its agent
206
+ never spawned). If the primary owner channel cannot deliver from the box, the daemon walks
207
+ the remaining configured channels as fallbacks (Telegram and intrusive/voice channels are
208
+ excluded). Green routines of any kind stay silent, the existing desktop thresholds are
209
+ unchanged, and delivery is deduped per job+runId. Source:
210
+ `apps/cli/src/lib/routine-notify-owner.ts`, `apps/cli/src/lib/daemon.ts`.
211
+
212
+ - Add `agents bench list`, `agents bench run`, and `agents bench results`: benchmark cells fan out through the existing `agents run` path with isolated fixture copies, bounded concurrency, custom harness names, wall-time/exit/token capture, and durable JSON results under `~/.agents/.history/bench/` (RUSH-2302, RUSH-2303).
213
+
214
+ - **New user quickstart: install, harnesses, teams, fleet.** `apps/cli/docs/QUICKSTART.md` walks a fresh install through `agents setup`, adding and logging into harnesses, a minimal `agents teams` smoke test, and setting up a fleet (`agents devices sync`, `agents devices set-interactive`, `agents apply`) — linked from `docs/README.md` and the root README's Quickstart. Docs only — no runtime change. Source: `apps/cli/docs/QUICKSTART.md`, `apps/cli/docs/README.md`, `README.md`.
215
+
216
+ - **`agents insights` detects agent silent stalls (model goes idle until you resume).**
217
+ When the assistant is last to speak and the next user message is ≥5 minutes later,
218
+ facets count duration-bucketed `silent stall: 5-15m` / `15-60m` / `1h+` friction
219
+ signals; resume nudges (`continue`, `keep going`, …) after that silence also count
220
+ as `resume after silent stall`. Report, actions, `--narrative`, and
221
+ `/sessions-insights` instruct models to call these out (not reframe as "user was
222
+ slow"). Extractor version bumped to 5 so cached facets recompute. Source:
223
+ `apps/cli/src/lib/session/insights.ts`, `commands/insights.ts`,
224
+ `docs/06-observability.md`.
225
+
226
+ - **`agents insights` splits silent stalls by harness and model.** The By-agent/account
227
+ table now shows per-group stall and resume-nudge counts (so laziness is visible without
228
+ `--json`). Stalls are also attributed to the model that last spoke
229
+ (`silentStallsByModel`, "Silent stalls by model" section). Extractor version 6.
230
+ Source: `apps/cli/src/lib/session/insights.ts`, `commands/insights.ts`.
231
+
232
+ - **Redact Claude OAuth setup-tokens (`sk-ant-oat01-…`) from logs and exports, and reject a malformed one before it reaches the auth header (#1767).** The log redactor masked API keys (`sk-ant-api03-…`) but not OAuth setup-tokens — the generic `sk-` rule can't reach an `oat01` token because the hyphen after `ant` breaks its run — so a captured setup-token could leak verbatim into a run log or session export. `resolveClaudeSetupToken` also now validates the stored value: a corrupt `auth` bundle entry (e.g. a captured `claude setup-token` TTY banner+ANSI blob, the exact #1767 shape) is refused instead of being injected as `CLAUDE_CODE_OAUTH_TOKEN` → an invalid `Authorization: Bearer` header that crashes the run; the caller falls back to the normal login. Source: `apps/cli/src/lib/redact.ts`, `apps/cli/src/lib/claude-account-token.ts`.
233
+
234
+ - **Stop the interactive host auto-reconnect spinning forever on a flapping link
235
+ (#1884).** A reattach only refills the retry budget now if it reached the host
236
+ **and** held the remote pane for at least 10 seconds. Before, the budget refilled
237
+ on the preflight probe alone, so a link that reconnected and dropped the user
238
+ straight back out — or an attach that died at TTY negotiation every time — printed
239
+ `Reconnecting … (attempt 1/6)` on every cycle forever and `MAX_ATTEMPTS` bounded
240
+ nothing. A link that keeps dropping now spends the budget and gives up with a
241
+ message that says so ("kept dropping again within 10 seconds of getting back in"),
242
+ distinct from the unreachable-host "couldn't reconnect". A session that blinks all
243
+ day and reconnects into a working pane each time is unaffected. Source:
244
+ `apps/cli/src/lib/hosts/reconnect.ts`, `docs/hosts.md`.
245
+
246
+ - **`agents models claude` no longer lists bare legacy ids that 404 (#1892).** The
247
+ native-binary id-scan fallback (`scanClaudeCatalogIds`, used when the curated maps
248
+ come up empty) is now word-boundary anchored and matches the id body atomically, so
249
+ it can't scrape a bare-major prefix (`claude-sonnet-4`) out of the binary's own dotted
250
+ `claude-sonnet-4.6` "Typo in model ID" troubleshooting string, out of a suffix-glued
251
+ token (`claude-opus-4-1x`), or out of a token glued to a preceding identifier char. The
252
+ existing `dropBareLegacyIds` sibling-drop still removes the standalone
253
+ `.includes("claude-opus-4")` prefix-check artifacts; genuine bare currents
254
+ (`claude-sonnet-5`) are kept. Catalog output is unchanged across all shipped Claude
255
+ binaries. Source: `apps/cli/src/lib/models.ts`.
256
+
257
+ - **Make ended-session focus open the correct recovery target (#2108).** `agents sessions focus <id>` now reaps metadata-less dead tmux panes, proves the indexed transcript belongs to the exact active version home before native resume, launches Claude from the transcript's original project directory, and passes replacement-version `/continue` as an interactive positional prompt. Source: `apps/cli/src/lib/session/recovery.ts`, `apps/cli/src/lib/tmux/session.ts`, `apps/cli/src/lib/exec.ts`.
258
+
259
+ - **The multi-install warning now inventories copies outside `PATH` and flags
260
+ legacy installs that can corrupt the shared macOS helper bundle (#2147).**
261
+ Discovery covers NVM, fnm, Volta, Bun, common npm global prefixes, and npm's
262
+ `_npx` cache in addition to resolving every `agents` entry on `PATH`. Dev
263
+ installs are no longer hidden: a copy without the atomic
264
+ `app-bundle-install` module is labelled `unsafe legacy helper installer —
265
+ remove this copy`, because invoking it can still replace a live `.app` with a
266
+ partial bundle. Source: `apps/cli/src/lib/self-update.ts`,
267
+ `apps/cli/src/index.ts`.
268
+
269
+ - **Newest signed agents-cli install owns the menu-bar helper (#2210).** On multi-install Macs (e.g. Homebrew + nvm), a newer release now takes over the helper immediately and an older install can no longer reclaim or downgrade it. Equal-version foreign installs keep the existing owner; missing-helper, Developer-ID repair, and unversioned legacy cooldown behavior are unchanged. Source: `apps/cli/src/lib/menubar/install-menubar.ts`.
270
+
271
+ - **Layered resource listing is ~40% faster.** `getActiveResourceProfile()` read
272
+ `agents.yaml` twice per call — once up front, then again inside
273
+ `getActiveResourceProfileName()` — and `listResources()` calls it once per
274
+ resolved resource, so a listing paid two memoized `readMeta()` round-trips
275
+ (`ensureAgentsDir()` plus four `stat`s each) for every entry. Reading it only
276
+ after the profile name is known drops one of them. Measured on `yosemite-s1`
277
+ against the real `~/.agents`: one pass over all eight resource kinds (135
278
+ entries) went 10.52 ms → 6.23 ms, and `agents doctor --json` spends ~243 ms in
279
+ this path across 95 listings. No behavior change: the read count is never
280
+ higher on any path and is unchanged whenever a profile name resolves — the one
281
+ saved read is the up-front one that the `if (!name) return null;` guard now
282
+ skips. The `ensureAgentsDir()` side effect is unchanged because
283
+ `getActiveResourceProfileName()` always reaches `readMeta()`, via
284
+ `brand.ts` `listBrands()` when a brand is set and via
285
+ `resource-profiles.ts` otherwise. Source:
286
+ `apps/cli/src/lib/resource-profiles.ts`.
287
+
288
+ - **Routine session discovery now supports an interactive picker and fuzzy names (RUSH-1998).** `agents sessions --routine` opens a routine picker on a TTY with each routine's last run, run count, and latest-run session count; the selected sessions are grouped by run ID and timestamp. `--routine <name>` accepts exact, substring, or unambiguous typo matches, and `--routines` is an alias for the same session filter.
289
+
290
+ - **Secret leases now have one scoped, time-boxed domain model (RUSH-2254).** A lease names one bundle, an explicit validated key subset, an absolute expiry, harness scope, and sleep-persistence posture. Durations use the broker's 1-minute to 30-day safety bounds, duplicate keys normalize once, unknown keys fail closed, and expired leases cannot project values. Source: `apps/cli/src/lib/secrets/lease.ts`.
291
+
292
+ - **Fleet devices accept an explicit SSH private-key path (RUSH-2265).** `agents devices set <name> --auth key --identity-file <path>` stores the path on that device and every shared SSH invocation passes it to OpenSSH with `-i`, so devices no longer depend on whichever key the ambient ssh agent happens to offer. Switching a device from password to key auth also removes stale password-bundle metadata from the registry.
293
+
294
+ - **`agents doctor` diagnoses Windows OpenSSH public-key enrollment (RUSH-2266).** On Windows it reads the effective `AuthorizedKeysFile`, selects the ProgramData administrator file or per-user profile file, verifies a public-key record is present, and checks that the administrator file grants `FullControl` to only `SYSTEM` and `Administrators`. The audit is read-only and never reads or prints private keys or passwords; fleet doctor forwards a locally authored summary and directs operators to run doctor on that box for details.
295
+
3
296
  ## 1.22.26
4
297
 
5
298
  - Make bare `agents setup` a re-runnable onboarding hub with live capability status and direct access to browser, computer, secrets, fleet, share, watchdog, and preference wizards.
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  <a href="https://github.com/phnx-labs/agents-cli"><img src="https://img.shields.io/badge/github-phnx--labs%2Fagents--cli-blue?style=flat-square" alt="github" /></a>
12
12
  </p>
13
13
 
14
- **The missing toolchain for CLI coding agents.** Run any agent on your existing subscription. Spawn parallel teams in isolated terminals or dispatch to the cloud for a PR. Watch live state across the fleet, nudge stalled runs, and message agents mid-flight. Schedule routines and set monitors that fire an agent when a source changes, drive browsers and Electron apps, store secrets behind Touch ID, and file tickets from a menu-bar bar — all from one CLI.
14
+ **A framework for running a distributed agent factory.** Dispatch Claude, Codex, Antigravity, Grok, and more across your own machines, in parallel, on your existing subscriptions. Measure every run with `agents perf` / `agents insights`, fold what you learn back into `AGENTS.md` and skills, then put the loop on a schedule with routines and monitors. Spawn parallel teams in isolated terminals or dispatch to the cloud for a PR. Watch live state across the fleet, nudge stalled runs, and message agents mid-flight. Store secrets behind Touch ID, drive real browsers and Electron apps, and steer the whole fleet from a menu bar — all from one CLI.
15
15
 
16
16
  <p align="center">
17
17
  <a href="https://github.com/anthropics/claude-code" title="Claude Code"><img src="assets/harnesses/anthropic.svg" height="32" alt="Claude Code" /></a>
@@ -52,26 +52,33 @@ agents run claude "explain this repo" # run any agent on your existing subscrip
52
52
 
53
53
  `agents setup` is interactive and idempotent -- safe to re-run on any machine. Once core setup exists, it opens a status-aware menu for browser, computer, secrets, fleet, share, watchdog, and device preferences; each choice delegates to the same wizard available under `agents setup <capability>`. In CI or another non-TTY, bare setup prints the checklist without prompting. The `agi-cli.sh` one-liner installs this same canonical `@phnx-labs/agents-cli` package. Prefer bun? `bun install -g @phnx-labs/agents-cli` works too.
54
54
 
55
+ Full path -- installing harnesses, logging in, smoke-testing `agents teams`, and setting up your own fleet: [`apps/cli/docs/QUICKSTART.md`](apps/cli/docs/QUICKSTART.md).
56
+
55
57
  Already installed? `agents upgrade` updates agents-cli itself to the latest version (`agents upgrade 1.2.3` for a specific version or dist-tag, `-y` to skip the confirm prompt). The command is `upgrade` on every platform -- there is no `agents update` (on macOS, `agents helper update` is a different command that reinstalls the keychain helper, not agents-cli).
56
58
 
57
59
  Source: [github.com/phnx-labs/agents-cli](https://github.com/phnx-labs/agents-cli)
58
60
 
59
61
  Also available as `ag` -- all commands work with both `agents` and `ag`.
60
62
 
61
- - [Pin versions per project](#pin-versions-per-project)
63
+ - [Factory loop](#factory-loop)
62
64
  - [One config, every agent](#one-config-every-agent)
63
65
  - [Run any agent](#run-any-agent)
64
66
  - [Sessions across agents](#sessions-across-agents)
65
67
  - [Control the fleet](#control-the-fleet)
66
68
  - [Sync the fleet](#sync-the-fleet)
67
- - [Run open models through Claude Code](#run-open-models-through-claude-code)
69
+ - [Pin versions per project](#pin-versions-per-project)
70
+ - [Run open models through Claude Code](#run-open-models-through-claude-code-experimental)
71
+ - [Run on your own machines](#run-on-your-own-machines)
68
72
  - [Teams](#teams)
69
73
  - [Cloud](#cloud)
70
74
  - [Workflows](#workflows)
75
+ - [Plugins](#plugins)
76
+ - [Make it yours](#make-it-yours)
71
77
  - [Browser](#browser)
72
78
  - [Secrets](#secrets)
73
79
  - [Routines](#routines)
74
80
  - [Monitors](#monitors)
81
+ - [Share](#share)
75
82
  - [PTY](#pty)
76
83
  - [Portable setup](#portable-setup)
77
84
  - [Menu bar](#menu-bar)
@@ -82,34 +89,35 @@ Also available as `ag` -- all commands work with both `agents` and `ag`.
82
89
 
83
90
  ---
84
91
 
85
- ## Pin versions per project
92
+ ## Factory loop
86
93
 
87
- ```bash
88
- # This project needs claude@2.0.65 -- newer versions changed tool calling.
89
- agents use claude@2.0.65 -p
90
-
91
- # The monorepo uses codex@0.116.0 across the team.
92
- agents use codex@0.116.0 -p
93
- ```
94
-
95
- This creates an `agents.yaml` at the project root:
96
-
97
- ```yaml
98
- # agents.yaml (commit this to your repo)
99
- agents:
100
- claude: "2.0.65"
101
- codex: "0.116.0"
102
- ```
103
-
104
- Think `requirements.txt` for CLI coding agents, on steroids. A shim reads `agents.yaml` from the project root and routes `claude` / `codex` / `antigravity` / `grok` (and others) to the right version automatically. Each version gets its own isolated home -- switching backs up config and re-syncs resources.
94
+ The same loop whether it's one agent on your laptop or a fleet across a dozen machines: dispatch work, measure what happened, fold the lesson back into the harness, then put it on a schedule.
105
95
 
106
96
  ```bash
107
- agents add claude@2.0.65 # Install a specific version
108
- agents add codex@latest # Install latest
109
- agents add codex@oldest # Install the oldest published version
110
- agents view # See everything installed
97
+ # Dispatch a team across the fleet -- each teammate in its own worktree
98
+ agents teams create checkout --devices yosemite-s0,yosemite-s1
99
+ agents teams add checkout claude "Owns: app/payments/*" --name payments
100
+ agents teams add checkout codex "Write tests for the new code" --name qa --after payments
101
+ agents teams start checkout --watch
102
+
103
+ # Measure what happened -- latency, friction, dead-weight skills
104
+ agents perf commands --days 7 # slowest CLI entrypoints
105
+ agents insights --since 30d # friction, harness comparison, ranked actions
106
+
107
+ # Fold the lesson back into the harness -- every agent picks it up next run
108
+ agents rules add ~/lessons/payments-review.md --agents claude
109
+ agents skills add ~/skills/payments-review --agents claude
110
+
111
+ # Put it on a schedule so it runs itself
112
+ agents routines add nightly-payments-audit \
113
+ --schedule "0 2 * * *" --agent claude --prompt "Audit payments/* for regressions"
114
+
115
+ # Steer the fleet this loop runs on from the menu bar
116
+ agents menubar setup
111
117
  ```
112
118
 
119
+ `agents perf` reads a disposable warehouse at `~/.agents/.cache/perf/perf.db` -- hook, command, and run timing rollups, deletable any time. `agents insights` (alias `agents sessions insights`) is deterministic and offline: it caches per-session facets, compares harnesses, and ranks actions by evidence count -- no model call unless you pass `--narrative`. Routines put any of this on a cron ([Routines](#routines)); monitors fire it on a change instead of a clock ([Monitors](#monitors)); the menu bar is the always-on control surface for the fleet these commands drive ([Menu bar](#menu-bar)).
120
+
113
121
  ---
114
122
 
115
123
  ## One config, every agent
@@ -370,7 +378,7 @@ agents resume 019fd0c8-b3e9-77a2-a1a4-444698c4d897 # original harness/version/d
370
378
  agents run auto --resume 019fd0c8-b3e9-77a2-a1a4-444698c4d897 # adapt if its account is unavailable
371
379
  ```
372
380
 
373
- `agents sessions resume` reopens several sessions in whatever terminal you're in -- auto-detected across iTerm, Ghostty, tmux, and the VSCodium agent-terminal, or forced with `--iterm` / `--ghostty` / `--tmux` / `--vscodium`. `agents resume <id>` resumes one session without requiring you to name its harness: exact IDs take a local SQLite fast path, then resolve fleet-wide and recover on the source device. If the origin version is installed, signed in, and healthy, its isolated home performs native resume. Otherwise a healthy version of the **same harness** starts with `/continue <id>`, which reads the indexed transcript even when the old version home is retained under version trash. It never native-resumes from a different isolated home. Back them with **tmux** and the runs turn durable: detach, close your editor, reboot the GUI -- the session is still alive to `agents tmux attach`. The whole `agents tmux` subsystem (persistent multiplexer sessions that survive editor restarts and can be shared with other tools) sits underneath.
381
+ `agents sessions resume` reopens several sessions in whatever terminal you're in -- auto-detected across iTerm, Ghostty, tmux, and the VSCodium agent-terminal, or forced with `--iterm` / `--ghostty` / `--tmux` / `--vscodium`. `agents resume <id>` resumes one session without requiring you to name its harness: exact IDs take a local SQLite fast path, then resolve fleet-wide and recover on the source device. If the origin version is installed, signed in, healthy, and still owns the indexed transcript, its isolated home performs native resume. Claude launches that native resume from the original project directory recorded before the first turn, so its `projects/<cwd-key>` lookup reaches the conversation even when the session later changed directories. Otherwise a healthy version of the **same harness** starts with `/continue <id>`, which reads the indexed transcript even when the old version home is retained under version trash or the same version number was reinstalled into a new home. It never native-resumes from a different isolated home. Back them with **tmux** and the runs turn durable: detach, close your editor, reboot the GUI -- the session is still alive to `agents tmux attach`. The whole `agents tmux` subsystem (persistent multiplexer sessions that survive editor restarts and can be shared with other tools) sits underneath.
374
382
 
375
383
  ### Send an agent to the background — and bring it back
376
384
 
@@ -462,6 +470,36 @@ See [docs/fleet.md](apps/cli/docs/fleet.md) for the manifest schema and reconcil
462
470
 
463
471
  ---
464
472
 
473
+ ## Pin versions per project
474
+
475
+ ```bash
476
+ # This project needs claude@2.0.65 -- newer versions changed tool calling.
477
+ agents use claude@2.0.65 -p
478
+
479
+ # The monorepo uses codex@0.116.0 across the team.
480
+ agents use codex@0.116.0 -p
481
+ ```
482
+
483
+ This creates an `agents.yaml` at the project root:
484
+
485
+ ```yaml
486
+ # agents.yaml (commit this to your repo)
487
+ agents:
488
+ claude: "2.0.65"
489
+ codex: "0.116.0"
490
+ ```
491
+
492
+ Think `requirements.txt` for CLI coding agents, on steroids. A shim reads `agents.yaml` from the project root and routes `claude` / `codex` / `antigravity` / `grok` (and others) to the right version automatically. Each version gets its own isolated home -- switching backs up config and re-syncs resources.
493
+
494
+ ```bash
495
+ agents add claude@2.0.65 # Install a specific version
496
+ agents add codex@latest # Install latest
497
+ agents add codex@oldest # Install the oldest published version
498
+ agents view # See everything installed
499
+ ```
500
+
501
+ ---
502
+
465
503
  ## Run open models through Claude Code (experimental)
466
504
 
467
505
  > **Note:** Profiles are experimental, but available by default — no enable step needed.
@@ -546,6 +584,9 @@ agents fleet status # online/offline rollup + NEEDS ATTENTIO
546
584
  agents fleet status --verbose # full per-device auth/CLI/sync/version grid
547
585
  agents fleet status --live # force a live resource probe (alias of --refresh)
548
586
  agents fleet status --json --strict # scriptable fleet health gate
587
+ agents devices harnesses # per device: agent@version · account · signed · quota · ready
588
+ agents devices accounts # same, one row per account (which harnesses share it)
589
+ agents devices harnesses --agents claude,codex --json # scoped, machine-readable
549
590
  agents doctor --check --devices # CI drift gate across every registered device
550
591
 
551
592
  # Your Tailscale fleet, auto-discovered
@@ -594,6 +635,18 @@ endpoint — e.g. codex/grok — benign), `◐degraded` (soft/self-healing: expi
594
635
  rate-limited), and `○revoked` (server rejected — re-login now). Only `○` means a real
595
636
  re-login is needed. Run `agents fleet ping` to force a live re-verification across the fleet.
596
637
 
638
+ `agents devices harnesses` answers "what can each box actually run right now?" — one row
639
+ per installed `agent@version` across the fleet with its **account**, **signed-in**,
640
+ **quota** (highest usage-window utilization; `*` = from the cached snapshot), and a single
641
+ **ready** verdict (signed in AND not rate-limited). It SSH-probes each online device
642
+ (bounded, so one unreachable box can't stall the glance) and reuses the daemon-warmed usage
643
+ cache, so it never blocks on a per-account network fetch — pass `--refresh` (`--live`) for a
644
+ live quota read. `agents devices accounts` is the same data through the **identity lens**:
645
+ one row per account, collapsing the installs that share it (e.g. five claude versions on one
646
+ email) and naming which harnesses use it — the fast way to see which accounts are logged in
647
+ and healthy across every machine. Scope either with `--agents <csv>` / `--device <csv>`, and
648
+ add `--json` for the machine-readable per-host rows.
649
+
597
650
  **Hosts** (`agents hosts`) are git-synced dispatch targets in `agents.yaml`; **devices** (`agents devices`) are your Tailscale machines in a local registry. Both ride SSH and feed one host pool: devices appear in `agents hosts list` and capability routing without a second enrollment. On `--host` runs every `agents run` option is either forwarded (`--effort --env --timeout --loop …`), rejected loud (`--secrets` never crosses SSH implicitly), or consumed locally — nothing silently drops. See [docs/00-concepts.md](apps/cli/docs/00-concepts.md#devices--hosts).
598
651
 
599
652
  Every `--host` command rides one multiplexed SSH engine, tuned for driving a fleet from a small laptop: the first call to a machine opens a control socket and every later call reuses it (no repeat TCP+auth handshake), connections carry keepalive so a dropped link dies in ~45 s instead of zombying, and following a remote run polls in a single round-trip per cycle. Measured against a Tailscale-relayed host: repeated calls **~6–7× faster**, dispatch readiness **~2×**, and the follow loop **~21× faster with 50% fewer local ssh spawns**. Design: [docs/09-ssh-transport.md](apps/cli/docs/09-ssh-transport.md) · reproduce: `node scripts/bench-ssh.mjs <host>`.
package/dist/bin/agents CHANGED
Binary file
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerBenchCommand(program: Command): void;
@@ -0,0 +1,101 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { detectSignedInRuntimes } from "../lib/crabbox/runtimes.js";
5
+ import { listRuns, loadRun, loadTask, runCells, saveRun, } from "../lib/bench/index.js";
6
+ import { setHelpSections } from "../lib/help.js";
7
+ const TASKS_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../bench/tasks");
8
+ function csv(value) {
9
+ return (value
10
+ ?.split(",")
11
+ .map((item) => item.trim())
12
+ .filter(Boolean) ?? []);
13
+ }
14
+ function taskIds(root = TASKS_ROOT) {
15
+ if (!fs.existsSync(root))
16
+ return [];
17
+ return fs
18
+ .readdirSync(root, { withFileTypes: true })
19
+ .filter((entry) => entry.isDirectory() &&
20
+ fs.existsSync(path.join(root, entry.name, "task.json")))
21
+ .map((entry) => entry.name)
22
+ .sort();
23
+ }
24
+ function renderResult(result) {
25
+ console.log(`Run ${result.run_id}${result.task_id ? ` · ${result.task_id}` : ""}`);
26
+ for (const cell of result.cells)
27
+ console.log(`${cell.status === "passed" ? "PASS" : "FAIL"} ${cell.agent}${cell.model ? `/${cell.model}` : ""} ${cell.wall_ms} ms exit ${cell.exit ?? "spawn-error"}`);
28
+ }
29
+ export function registerBenchCommand(program) {
30
+ const bench = program
31
+ .command("bench")
32
+ .description("Run the same task across agent and model cells, with isolated fixtures and durable JSON results.");
33
+ setHelpSections(bench, {
34
+ examples: `agents bench list\nagents bench run hello-repo --agent claude,codex --model cheap,default\nagents bench results --json`,
35
+ notes: `Task definitions live under apps/cli/bench/tasks/<id>/task.json. Custom harness names accepted by agents run are valid --agent values.`,
36
+ });
37
+ bench
38
+ .command("list")
39
+ .description("List available benchmark tasks.")
40
+ .option("--json", "Emit JSON.")
41
+ .action((options) => {
42
+ const tasks = taskIds();
43
+ if (options.json)
44
+ console.log(JSON.stringify(tasks, null, 2));
45
+ else if (tasks.length === 0)
46
+ console.log("No benchmark tasks installed.");
47
+ else
48
+ tasks.forEach((id) => console.log(id));
49
+ });
50
+ bench
51
+ .command("results [run-id]")
52
+ .description("Show one saved run, or list saved runs newest first.")
53
+ .option("--json", "Emit JSON.")
54
+ .action((runId, options) => {
55
+ const value = runId ? loadRun(runId) : listRuns();
56
+ if (options.json)
57
+ console.log(JSON.stringify(value, null, 2));
58
+ else if (Array.isArray(value)) {
59
+ if (value.length === 0)
60
+ console.log("No benchmark results yet.");
61
+ else
62
+ value.forEach(renderResult);
63
+ }
64
+ else
65
+ renderResult(value);
66
+ });
67
+ bench
68
+ .command("run [task-id]")
69
+ .description("Run one task or prompt across an agent × model matrix.")
70
+ .option("--prompt <text>", "Prompt to benchmark instead of a house task.")
71
+ .option("--agent <names>", "Comma-separated native agents or custom harness names. Defaults to signed-in native agents.")
72
+ .option("--model <models>", "Comma-separated model tiers or concrete model ids.")
73
+ .option("--concurrency <n>", "Maximum cells running at once.", "3")
74
+ .option("--json", "Emit the saved JSON result.")
75
+ .action(async (taskId, options) => {
76
+ if (!!taskId === !!options.prompt)
77
+ throw new Error("Pass exactly one of <task-id> or --prompt.");
78
+ const task = taskId ? loadTask(taskId, TASKS_ROOT) : undefined;
79
+ const prompt = options.prompt ?? task.prompt;
80
+ let agents = csv(options.agent);
81
+ if (agents.length === 0)
82
+ agents = (await detectSignedInRuntimes())
83
+ .filter((runtime) => runtime.signedIn)
84
+ .map((runtime) => runtime.id);
85
+ if (agents.length === 0)
86
+ throw new Error("No signed-in native agents found. Pass --agent <name>.");
87
+ const models = csv(options.model);
88
+ const cells = agents.flatMap((agent) => models.length > 0
89
+ ? models.map((model) => ({ agent, model }))
90
+ : [{ agent }]);
91
+ const concurrency = Number(options.concurrency);
92
+ const result = await runCells({ task, prompt, cells, concurrency });
93
+ saveRun(result);
94
+ if (options.json)
95
+ console.log(JSON.stringify(result, null, 2));
96
+ else
97
+ renderResult(result);
98
+ if (result.cells.some((cell) => cell.status === "failed"))
99
+ process.exitCode = 1;
100
+ });
101
+ }