@phnx-labs/agents-cli 1.22.58 → 1.22.60

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 (83) hide show
  1. package/CHANGELOG.md +260 -0
  2. package/README.md +29 -0
  3. package/dist/bootstrap.js +32 -1
  4. package/dist/commands/monitors.js +187 -23
  5. package/dist/commands/perf.js +10 -0
  6. package/dist/commands/routines.test-fixture.js +5 -0
  7. package/dist/commands/send.d.ts +2 -1
  8. package/dist/commands/send.js +7 -5
  9. package/dist/commands/sessions-picker.d.ts +13 -0
  10. package/dist/commands/sessions-picker.js +17 -8
  11. package/dist/commands/sessions-stats.js +37 -5
  12. package/dist/commands/sessions.js +52 -16
  13. package/dist/commands/ssh.js +12 -1
  14. package/dist/commands/teams-picker.js +20 -6
  15. package/dist/commands/teams.d.ts +2 -2
  16. package/dist/commands/teams.js +77 -21
  17. package/dist/commands/versions.js +12 -4
  18. package/dist/commands/view.js +7 -2
  19. package/dist/lib/accounting/rotate.js +12 -4
  20. package/dist/lib/accounting/usage-sync.d.ts +12 -2
  21. package/dist/lib/accounting/usage-sync.js +34 -6
  22. package/dist/lib/auto-pull-worker.js +7 -2
  23. package/dist/lib/cloud/rush.d.ts +7 -0
  24. package/dist/lib/cloud/rush.js +29 -1
  25. package/dist/lib/daemon/daemon.d.ts +22 -0
  26. package/dist/lib/daemon/daemon.js +39 -0
  27. package/dist/lib/daemon/session-index-service.js +9 -1
  28. package/dist/lib/daemon-ticks.d.ts +15 -0
  29. package/dist/lib/daemon-ticks.js +26 -0
  30. package/dist/lib/device-config.d.ts +5 -1
  31. package/dist/lib/device-config.js +2 -2
  32. package/dist/lib/devices/health.js +5 -1
  33. package/dist/lib/devices/pool.d.ts +25 -2
  34. package/dist/lib/devices/pool.js +32 -2
  35. package/dist/lib/devices/stats-cache.d.ts +0 -6
  36. package/dist/lib/devices/stats-cache.js +2 -9
  37. package/dist/lib/doctor-diff.d.ts +14 -0
  38. package/dist/lib/doctor-diff.js +43 -2
  39. package/dist/lib/feed/events.js +4 -0
  40. package/dist/lib/git.d.ts +38 -0
  41. package/dist/lib/git.js +58 -0
  42. package/dist/lib/hosts/ready.d.ts +8 -0
  43. package/dist/lib/hosts/ready.js +13 -2
  44. package/dist/lib/installations/versions.d.ts +17 -0
  45. package/dist/lib/installations/versions.js +53 -2
  46. package/dist/lib/monitors/config.d.ts +71 -3
  47. package/dist/lib/monitors/config.js +100 -12
  48. package/dist/lib/monitors/pid-watch.d.ts +35 -0
  49. package/dist/lib/monitors/pid-watch.js +45 -0
  50. package/dist/lib/monitors/remote.d.ts +18 -0
  51. package/dist/lib/monitors/remote.js +11 -0
  52. package/dist/lib/perf/db.d.ts +1 -1
  53. package/dist/lib/perf/db.js +53 -2
  54. package/dist/lib/perf/types.d.ts +14 -0
  55. package/dist/lib/permissions.js +7 -2
  56. package/dist/lib/plugins/plugins.d.ts +17 -3
  57. package/dist/lib/plugins/plugins.js +84 -9
  58. package/dist/lib/pty-server.d.ts +14 -0
  59. package/dist/lib/pty-server.js +49 -5
  60. package/dist/lib/secrets/drivers/rush.js +5 -0
  61. package/dist/lib/self-update.d.ts +42 -0
  62. package/dist/lib/self-update.js +88 -0
  63. package/dist/lib/session/cloud.js +5 -0
  64. package/dist/lib/session/db.d.ts +32 -6
  65. package/dist/lib/session/db.js +128 -12
  66. package/dist/lib/session/live-metadata.js +3 -3
  67. package/dist/lib/smart-launch.d.ts +6 -0
  68. package/dist/lib/smart-launch.js +5 -2
  69. package/dist/lib/staleness/writers/plugins.js +5 -2
  70. package/dist/lib/staleness/writers/subagents.js +13 -3
  71. package/dist/lib/state.d.ts +7 -4
  72. package/dist/lib/state.js +7 -4
  73. package/dist/lib/subagents.js +8 -2
  74. package/dist/lib/teams/api.d.ts +8 -0
  75. package/dist/lib/teams/api.js +50 -6
  76. package/dist/lib/teams/delivery.d.ts +14 -4
  77. package/dist/lib/teams/delivery.js +15 -5
  78. package/dist/lib/teams/scheduler.d.ts +10 -0
  79. package/dist/lib/teams/scheduler.js +8 -0
  80. package/dist/lib/traces/sync.d.ts +113 -6
  81. package/dist/lib/traces/sync.js +193 -19
  82. package/dist/lib/view-types.d.ts +12 -0
  83. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,7 +1,267 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.22.60
