@phnx-labs/agents-cli 1.22.25 → 1.22.26
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.
- package/CHANGELOG.md +183 -0
- package/README.md +17 -2
- package/dist/bin/agents +0 -0
- package/dist/browser.js +14 -4
- package/dist/commands/apply.js +52 -8
- package/dist/commands/browser.js +35 -0
- package/dist/commands/doctor.js +8 -0
- package/dist/commands/insights.d.ts +25 -19
- package/dist/commands/insights.js +107 -33
- package/dist/commands/reconnect.d.ts +46 -0
- package/dist/commands/reconnect.js +109 -0
- package/dist/commands/routines.js +2 -2
- package/dist/commands/secrets.d.ts +2 -8
- package/dist/commands/secrets.js +29 -105
- package/dist/commands/sessions.js +4 -0
- package/dist/commands/setup-secrets.d.ts +1 -0
- package/dist/commands/setup-secrets.js +1 -1
- package/dist/commands/setup.d.ts +26 -3
- package/dist/commands/setup.js +105 -46
- package/dist/commands/teams.d.ts +6 -0
- package/dist/commands/teams.js +43 -0
- package/dist/commands/trends.d.ts +8 -0
- package/dist/commands/trends.js +10 -156
- package/dist/index.js +1 -1
- package/dist/lib/agents.d.ts +11 -0
- package/dist/lib/agents.js +29 -2
- package/dist/lib/analytics/dashboard.d.ts +10 -6
- package/dist/lib/analytics/dashboard.js +6 -4
- package/dist/lib/analytics/mix-commands.d.ts +53 -0
- package/dist/lib/analytics/mix-commands.js +229 -0
- package/dist/lib/analytics/recipes.d.ts +19 -14
- package/dist/lib/analytics/recipes.js +4 -2
- package/dist/lib/browser/ipc.d.ts +26 -0
- package/dist/lib/browser/ipc.js +139 -24
- package/dist/lib/browser/profiles.d.ts +11 -0
- package/dist/lib/browser/profiles.js +1 -1
- package/dist/lib/browser/stream.d.ts +14 -0
- package/dist/lib/browser/stream.js +71 -0
- package/dist/lib/channels/owner-sink.d.ts +27 -0
- package/dist/lib/channels/owner-sink.js +93 -0
- package/dist/lib/devices/doctor-findings.d.ts +7 -1
- package/dist/lib/devices/doctor-findings.js +33 -1
- package/dist/lib/fleet/apply.d.ts +59 -3
- package/dist/lib/fleet/apply.js +183 -6
- package/dist/lib/fleet/types.d.ts +21 -2
- package/dist/lib/hooks/cache.js +15 -0
- package/dist/lib/hosts/passthrough.d.ts +23 -0
- package/dist/lib/hosts/passthrough.js +45 -0
- package/dist/lib/hosts/ready.d.ts +2 -0
- package/dist/lib/hosts/ready.js +10 -1
- package/dist/lib/hosts/reconnect.d.ts +14 -12
- package/dist/lib/hosts/reconnect.js +41 -40
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/routines.js +14 -2
- package/dist/lib/runner.d.ts +0 -3
- package/dist/lib/runner.js +1 -14
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/push.d.ts +94 -0
- package/dist/lib/secrets/push.js +145 -0
- package/dist/lib/secrets/reaper.d.ts +15 -1
- package/dist/lib/secrets/reaper.js +30 -3
- package/dist/lib/session/db.d.ts +21 -3
- package/dist/lib/session/db.js +221 -13
- package/dist/lib/session/discover.d.ts +1 -0
- package/dist/lib/session/discover.js +115 -19
- package/dist/lib/session/insights.d.ts +18 -0
- package/dist/lib/session/insights.js +143 -1
- package/dist/lib/session/tool-index.js +133 -22
- package/dist/lib/session/tool-store.d.ts +26 -2
- package/dist/lib/session/tool-store.js +36 -17
- package/dist/lib/ssh-exec.js +8 -2
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +4 -0
- package/dist/lib/teams/agents.d.ts +13 -0
- package/dist/lib/teams/agents.js +75 -7
- package/dist/lib/teams/placement-probe.d.ts +21 -0
- package/dist/lib/teams/placement-probe.js +135 -0
- package/dist/lib/teams/scheduler.d.ts +74 -1
- package/dist/lib/teams/scheduler.js +187 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,178 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.22.26
|
|
4
|
+
|
|
5
|
+
- 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.
|
|
6
|
+
|
|
7
|
+
- **`agents apply --provision-secrets` pushes the manifest's declared secrets
|
|
8
|
+
bundles to each device, instead of only printing a reminder (RUSH-1968).** This
|
|
9
|
+
gap is a direct cause of the ticket: an operator who needed secrets on a worker
|
|
10
|
+
box had no supported path — `apply` said "recreate manually" and nothing else —
|
|
11
|
+
so they hand-exported the file store's master key across the fleet. The
|
|
12
|
+
provisioning primitive now exists, and `apply` runs it as a fifth reconcile
|
|
13
|
+
phase, last, because it is the most sensitive mutation `apply` performs.
|
|
14
|
+
|
|
15
|
+
It is **off by default** and is a **flag, not a manifest field**: `agents.yaml`
|
|
16
|
+
is shared, so a file-level default would mean someone else's `apply -y` silently
|
|
17
|
+
ships credential values. Three gates, and every refusal still prints a
|
|
18
|
+
`needs-secret` reminder so a skipped device is never silent — the flag must be
|
|
19
|
+
set, the device must be reachable, and its host key must be **pinned** (the same
|
|
20
|
+
bar `agents exec --copy-creds` sets, EXEC-34).
|
|
21
|
+
|
|
22
|
+
**Backend follows the platform: `file` on Linux, `keychain` on macOS/Windows.**
|
|
23
|
+
That is the load-bearing default — a headless Linux box has no keychain and its
|
|
24
|
+
file store auto-provisions its OWN machine-local key, so each device gets an
|
|
25
|
+
unshared at-rest key and **no passphrase is forwarded**. That is the direct
|
|
26
|
+
alternative to the fleet-wide shared secret this ticket is about.
|
|
27
|
+
|
|
28
|
+
With provisioning on, `apply` runs one extra `agents secrets list --json` per
|
|
29
|
+
device (metadata only — names and timestamps, never values) and skips a bundle
|
|
30
|
+
the device already has; without that, every run re-resolves the bundle locally
|
|
31
|
+
and a resolve can prompt for Touch ID, so a converged fleet would nag on every
|
|
32
|
+
apply. It compares presence, not content — `--force` re-pushes regardless. The
|
|
33
|
+
`--plan` matrix gains a `secrets` column, shown only when the manifest declares
|
|
34
|
+
bundles, and names the flag when the capability is available but off. Source:
|
|
35
|
+
`apps/cli/src/lib/secrets/push.ts` (extracted from the `export --host` action so
|
|
36
|
+
a lib no longer needs a command module), `apps/cli/src/lib/fleet/apply.ts`,
|
|
37
|
+
`apps/cli/src/commands/apply.ts`.
|
|
38
|
+
|
|
39
|
+
- **`agents teams` auto-scheduling is health-, harness-, and load-aware, and fails loud when no pool device can run the agent (RUSH-2002).** Placing an unpinned teammate onto a `--devices` pool used to be a pure roster count that could land it on an unreachable box, an overloaded one, or one where its agent isn't installed. `teams start` now probes the pool once (reachability + load from the same snapshot `agents devices` shows, plus whether the teammate's agent is installed there) and: excludes unreachable / overloaded (`loaded` headroom) / `agents.max-concurrent`-capped / not-installed devices, then ranks the survivors by agent installed + signed in, then lower load, then fewer running teammates. If no pool device can run a pending teammate's agent, `teams start` fails loud — `No device in the team pool can run claude@2.1.112. Run 'agents devices ping' to see which devices have the agent installed + signed in.` — instead of stranding the teammate or silently falling back to a local run; `--force` downgrades it to a warning. A probe that could not reach the pool does not trigger the failure (no false positives). The pick stays pure and fully unit-tested; `teams add` is unchanged (no probe on the add path). Source: `apps/cli/src/lib/teams/scheduler.ts`, `apps/cli/src/lib/teams/placement-probe.ts`, `apps/cli/src/lib/teams/agents.ts`, `apps/cli/src/commands/teams.ts`.
|
|
40
|
+
|
|
41
|
+
- **`agents reconnect [session-id]` re-enters a dropped remote agent terminal, and the auto-reconnect no longer dead-ends on a dead pane (RUSH-2085).** When the network dropped during `agents run --device <box>` and the peer's tmux pane was gone by the time the link came back, the reattach ran `agents sessions focus <id> --local --attach-only`, which hard-failed with `No live session matching …` and dropped the user at a bare shell with the id scrolled off screen. Two fixes: the auto-reconnect reattach now runs `agents sessions focus <id> --local` (no `--attach-only`), so a surviving pane is joined and a dead one RESUMES in place instead of dead-ending; and a new `agents reconnect` (also `agents sessions reconnect`) is the manual companion for after the auto-loop gave up or a VS Code terminal tab closed — attach the live pane if it survived, else resume the session. With no id it targets the most recent session started from the current directory (the terminal that most likely just dropped), not the full fleet picker. The exhausted / remote-exit notices now print the exact `agents reconnect <id>` command instead of a raw id and a shell prompt. Source: `apps/cli/src/lib/hosts/reconnect.ts`, `apps/cli/src/commands/reconnect.ts`.
|
|
42
|
+
|
|
43
|
+
- **`agents routines add` now rejects an agent the local daemon can't fire, at add time (RUSH-2102).** `--agent opencode` (or any real, installable agent outside the daemon's `AGENT_COMMANDS` table — currently `claude`, `codex`, `gemini`, `cursor`, `kimi`, `droid`, `muse`) used to pass `validateJob` because it only checked the agent against the full agent registry, not the daemon-runnable subset — the routine was written to disk and only failed once the scheduler fired it (`Unsupported agent for daemon jobs: opencode`). `validateJob` now rejects it immediately for the default local placement, with an error naming the supported agents. Routines placed with `hostStrategy: host`/`fleet`/`cloud` are unaffected — those dispatch through `agents run`/a cloud provider, not this table, so a wider agent set is legitimately supported there. Source: `apps/cli/src/lib/routines.ts`, `apps/cli/src/lib/agents.ts`.
|
|
44
|
+
|
|
45
|
+
- **OpenClaw's capability table no longer claims `hooks: true` with zero hooks
|
|
46
|
+
ever installed (RUSH-2122).** `registerHooksToSettings` has no `openclaw`
|
|
47
|
+
branch and silently returned `{ registered: [], errors: [] }` for it, so
|
|
48
|
+
`agents sync openclaw` reported success while installing nothing, and
|
|
49
|
+
`agents doctor` treated the agent as hooks-capable with no way to detect the
|
|
50
|
+
gap. OpenClaw only exposes a fixed set of internal, named hooks (e.g.
|
|
51
|
+
`boot-md`, which runs `BOOT.md` on gateway restart) — there is no general
|
|
52
|
+
event->shell-command registration surface an agents-cli `hooks.yaml`
|
|
53
|
+
manifest could target — so `capabilities.hooks` and `supportsHooks` now read
|
|
54
|
+
`false`, matching what the CLI can actually do. A new completeness test
|
|
55
|
+
(`hooks-capability-completeness.test.ts`) pins every `hooks: true` agent to a
|
|
56
|
+
real branch in `registerHooksToSettings` so a capability flip can never ship
|
|
57
|
+
again without a registrar behind it. Source: `apps/cli/src/lib/agents.ts`,
|
|
58
|
+
`apps/cli/src/lib/hooks-capability-completeness.test.ts`.
|
|
59
|
+
|
|
60
|
+
- **`agents browser stream` keeps one Node process and browser-daemon IPC socket warm across repeated actions (RUSH-2149).** The newline-delimited JSON interface sends every request through the existing browser daemon and its cached CDP connection, so screenshot/click loops no longer need a fresh `agents` process or IPC connection per action. `--task` (or `AGENTS_BROWSER_TASK`) supplies the default task, a `start` response becomes the default for later lines, and malformed input returns an error response without ending the stream. Fleet-remote `start` requests enforce the same device-local `browser remote-control` consent gate as the ordinary command. Source: `apps/cli/src/lib/browser/ipc.ts`, `apps/cli/src/lib/browser/stream.ts`, `apps/cli/src/commands/browser.ts`.
|
|
61
|
+
|
|
62
|
+
- **`agents sessions backfill tools` reads a growing transcript incrementally, and
|
|
63
|
+
the search index compacts itself (RUSH-2208).** Incremental discovery already
|
|
64
|
+
appended tool calls for a Claude/Codex session that grew; the backfill did not.
|
|
65
|
+
Every `ensureToolIndex` pass re-read the transcript from byte 0 and deleted the
|
|
66
|
+
session's stored evidence to rewrite it, so a session backfilled N times cost N
|
|
67
|
+
full parses of an ever-larger file — measured on a 4.5 MiB, 4000-call transcript
|
|
68
|
+
that grew by 20 calls: 344 ms and 4020 calls re-parsed, now 12 ms and 20 calls.
|
|
69
|
+
Schema v36 adds a resume point to `tool_scan_ledger` (`parsed_offset`, the byte
|
|
70
|
+
just past the last complete record consumed, and `parser_state`, the collector
|
|
71
|
+
snapshot at that offset), so a transcript that only grew is read from where the
|
|
72
|
+
last pass stopped and merged into what is already stored; the batch byte budget
|
|
73
|
+
now counts the bytes a pass actually reads rather than the file's size, so one
|
|
74
|
+
bounded batch covers far more growing sessions. A different extractor version, a
|
|
75
|
+
mismatched ledger path, a file shorter than what was parsed, or an unreadable
|
|
76
|
+
snapshot still re-reads the whole file. Two related fixes ride along:
|
|
77
|
+
`tool_call_text` rows are addressed by the `rowid` of the call they describe
|
|
78
|
+
instead of the UNINDEXED `call_key`, which made every delete a full scan of the
|
|
79
|
+
FTS index, and the scan path now runs a bounded, threshold-gated FTS `'merge'`
|
|
80
|
+
after each batch of writes, so index health no longer depends on someone running
|
|
81
|
+
`agents sessions optimize` by hand. Source: `apps/cli/src/lib/session/tool-index.ts`,
|
|
82
|
+
`apps/cli/src/lib/session/tool-store.ts`, `apps/cli/src/lib/session/db.ts`.
|
|
83
|
+
|
|
84
|
+
- **OpenCode session scans re-index only the sessions that changed (RUSH-2210).** OpenCode
|
|
85
|
+
keeps every session in one shared `opencode.db`, and the scanner stamped each session
|
|
86
|
+
with that whole file's mtime/size. Any write to any session therefore invalidated every
|
|
87
|
+
indexed session, so a single new turn re-emitted up to 1000 sessions — and the indexer
|
|
88
|
+
re-opened `opencode.db` once per re-emitted session to re-parse a transcript that had
|
|
89
|
+
not moved. Each session is now stamped with its own newest write time (across its
|
|
90
|
+
`session` row, its messages, and its parts) and the byte length of its message + part
|
|
91
|
+
payloads, so an unchanged session is skipped; the file-level stat stays only as the
|
|
92
|
+
cheap "nothing changed at all" short-circuit, and a scan opens `opencode.db` once
|
|
93
|
+
instead of once per session. The stamp deliberately does not rely on
|
|
94
|
+
`session.time_updated` alone, which real databases leave hours behind the session's
|
|
95
|
+
newest part. A side effect: `sessions.file_size` for an OpenCode row is now that
|
|
96
|
+
session's payload size instead of the whole database's size, so the tool-backfill byte
|
|
97
|
+
budget and its 16 MiB in-memory parser cap finally reflect the real cost of parsing
|
|
98
|
+
that one session. Source: `apps/cli/src/lib/session/discover.ts`.
|
|
99
|
+
|
|
100
|
+
- **Session query hot path is indexable again (RUSH-2211).** The default `agents
|
|
101
|
+
sessions` listing sort (`ORDER BY IFNULL(last_activity, timestamp) DESC`)
|
|
102
|
+
wrapped the sort column in `IFNULL()`, which defeats `idx_sessions_last_activity`
|
|
103
|
+
and forces a full table sort on every list/resume query; a new migration
|
|
104
|
+
backfills `last_activity` so it's unconditionally `NOT NULL` and the sort now
|
|
105
|
+
runs on the bare column (`EXPLAIN QUERY PLAN` confirms `USING INDEX
|
|
106
|
+
idx_sessions_last_activity`). The post-query existence check now batches
|
|
107
|
+
`fs.existsSync` per directory instead of one stat syscall per row — real
|
|
108
|
+
transcript trees put many sessions in one project directory, so this collapses
|
|
109
|
+
thousands of stats into a handful of `readdirSync` calls with the same result.
|
|
110
|
+
Interactive label search (`ftsSearch`) no longer runs a leading-wildcard
|
|
111
|
+
`LOWER(label) LIKE '%q%'` scan of the whole `sessions` table on every keystroke;
|
|
112
|
+
it now queries the already-indexed FTS5 `label` column. Source:
|
|
113
|
+
`apps/cli/src/lib/session/db.ts`.
|
|
114
|
+
|
|
115
|
+
- **The standalone `browser` binary now routes `--host`/`--device` (RUSH-2214).** `browser start --host <box>` dispatches to the remote over SSH, exactly like `agents browser start --host <box>` already did — previously the standalone bin dropped the flag with `unknown option '--host'` because it never entered the top-level router. A self-named or absent host still runs locally. Source: `apps/cli/src/browser.ts`, `apps/cli/src/lib/hosts/passthrough.ts`.
|
|
116
|
+
|
|
117
|
+
- **`agents run --device` no longer reports a host as unreachable when the SSH probe times out (RUSH-2249).** The ready probe (`readyProbe` in `hosts/ready.ts`) now disables SSH multiplexing so a stale control socket cannot hang the local client, and it checks `r.timedOut` before parsing stdout — a slow login shell (nvm/sdkman init, cold node startup) producing an empty stdout was silently treated as "not reachable". A timeout now surfaces a distinct, actionable error that names the cause and suggests `agents ssh <host> agents view` to confirm manually, rather than the misleading "not reachable over SSH" message. Source: `apps/cli/src/lib/hosts/ready.ts`.
|
|
118
|
+
|
|
119
|
+
- **`agents teams start` nudges the operator toward feed milestones (RUSH-2250).**
|
|
120
|
+
After launching teammates, `teams start` now prints a one-line tip — teammates are
|
|
121
|
+
briefed to post IMPORTANT milestones to the feed (watch them with
|
|
122
|
+
`agents feed timeline`), and team progress is watched with `agents teams status
|
|
123
|
+
<team>`. Print-only in both the single-wave and `--watch` paths (suppressed under
|
|
124
|
+
`--json`); no engine behavior changes. Pairs with the `.agents-system` guidance
|
|
125
|
+
that instructs teammates to post those milestones. Source:
|
|
126
|
+
`apps/cli/src/commands/teams.ts`.
|
|
127
|
+
|
|
128
|
+
- **`secrets list` no longer skips every biometry-ACL'd item, so `hold`/`always`-policy
|
|
129
|
+
bundles are readable again (RUSH-2251).** A regression first shipped in v1.22.10 added
|
|
130
|
+
`kSecUseAuthenticationUI: kSecUseAuthenticationUISkip` to the keychain helper's `list`
|
|
131
|
+
data-protection pass. `UISkip` makes `SecItemCopyMatching` silently omit every item
|
|
132
|
+
protected by a biometry access control — which is exactly the value items `set` writes —
|
|
133
|
+
so enumeration returned only the no-ACL metadata and `never`-policy items. Every consumer
|
|
134
|
+
that builds its keychain read set from that enumeration (`secrets exec`/`get`/`unlock`/
|
|
135
|
+
`view --reveal`/`export`, `agents run --secrets`, `ssh`, `browser`, `share`) then reported
|
|
136
|
+
the real secrets as `stored item '…' not found`, and `unlock` could not even warm the
|
|
137
|
+
broker to work around it. The DP pass is now attributes-only with **no** `kSecUseAuthenticationUI`
|
|
138
|
+
key: `kSecReturnAttributes` without `kSecReturnData` never evaluates the ACL, so it
|
|
139
|
+
neither prompts for Touch ID nor filters the ACL'd items out — restoring the design the
|
|
140
|
+
code comment already described. The RUSH-2233 timeout bound on that pass is unchanged.
|
|
141
|
+
Source: `apps/cli/src/lib/secrets/keychain-helper.swift`.
|
|
142
|
+
|
|
143
|
+
- **Hook-cache background refresh recovers from an orphaned single-flight lock (RUSH-2259).**
|
|
144
|
+
The stale-while-revalidate lock (`<cache>.bg.lck`) was only released by the
|
|
145
|
+
background refresh's `EXIT` trap, so a hard kill (SIGKILL, OOM, reboot) that
|
|
146
|
+
skipped the trap orphaned the dir and every future refresh's `mkdir` failed —
|
|
147
|
+
permanently stalling background refresh while stale cache was served forever.
|
|
148
|
+
The shim now reclaims a lock older than a 5-minute TTL before acquiring, so a
|
|
149
|
+
dead lock self-heals on the next fire. Source: `apps/cli/src/lib/hooks/cache.ts`.
|
|
150
|
+
|
|
151
|
+
- **`agents doctor` now fails loud when this box cannot reach the owner-delivery lane (RUSH-2262).** The feed/notify owner lane (`agents notify`, `agents feed post --level important` / `--blocked`) delivers over the rush-backed owner channel (iMessage), which only works from a context that has `rush` on PATH and can read its keychain-bound session — so a headless Linux fleet box (no rush) or a non-GUI SSH session on a mac (login keychain locked) silently could not escalate a blocked agent, surfacing only as an after-the-fact `owner failed: …` line. `agents doctor` had no signal for it. A new critical finding, `owner-sink-unreachable`, probes the same transport from the same context doctor runs in (`which rush` + `rush whoami`, never `~/.rush/user.yaml`, since the token is a keychain item) and reports `owner → unreachable: rush CLI not on this box's PATH` / `rush has no usable session here` with the fix. It fires only when owner delivery is configured for the fleet, so an un-opted-in box is never flagged; `agents notify --dry-run` is not this check (it short-circuits before the `which rush` preflight and reports success even where rush is absent). Source: `apps/cli/src/lib/channels/owner-sink.ts`, `apps/cli/src/lib/devices/doctor-findings.ts`, `apps/cli/src/commands/doctor.ts`.
|
|
152
|
+
|
|
153
|
+
- **`agents insights` owns counter mix; `agents trends` is a deprecated alias.** The
|
|
154
|
+
former top-level `trends` tree (harness/model mix, tools-per-session, token ratios,
|
|
155
|
+
secrets/browser recipes, raw usage query) now lives under `agents insights mix` and
|
|
156
|
+
`agents insights <recipe>` / `query` / `recipes`. Bare `agents insights` remains the
|
|
157
|
+
behavioural report (transcript content, account split). `agents trends` still works
|
|
158
|
+
but prints one deprecation line and runs the same mix tree — no second implementation.
|
|
159
|
+
**Why:** two peer "analytics" verbs (`insights` + `trends`) taught agents and humans
|
|
160
|
+
to guess; one verb, two engines (content vs counters). Latency stays on `agents perf`;
|
|
161
|
+
quota on `agents usage`; skill/slash popularity on `agents sessions stats`. Source:
|
|
162
|
+
`apps/cli/src/lib/analytics/mix-commands.ts`, `commands/insights.ts`,
|
|
163
|
+
`commands/trends.ts`, `docs/06-observability.md`.
|
|
164
|
+
|
|
165
|
+
- **The keychain reaper no longer kills the auto-lock-on-sleep watcher (RUSH-2232 follow-up).**
|
|
166
|
+
The reaper (shipped in 1.22.23) classified a process as a reap target purely by the
|
|
167
|
+
helper binary path, which also matches the broker's deliberately long-lived
|
|
168
|
+
`watch-lock` watcher — a healthy child of the live daemon that wipes the in-memory
|
|
169
|
+
secret store on sleep. Its class-(b) rule ("helper child of a live parent, older than
|
|
170
|
+
90s") therefore killed the watcher on its second sweep (~10 min after the daemon
|
|
171
|
+
started hosting the broker), silently disabling auto-lock-on-sleep. Reap-eligibility
|
|
172
|
+
now matches the full command line and excludes the `watch-lock` verb, so only the
|
|
173
|
+
short-lived keychain reads/writes a wedged `coreauthd` can hang are ever reaped.
|
|
174
|
+
Source: `apps/cli/src/lib/secrets/reaper.ts` (`isReapableHelperCommand`).
|
|
175
|
+
|
|
3
176
|
## 1.22.25
|
|
4
177
|
|
|
5
178
|
---
|
|
@@ -168,6 +341,16 @@ Add Cursor Cloud Agents as a native cloud provider so `agents run cursor --cloud
|
|
|
168
341
|
|
|
169
342
|
- **`agents run --lease` now shares one warm pool across repositories by default (RUSH-2225).** Repo sandbox/CI `profile:` labels no longer split lease reuse into one idle box per repo; a dedicated lease pool is explicit with `.crabbox.yaml` `leaseProfile:`. An empty pool keeps its newly warmed box for later callers. Concurrent runs attach with crabbox `--reclaim` and launch with separate working trees, agent homes, and credential files, so callers share compute without clobbering run state. Switching repos re-syncs the checkout, trading cache latency for lower idle-compute cost. Source: `apps/cli/src/lib/crabbox/config.ts`, `apps/cli/src/lib/crabbox/lease.ts`, `apps/cli/src/commands/exec.ts`.
|
|
170
343
|
|
|
344
|
+
- **`agents sessions insights` turns multi-harness session history into an action list
|
|
345
|
+
(RUSH-2280).** The existing `agents insights` command is now also nested under the
|
|
346
|
+
sessions noun, accepts repeatable `--agent` filters, and reports deterministic offline
|
|
347
|
+
friction/thrash, owner corrections, automatable repeats, harness split, and ranked
|
|
348
|
+
rule/skill/automation/product actions with evidence counts and shortened sample session
|
|
349
|
+
ids. `/sessions-insights` is a thin agent entry over the same CLI implementation;
|
|
350
|
+
`--narrative` remains opt-in and receives aggregate data only. Source:
|
|
351
|
+
`apps/cli/src/commands/insights.ts`, `apps/cli/src/lib/session/insights.ts`,
|
|
352
|
+
`.agents/commands/sessions-insights.md`.
|
|
353
|
+
|
|
171
354
|
- **`agents sessions focus` recovers dead panes and shares the sessions browser's selectors (GH-2108).** A retained tmux `remain-on-exit` pane is probed through `#{pane_dead}` immediately before attach, so dead or missing panes no longer open a `Pane is dead` screen. `focus` accepts session ids, topic/path searches, `agent@version` selectors (including per-device `latest`/`oldest`), device, project/time, team/routine, skill/plugin, favorites, and the complete live-state union. Focus, resume, attach, and `run --resume` now use one recovery decision on the origin device: a healthy exact origin performs native resume; otherwise balanced selection chooses a healthy version of the same harness and sends `/continue <id>` to read the indexed transcript, including transcripts retained under version trash. Host-dispatched rows persist the dispatch host as their origin, and `attach` routes its detach-record cleanup there before resuming. No usable same-harness version fails with the device, origin version, and account-health reason. Source: `apps/cli/src/commands/focus.ts`, `apps/cli/src/commands/sessions-browser.ts`, `apps/cli/src/lib/session/recovery.ts`.
|
|
172
355
|
|
|
173
356
|
- **`agents secrets setup` no longer tells you to set `AGENTS_SECRETS_PASSPHRASE`, and
|
package/README.md
CHANGED
|
@@ -45,11 +45,12 @@ https://agents-cli.sh/demo.mp4
|
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
47
|
npm install -g @phnx-labs/agents-cli # or: curl -fsSL agi-cli.sh | sh
|
|
48
|
-
agents setup # first-time setup
|
|
48
|
+
agents setup # first-time setup, or re-open the capability hub
|
|
49
|
+
agents setup status # readiness for browser, computer, fleet, and more
|
|
49
50
|
agents run claude "explain this repo" # run any agent on your existing subscription
|
|
50
51
|
```
|
|
51
52
|
|
|
52
|
-
`agents setup` is interactive and idempotent -- safe to re-run on a
|
|
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.
|
|
53
54
|
|
|
54
55
|
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).
|
|
55
56
|
|
|
@@ -295,8 +296,16 @@ agents sessions backfill tools --fleet
|
|
|
295
296
|
agents sessions stats
|
|
296
297
|
agents sessions stats --zero # only the never-invoked (dead weight)
|
|
297
298
|
agents sessions backfill resources # fold historical sessions into the usage index
|
|
299
|
+
|
|
300
|
+
# Friction, owner corrections, repeated recipes, and ranked actions across harnesses
|
|
301
|
+
agents sessions insights --since 30d
|
|
302
|
+
agents sessions insights --agent claude --agent codex --json
|
|
303
|
+
# Top-level alias
|
|
304
|
+
agents insights --since 7d
|
|
298
305
|
```
|
|
299
306
|
|
|
307
|
+
`sessions insights` is deterministic and offline by default. It caches per-session facets, compares harnesses, and emits an actions table with evidence counts plus shortened sample session ids. `--narrative` is opt-in and receives aggregates only, never raw transcripts. The installed `/sessions-insights` slash command invokes the same CLI source of truth.
|
|
308
|
+
|
|
300
309
|
Interactive picker when you're in a terminal. Structured output (`--json`, `--markdown`, filtered by role or turn count) when piped.
|
|
301
310
|
|
|
302
311
|
Backed by a SQLite + FTS5 index at `~/.agents/.history/sessions/sessions.db` with incremental scanning -- warm reads in ~100ms. Tool-call evidence is redacted and bounded before it is cached; repeated `--query` clauses must match distinct calls in one session. Tool queries read SQLite only: `agents sessions backfill tools` performs the one-time historical parse, while normal incremental scans index new and changed sessions. The index stores ordered static Bash program sites, so `--count` reports occurrences, containing tool calls, and distinct sessions without reparsing. `--fleet` executes one origin partition per device, so synced mirrors cannot duplicate compact evidence or counts returned over SSH; transcript bodies stay on their origin machine. This uses relational SQLite rows and literal FTS5 only, with no embeddings, vector database, or model calls. External tools can consume `--json` output as a programmatic observability layer; see [docs/05-sessions.md](apps/cli/docs/05-sessions.md) for the schemas and [docs/06-observability.md](apps/cli/docs/06-observability.md) for the consumption patterns.
|
|
@@ -820,6 +829,12 @@ agents browser done # Close task's tabs when finished
|
|
|
820
829
|
|
|
821
830
|
# Need to address a different task in the same shell? Override per call:
|
|
822
831
|
agents browser screenshot --task other-flow
|
|
832
|
+
|
|
833
|
+
# Repeated observe/action loops: one Node process and daemon socket stay warm.
|
|
834
|
+
printf '%s\n' \
|
|
835
|
+
'{"action":"screenshot","path":"/tmp/page.jpg"}' \
|
|
836
|
+
'{"action":"click","atX":320,"atY":540}' \
|
|
837
|
+
| agents browser stream --task "$AGENTS_BROWSER_TASK"
|
|
823
838
|
```
|
|
824
839
|
|
|
825
840
|
### Why this works where Playwright fails
|
package/dist/bin/agents
CHANGED
|
Binary file
|
package/dist/browser.js
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import { registerBrowserSubcommands } from './commands/browser.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import { maybeRunStandaloneOnHost } from './lib/hosts/passthrough.js';
|
|
5
|
+
async function main() {
|
|
6
|
+
// `browser … --host <box>` routes to a remote over SSH, exactly like
|
|
7
|
+
// `agents browser … --host <box>` does through index.ts. Standalone-only:
|
|
8
|
+
// this binary never enters index.ts, so without this the flag was dropped.
|
|
9
|
+
if (await maybeRunStandaloneOnHost('browser')) {
|
|
10
|
+
process.exit(process.exitCode ?? 0);
|
|
11
|
+
}
|
|
12
|
+
const program = new Command();
|
|
13
|
+
program.name('browser').description('Browser automation via CDP');
|
|
14
|
+
registerBrowserSubcommands(program);
|
|
15
|
+
program.parse();
|
|
16
|
+
}
|
|
17
|
+
void main();
|
package/dist/commands/apply.js
CHANGED
|
@@ -15,6 +15,7 @@ import chalk from 'chalk';
|
|
|
15
15
|
import { setHelpSections } from '../lib/help.js';
|
|
16
16
|
import { machineId } from '../lib/session/sync/config.js';
|
|
17
17
|
import { loadDevices, isControlDevice } from '../lib/devices/registry.js';
|
|
18
|
+
import { isHostPinned, managedKnownHostsPath } from '../lib/devices/known-hosts.js';
|
|
18
19
|
import { ensureDevicesRegistered } from '../lib/devices/sync.js';
|
|
19
20
|
import { readFleetFile, resolveDesired } from '../lib/fleet/manifest.js';
|
|
20
21
|
import { snapshotAuth, materializeAuth, parseAuthBundle, KEYCHAIN_BOUND_ON_MAC, isCredentialSafeToPropagate } from '../lib/fleet/auth-sync.js';
|
|
@@ -82,7 +83,10 @@ function renderPlan(plan) {
|
|
|
82
83
|
}
|
|
83
84
|
return chalk.cyan('↑ ' + acts.map((a) => a.agent ?? a.kind.replace('-cli', '')).join(','));
|
|
84
85
|
};
|
|
85
|
-
|
|
86
|
+
// Only show the secrets column when the manifest declares any — an all-`-`
|
|
87
|
+
// column on every fleet that uses no bundles is noise.
|
|
88
|
+
const anySecrets = rows.some((r) => r.actions.some((a) => a.kind === 'push-secret' || a.kind === 'needs-secret'));
|
|
89
|
+
const header = ` ${'device'.padEnd(nameWidth)} ${'agents-cli'.padEnd(12)}${'agents'.padEnd(20)}${'config'.padEnd(10)}${anySecrets ? 'login'.padEnd(18) + 'secrets' : 'login'}`;
|
|
86
90
|
console.log(chalk.gray(header));
|
|
87
91
|
for (const row of rows) {
|
|
88
92
|
const cli = row.probe.reachable
|
|
@@ -100,10 +104,33 @@ function renderPlan(plan) {
|
|
|
100
104
|
? (row.actions.some((a) => a.kind === 'sync-config') ? chalk.cyan('↑ sync') : chalk.green('ok'))
|
|
101
105
|
: chalk.gray('-');
|
|
102
106
|
const loginCell = cell(row, ['push-login', 'needs-login'], `${row.desired.agents.length}/${row.desired.agents.length}`);
|
|
103
|
-
|
|
107
|
+
const secretsCell = (() => {
|
|
108
|
+
if (!anySecrets)
|
|
109
|
+
return '';
|
|
110
|
+
if (!row.probe.reachable)
|
|
111
|
+
return chalk.gray('- offline');
|
|
112
|
+
const push = row.actions.filter((a) => a.kind === 'push-secret').length;
|
|
113
|
+
const blocked = row.actions.filter((a) => a.kind === 'needs-secret').length;
|
|
114
|
+
if (push === 0 && blocked === 0)
|
|
115
|
+
return chalk.green('ok');
|
|
116
|
+
if (push > 0 && blocked === 0)
|
|
117
|
+
return chalk.cyan(`↑ push ${push}`);
|
|
118
|
+
if (push === 0)
|
|
119
|
+
return chalk.yellow(`blocked ${blocked}`);
|
|
120
|
+
return chalk.yellow(`↑ push ${push} · blocked ${blocked}`);
|
|
121
|
+
})();
|
|
122
|
+
const loginPart = anySecrets ? stripPad(loginCell, 18) + secretsCell : loginCell;
|
|
123
|
+
console.log(` ${row.device.padEnd(nameWidth)} ${stripPad(cli, 12)}${stripPad(agentsCell, 20)}${stripPad(configCell, 10)}${loginPart}`);
|
|
104
124
|
}
|
|
105
125
|
console.log();
|
|
106
126
|
console.log(chalk.gray(` ${plan.actions.length} action(s) across ${rows.filter((r) => r.probe.reachable).length} reachable device(s)`));
|
|
127
|
+
// The capability is opt-in, so when it is OFF and the manifest declares
|
|
128
|
+
// bundles, say that it exists. Otherwise an operator reads "manual recreate"
|
|
129
|
+
// and concludes there is no supported path — which is exactly the conclusion
|
|
130
|
+
// that led to a master key being hand-exported across the fleet (RUSH-1968).
|
|
131
|
+
if (anySecrets && !rows.some((r) => r.actions.some((a) => a.kind === 'push-secret'))) {
|
|
132
|
+
console.log(chalk.gray(' secrets: not pushed. `--provision-secrets` pushes declared bundles to devices whose host key is pinned.'));
|
|
133
|
+
}
|
|
107
134
|
// Distinguish *why* a login can't be propagated: macOS keychain-bound,
|
|
108
135
|
// single-use rotating refresh token (never copied), or the source simply not
|
|
109
136
|
// being signed in to that agent (no portable file).
|
|
@@ -130,14 +157,15 @@ function renderPlan(plan) {
|
|
|
130
157
|
if (noToken.length > 0) {
|
|
131
158
|
console.log(chalk.yellow(` manual login needed (no portable token on source): ${noToken.join(', ')}`));
|
|
132
159
|
}
|
|
133
|
-
// Secrets bundles are declared once for the fleet; surface the distinct set
|
|
134
|
-
//
|
|
160
|
+
// Secrets bundles are declared once for the fleet; surface the distinct set the
|
|
161
|
+
// gate did NOT push, so a refusal is never silent. "never pushed" used to be
|
|
162
|
+
// literally true here and no longer is — `--provision-secrets` pushes them.
|
|
135
163
|
const bundles = [...new Set(rows.flatMap((r) => r.secretsNeeded))];
|
|
136
164
|
if (bundles.length > 0) {
|
|
137
165
|
const shown = bundles.slice(0, 12);
|
|
138
166
|
const more = bundles.length - shown.length;
|
|
139
167
|
const list = shown.join(', ') + (more > 0 ? `, +${more} more` : '');
|
|
140
|
-
console.log(chalk.yellow(` ${bundles.length} secrets bundle(s)
|
|
168
|
+
console.log(chalk.yellow(` ${bundles.length} secrets bundle(s) not pushed — recreate where missing: ${list}`));
|
|
141
169
|
}
|
|
142
170
|
}
|
|
143
171
|
/** padEnd on the visible width, ignoring chalk color codes. Exported for tests. */
|
|
@@ -218,11 +246,24 @@ async function runApply(opts) {
|
|
|
218
246
|
// Probe every target device in parallel.
|
|
219
247
|
const nameToProfile = new Map(desired.map((d) => [d.device, registry[d.device]]));
|
|
220
248
|
const withVersions = rosterNeedsVersions(desired);
|
|
249
|
+
// One extra `secrets list --json` per device, and only when it can change the
|
|
250
|
+
// plan: the manifest declares bundles AND provisioning is on. Same cost
|
|
251
|
+
// discipline as `withVersions` — a fleet that uses no bundles never pays it.
|
|
252
|
+
const withSecrets = opts.provisionSecrets === true && (manifest.secrets?.bundles?.length ?? 0) > 0;
|
|
221
253
|
console.log(chalk.gray(`Probing ${desired.length} device(s)…`));
|
|
222
|
-
const probeList = await pool(desired, 6, async (d) => probeDevice(nameToProfile.get(d.device), { withVersions }));
|
|
254
|
+
const probeList = await pool(desired, 6, async (d) => probeDevice(nameToProfile.get(d.device), { withVersions, withSecrets }));
|
|
223
255
|
const probes = new Map(probeList.map((p) => [p.device, p]));
|
|
224
256
|
const targetCliVersion = localCliVersion();
|
|
225
|
-
let plan = diffFleet(desired, probes, {
|
|
257
|
+
let plan = diffFleet(desired, probes, {
|
|
258
|
+
targetCliVersion,
|
|
259
|
+
sourceAuth,
|
|
260
|
+
secretsBundles: manifest.secrets?.bundles,
|
|
261
|
+
provisionSecrets: opts.provisionSecrets === true,
|
|
262
|
+
forceSecrets: opts.force === true,
|
|
263
|
+
// Same bar as `exec --copy-creds` (EXEC-34): credential values only ever go
|
|
264
|
+
// to a host whose key we already pinned.
|
|
265
|
+
isHostPinned: (device) => isHostPinned(device, managedKnownHostsPath()),
|
|
266
|
+
});
|
|
226
267
|
// --only filter.
|
|
227
268
|
if (opts.only) {
|
|
228
269
|
const keep = new Set();
|
|
@@ -242,7 +283,8 @@ async function runApply(opts) {
|
|
|
242
283
|
return;
|
|
243
284
|
// `needs-login`/`needs-secret` are surfaced manual reminders, not executable
|
|
244
285
|
// mutations — exclude them so an otherwise-converged fleet still says "nothing
|
|
245
|
-
// to do" instead of looping forever on un-actionable surfacing.
|
|
286
|
+
// to do" instead of looping forever on un-actionable surfacing. `push-secret`
|
|
287
|
+
// IS executable and deliberately stays counted.
|
|
246
288
|
if (plan.actions.filter((a) => a.kind !== 'needs-login' && a.kind !== 'needs-secret').length === 0) {
|
|
247
289
|
console.log(chalk.green('\nNothing to do — fleet already matches the profile.'));
|
|
248
290
|
return;
|
|
@@ -301,6 +343,8 @@ export function configureApplyCommand(cmd) {
|
|
|
301
343
|
.option('--agent <specs...>', 'Override the roster for targeted device(s): install these specs instead of the manifest\'s. Use `claude@all` to replicate every version installed on this machine.')
|
|
302
344
|
.option('--only <dims>', 'Limit to dimensions: comma list of agents,config,login')
|
|
303
345
|
.option('--no-login', 'Do not propagate logins')
|
|
346
|
+
.option('--provision-secrets', "Push the manifest's declared secrets bundles to each device (OFF by default; moves credential values over SSH, and only to a device whose host key is already pinned)")
|
|
347
|
+
.option('--force', 'With --provision-secrets: re-push a bundle the device already has')
|
|
304
348
|
.addOption(new Option('--recv-auth', 'internal: receive an auth bundle on stdin').hideHelp())
|
|
305
349
|
.action(async (opts) => {
|
|
306
350
|
try {
|
package/dist/commands/browser.js
CHANGED
|
@@ -20,6 +20,7 @@ import { isInteractiveTerminal } from './utils.js';
|
|
|
20
20
|
import { registerCommandGroups, setHelpSections } from '../lib/help.js';
|
|
21
21
|
import { buildHar } from '../lib/browser/har.js';
|
|
22
22
|
import { getCliVersion } from '../lib/version.js';
|
|
23
|
+
import { runBrowserIPCStream } from '../lib/browser/stream.js';
|
|
23
24
|
/**
|
|
24
25
|
* Resolve which browser task a command targets. Order:
|
|
25
26
|
* 1. `--task <name>` flag (explicit per-command override)
|
|
@@ -48,6 +49,7 @@ const TASK_OPTION_DESC = 'Task name (defaults to $AGENTS_BROWSER_TASK)';
|
|
|
48
49
|
// trailing "Other commands" section automatically.
|
|
49
50
|
const BROWSER_HELP_GROUPS = [
|
|
50
51
|
{ title: 'Session lifecycle', names: ['start', 'done', 'status'] },
|
|
52
|
+
{ title: 'Fast action loop', names: ['stream'] },
|
|
51
53
|
{
|
|
52
54
|
title: 'Drive the page',
|
|
53
55
|
names: ['navigate', 'tabs', 'screenshot', 'evaluate', 'click', 'type', 'press', 'wait'],
|
|
@@ -79,6 +81,9 @@ export function registerBrowserCommand(program) {
|
|
|
79
81
|
agents browser navigate https://example.com
|
|
80
82
|
agents browser screenshot
|
|
81
83
|
|
|
84
|
+
# Keep one process and daemon socket warm for repeated actions
|
|
85
|
+
agents browser stream --task "$AGENTS_BROWSER_TASK"
|
|
86
|
+
|
|
82
87
|
# Drive another machine's browser (needs its consent — see remote-control)
|
|
83
88
|
agents browser start --host zion
|
|
84
89
|
|
|
@@ -654,6 +659,36 @@ function registerTaskCommands(browser) {
|
|
|
654
659
|
? 'Other fleet machines can now drive this browser via `browser --host <this-device>`.'
|
|
655
660
|
: 'Cross-machine `browser --host` drives to this machine are refused.');
|
|
656
661
|
});
|
|
662
|
+
const stream = browser
|
|
663
|
+
.command('stream')
|
|
664
|
+
.description('Keep one process and daemon IPC socket open; read NDJSON requests from stdin and write NDJSON responses')
|
|
665
|
+
.option(TASK_OPTION_FLAG, 'Default task for requests that omit `task` (defaults to $AGENTS_BROWSER_TASK)')
|
|
666
|
+
.action(async (opts) => {
|
|
667
|
+
await runBrowserIPCStream({
|
|
668
|
+
input: process.stdin,
|
|
669
|
+
output: process.stdout,
|
|
670
|
+
task: opts.task ?? process.env.AGENTS_BROWSER_TASK,
|
|
671
|
+
actor: resolveActor().id,
|
|
672
|
+
launchId: process.env.AGENT_LAUNCH_ID,
|
|
673
|
+
});
|
|
674
|
+
});
|
|
675
|
+
setHelpSections(stream, {
|
|
676
|
+
examples: `
|
|
677
|
+
# Batch two warm actions through one process and one daemon connection
|
|
678
|
+
printf '%s\\n' \\
|
|
679
|
+
'{"action":"screenshot","path":"/tmp/page.jpg"}' \\
|
|
680
|
+
'{"action":"click","atX":320,"atY":540}' \\
|
|
681
|
+
| agents browser stream --task "$AGENTS_BROWSER_TASK"
|
|
682
|
+
|
|
683
|
+
# Keep the command open and send one JSON object per line from a long-lived shell
|
|
684
|
+
agents browser stream --task "$AGENTS_BROWSER_TASK"
|
|
685
|
+
`,
|
|
686
|
+
notes: `
|
|
687
|
+
stdout is protocol-only: one compact JSON response for each non-empty input line.
|
|
688
|
+
Malformed JSON returns an error response without closing the stream.
|
|
689
|
+
The first start response becomes the default task for later lines in the same stream.
|
|
690
|
+
`,
|
|
691
|
+
});
|
|
657
692
|
browser
|
|
658
693
|
.command('start')
|
|
659
694
|
.description('Start a browser task. Pass --profile <name>; omit to use your configured default (`agents browser profiles set-default`), else auto-pick an installed Chromium-family browser.')
|
package/dist/commands/doctor.js
CHANGED
|
@@ -26,6 +26,8 @@ import { checkVersionHookWiring, inspectDuplicateVersionHooks, registerHooksToSe
|
|
|
26
26
|
import { isVersionIsolated } from '../lib/versions.js';
|
|
27
27
|
import { computeDrift, checkSyncStatus, countOrphans, computeSourceBehind } from '../lib/drift.js';
|
|
28
28
|
import { readAuthHealthCache, summarizeHostAuth } from '../lib/auth-health.js';
|
|
29
|
+
import { readMeta } from '../lib/state.js';
|
|
30
|
+
import { probeOwnerSink } from '../lib/channels/owner-sink.js';
|
|
29
31
|
import { unifiedDiff, colorizeUnifiedDiff } from '../lib/diff-text.js';
|
|
30
32
|
import { listCliStatus, listCliStatusAsync } from '../lib/cli-resources.js';
|
|
31
33
|
import { setHelpSections } from '../lib/help.js';
|
|
@@ -405,6 +407,9 @@ async function runDevicesDoctor(opts) {
|
|
|
405
407
|
isolatedVersions: localReports
|
|
406
408
|
.filter((rep) => isVersionIsolated(rep.agent, rep.version))
|
|
407
409
|
.map((rep) => `${rep.agent}@${rep.version}`),
|
|
410
|
+
// Can the owner-delivery lane escalate a block from THIS box? Local only —
|
|
411
|
+
// remote boxes self-report it in their own `agents doctor --json`.
|
|
412
|
+
ownerSink: await probeOwnerSink(readMeta()),
|
|
408
413
|
}));
|
|
409
414
|
accounts[localName] = r.inventory?.signIn ?? {};
|
|
410
415
|
continue;
|
|
@@ -1486,6 +1491,9 @@ export function registerDoctorCommand(program) {
|
|
|
1486
1491
|
? { platform: process.platform, policy: getEffectiveExecutionPolicy() }
|
|
1487
1492
|
: undefined,
|
|
1488
1493
|
isolatedVersions,
|
|
1494
|
+
// Can the owner-delivery lane (feed/notify) escalate a block from this
|
|
1495
|
+
// box? A factory that cannot escalate is not healthy (RUSH-2262).
|
|
1496
|
+
ownerSink: await probeOwnerSink(readMeta()),
|
|
1489
1497
|
});
|
|
1490
1498
|
if (opts.json) {
|
|
1491
1499
|
const overviewPayload = {
|
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Insights command —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* agents
|
|
9
|
-
*
|
|
10
|
-
* agents
|
|
11
|
-
* agents
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
2
|
+
* Insights command — one observe verb for "how work looks".
|
|
3
|
+
*
|
|
4
|
+
* Two data paths under one name (do not re-split into peer top-level commands):
|
|
5
|
+
*
|
|
6
|
+
* agents insights HOW you work (transcript content: tools, friction,
|
|
7
|
+
* rhythm, edits) — split by Claude account by default
|
|
8
|
+
* agents insights mix COUNTERS (sessions index + usage.db recipes:
|
|
9
|
+
* harness/model mix, token ratios, secrets, browser)
|
|
10
|
+
* agents insights <recipe> One baked mix recipe (harness-mix, tools-per-session, …)
|
|
11
|
+
* agents insights query Raw usage.db rows
|
|
12
|
+
*
|
|
13
|
+
* Sibling observe verbs (stay separate — different questions):
|
|
14
|
+
*
|
|
15
|
+
* agents cost what you spent ($ and duration)
|
|
16
|
+
* agents output what shipped (burn vs PRs and commits)
|
|
17
|
+
* agents usage live quota headroom
|
|
18
|
+
* agents perf latency (hooks, CLI commands, agent.run) — not popularity
|
|
19
|
+
* agents sessions stats which skills/slash-commands were explicitly invoked
|
|
20
|
+
*
|
|
21
|
+
* Why mix lives here (not a second top-level `trends`): two abstract "analytics"
|
|
22
|
+
* nouns taught agents and humans to guess. One verb, two engines — cheap SQL mix
|
|
23
|
+
* vs transcript facets. Latency stays on `perf` so it is never confused with mix.
|
|
21
24
|
*
|
|
22
25
|
* Modelled on Claude Code's `/insights`, with the difference that motivated it: that
|
|
23
26
|
* command reads one account's directory, while `balanced` rotation sprays sessions
|
|
@@ -27,6 +30,9 @@
|
|
|
27
30
|
* The deterministic report makes zero network calls. `--narrative` is opt-in and adds
|
|
28
31
|
* the coaching prose by piping the AGGREGATE (never raw transcripts) through a headless
|
|
29
32
|
* `claude -p`.
|
|
33
|
+
*
|
|
34
|
+
* `agents trends` is a thin deprecated alias of the mix tree only (see commands/trends.ts).
|
|
30
35
|
*/
|
|
31
36
|
import type { Command } from 'commander';
|
|
32
37
|
export declare function registerInsightsCommand(program: Command): void;
|
|
38
|
+
export declare function registerSessionsInsightsCommand(sessions: Command): void;
|