@phnx-labs/agents-cli 1.22.58 → 1.22.59

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 (65) hide show
  1. package/CHANGELOG.md +238 -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/routines.test-fixture.js +5 -0
  6. package/dist/commands/send.d.ts +2 -1
  7. package/dist/commands/send.js +7 -5
  8. package/dist/commands/sessions-stats.js +37 -5
  9. package/dist/commands/sessions.js +39 -5
  10. package/dist/commands/ssh.js +12 -1
  11. package/dist/commands/versions.js +12 -4
  12. package/dist/commands/view.js +7 -2
  13. package/dist/lib/auto-pull-worker.js +7 -2
  14. package/dist/lib/cloud/rush.d.ts +7 -0
  15. package/dist/lib/cloud/rush.js +29 -1
  16. package/dist/lib/daemon/daemon.d.ts +22 -0
  17. package/dist/lib/daemon/daemon.js +39 -0
  18. package/dist/lib/daemon/session-index-service.js +9 -1
  19. package/dist/lib/daemon-ticks.d.ts +15 -0
  20. package/dist/lib/daemon-ticks.js +26 -0
  21. package/dist/lib/device-config.d.ts +5 -1
  22. package/dist/lib/device-config.js +2 -2
  23. package/dist/lib/devices/health.js +5 -1
  24. package/dist/lib/devices/pool.d.ts +25 -2
  25. package/dist/lib/devices/pool.js +32 -2
  26. package/dist/lib/devices/stats-cache.d.ts +0 -6
  27. package/dist/lib/devices/stats-cache.js +2 -9
  28. package/dist/lib/doctor-diff.d.ts +14 -0
  29. package/dist/lib/doctor-diff.js +43 -2
  30. package/dist/lib/git.d.ts +38 -0
  31. package/dist/lib/git.js +58 -0
  32. package/dist/lib/hosts/ready.d.ts +8 -0
  33. package/dist/lib/hosts/ready.js +13 -2
  34. package/dist/lib/installations/versions.d.ts +17 -0
  35. package/dist/lib/installations/versions.js +53 -2
  36. package/dist/lib/monitors/config.d.ts +71 -3
  37. package/dist/lib/monitors/config.js +100 -12
  38. package/dist/lib/monitors/pid-watch.d.ts +35 -0
  39. package/dist/lib/monitors/pid-watch.js +45 -0
  40. package/dist/lib/monitors/remote.d.ts +18 -0
  41. package/dist/lib/monitors/remote.js +11 -0
  42. package/dist/lib/permissions.js +7 -2
  43. package/dist/lib/plugins/plugins.d.ts +17 -3
  44. package/dist/lib/plugins/plugins.js +84 -9
  45. package/dist/lib/pty-server.d.ts +14 -0
  46. package/dist/lib/pty-server.js +49 -5
  47. package/dist/lib/secrets/drivers/rush.js +5 -0
  48. package/dist/lib/self-update.d.ts +42 -0
  49. package/dist/lib/self-update.js +88 -0
  50. package/dist/lib/session/cloud.js +5 -0
  51. package/dist/lib/session/db.d.ts +32 -6
  52. package/dist/lib/session/db.js +128 -12
  53. package/dist/lib/smart-launch.d.ts +6 -0
  54. package/dist/lib/smart-launch.js +5 -2
  55. package/dist/lib/staleness/writers/plugins.js +5 -2
  56. package/dist/lib/staleness/writers/subagents.js +13 -3
  57. package/dist/lib/state.d.ts +7 -4
  58. package/dist/lib/state.js +7 -4
  59. package/dist/lib/subagents.js +8 -2
  60. package/dist/lib/teams/scheduler.d.ts +10 -0
  61. package/dist/lib/teams/scheduler.js +8 -0
  62. package/dist/lib/traces/sync.d.ts +113 -6
  63. package/dist/lib/traces/sync.js +193 -19
  64. package/dist/lib/view-types.d.ts +12 -0
  65. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,7 +1,245 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.22.59