4
+
5
+ - **Detect stranded uncommitted work in `agents teams status` (PHNX-2951).** A teammate that exits `COMPLETED` with no PR and uncommitted changes in its local worktree now reports `STRANDED` instead of `done`, and the status line names the worktree path so the work can be rescued before cleanup. The stranded count is computed over the full team even when `--filter` narrows the displayed agents, so `agents teams status <team> --filter running` still surfaces stranded completed teammates. Team rollups and `teams list --status` classify these teams as `stranded`, not `done`. Source: `cli/src/lib/teams/delivery.ts`, `cli/src/lib/teams/api.ts`, `cli/src/commands/teams.ts`.
6
+
7
+ - **Track agent-launch boot cost: persist + surface the `startup` phase (PHNX-3468).** `agent.run` already timed a `startup` phase (entering `spawnAgent` → child spawn) but only the total run duration reached the perf warehouse, so boot cost was untrackable across the fleet. The timer now persists its sub-phase marks into each `perf.timing` sample's `meta_json`, `aggregateSamples` folds them into a per-label `phases` break-out (p50/p90 over the samples that carried each phase), and `agents insights perf run` renders a `└ startup: p50 … p90 … (n=…)` sub-line under `agent.run`. This is the measurement the boot-perf work is graded against. Source: `cli/src/lib/feed/events.ts`, `cli/src/lib/perf/db.ts`, `cli/src/lib/perf/types.ts`, `cli/src/commands/perf.ts`.
8
+
9
+ - **`agents sessions preview <id>` now pulls the transcript digest from the
10
+ owning device for host-dispatched sessions (PHNX-3481).** A host dispatch
11
+ leaves a synthetic local index row with `machine = <peer>` and an empty
12
+ `filePath`, but that row does not carry the live fan-out's `_remote` marker.
13
+ Full-UUID preview resolved the local row and then treated it as a local live
14
+ session, rendering "full transcript not indexed here" without making any SSH
15
+ hop. Transcript reads now follow one file-aware predicate: an explicit remote
16
+ row still reads on its peer; a row naming another machine also reads there
17
+ when no transcript exists on this disk; and a synced mirror with a real local
18
+ file still renders locally. The direct preview, `sessions <id>`, picker view,
19
+ and picker/browser digest pane all share that rule. Live-registry metadata now
20
+ also stamps `_remote` when its execution machine differs from this box, and an
21
+ unreachable owner remains a loud error instead of a local placeholder.
22
+ Source: `cli/src/commands/sessions-picker.ts`, `cli/src/commands/sessions.ts`,
23
+ `cli/src/lib/session/live-metadata.ts`.
24
+
25
+ ## 1.22.59
26
+
27
+ - **`agents devices disable/prefer` now actually change `--device auto` placement (PHNX-2092).**
28
+ The per-device `auto-launch.enabled` / `auto-launch.preferred` flags (written by
29
+ `agents devices disable/enable` and `prefer/unprefer`, and by `agents devices config
30
+ <name> auto-launch.*`) were stored and synced but never consulted by the CLI's one
31
+ placement path, so a disabled box was still picked and a preferred box got no boost.
32
+ They now feed the single automatic-placement rule: `filterAutoPool` drops any device
33
+ with `auto-launch.enabled` = false from EVERY auto path (`run`, `teams`, `ssh auto`,
34
+ the AGI EXT launch commands, which resolve placement through the CLI) exactly as a
35
+ `personal`/`desktop` role does, and `pickBestDevice` ranks an `auto-launch.preferred`
36
+ device ahead of its load-equal peers — after the signed-in tier, before load, so an
37
+ operator boost overrides load-based ordering without overriding hard health. A
38
+ fleet-wide default (`--fleet`) reaches doc-less devices via the candidate roster. No
39
+ second placement path was added — the existing `filterAutoPool`/`pickBestDevice`
40
+ rule was extended. Source: `cli/src/lib/devices/pool.ts`,
41
+ `cli/src/lib/teams/scheduler.ts`, `cli/src/lib/smart-launch.ts`.
42
+
43
+ - **`agents sessions stats` coverage now reports scan coverage, not with-usage,
44
+ so a completed backfill clears the "run the backfill" hint (PHNX-2301).** The
45
+ coverage line read `sessionsWithUsage / sessionsIndexed` — a ratio that stays
46
+ near-zero (~1.2% on a real fleet) even after `agents sessions backfill
47
+ resources` has fully run, because most sessions genuinely invoke no
48
+ skill/slash-command and a non-recording harness contributes none by
49
+ construction. The nag therefore never cleared and the low number read as a
50
+ coverage bug when it was not. `resourceUsageCoverage()` now also returns
51
+ `scanned` — the count of sessions carrying a `resource_scan_ledger` row at the
52
+ current `RESOURCE_INDEX_VERSION`, the honest "has the backfill folded in
53
+ history?" signal (the ledger is stamped for EVERY scanned session, including
54
+ ones that invoked nothing). The human line shows both — `N/T sessions scanned ·
55
+ M carry an explicit invocation` — and the backfill hint keys on
56
+ `scanned/total`, so it appears only when history really is un-scanned. The
57
+ `--json` `coverage` object gains `sessionsScanned` alongside the unchanged
58
+ `sessionsWithUsage`/`sessionsIndexed` (SES-IF-4b envelope preserved;
59
+ `schemaVersion` bumped to 2), and `signal.recording` now names the recorded set
60
+ (`skills: claude+kimi`, `commands: claude`) so a machine consumer can tell a
61
+ zero from a non-recording harness apart from genuine non-use. The zero-invoked
62
+ heading reads "never explicitly invoked" to match. Recording coverage itself is
63
+ unchanged — extending the `Skill`-tool set to another harness needs a verified
64
+ transcript tool-name, not a blind capability-table edit. Source:
65
+ `cli/src/lib/session/db.ts` (`resourceUsageCoverage`),
66
+ `cli/src/commands/sessions-stats.ts`.
67
+
68
+ - **Menu bar: favorite (pin) devices below the current Mac (PHNX-2376).** Each
69
+ device's submenu in the menu bar's DEVICES section now has a **★ Favorite /
70
+ Unfavorite** toggle. A favorited device renders with a ★ and sorts immediately
71
+ below the current machine (`<name> (this Mac)`), above all other devices, with a
72
+ divider between the favorites and the rest. "Favorite" reuses the EXISTING
73
+ per-device `auto-launch.preferred` config that automatic placement already
74
+ honors (PHNX-2092) — the toggle writes `agents devices config <name>
75
+ auto-launch.preferred on|off`, not a new store — so a box favorited from the
76
+ menu bar is also boosted in `--device auto` placement, and vice-versa. Source:
77
+ `cli/menubar/Sources/MenubarHelper/StatusItemController.swift`,
78
+ `cli/menubar/Sources/MenubarHelper/AgentsCLI.swift`.
79
+
80
+ - **Built-in monitors ship visible + enabled, `monitors list` is fleet-aware, and built-ins are tagged `(built-in)` (PHNX-2506).** A monitor shipped in the system mirror (`~/.agents/.system/monitors/`) used to be the lone system-layer resource that shipped **disabled and untagged** — `readMonitorFile` special-cased system scope to `enabled: false`, so a shipped built-in read `off` on every install and nobody could tell it came from the system layer. It now defaults to enabled like every other system resource (rules, hooks, commands, skills), on for every install unless the user shadows it with `enabled: false` (via `agents monitors pause`, which materializes a user copy — the system mirror stays pull-only; there is deliberately no `enable`/`disable` verb). Each monitor now carries its read-time `scope` (`user`/`system`), so `agents monitors list` tags a built-in `(built-in)` and `--json` emits `scope` + `builtin`, mirroring routines. And `agents monitors list` now **fans out across the fleet** (the same `gatherRemoteAgentsJson` sweep `sessions --active` and the add-time duplicate guard use), so every monitor on every device is visible with the box it lives on — the original "monitors shouldn't be device-local" complaint was a **visibility** gap, not a git-sync one (monitors are per-work-item watchers and are deliberately NOT synced through the DotAgents repo). `--local` pins the listing to this device; a peer answering the fan-out reports itself only, so the duplicate guard's bare-array contract is unchanged. **Enabled-by-default is not, on its own, permission to fire on every daemon (SING-9a).** A shared-input built-in — one whose source polls a fleet-shared queue such as `gh pr list --author @me`, `pr-merge-on-green` being the canonical case — is placed on a single owner **in code**, not just by a `device:` pin in the shipped YAML: an unpinned `scope: system` monitor is treated as shared-input unless it sets `sharedInput: false`, and fires only on the resolved owner (`interactive.host`, else the sole box on a single-device fleet, else nowhere) via `requiresSingleOwner` / `monitorRunsOnThisDevice`. So even a built-in whose YAML forgot the pin can never fan out across the fleet and race to merge the same PR. A device-local built-in (input = the firing box's own state) opts back into fleet-wide firing with `sharedInput: false`; a user monitor keeps its fleet-wide default and opts into owner-only with `sharedInput: true`. Source: `cli/src/lib/monitors/config.ts`, `cli/src/lib/monitors/remote.ts`, `cli/src/commands/monitors.ts`, `cli/src/lib/state.ts`.
81
+
82
+ - **A version-home plugin left behind by a marketplace move is now reconciled
83
+ away instead of shadowing the current copy (PHNX-2618).** Plugin orphan
84
+ detection keyed on plugin **name** alone, but a version home installs plugins
85
+ per **marketplace** (`marketplaces/<name>/plugins/<plugin>`). When a plugin
86
+ moved marketplaces — e.g. `code` once shipped from the user repo (the
87
+ `agents-cli` marketplace) and later from the system repo (`agents-system`) —
88
+ the stale `agents-cli` copy was never cleaned, because the name `code` was
89
+ still active via `agents-system`. Fleet boxes then carried **two** `code`
90
+ plugins: the current one, plus a shadow serving skills the repo deleted on
91
+ purpose (`code:quality` / `code:ship` / `code:verify`), and resolution order
92
+ decided which `code:review` an agent got. `cleanOrphanedPluginSkills` /
93
+ `diffVersionPlugins` now key on the `(marketplace, name)` pair: an installed
94
+ copy whose marketplace source repo is present but no longer ships that plugin
95
+ is trashed (soft-deleted to `~/.agents/.trash/plugins/`), even when another
96
+ marketplace still ships the same name — so a plain `agents sync` reconciles the
97
+ shadow away. When a marketplace's source repo is **absent** (a project we're
98
+ not in, a removed extra repo) the original name-only test is kept, so an
99
+ unrelated sync from another cwd never trashes a plugin whose source simply is
100
+ not reachable right now. Source: `cli/src/lib/plugins/plugins.ts`
101
+ (`cleanOrphanedPluginSkills`, `diffVersionPlugins`, `isOrphanMarketplacePlugin`),
102
+ `cli/src/lib/staleness/writers/plugins.ts`,
103
+ `cli/src/lib/installations/versions.ts`.
104
+
105
+ - **`agents sessions --device all/fleet --json` now actually searches the whole fleet (PHNX-2673).** The documented "search the whole fleet" sentinel was filtered to an empty host set — correct for `--active` and the interactive listing, which fan out by default — but the HISTORICAL `--json` listing does not fan out by default (it stays a deterministic local slice for scripts), so the sentinel was silently dropped and a fleet-wide historical query returned local rows only. Naming devices explicitly (`--device box-a --device box-b`) reached the fleet; `--device all` did not. The `--json` listing path now remembers the sentinel and runs the SAME peer SSH sweep the interactive listing uses (`gatherRemoteList` over the registered online devices, whole-index per peer), merging peer rows in machine-first. A bare `--json` (no `--device`) stays local-only, `--local` still pins to this machine, and dead peers are skipped, never fatal. Source: `cli/src/commands/sessions.ts`.
106
+
107
+ - **`agents pty` now works on macOS/arm64 and Node 25/26 (PHNX-2740).** The pinned
108
+ `@homebridge/node-pty-prebuilt-multiarch@0.13.1` shipped no darwin-arm64 prebuild
109
+ above Node 24 (ABI 137), so on a Mac running Node 25 (ABI 141) or 26 (ABI 147) the
110
+ native binding never resolved and every pty command died with a bare
111
+ `Cannot find module '.../pty.node'` MODULE_NOT_FOUND. Bumped to `0.14.1`, which
112
+ publishes darwin-arm64 (and darwin-x64) prebuilds through ABI 147, covering current
113
+ Node. The PTY sidecar now also **fails loud** when the binding genuinely can't load:
114
+ instead of a raw MODULE_NOT_FOUND it prints the platform, the running Node ABI, and a
115
+ concrete remediation (`npm rebuild @homebridge/node-pty-prebuilt-multiarch` / reinstall
116
+ the CLI), preserving the underlying error. Source: `cli/package.json`,
117
+ `cli/src/lib/pty-server.ts`.
118
+
119
+ - **An upgrade can no longer strand a box with the package installed but no working `agents` command (PHNX-2768).** `agents upgrade` (and every `agents fleet update` box, which runs it) now **owns the global bin links**: after installing and verifying the new version, it checks that `<prefix>/bin/{agents,ag,browser,computer}` resolve to the freshly-installed copy and **restores any the package manager dropped**. This closes the failure that left zion upgraded to 1.22.40 with `/opt/homebrew/bin/{agents,ag,browser,computer}` gone — every `agents` invocation "command not found" until the links were relinked by hand. It covers the sibling entrypoints (`ag`/`browser`/`computer`), not just `agents`. A link it cannot make resolve fails the upgrade **loud** (non-zero exit), so a genuinely-broken box is reported `failed` by the rollout instead of a stranded `ok`/`unverified`. Scope: the npm-prefix POSIX layout — bun and Windows use their own bin shims. Source: `cli/src/lib/self-update.ts` (`ensureGlobalBinLinks`), `cli/src/bootstrap.ts`, `cli/src/commands/ssh.ts`.
120
+
121
+ - **The auto-pulled system repo is verified against its expected origin before a
122
+ fast-forward — a repointed origin is refused, not executed (PHNX-2957).** The
123
+ system repo (`~/.agents/.system/`) ships **hooks** that register as shell
124
+ `command` strings run on every tool event, and its checkout auto-fast-forwards
125
+ from `origin` (on `agents use`, and on the opt-in `AGENTS_AUTO_PULL=1`
126
+ background worker). Neither path verified that `origin` was the repo the
127
+ operator actually chose — so an `origin` repointed to an attacker's fork (or a
128
+ clone seeded from one) would silently fast-forward arbitrary hook code that then
129
+ ran as shell on the next command. Both pull sites now route through
130
+ `tryAutoPullSystemRepo`, which pulls only when `origin` is the canonical system
131
+ repo (`isSystemRepoRemote`) or the exact `AGENTS_SYSTEM_REPO` the operator
132
+ pointed at; an unexpected origin is **refused loud** (`agents use` prints the
133
+ offending remote and the re-point/`AGENTS_SYSTEM_REPO` fix), never pulled. The
134
+ canonical system repo and a legitimate `AGENTS_SYSTEM_REPO` override still sync
135
+ exactly as before — no regression to trusted pulls. Source:
136
+ `cli/src/lib/git.ts` (`isExpectedSystemRepoRemote`, `tryAutoPullSystemRepo`),
137
+ `cli/src/commands/versions.ts`, `cli/src/lib/auto-pull-worker.ts`.
138
+
139
+ - **`agents monitors add --watch-pid <pid>` (PHNX-3023).** A reliable, daemon-polled
140
+ watcher for a backgrounded process — the fix for "will re-invoke me" watchers that
141
+ never fire because a harness's exit hook only wakes the agent when the watched
142
+ process dies, and a watch loop (`gh pr checks --watch`, a long sleep, a tick poll)
143
+ never itself exits. `--watch-pid` has the monitor engine's own poll loop check the
144
+ pid's liveness independent of the arming session, defaults its condition to fire on
145
+ exit, and fails loud at creation time when the pid is already dead instead of
146
+ silently arming a watcher that can never fire. Source: `cli/src/lib/monitors/pid-watch.ts`,
147
+ `cli/src/commands/monitors.ts`.
148
+
149
+ - **`agents doctor --fix` now reconciles hooks / permissions / subagents / rule
150
+ aliases on Windows instead of an unactionable "hold" (PHNX-3187).** On win-mini
151
+ `--fix` healed commands/skills/rules/plugins but reported hooks, permissions,
152
+ subagents, and the `CLAUDE`/`GEMINI` rule aliases as "couldn't reconcile" and
153
+ left the box permanently red — with real guards (`git-guard`, `rm-guard`,
154
+ `secrets-guard`, `ask-user-question-guard`, `public-artifact-guard`)
155
+ uninstalled. Four Windows-specific defects, each fixed at its source: (1)
156
+ **subagents** — `parseSubagentFrontmatter`/`getSubagentBody` split on `'\n'`
157
+ and compared the fence with `=== '---'`, so a git-CRLF-checked-out `AGENT.md`
158
+ (`'---\r'`) was rejected and the subagent silently dropped from discovery, so
159
+ it could never be installed; now CRLF-robust (`/\r?\n/`). (2) **permissions** —
160
+ `buildPermissionsFromGroups` extracted rules with a line regex anchored on the
161
+ closing quote (`"$`), which a trailing `\r` broke, extracting zero rules and
162
+ writing an empty permission set; now CRLF-robust. (3) **rule aliases** — git
163
+ checks the `rules/CLAUDE.md` and `rules/GEMINI.md` symlinks out as plain text
164
+ files on Windows, so `lstat().isSymbolicLink()` was false and the diff treated
165
+ them as independent rule sources no sync could ever produce; a new
166
+ `isCheckedOutSymlink` detector skips them on every platform. (4) **hooks** —
167
+ the heal pass fed the resource diff's extensionless hook names
168
+ (`git-guard`) back to the sync writer, whose `available.hooks` set carries the
169
+ source filename **with** its extension (`git-guard.sh`), so the exact-set match
170
+ found nothing and no flagged hook could be written; a new basename-tolerant
171
+ `resolveHookSelection` maps them, and the orphan sweep still prunes any stale
172
+ extensionless hook copy left in a version home (one would be invisible on
173
+ Windows, lacking both an extension and an exec bit). The subagents writer also now
174
+ surfaces a per-item write failure with its reason instead of swallowing it in a
175
+ bare `catch`, so a genuine failure fails loud rather than reading as a silent
176
+ "hold". Source: `cli/src/lib/subagents.ts`, `cli/src/lib/permissions.ts`,
177
+ `cli/src/lib/doctor-diff.ts`, `cli/src/lib/installations/versions.ts`,
178
+ `cli/src/lib/staleness/writers/subagents.ts`.
179
+
180
+ - Fix the `agents-cli` discovery skill/plugin install commands to use the real GitHub
181
+ repo path `phnx-labs/agi-cli` (they pointed at `phnx-labs/agents-cli`, which only
182
+ resolved via GitHub's rename redirect). The npm package stays `@phnx-labs/agents-cli`.
183
+ Also maps the plugin manifests + skill to `agents-cli-plugin.test.ts` in CI impact
184
+ analysis so a manifest/skill edit runs its test on the PR. (PHNX-3337 review follow-up)
185
+
186
+ - **Ship a cross-harness `agents-cli` discovery skill + Claude plugin marketplace (PHNX-3337).**
187
+ New `skills/agents-cli/SKILL.md` is an authoritative skill whose `description`
188
+ carries the exact intents a developer types — *run multiple coding agents in
189
+ parallel*, *manage multiple Claude Code accounts*, *I hit my usage limit*,
190
+ *resume a session on another machine*, *pin the agent CLI version* — each with a
191
+ verified `agents` command recipe (`teams`, `accounts`, `run --fallback`/`-b`/`auto`,
192
+ `sessions resume`, `add`/`use`). A repo-root `.claude-plugin/marketplace.json` +
193
+ `.claude-plugin/plugin.json` make the repo installable via
194
+ `claude plugin marketplace add phnx-labs/agents-cli` and `npx skills add
195
+ phnx-labs/agents-cli`; the plugin's `source: "./"` bundles the discovery skill
196
+ plus the existing per-command skills. Harness parity is registry-driven, not
197
+ per-harness copies: the one SKILL.md is authored once and the existing
198
+ capability-gated skill sync (`supports(agent, 'skills', …)`) fans it into every
199
+ skill-capable harness home. `claude plugin validate .` passes on the committed
200
+ manifest, and a real (no-mock) test reads the repo-root files and runs the CLI's
201
+ own `validateClaudePluginManifest`. Source:
202
+ `skills/agents-cli/SKILL.md`, `.claude-plugin/marketplace.json`,
203
+ `.claude-plugin/plugin.json`, `cli/src/lib/plugins/agents-cli-plugin.test.ts`,
204
+ `README.md`.
205
+
206
+ - **`agents cloud providers` no longer reports Rush as `ready` when the session token is expired (PHNX-3382).** `capabilities().available` checked only that `~/.rush/user.yaml` existed — it returned `true` even for an expired session, so the provider appeared ready but every dispatch failed with a cryptic HTTP 401. `readToken()` also silently returned an expired token, giving the same bad error on every cloud call (`dispatch`, `status`, `list`, `stream`, `cancel`, `message`). Both paths now check `expires_at` (Unix seconds): `capabilities()` returns `available: false` for a missing file, missing token, or expired session; `readToken()` throws `Rush session expired at <ISO>. Run 'rush login' to refresh.` — an actionable message instead of a 401. The exported `isRushSessionValid(yamlPath?)` helper is testable in isolation. Source: `cli/src/lib/cloud/rush.ts`.
207
+
208
+ - **Traces: session duration is populated for every harness, and the console duration median is active-time, not calendar span (PHNX-3457).** Only claude/codex/droid/gemini/opencode ever derived a `duration_ms` at scan; rush/grok/kimi/cursor/muse/antigravity left it NULL — 52% of sessions, 100% of the dominant `rush` usage — so the Evals console median was computed over only the ~48% that carried it and skewed misleadingly short. `resolveDurationMs` now fills the span at the single upsert boundary from the timestamps the row already stores (a v43→v44 migration backfills existing rows in place, no re-parse), so every harness gets a span. The `agents traces sync` index shard's `medianMs`/`p90Ms` now run over **active time** — span minus every idle gap > 120s (before the first tool call, between calls measured from each call's end, and after the last call to the session end), derived from the ordered `tool_calls` already loaded — so a session resumed after hours or left idle mid-turn no longer inflates them (it killed a 345h calendar-span outlier). The stat keys are unchanged, so the fleet-aggregate worker keeps averaging them; the raw span stays available per session as `SessionDetail.meta.spanMs`, which also gains `activeMs`. Source: `cli/src/lib/session/db.ts`, `cli/src/lib/traces/sync.ts`.
209
+ - **Traces: treemap tiles are drillable — each topic bucket carries example session refs (PHNX-3408).** The `agents traces sync` index shard now emits up to 30 most-recent `sessions` (`{id,title}`) per topic bucket, so the Evals console can drill from a treemap tile into that category's session list instead of rendering every tile display-only. The tile's `count` stays the true total. Source: `cli/src/lib/traces/sync.ts`.
210
+
211
+ - **`agents run --device auto` no longer lands a launch on a fleet box that is logged out for the target harness (PHNX-3466).**
212
+ The `--device auto` placement gate already excluded a device whose harness reads
213
+ signed-out — but it judged a REMOTE candidate by `agents view --json`'s display
214
+ `signedIn`, which is true whenever the box's active/global HOME carries a login even
215
+ if the per-version home the isolated run actually launches has no credential of its
216
+ own. The LOCAL candidate, by contrast, used the strict per-version launch truth
217
+ (`collectRunCandidates` → `isLaunchableSignedIn`). So a worker whose selected harness
218
+ version home was not launchable passed the remote gate, got picked, and the launch
219
+ died at spawn — from AGI EXT the dispatched tab exited 1 and vanished. `agents view
220
+ --json` now emits a per-version `launchable` field (the same `isLaunchableSignedIn`
221
+ signal the local path uses), and remote placement (`viewAgentAccountEligibility`)
222
+ gates on it, so both paths agree. A device with no launchable account for the harness
223
+ is excluded from the `--device auto` candidate set; when that empties the pool the
224
+ existing fail-loud `no healthy device` error fires instead of a silently-lost launch.
225
+ An older remote CLI that omits `launchable` falls back to `signedIn`, so a rolling
226
+ fleet does not regress. No second placement path was added — the single CLI gate is
227
+ hardened, so both the CLI and AGI EXT (which delegates placement to it) benefit.
228
+ Source: `cli/src/commands/view.ts`, `cli/src/lib/view-types.ts`,
229
+ `cli/src/lib/hosts/ready.ts`.
230
+
231
+ - **`agents traces sync` now emits SEGMENTED active-time medians so the console can
232
+ headline agent runs, not the corpus blend (PHNX-3472).** 63% of sessions are
233
+ one-shot queries (≤2 messages, ~15s active), so the blended `medianMs`/`p90Ms` sat
234
+ at ~15s while substantial agent runs have a ~15-minute median — the blend headlined
235
+ neither. The index-shard `stats` now classify each session as an AGENT run (any tool
236
+ call OR more than 8 messages) or INTERACTIVE, and carry `agentMedianMs`/`agentP90Ms`
237
+ (active-time median/p90 over agent sessions), `interactiveMedianMs`, and
238
+ `measuredFraction` (share of sessions with a non-null duration) alongside the
239
+ unchanged blended `medianMs`/`p90Ms`. Reuses the PHNX-3457 active-time computation
240
+ (span minus idle gaps > 120s); no calendar span reintroduced. Source:
241
+ `cli/src/lib/traces/sync.ts`.
242
+
243
+ - **`agents traces sync` classifies session kind and EXCLUDES internal utility calls
244
+ from the Evals corpus (PHNX-3474).** ~68% of the raw corpus is machine plumbing —
245
+ single-shot calls (no tool call AND ≤2 messages) plus known internal-prompt
246
+ signatures (title generation, watchdog ticks, commit-message writes, factory
247
+ workers), all spawned under the `claude` harness by the Rush app — and it poisoned
248
+ every console statistic (count, median, need-attention, tool-error-rate). Each
249
+ session now carries a `kind` (`utility` vs `agent`, `classifySessionKind` in
250
+ `traces/sync.ts`) and every index statistic is computed over the AGENT set ONLY:
251
+ `sessionsImported` is the real agent count (not the raw row count), and the
252
+ medians / `needAttention` / `toolErrorRate` / topic-bucket counts all exclude
253
+ utility. A new top-level `utilityCount` reports how many were dropped. Each session
254
+ ref (topic-tile `sessions[]` and `needsAttention`) also emits `kind` and `harness`
255
+ so the console can filter by both. Pure reclassification — utility rows are tagged
256
+ and excluded at shard-build time, never deleted from `sessions.db`. Source:
257
+ `cli/src/lib/traces/sync.ts`.
258
+ </content>
259
+ </invoke>
260
+
3
261
  ## 1.22.58
4
262
 
263
+ - **`agents notify` is deprecated in favor of `agents feed post` (PHNX-3323).** The command still works for existing callers, but it now prints a stderr deprecation notice naming `agents feed post` as the replacement, and `agents notify --help` carries a `[DEPRECATED]` label and examples that use `agents feed post`. Source: `cli/src/commands/send.ts`.
264
+
5
265
  - **A remote browser task survives a browser-daemon restart on the driving box (PHNX-2663).** When the local daemon restarted, its in-memory task map was rebuilt from disk for local-CDP profiles but **skipped `ssh://` tunnelled tasks** — so an agent driving a browser host over `--device` got "Unknown browser task" / "Tab not found" for a tab that was still alive on the far side, and gave up. `attachRunningProfile` now re-establishes the SSH tunnel and reconnects CDP for a tunnelled task during rehydrate (reusing `connectSSH`, which attaches to the already-running remote browser via `isOwnTunnel` — it never launches one), carrying the tunnel teardown so it can't leak across the next restart. A failed reconnect falls through to disk reconcile exactly like the local-CDP path. Source: `cli/src/lib/browser/service.ts`.
6
266
 
7
267
  - **`agents prune cleanup` no longer over-reports source-present hooks as orphans (PHNX-2693).** Orphan detection diffed the hook files in a version home against the *registered-hook manifest*, but sync copies helper / test / benchmark scripts into every version home alongside registered hooks without registering them — so each one (e.g. `permission-handler`, `verify-work-state`, `*_test`, `benchmark_*`) read as an orphan, and `agents prune cleanup [hooks | --all]` offered to trash ~1000 in-use files across version homes. It now diffs against the resolved **source** set (user + system + enabled extras hook dirs), so a hook present in any configured source is never an orphan, exactly as the command's own help promised — while a genuinely dead file (present in a home, absent from every source) is still flagged. Same detector backs `agents doctor`'s `orphan` warning. Source: `cli/src/lib/hooks/install.ts`.