4
+
5
+ - **`agents devices disable/prefer` now actually change `--device auto` placement (PHNX-2092).**
6
+ The per-device `auto-launch.enabled` / `auto-launch.preferred` flags (written by
7
+ `agents devices disable/enable` and `prefer/unprefer`, and by `agents devices config
8
+ <name> auto-launch.*`) were stored and synced but never consulted by the CLI's one
9
+ placement path, so a disabled box was still picked and a preferred box got no boost.
10
+ They now feed the single automatic-placement rule: `filterAutoPool` drops any device
11
+ with `auto-launch.enabled` = false from EVERY auto path (`run`, `teams`, `ssh auto`,
12
+ the AGI EXT launch commands, which resolve placement through the CLI) exactly as a
13
+ `personal`/`desktop` role does, and `pickBestDevice` ranks an `auto-launch.preferred`
14
+ device ahead of its load-equal peers — after the signed-in tier, before load, so an
15
+ operator boost overrides load-based ordering without overriding hard health. A
16
+ fleet-wide default (`--fleet`) reaches doc-less devices via the candidate roster. No
17
+ second placement path was added — the existing `filterAutoPool`/`pickBestDevice`
18
+ rule was extended. Source: `cli/src/lib/devices/pool.ts`,
19
+ `cli/src/lib/teams/scheduler.ts`, `cli/src/lib/smart-launch.ts`.
20
+
21
+ - **`agents sessions stats` coverage now reports scan coverage, not with-usage,
22
+ so a completed backfill clears the "run the backfill" hint (PHNX-2301).** The
23
+ coverage line read `sessionsWithUsage / sessionsIndexed` — a ratio that stays
24
+ near-zero (~1.2% on a real fleet) even after `agents sessions backfill
25
+ resources` has fully run, because most sessions genuinely invoke no
26
+ skill/slash-command and a non-recording harness contributes none by
27
+ construction. The nag therefore never cleared and the low number read as a
28
+ coverage bug when it was not. `resourceUsageCoverage()` now also returns
29
+ `scanned` — the count of sessions carrying a `resource_scan_ledger` row at the
30
+ current `RESOURCE_INDEX_VERSION`, the honest "has the backfill folded in
31
+ history?" signal (the ledger is stamped for EVERY scanned session, including
32
+ ones that invoked nothing). The human line shows both — `N/T sessions scanned ·
33
+ M carry an explicit invocation` — and the backfill hint keys on
34
+ `scanned/total`, so it appears only when history really is un-scanned. The
35
+ `--json` `coverage` object gains `sessionsScanned` alongside the unchanged
36
+ `sessionsWithUsage`/`sessionsIndexed` (SES-IF-4b envelope preserved;
37
+ `schemaVersion` bumped to 2), and `signal.recording` now names the recorded set
38
+ (`skills: claude+kimi`, `commands: claude`) so a machine consumer can tell a
39
+ zero from a non-recording harness apart from genuine non-use. The zero-invoked
40
+ heading reads "never explicitly invoked" to match. Recording coverage itself is
41
+ unchanged — extending the `Skill`-tool set to another harness needs a verified
42
+ transcript tool-name, not a blind capability-table edit. Source:
43
+ `cli/src/lib/session/db.ts` (`resourceUsageCoverage`),
44
+ `cli/src/commands/sessions-stats.ts`.
45
+
46
+ - **Menu bar: favorite (pin) devices below the current Mac (PHNX-2376).** Each
47
+ device's submenu in the menu bar's DEVICES section now has a **★ Favorite /
48
+ Unfavorite** toggle. A favorited device renders with a ★ and sorts immediately
49
+ below the current machine (`<name> (this Mac)`), above all other devices, with a
50
+ divider between the favorites and the rest. "Favorite" reuses the EXISTING
51
+ per-device `auto-launch.preferred` config that automatic placement already
52
+ honors (PHNX-2092) — the toggle writes `agents devices config <name>
53
+ auto-launch.preferred on|off`, not a new store — so a box favorited from the
54
+ menu bar is also boosted in `--device auto` placement, and vice-versa. Source:
55
+ `cli/menubar/Sources/MenubarHelper/StatusItemController.swift`,
56
+ `cli/menubar/Sources/MenubarHelper/AgentsCLI.swift`.
57
+
58
+ - **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`.
59
+
60
+ - **A version-home plugin left behind by a marketplace move is now reconciled
61
+ away instead of shadowing the current copy (PHNX-2618).** Plugin orphan
62
+ detection keyed on plugin **name** alone, but a version home installs plugins
63
+ per **marketplace** (`marketplaces/<name>/plugins/<plugin>`). When a plugin
64
+ moved marketplaces — e.g. `code` once shipped from the user repo (the
65
+ `agents-cli` marketplace) and later from the system repo (`agents-system`) —
66
+ the stale `agents-cli` copy was never cleaned, because the name `code` was
67
+ still active via `agents-system`. Fleet boxes then carried **two** `code`
68
+ plugins: the current one, plus a shadow serving skills the repo deleted on
69
+ purpose (`code:quality` / `code:ship` / `code:verify`), and resolution order
70
+ decided which `code:review` an agent got. `cleanOrphanedPluginSkills` /
71
+ `diffVersionPlugins` now key on the `(marketplace, name)` pair: an installed
72
+ copy whose marketplace source repo is present but no longer ships that plugin
73
+ is trashed (soft-deleted to `~/.agents/.trash/plugins/`), even when another
74
+ marketplace still ships the same name — so a plain `agents sync` reconciles the
75
+ shadow away. When a marketplace's source repo is **absent** (a project we're
76
+ not in, a removed extra repo) the original name-only test is kept, so an
77
+ unrelated sync from another cwd never trashes a plugin whose source simply is
78
+ not reachable right now. Source: `cli/src/lib/plugins/plugins.ts`
79
+ (`cleanOrphanedPluginSkills`, `diffVersionPlugins`, `isOrphanMarketplacePlugin`),
80
+ `cli/src/lib/staleness/writers/plugins.ts`,
81
+ `cli/src/lib/installations/versions.ts`.
82
+
83
+ - **`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`.
84
+
85
+ - **`agents pty` now works on macOS/arm64 and Node 25/26 (PHNX-2740).** The pinned
86
+ `@homebridge/node-pty-prebuilt-multiarch@0.13.1` shipped no darwin-arm64 prebuild
87
+ above Node 24 (ABI 137), so on a Mac running Node 25 (ABI 141) or 26 (ABI 147) the
88
+ native binding never resolved and every pty command died with a bare
89
+ `Cannot find module '.../pty.node'` MODULE_NOT_FOUND. Bumped to `0.14.1`, which
90
+ publishes darwin-arm64 (and darwin-x64) prebuilds through ABI 147, covering current
91
+ Node. The PTY sidecar now also **fails loud** when the binding genuinely can't load:
92
+ instead of a raw MODULE_NOT_FOUND it prints the platform, the running Node ABI, and a
93
+ concrete remediation (`npm rebuild @homebridge/node-pty-prebuilt-multiarch` / reinstall
94
+ the CLI), preserving the underlying error. Source: `cli/package.json`,
95
+ `cli/src/lib/pty-server.ts`.
96
+
97
+ - **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`.
98
+
99
+ - **The auto-pulled system repo is verified against its expected origin before a
100
+ fast-forward — a repointed origin is refused, not executed (PHNX-2957).** The
101
+ system repo (`~/.agents/.system/`) ships **hooks** that register as shell
102
+ `command` strings run on every tool event, and its checkout auto-fast-forwards
103
+ from `origin` (on `agents use`, and on the opt-in `AGENTS_AUTO_PULL=1`
104
+ background worker). Neither path verified that `origin` was the repo the
105
+ operator actually chose — so an `origin` repointed to an attacker's fork (or a
106
+ clone seeded from one) would silently fast-forward arbitrary hook code that then
107
+ ran as shell on the next command. Both pull sites now route through
108
+ `tryAutoPullSystemRepo`, which pulls only when `origin` is the canonical system
109
+ repo (`isSystemRepoRemote`) or the exact `AGENTS_SYSTEM_REPO` the operator
110
+ pointed at; an unexpected origin is **refused loud** (`agents use` prints the
111
+ offending remote and the re-point/`AGENTS_SYSTEM_REPO` fix), never pulled. The
112
+ canonical system repo and a legitimate `AGENTS_SYSTEM_REPO` override still sync
113
+ exactly as before — no regression to trusted pulls. Source:
114
+ `cli/src/lib/git.ts` (`isExpectedSystemRepoRemote`, `tryAutoPullSystemRepo`),
115
+ `cli/src/commands/versions.ts`, `cli/src/lib/auto-pull-worker.ts`.
116
+
117
+ - **`agents monitors add --watch-pid <pid>` (PHNX-3023).** A reliable, daemon-polled
118
+ watcher for a backgrounded process — the fix for "will re-invoke me" watchers that
119
+ never fire because a harness's exit hook only wakes the agent when the watched
120
+ process dies, and a watch loop (`gh pr checks --watch`, a long sleep, a tick poll)
121
+ never itself exits. `--watch-pid` has the monitor engine's own poll loop check the
122
+ pid's liveness independent of the arming session, defaults its condition to fire on
123
+ exit, and fails loud at creation time when the pid is already dead instead of
124
+ silently arming a watcher that can never fire. Source: `cli/src/lib/monitors/pid-watch.ts`,
125
+ `cli/src/commands/monitors.ts`.
126
+
127
+ - **`agents doctor --fix` now reconciles hooks / permissions / subagents / rule
128
+ aliases on Windows instead of an unactionable "hold" (PHNX-3187).** On win-mini
129
+ `--fix` healed commands/skills/rules/plugins but reported hooks, permissions,
130
+ subagents, and the `CLAUDE`/`GEMINI` rule aliases as "couldn't reconcile" and
131
+ left the box permanently red — with real guards (`git-guard`, `rm-guard`,
132
+ `secrets-guard`, `ask-user-question-guard`, `public-artifact-guard`)
133
+ uninstalled. Four Windows-specific defects, each fixed at its source: (1)
134
+ **subagents** — `parseSubagentFrontmatter`/`getSubagentBody` split on `'\n'`
135
+ and compared the fence with `=== '---'`, so a git-CRLF-checked-out `AGENT.md`
136
+ (`'---\r'`) was rejected and the subagent silently dropped from discovery, so
137
+ it could never be installed; now CRLF-robust (`/\r?\n/`). (2) **permissions** —
138
+ `buildPermissionsFromGroups` extracted rules with a line regex anchored on the
139
+ closing quote (`"$`), which a trailing `\r` broke, extracting zero rules and
140
+ writing an empty permission set; now CRLF-robust. (3) **rule aliases** — git
141
+ checks the `rules/CLAUDE.md` and `rules/GEMINI.md` symlinks out as plain text
142
+ files on Windows, so `lstat().isSymbolicLink()` was false and the diff treated
143
+ them as independent rule sources no sync could ever produce; a new
144
+ `isCheckedOutSymlink` detector skips them on every platform. (4) **hooks** —
145
+ the heal pass fed the resource diff's extensionless hook names
146
+ (`git-guard`) back to the sync writer, whose `available.hooks` set carries the
147
+ source filename **with** its extension (`git-guard.sh`), so the exact-set match
148
+ found nothing and no flagged hook could be written; a new basename-tolerant
149
+ `resolveHookSelection` maps them, and the orphan sweep still prunes any stale
150
+ extensionless hook copy left in a version home (one would be invisible on
151
+ Windows, lacking both an extension and an exec bit). The subagents writer also now
152
+ surfaces a per-item write failure with its reason instead of swallowing it in a
153
+ bare `catch`, so a genuine failure fails loud rather than reading as a silent
154
+ "hold". Source: `cli/src/lib/subagents.ts`, `cli/src/lib/permissions.ts`,
155
+ `cli/src/lib/doctor-diff.ts`, `cli/src/lib/installations/versions.ts`,
156
+ `cli/src/lib/staleness/writers/subagents.ts`.
157
+
158
+ - Fix the `agents-cli` discovery skill/plugin install commands to use the real GitHub
159
+ repo path `phnx-labs/agi-cli` (they pointed at `phnx-labs/agents-cli`, which only
160
+ resolved via GitHub's rename redirect). The npm package stays `@phnx-labs/agents-cli`.
161
+ Also maps the plugin manifests + skill to `agents-cli-plugin.test.ts` in CI impact
162
+ analysis so a manifest/skill edit runs its test on the PR. (PHNX-3337 review follow-up)
163
+
164
+ - **Ship a cross-harness `agents-cli` discovery skill + Claude plugin marketplace (PHNX-3337).**
165
+ New `skills/agents-cli/SKILL.md` is an authoritative skill whose `description`
166
+ carries the exact intents a developer types — *run multiple coding agents in
167
+ parallel*, *manage multiple Claude Code accounts*, *I hit my usage limit*,
168
+ *resume a session on another machine*, *pin the agent CLI version* — each with a
169
+ verified `agents` command recipe (`teams`, `accounts`, `run --fallback`/`-b`/`auto`,
170
+ `sessions resume`, `add`/`use`). A repo-root `.claude-plugin/marketplace.json` +
171
+ `.claude-plugin/plugin.json` make the repo installable via
172
+ `claude plugin marketplace add phnx-labs/agents-cli` and `npx skills add
173
+ phnx-labs/agents-cli`; the plugin's `source: "./"` bundles the discovery skill
174
+ plus the existing per-command skills. Harness parity is registry-driven, not
175
+ per-harness copies: the one SKILL.md is authored once and the existing
176
+ capability-gated skill sync (`supports(agent, 'skills', …)`) fans it into every
177
+ skill-capable harness home. `claude plugin validate .` passes on the committed
178
+ manifest, and a real (no-mock) test reads the repo-root files and runs the CLI's
179
+ own `validateClaudePluginManifest`. Source:
180
+ `skills/agents-cli/SKILL.md`, `.claude-plugin/marketplace.json`,
181
+ `.claude-plugin/plugin.json`, `cli/src/lib/plugins/agents-cli-plugin.test.ts`,
182
+ `README.md`.
183
+
184
+ - **`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`.
185
+
186
+ - **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`.
187
+ - **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`.
188
+
189
+ - **`agents run --device auto` no longer lands a launch on a fleet box that is logged out for the target harness (PHNX-3466).**
190
+ The `--device auto` placement gate already excluded a device whose harness reads
191
+ signed-out — but it judged a REMOTE candidate by `agents view --json`'s display
192
+ `signedIn`, which is true whenever the box's active/global HOME carries a login even
193
+ if the per-version home the isolated run actually launches has no credential of its
194
+ own. The LOCAL candidate, by contrast, used the strict per-version launch truth
195
+ (`collectRunCandidates` → `isLaunchableSignedIn`). So a worker whose selected harness
196
+ version home was not launchable passed the remote gate, got picked, and the launch
197
+ died at spawn — from AGI EXT the dispatched tab exited 1 and vanished. `agents view
198
+ --json` now emits a per-version `launchable` field (the same `isLaunchableSignedIn`
199
+ signal the local path uses), and remote placement (`viewAgentAccountEligibility`)
200
+ gates on it, so both paths agree. A device with no launchable account for the harness
201
+ is excluded from the `--device auto` candidate set; when that empties the pool the
202
+ existing fail-loud `no healthy device` error fires instead of a silently-lost launch.
203
+ An older remote CLI that omits `launchable` falls back to `signedIn`, so a rolling
204
+ fleet does not regress. No second placement path was added — the single CLI gate is
205
+ hardened, so both the CLI and AGI EXT (which delegates placement to it) benefit.
206
+ Source: `cli/src/commands/view.ts`, `cli/src/lib/view-types.ts`,
207
+ `cli/src/lib/hosts/ready.ts`.
208
+
209
+ - **`agents traces sync` now emits SEGMENTED active-time medians so the console can
210
+ headline agent runs, not the corpus blend (PHNX-3472).** 63% of sessions are
211
+ one-shot queries (≤2 messages, ~15s active), so the blended `medianMs`/`p90Ms` sat
212
+ at ~15s while substantial agent runs have a ~15-minute median — the blend headlined
213
+ neither. The index-shard `stats` now classify each session as an AGENT run (any tool
214
+ call OR more than 8 messages) or INTERACTIVE, and carry `agentMedianMs`/`agentP90Ms`
215
+ (active-time median/p90 over agent sessions), `interactiveMedianMs`, and
216
+ `measuredFraction` (share of sessions with a non-null duration) alongside the
217
+ unchanged blended `medianMs`/`p90Ms`. Reuses the PHNX-3457 active-time computation
218
+ (span minus idle gaps > 120s); no calendar span reintroduced. Source:
219
+ `cli/src/lib/traces/sync.ts`.
220
+
221
+ - **`agents traces sync` classifies session kind and EXCLUDES internal utility calls
222
+ from the Evals corpus (PHNX-3474).** ~68% of the raw corpus is machine plumbing —
223
+ single-shot calls (no tool call AND ≤2 messages) plus known internal-prompt
224
+ signatures (title generation, watchdog ticks, commit-message writes, factory
225
+ workers), all spawned under the `claude` harness by the Rush app — and it poisoned
226
+ every console statistic (count, median, need-attention, tool-error-rate). Each
227
+ session now carries a `kind` (`utility` vs `agent`, `classifySessionKind` in
228
+ `traces/sync.ts`) and every index statistic is computed over the AGENT set ONLY:
229
+ `sessionsImported` is the real agent count (not the raw row count), and the
230
+ medians / `needAttention` / `toolErrorRate` / topic-bucket counts all exclude
231
+ utility. A new top-level `utilityCount` reports how many were dropped. Each session
232
+ ref (topic-tile `sessions[]` and `needsAttention`) also emits `kind` and `harness`
233
+ so the console can filter by both. Pure reclassification — utility rows are tagged
234
+ and excluded at shard-build time, never deleted from `sessions.db`. Source:
235
+ `cli/src/lib/traces/sync.ts`.
236
+ </content>
237
+ </invoke>
238
+
3
239
  ## 1.22.58
4
240
 
241
+ - **`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`.
242
+
5
243
  - **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
244
 
7
245
  - **`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) {