package/README.md CHANGED
@@ -59,6 +59,20 @@ Everything here — and every other command in this README — is free and needs
59
59
  The command surface teaches setup through `agents setup` and group-level `--help`.
60
60
  The durable system model starts at [`cli/docs/README.md`](cli/docs/README.md).
61
61
 
62
+ **What `agents setup` installs, and what auto-updates.** Setup clones a small public
63
+ **system repo** (`phnx-labs/.agents-system`) into `~/.agents/.system/`. It ships the
64
+ default resources — including **hooks**, which run as shell commands on tool events —
65
+ and its checkout **fast-forwards from its origin** when you run `agents use` (and, only
66
+ if you opt in with `AGENTS_AUTO_PULL=1`, in the background). Two safeguards bound that:
67
+ the pull is `merge --ff-only` (it can never rewrite your local history), and it is
68
+ **verified against the expected origin** — a checkout whose `origin` is not the canonical
69
+ system repo (or the exact repo you named in `AGENTS_SYSTEM_REPO`) is refused, never
70
+ pulled, so a repointed remote cannot slip hook code onto your machine. To pin or opt out:
71
+ set `AGENTS_SYSTEM_REPO=gh:you/your-fork` to track your own audited copy, run
72
+ `agents setup --no-system-repo` to skip the clone entirely, or check out a specific tag
73
+ in `~/.agents/.system/` (a fast-forward only advances a moving branch, so a detached tag
74
+ stays put).
75
+
62
76
  **Learn (concepts):** [Loop + graph engineering](https://agi-cli.sh/learn/loop-and-graph-engineering) · [Teams as graph engineering](https://agi-cli.sh/learn/teams-graph-engineering) · [Sessions · index + cross-device](https://agi-cli.sh/learn/sessions-index) · [Distributed fleet execution](https://agi-cli.sh/learn/distributed-fleet). Also: [harness engineering](https://agi-cli.sh/learn/harness-engineering) · [visual longform](https://share.agents-cli.sh/muqsitnawaz/agents-loop-and-graph-engineering).
63
77
 
64
78
  Already installed? `agents upgrade` updates agi-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 -- do not reach for `agents update`, which updates an installed **agent harness**, not agi-cli (and on macOS, `agents helper update` is a third thing: it reinstalls the keychain helper).
@@ -898,6 +912,21 @@ Skills, commands, and subagents are declarative and never trip the gate. The gat
898
912
 
899
913
  Plugins live in the user repo (`~/.agents/plugins/`), not inside any single version home. Switching Claude via `agents use claude@<v>` re-syncs the plugin into the new version automatically — no re-install. New Claude versions added later pick it up on their first sync. Project-level `<repo>/.agents/plugins/<name>/` overrides a same-named user plugin (resolution is project > user > system, same as every other resource).
900
914
 
915
+ ### Install the agents-cli skill in any agent
916
+
917
+ This repo is itself a Claude plugin marketplace and a [skills.sh](https://skills.sh) source. The `agents-cli` skill teaches any coding agent (Claude Code, Codex, Cursor, …) how to drive the `agents` CLI — so when you ask *"how do I run multiple coding agents in parallel?"* the agent surfaces `agents teams` instead of guessing.
918
+
919
+ ```bash
920
+ # Claude Code — add this repo as a marketplace, then install the plugin
921
+ claude plugin marketplace add phnx-labs/agi-cli
922
+ claude plugin install agents-cli@agents-cli
923
+
924
+ # skills.sh — install the skill directly from the repo
925
+ npx skills add phnx-labs/agi-cli
926
+ ```
927
+
928
+ The manifest is `.claude-plugin/marketplace.json` (validate with `claude plugin validate .`); the skill source is [`skills/agents-cli/SKILL.md`](skills/agents-cli/SKILL.md). Its `description` carries the exact intents the runtime matches against — *run multiple coding agents in parallel*, *manage multiple Claude Code accounts*, *I hit my usage limit*, *resume a session on another machine*, *pin the agent CLI version* — each with a verified command recipe.
929
+
901
930
  ---
902
931
 
903
932
  ## Make it yours
package/dist/bootstrap.js CHANGED
@@ -29,7 +29,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
29
29
  const packageJsonPath = path.join(__dirname, '..', 'package.json');
30
30
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
31
31
  const VERSION = packageJson.version;
32
- import { NPM_PACKAGE_NAME, deriveGlobalPrefix, detectPackageManager, installPackageIntoPrefix, installPackageWithBun, verifyInstalledVersion, refreshAliasShims, downloadVerifiedTarball, sweepStaleInstallStaging, } from './lib/self-update.js';
32
+ import { NPM_PACKAGE_NAME, deriveGlobalPrefix, detectPackageManager, ensureGlobalBinLinks, installPackageIntoPrefix, installPackageWithBun, verifyInstalledVersion, refreshAliasShims, downloadVerifiedTarball, sweepStaleInstallStaging, } from './lib/self-update.js';
33
33
  import { registerUpgradeCommand } from './commands/upgrade.js';
34
34
  // Detect dev/working-tree builds and default the noisy startup steps off.
35
35
  // Three cases trip this:
@@ -350,6 +350,32 @@ async function installResolvedPackage(metadata) {
350
350
  }
351
351
  verifyInstalledVersion(packageRoot, metadata.version);
352
352
  refreshAliasShims(packageRoot);
353
+ // PHNX-2768: the npm install above can leave the package at the new version
354
+ // but the global bin links GONE — the state that stranded zion (package at
355
+ // 1.22.40, `/opt/homebrew/bin/{agents,ag,browser,computer}` missing, every
356
+ // `agents` invocation "command not found"). The upgrade OWNS those links, so
357
+ // it restores any that npm dropped and fails LOUD when one cannot be made to
358
+ // resolve — never returning a box the package upgraded but cannot run. Only
359
+ // the npm-prefix POSIX layout has these symlinks; bun and Windows use their
360
+ // own bin shims and are out of scope.
361
+ if (detectPackageManager(packageRoot) !== 'bun' && process.platform !== 'win32') {
362
+ const prefix = deriveGlobalPrefix(packageRoot);
363
+ const repairs = ensureGlobalBinLinks(packageRoot, prefix);
364
+ const repaired = repairs.filter((r) => r.action === 'repaired');
365
+ const failed = repairs.filter((r) => r.action === 'failed');
366
+ if (repaired.length > 0) {
367
+ console.error(chalk.yellow(`Relinked ${repaired.map((r) => r.name).join(', ')} in ${path.join(prefix, 'bin')} — the install left them missing.`));
368
+ }
369
+ if (failed.length > 0) {
370
+ const relink = failed
371
+ .map((r) => `ln -sf ${path.relative(path.dirname(r.linkPath), r.target)} ${r.linkPath}`)
372
+ .join(' && ');
373
+ throw new Error(`upgraded to ${metadata.version} but could not restore the ` +
374
+ `${failed.map((r) => r.name).join(', ')} command link${failed.length === 1 ? '' : 's'} in ` +
375
+ `${path.join(prefix, 'bin')} (${failed.map((r) => r.error).join('; ')}). ` +
376
+ `The box has the new package but no working \`agents\` — relink manually: ${relink}`);
377
+ }
378
+ }
353
379
  // The npm install above runs with --ignore-scripts, so the postinstall that
354
380
  // installs the macOS Keychain helper never fires on upgrade. Force-refresh the
355
381
  // helper here so a user upgrading FROM a broken build (e.g. the entitlement-less
@@ -727,6 +753,11 @@ async function runUpgrade(version, options) {
727
753
  return;
728
754
  spinner.fail(`Upgrade failed: ${err instanceof Error ? err.message : String(err)}`);
729
755
  console.log(chalk.gray(`Run manually: agents upgrade ${version ? version + ' ' : ''}--yes`));
756
+ // A failed upgrade MUST exit non-zero (PHNX-2768). The fleet rollout
757
+ // keys a box `ok` on `agents upgrade` exiting 0 alone; exiting 0 on
758
+ // failure is what let a stranded box (package upgraded, bin links gone)
759
+ // be reported merely `unverified` instead of `failed`.
760
+ process.exitCode = 1;
730
761
  }
731
762
  }
732
763
  function registerUpgradeRuntimeCommand(p) {