@phnx-labs/agents-cli 1.20.88 → 1.20.90
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 +503 -0
- package/README.md +15 -1
- package/dist/bin/agents +0 -0
- package/dist/commands/commands.js +7 -7
- package/dist/commands/exec.js +7 -1
- package/dist/commands/factory.js +26 -2
- package/dist/commands/funnel.js +16 -1
- package/dist/commands/harness.d.ts +27 -0
- package/dist/commands/harness.js +120 -13
- package/dist/commands/menubar.js +117 -34
- package/dist/commands/profiles.d.ts +3 -0
- package/dist/commands/profiles.js +1 -1
- package/dist/commands/routines.d.ts +19 -0
- package/dist/commands/routines.js +51 -7
- package/dist/commands/secrets-rotate-passphrase.d.ts +17 -0
- package/dist/commands/secrets-rotate-passphrase.js +96 -0
- package/dist/commands/secrets.d.ts +10 -1
- package/dist/commands/secrets.js +20 -6
- package/dist/commands/sessions-browser.d.ts +4 -0
- package/dist/commands/sessions-browser.js +51 -9
- package/dist/commands/sessions-favorite.d.ts +20 -0
- package/dist/commands/sessions-favorite.js +120 -0
- package/dist/commands/sessions.d.ts +110 -21
- package/dist/commands/sessions.js +395 -74
- package/dist/commands/setup-secrets.d.ts +7 -0
- package/dist/commands/setup-secrets.js +12 -9
- package/dist/commands/versions.js +12 -4
- package/dist/commands/view.d.ts +14 -1
- package/dist/commands/view.js +103 -128
- package/dist/commands/webhook.js +7 -2
- package/dist/lib/agents.d.ts +4 -2
- package/dist/lib/agents.js +21 -6
- package/dist/lib/commands.js +9 -1
- package/dist/lib/daemon.d.ts +29 -0
- package/dist/lib/daemon.js +58 -4
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/factory/snapshot.d.ts +78 -0
- package/dist/lib/factory/snapshot.js +209 -0
- package/dist/lib/fs-atomic.d.ts +14 -1
- package/dist/lib/fs-atomic.js +35 -3
- package/dist/lib/funnel.d.ts +1 -0
- package/dist/lib/funnel.js +8 -0
- package/dist/lib/hosts/dispatch.js +19 -1
- package/dist/lib/hq/floor.js +12 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +2 -2
- package/dist/lib/menubar/install-menubar.d.ts +53 -2
- package/dist/lib/menubar/install-menubar.js +183 -28
- package/dist/lib/picker.d.ts +27 -2
- package/dist/lib/picker.js +71 -7
- package/dist/lib/platform/process.d.ts +2 -0
- package/dist/lib/platform/process.js +5 -3
- package/dist/lib/profiles.d.ts +48 -0
- package/dist/lib/profiles.js +67 -0
- package/dist/lib/resources.d.ts +8 -0
- package/dist/lib/resources.js +34 -1
- package/dist/lib/rotate.d.ts +24 -2
- package/dist/lib/rotate.js +63 -6
- package/dist/lib/routines-placement.d.ts +2 -1
- package/dist/lib/routines-placement.js +8 -4
- package/dist/lib/routines.d.ts +57 -1
- package/dist/lib/routines.js +74 -1
- package/dist/lib/runner.d.ts +2 -0
- package/dist/lib/runner.js +21 -8
- 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/bundles.js +9 -34
- package/dist/lib/secrets/filestore.d.ts +152 -34
- package/dist/lib/secrets/filestore.js +676 -123
- package/dist/lib/session/active.d.ts +109 -3
- package/dist/lib/session/active.js +269 -13
- package/dist/lib/session/db.d.ts +14 -0
- package/dist/lib/session/db.js +35 -0
- package/dist/lib/session/favorites.d.ts +39 -0
- package/dist/lib/session/favorites.js +101 -0
- package/dist/lib/session/host-link.d.ts +68 -0
- package/dist/lib/session/host-link.js +64 -0
- package/dist/lib/session/presence.d.ts +85 -0
- package/dist/lib/session/presence.js +150 -0
- package/dist/lib/session/remote-active.d.ts +4 -1
- package/dist/lib/session/remote-active.js +8 -2
- package/dist/lib/session/remote-list.d.ts +10 -0
- package/dist/lib/session/remote-list.js +47 -9
- package/dist/lib/session/viewing-in.d.ts +31 -0
- package/dist/lib/session/viewing-in.js +47 -0
- package/dist/lib/state.d.ts +17 -0
- package/dist/lib/state.js +30 -2
- package/dist/lib/tmux/binary.d.ts +7 -0
- package/dist/lib/tmux/binary.js +11 -1
- package/dist/lib/triggers/handlers.d.ts +95 -0
- package/dist/lib/triggers/handlers.js +384 -0
- package/dist/lib/triggers/webhook.d.ts +10 -2
- package/dist/lib/triggers/webhook.js +65 -11
- package/dist/lib/types.d.ts +4 -3
- package/dist/lib/usage-backoff.d.ts +29 -0
- package/dist/lib/usage-backoff.js +165 -0
- package/dist/lib/usage.d.ts +112 -5
- package/dist/lib/usage.js +464 -46
- package/dist/lib/watchdog/runner.d.ts +13 -0
- package/dist/lib/watchdog/runner.js +16 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,508 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.20.90
|
|
4
|
+
|
|
5
|
+
- **`agents sessions --active` now shows one row per agent, not one per directory.**
|
|
6
|
+
A live tmux agent pane whose durable identity records were missing (the common case
|
|
7
|
+
once meta/pid-registry entries age out) was dropped, then re-surfaced by the ps-scan
|
|
8
|
+
under the newest transcript in its cwd — so many distinct sessions collapsed onto one
|
|
9
|
+
stranger's id with an inflated `×N` badge, and `agents sessions focus <id>` could not
|
|
10
|
+
find them. The scanner now recovers the session id straight from the `ag-<agent>-<shortid>`
|
|
11
|
+
tmux pane name (resolved to the full UUID via the short-id index in one batched query),
|
|
12
|
+
and refuses to borrow a co-located sibling's transcript when no id is known — so every
|
|
13
|
+
live session surfaces as its own row and is focus-able again. Also adds a `runTmux`
|
|
14
|
+
timeout so a wedged tmux server can't hang the scan. Source: `apps/cli/src/lib/session/active.ts`.
|
|
15
|
+
|
|
16
|
+
- **`agents view` now shows live usage bars for Antigravity.** The `agy` account
|
|
17
|
+
row renders one bar per model quota bucket (`3.1P: ███░░ 42% (1d)` style),
|
|
18
|
+
sourced from the same Google Code Assist `:retrieveUserQuota` endpoint `agy`
|
|
19
|
+
itself talks to. Auth reuses the stored `agy` OAuth credential (macOS Keychain
|
|
20
|
+
item `gemini`/`antigravity`, Linux Secret Service, or the
|
|
21
|
+
`~/.gemini/antigravity-cli/antigravity-oauth-token` file fallback), refreshing
|
|
22
|
+
the access token in memory when expired — safe from a read path because
|
|
23
|
+
Google's refresh tokens are non-rotating, and never written back to the
|
|
24
|
+
keychain. Each per-model bucket also flows into the throttle badge, run
|
|
25
|
+
rotation eligibility, and `agents view --json` (whose usage windows now carry
|
|
26
|
+
a `label` so same-keyed per-model bars are distinguishable). Source:
|
|
27
|
+
`apps/cli/src/lib/usage.ts`, `apps/cli/src/lib/agents.ts`,
|
|
28
|
+
`apps/cli/src/commands/view.ts`.
|
|
29
|
+
|
|
30
|
+
- **A custom harness is now its own agent type in `agents view`.** A harness created
|
|
31
|
+
with `agents harness add` (or `agents profiles add`) used to render as an indented
|
|
32
|
+
`profile` row under whichever host CLI executes it. It now gets its own block beside
|
|
33
|
+
Claude and Codex — a bold name header, then one row carrying the pinned model, the
|
|
34
|
+
account/auth state, and `via <host> <version>` naming the native harness underneath.
|
|
35
|
+
That matches how it is already launched: `agents run <name>` treats a custom harness
|
|
36
|
+
exactly like a native agent id. A harness whose host CLI has no install is flagged
|
|
37
|
+
`(host <id> not installed)` rather than listed as runnable, and the separate
|
|
38
|
+
"Profile-only Agents" section is gone — those harnesses now render in the main list
|
|
39
|
+
like every other one. Source: `apps/cli/src/commands/view.ts`.
|
|
40
|
+
- **`agents view <harness>` describes a custom harness** — host, model, provider, auth,
|
|
41
|
+
fork lineage, YAML path — instead of failing with "unknown agent";
|
|
42
|
+
`agents view <harness> --json` emits its summary. Source:
|
|
43
|
+
`apps/cli/src/commands/harness.ts` (`renderHarnessDetail`).
|
|
44
|
+
- **New `agents harness fork <source> <name>`.** One verb over both starting points:
|
|
45
|
+
fork a native harness (`agents harness fork opencode deepseek --model
|
|
46
|
+
deepseek/deepseek-v4-flash-0731 --auth-provider openrouter`) or copy a custom one you
|
|
47
|
+
already tuned and change only what you name (`agents harness fork deepseek
|
|
48
|
+
deepseek-chat --model deepseek/deepseek-chat-v3`). Forking a custom harness is a full
|
|
49
|
+
copy — env, endpoint, auth binding, `fallback_model`, host version pin — so the two
|
|
50
|
+
diverge and deleting the source never affects the fork; forking a native harness
|
|
51
|
+
requires `--model` because there is no model to inherit. Flags: `--model`,
|
|
52
|
+
`--base-url`, `--auth-provider`, `--version`, `--label`, `--description`,
|
|
53
|
+
`--key-stdin`, `--force`. Source: `apps/cli/src/lib/profiles.ts` (`forkProfile`).
|
|
54
|
+
- **Profile YAML gains optional `label:` and `forkedFrom:`.** `label` sets the name
|
|
55
|
+
`agents view` prints for the harness (defaults to the file name); `forkedFrom` records
|
|
56
|
+
the parent as display-only lineage. Existing profiles keep working untouched. Source:
|
|
57
|
+
`apps/cli/src/lib/profiles.ts`.
|
|
58
|
+
- **Breaking (`--json`):** in `agents view <agent> --json`, the per-agent `profiles` key
|
|
59
|
+
is now `harnesses`, and each entry carries new `label`, `hostVersion`, `description`,
|
|
60
|
+
and `forkedFrom` fields alongside the existing ones. Source:
|
|
61
|
+
`apps/cli/src/commands/view.ts` (`ViewJsonAgent`).
|
|
62
|
+
|
|
63
|
+
- **Menu bar ACTIVE: project accordion + session detail submenu.** Projects are
|
|
64
|
+
collapsed by default as a status strip (`▶ agents-cli ●8 ◐1 zion`); click
|
|
65
|
+
`▶`/`▼` to fold agents open under the project (idle-row caps removed — collapse
|
|
66
|
+
is the wall protection). Focusing an agent opens a side submenu with linkable
|
|
67
|
+
detail (work title URL, cwd, Linear ticket, GitHub PR, duration, copy session
|
|
68
|
+
id) from the warm `sessions --active` cache. Accordion reopen rebuilds from
|
|
69
|
+
cache only (no teams walk / no CLI schedule). Local/remote uses the same host
|
|
70
|
+
normalize as CLI `machineId()` so local rows are not mislabeled remote. Source:
|
|
71
|
+
`apps/cli/menubar/Sources/MenubarHelper/StatusItemController.swift`,
|
|
72
|
+
`LocalState.swift`, `Models.swift`.
|
|
73
|
+
|
|
74
|
+
- **An offloaded editor tab no longer displays another session's id.** A Factory
|
|
75
|
+
tab launched with `agents run --host <device>` has no local agent process, but
|
|
76
|
+
the extension still resolved its "live" session id by reading the SessionStart
|
|
77
|
+
hook's `~/.agents/.cache/state/sessions/<pid>.json` for the local pid tree —
|
|
78
|
+
the pid of the ssh client. Those files are keyed by pid alone and are only
|
|
79
|
+
pruned when the pid is dead, so once the OS recycled a pid the tab adopted
|
|
80
|
+
whatever session had last held it: one remote tab showed the id and version of
|
|
81
|
+
an unrelated synthetic run from 20 days earlier while `/status` inside it
|
|
82
|
+
reported the truth. An offloaded tab now takes its identity from the device
|
|
83
|
+
instead of local disk, and a local tab rejects any state record whose
|
|
84
|
+
SessionStart timestamp predates the tab itself.
|
|
85
|
+
- **`AGENT_TERMINAL_ID` now rides the SSH hop.** `agents run --host` forwarded
|
|
86
|
+
actor provenance but not the launching tab's terminal id, so the remote pid
|
|
87
|
+
registry recorded no terminal — leaving `agents sessions --active --host
|
|
88
|
+
<device>` unable to answer "which session is this tab running?" once the agent
|
|
89
|
+
moved on (a `/clear`, or an exit and rerun in the same tab).
|
|
90
|
+
- **`agents sessions --active --json` now carries `terminalId`.** The pid registry
|
|
91
|
+
has always recorded it; the emitted row dropped it, so no consumer could join a
|
|
92
|
+
live session back to the editor tab that launched it.
|
|
93
|
+
|
|
94
|
+
- **Balanced routing no longer launches into an account it only *thinks* has
|
|
95
|
+
headroom.** Account usage is cached per machine under stale-while-revalidate:
|
|
96
|
+
a snapshot up to 24h old was served instantly, and the background refresh that
|
|
97
|
+
should have corrected it lands after the pick is already made. On a box whose
|
|
98
|
+
refresh is failing that state is permanent — measured on `yosemite-s1`, every
|
|
99
|
+
Claude snapshot sat 26 hours to 2.7 days old, so balanced read
|
|
100
|
+
`muqsit@getrush.ai` as 48% used and launched into it while the account was at
|
|
101
|
+
its weekly cap; the session answered "You've hit your weekly limit" on its
|
|
102
|
+
first turn. Routing now caps how stale a snapshot may be when it is about to
|
|
103
|
+
decide (5 minutes), blocking on one bounded, parallel live read past that — and
|
|
104
|
+
no read at all inside the existing 2-minute fresh window, which back-to-back
|
|
105
|
+
launches hit. Display paths (`agents view`) keep the full 24h window and stay
|
|
106
|
+
off the network.
|
|
107
|
+
- **A pick made on unconfirmed data says so.** When no account on the machine
|
|
108
|
+
could be refreshed, routing still launches — a broken refresh must not make a
|
|
109
|
+
box unusable — but the banner now reads `… (2 of 5 healthy, usage unverified —
|
|
110
|
+
no account could be refreshed)` instead of presenting a guess as a fact. An
|
|
111
|
+
account with a verified snapshot always wins over one with a stale snapshot,
|
|
112
|
+
even when the stale number looks emptier. This applies to `--strategy
|
|
113
|
+
available` as well as `balanced` — both route on the same cache, and
|
|
114
|
+
`available`'s headroom sort was inverted by a stale number in exactly the same
|
|
115
|
+
way. An explicit version preference is an instruction, not a ranking signal, so
|
|
116
|
+
it still wins.
|
|
117
|
+
- **The mid-run failover chain is unchanged.** Declining to *pick* an account on
|
|
118
|
+
unconfirmed data and declining to *fail over to* it after the primary already
|
|
119
|
+
hit a 429 are different risks — by then the alternative is not launching at
|
|
120
|
+
all. Every eligible account stays in the failover chain; only the initial pick
|
|
121
|
+
prefers verified ones.
|
|
122
|
+
|
|
123
|
+
- **`agents routines list` no longer reports another device's routine as failed.** Run
|
|
124
|
+
records are written into the runs dir of whichever machine fired the routine and carry
|
|
125
|
+
no device attribution, but the listing resolved Last Status from any local record and
|
|
126
|
+
rendered it even on rows for routines pinned elsewhere. A routine re-pinned to another
|
|
127
|
+
device therefore kept reporting the old machine's leftover records forever — on zion,
|
|
128
|
+
`security-sweep`, `review-open-prs` and `hetzner-lease-gc` all read `failed` from late
|
|
129
|
+
July while `yosemite-s0`/`s1`, the devices that actually fire them, had completed them
|
|
130
|
+
that morning. The macOS menu bar reads this JSON, so it painted a column of red `exit 1`
|
|
131
|
+
rows for routines that were green. Last Status is now scoped to the device that owns the
|
|
132
|
+
run: a routine this device does not fire shows `-`, and `--json` returns `null` for
|
|
133
|
+
`lastStatus`, `exitCode`, `failureReason`, `lastRunStartedAt` and `lastRunCompletedAt`
|
|
134
|
+
(`runsHere: false` already says why). A routine pinned to several devices renders one row
|
|
135
|
+
per device but carries a status only on its **This machine** row. Read a peer's status
|
|
136
|
+
with `agents routines list --device <name>`; the local history is untouched and still
|
|
137
|
+
readable via `agents routines runs <name>`. Source: `apps/cli/src/commands/routines.ts`
|
|
138
|
+
(`localLatestRun`, `groupRoutineJobsByDevice`), `apps/cli/docs/03-routines.md`.
|
|
139
|
+
|
|
140
|
+
- **`agents watchdog` now tracks per-session presence (RUSH-2007 Layer C).** Each
|
|
141
|
+
tick reconciles a per-session presence record — `{location, device, transport,
|
|
142
|
+
lastSeen, status}` at `~/.agents/.cache/state/watchdog/presence.json` — from the
|
|
143
|
+
tick's active scan, deriving `connected` / `disconnected` by diffing consecutive
|
|
144
|
+
ticks. A session that was tracked but is now absent (its SSH link dropped or the
|
|
145
|
+
peer went unreachable) flips to `disconnected`, and the flip is surfaced in
|
|
146
|
+
`agents watchdog --json` under `presence.transitions` — an interactive drop as a
|
|
147
|
+
`reconnect-nudge` candidate, a headless remote as `keep-alive`. Folded into the
|
|
148
|
+
existing tick (no revived daemon, no extra SSH fan-out); additive and does not
|
|
149
|
+
change the tick's nudge decisions. Source:
|
|
150
|
+
`apps/cli/src/lib/session/presence.ts`, `apps/cli/src/lib/watchdog/runner.ts`.
|
|
151
|
+
|
|
152
|
+
- **`agents setup secrets --policy hold` no longer fails, and `agents secrets
|
|
153
|
+
status` stops naming the retired `daily` policy.** The 1.20.79 `daily` → `hold`
|
|
154
|
+
rename swept the help, docs, and the `secrets list` POLICY column, but two
|
|
155
|
+
surfaces were never migrated. The worse one was functional: the onboarding
|
|
156
|
+
wizard carried its own copy of the policy vocabulary, so
|
|
157
|
+
`agents setup secrets --policy hold` — the canonical name every other secrets
|
|
158
|
+
command prints — exited with `Invalid --policy 'hold'. Use daily, always, or
|
|
159
|
+
never.`, and its interactive prompt still offered `daily` as the default
|
|
160
|
+
choice. It now shares `parsePolicyOpt` with `agents secrets policy`, so the two
|
|
161
|
+
commands can't disagree about what a policy is called; `daily`/`session` stay
|
|
162
|
+
accepted as aliases and the wizard's default is unchanged (the hold tier). The
|
|
163
|
+
second was cosmetic: `agents secrets status` printed "a daily bundle prompts
|
|
164
|
+
once…" and "the next read of each daily bundle…" — the one command a user runs
|
|
165
|
+
to answer *why did it prompt again*, naming a policy its sibling commands no
|
|
166
|
+
longer emit. Both lines now say `hold` and are pure values pinned by tests, so
|
|
167
|
+
the vocabulary can't drift again. Source:
|
|
168
|
+
`apps/cli/src/commands/setup-secrets.ts`, `apps/cli/src/commands/secrets.ts`.
|
|
169
|
+
|
|
170
|
+
- **Favorite sessions from the browser.** `*` stars the highlighted session in
|
|
171
|
+
`agents sessions` and `f` filters the list to the starred ones; outside a TTY,
|
|
172
|
+
`agents sessions favorite <id>` (`--remove` / `--list` / `--json`) and
|
|
173
|
+
`agents sessions --favorites` do the same. Stars live in
|
|
174
|
+
`~/.agents/.history/favorites.json` keyed by session id, so they survive a reindex
|
|
175
|
+
of the session cache. They are per-machine — session sync carries transcripts, not
|
|
176
|
+
this file. Source:
|
|
177
|
+
`apps/cli/src/lib/session/favorites.ts`, `apps/cli/src/commands/sessions-favorite.ts`.
|
|
178
|
+
- **Detect sessions that lost their host — two new statuses, `crashed` and `orphaned`.**
|
|
179
|
+
A session whose editor window or connection went down hard used to just VANISH from
|
|
180
|
+
`agents sessions --active` (its dead-pid registry entry was filtered out), and one
|
|
181
|
+
still running in tmux with nobody attached reported a plain `idle`. Both now say so:
|
|
182
|
+
`✗ crashed` when the host window stopped republishing and the agent died with it,
|
|
183
|
+
`◍ orphan` when the agent is alive with zero clients attached. Derived from tmux's
|
|
184
|
+
`#{session_attached}` and the IDE window's registry heartbeat — never from a
|
|
185
|
+
deliberate `agents sessions detach`, and never over a session that is still working.
|
|
186
|
+
Source: `apps/cli/src/lib/session/host-link.ts`, `apps/cli/src/lib/session/active.ts`.
|
|
187
|
+
- **`agents sessions --active --favorites` now actually filters.** The flag was wired
|
|
188
|
+
into the interactive browser only, so every path that skips it — `--json`,
|
|
189
|
+
`--waiting`, a pipe, a multi-host scope, an SSH-fanout peer — silently returned the
|
|
190
|
+
whole fleet. Source: `apps/cli/src/commands/sessions.ts`.
|
|
191
|
+
- **`agents sessions --active --waiting` no longer counts a dead session.** `activity`
|
|
192
|
+
is not rewritten when a session dies, so one that crashed mid-question reported "needs
|
|
193
|
+
your input" forever — what it needs is a relaunch. Source:
|
|
194
|
+
`apps/cli/src/commands/sessions.ts`.
|
|
195
|
+
|
|
196
|
+
- **Resolve historical sessions safely across the fleet (#1757).** `agents sessions --resolve <full-id|prefix|keywords> --json` uses a versioned safe peer protocol, returns only resolver metadata, reports every full-ID candidate on ambiguity, treats synced copies as one match, and exits 2 without deciding when a peer fails, returns malformed output, or runs an older CLI. Source: `apps/cli/src/commands/sessions.ts`.
|
|
197
|
+
|
|
198
|
+
- **A rate-limited usage endpoint is now backed off instead of hammered.** The
|
|
199
|
+
daemon warms auth-health every 3 minutes and probes *every installed version
|
|
200
|
+
home* in one parallel batch, so a machine with five Claude accounts sent five
|
|
201
|
+
concurrent requests to `api.anthropic.com/api/oauth/usage` every three minutes
|
|
202
|
+
— roughly 100/hour — before the usage refresh added its own. Nothing read
|
|
203
|
+
`Retry-After`. Measured on `yosemite-s1`: the endpoint answered
|
|
204
|
+
`429 rate_limit_error` with `retry-after: 2678` (about 45 minutes) for every
|
|
205
|
+
account while the credentials themselves read healthy, and the next tick fired
|
|
206
|
+
three minutes later, deep inside the penalty window, re-arming it. The box
|
|
207
|
+
never recovered, every usage read failed, and its cache froze — the
|
|
208
|
+
permanently-stale state balanced routing was already having to defend against.
|
|
209
|
+
- **A 429 now records its deadline and every read honours it.** Usage fetches and
|
|
210
|
+
health probes for that provider short-circuit until the window passes — no
|
|
211
|
+
request, no renewed penalty — and report
|
|
212
|
+
`Claude rate-limited this machine — not retrying for 45 minutes.` The state is
|
|
213
|
+
on disk, because the callers are separate processes: the long-lived daemon and
|
|
214
|
+
every one-shot `agents view` / `agents run` — one empty file per penalty under
|
|
215
|
+
`~/.agents/.cache/usage-backoff/`, named `<agent>.<deadline>`, so two
|
|
216
|
+
processes recording the same provider at once cannot displace each other and a
|
|
217
|
+
read takes the furthest deadline. A server delay is capped at an hour, and a
|
|
218
|
+
missing or unparseable `Retry-After` still backs off.
|
|
219
|
+
|
|
220
|
+
- **A usage read that fails now says so, instead of returning a silent null.**
|
|
221
|
+
Four branches in every networked usage fetch — Claude, Kimi, Droid and
|
|
222
|
+
Cursor — returned `{ snapshot: null, error: null }`: no readable credential, a
|
|
223
|
+
locally-expired one, a rejected request, and a request that threw (timeout,
|
|
224
|
+
DNS/TLS, an unparseable payload). The caller could not tell any of them apart
|
|
225
|
+
from a healthy read, so it fell
|
|
226
|
+
back to whatever the stale-while-revalidate cache held and drew those bars as
|
|
227
|
+
fact. Measured on `yosemite-s1`: every Claude account's stored access token had
|
|
228
|
+
expired (one of them eleven days earlier), so no read could succeed, and
|
|
229
|
+
`agents view claude --refresh` printed a full, healthy-looking table twice
|
|
230
|
+
while writing nothing to the cache. A usage read never refreshes a token
|
|
231
|
+
(RUSH-1822), so an expired credential does not heal on its own — the account
|
|
232
|
+
stays unreadable until that agent actually runs. A rate-limited endpoint (429)
|
|
233
|
+
now reads differently from a rejected credential (401), because re-authing
|
|
234
|
+
fixes one and not the other.
|
|
235
|
+
- **`agents view` marks bars the live read could not confirm.** A row whose
|
|
236
|
+
snapshot came from the cache after a failed live read renders the reading plus
|
|
237
|
+
`unverified`, rather than looking identical to a confirmed one. The number
|
|
238
|
+
still shows — it is the last thing we saw — but it no longer reads as current.
|
|
239
|
+
- **`agents view --refresh` reports what it could not refresh.** It now lists
|
|
240
|
+
each account it failed to reach and why, instead of rendering a table that
|
|
241
|
+
looks fully refreshed regardless.
|
|
242
|
+
|
|
243
|
+
## 1.20.89
|
|
244
|
+
|
|
245
|
+
- **Webhook handler layer for one-off agent/workflow/command/routine triggers.**
|
|
246
|
+
Routines still fire from signed webhooks, but a new `~/.agents/webhooks/*.yml`
|
|
247
|
+
layer can also run one-off actions: `run.agent`, `run.workflow`, `run.command`,
|
|
248
|
+
or delegate to an existing `routine`. Handlers support the same source/event/
|
|
249
|
+
action/label/repo/branch filters as routine triggers, plus Linear
|
|
250
|
+
`stateTo`/`stateFrom` state-change filters. Prompts and commands can use
|
|
251
|
+
`{{issue.identifier}}`, `{{updatedFrom.state.name}}`, etc. The receiver emits
|
|
252
|
+
`webhook.received`, `webhook.authorized`, `webhook.rejected`, `webhook.matched`,
|
|
253
|
+
`webhook.fired`, `webhook.handler.start`, and `webhook.handler.end` events.
|
|
254
|
+
Source: `apps/cli/src/lib/triggers/handlers.ts`,
|
|
255
|
+
`apps/cli/src/lib/triggers/webhook.ts`, `apps/cli/src/lib/routines.ts`,
|
|
256
|
+
`apps/cli/src/commands/routines.ts`, `apps/cli/docs/03-routines.md`.
|
|
257
|
+
|
|
258
|
+
- **`agents routines add` gains `--state-to` and `--state-from` filters for Linear
|
|
259
|
+
triggers.** A Linear routine or handler can now fire only on a specific state
|
|
260
|
+
transition (for example `--state-to Plan`), instead of on every issue update.
|
|
261
|
+
|
|
262
|
+
- **Values substituted into `run.command` are shell-quoted.** A webhook context is
|
|
263
|
+
built from an external payload, and fields like `issue.title` or a GitHub
|
|
264
|
+
`pull_request` title are free text any outside contributor can set — pasted raw
|
|
265
|
+
into a shell command they would be a command-injection sink. Substituted values
|
|
266
|
+
are now single-quoted (POSIX `sh`), so a payload stays one inert argument while
|
|
267
|
+
the operator's own template keeps its pipes, redirects, and `&&`. On Windows,
|
|
268
|
+
where `exec` runs through `cmd.exe` and these quoting rules do not hold, a
|
|
269
|
+
`run.command` containing `{{…}}` is refused with a clear error rather than run.
|
|
270
|
+
`run.prompt` is unaffected — it never reaches a shell.
|
|
271
|
+
Source: `apps/cli/src/lib/routines.ts` (`substituteWebhookCommand`,
|
|
272
|
+
`assertShellSubstitutionSupported`), `apps/cli/src/lib/triggers/handlers.ts`.
|
|
273
|
+
|
|
274
|
+
- **The `Cmd-Shift-O` quick-dispatch bar now lists the repo's open Linear tickets,
|
|
275
|
+
and dispatches one on a click (RUSH-2098).** The panel only captured NEW work;
|
|
276
|
+
it now also shows what already exists. Switching the repo dropdown switches the
|
|
277
|
+
Linear project (the repo name is matched against `linear projects` reduced to
|
|
278
|
+
lowercase alphanumerics, so `agents-cli` finds "Agents CLI" with nothing to
|
|
279
|
+
configure; a worktree resolves to its parent repo, and a repo that matches no
|
|
280
|
+
project says so and lets you pick one, remembered per repo). Rows are ranked
|
|
281
|
+
urgent-first — Linear priority, then overdue, then in progress, then newest —
|
|
282
|
+
and typing filters them, so an existing ticket surfaces before Return files a
|
|
283
|
+
duplicate. Clicking a row (or `⌘1`–`⌘5`) dispatches that ticket to the selected
|
|
284
|
+
agents in the picked repo: **Run** claims it and implements it, **Plan** posts a
|
|
285
|
+
plan as a ticket comment. `⌘`-click opens it in Linear instead. The list renders
|
|
286
|
+
from a 90-second warm cache so the panel still appears instantly. Source:
|
|
287
|
+
`apps/cli/menubar/Sources/MenubarHelper/LinearTickets.swift`,
|
|
288
|
+
`apps/cli/menubar/Sources/MenubarHelper/PromptPanel.swift`,
|
|
289
|
+
`apps/cli/menubar/Sources/MenubarHelper/AgentsCLI.swift`.
|
|
290
|
+
|
|
291
|
+
- **Fixed: a menu-bar dispatch whose child printed more than ~64 KiB hung forever
|
|
292
|
+
and never notified.** The helper read a monitored child's stdout only from the
|
|
293
|
+
process-termination handler, so a child that filled the pipe buffer blocked on
|
|
294
|
+
write, never exited, and the completion callback never fired — two `linear`
|
|
295
|
+
processes were left wedged by a single ticket fetch. Both monitored paths (the
|
|
296
|
+
ticket agent and `linear create`) now drain stdout, and feed stdin, on a
|
|
297
|
+
background queue while the child runs. Source:
|
|
298
|
+
`apps/cli/menubar/Sources/MenubarHelper/AgentsCLI.swift`.
|
|
299
|
+
|
|
300
|
+
- **The daemon warns when it was launched from an ephemeral root.** A daemon
|
|
301
|
+
started from a temp dir (`/tmp`, `/var/folders`, `/dev/shm`) or a git worktree
|
|
302
|
+
resolves its own job modules by dynamic `import()` rooted at the launch entry
|
|
303
|
+
(`getAgentsBinPath` → `process.argv[1]`). When that directory is later removed
|
|
304
|
+
— a `/tmp` cleanup, a review/verify checkout teardown, `git worktree remove` —
|
|
305
|
+
the long-lived daemon keeps ENOENT-ing on every routine's imports
|
|
306
|
+
(`auto-dispatch.ts`, `routines-placement.ts`, `devices/fleet.ts`), silently
|
|
307
|
+
wedging until restart. `anchorDaemonCwd` already rescues the cwd, but nothing
|
|
308
|
+
can re-root a deleted module tree. `runDaemon` now calls
|
|
309
|
+
`warnEphemeralDaemonRoot` at startup, so the risk is logged the moment the
|
|
310
|
+
daemon comes up — including a direct `agents __daemon-run` that never passes
|
|
311
|
+
through the launch-time `validateDaemonBinary` check. That launch-time check is
|
|
312
|
+
also broadened from git-worktree-only to any ephemeral root via the shared
|
|
313
|
+
`describeEphemeralDaemonRoot` predicate. The fix for a wedged daemon is
|
|
314
|
+
unchanged: run it from the globally installed binary
|
|
315
|
+
(`npm i -g @phnx-labs/agents-cli`) so its entry roots at a stable version home.
|
|
316
|
+
Source: `apps/cli/src/lib/daemon.ts`
|
|
317
|
+
(`describeEphemeralDaemonRoot`, `warnEphemeralDaemonRoot`, `validateDaemonBinary`).
|
|
318
|
+
|
|
319
|
+
- **A `README.md` / `AGENTS.md` sitting in a resource directory is no longer
|
|
320
|
+
installed as a resource.** `listResources` skipped only dotfiles, so every `.md`
|
|
321
|
+
beside the actual resources was materialized as one: `commands/README.md` — which
|
|
322
|
+
the system repo has shipped for months — installed a bogus `/README` slash command
|
|
323
|
+
into every agent home, and adding per-directory `AGENTS.md` docs would have added
|
|
324
|
+
`/AGENTS`, `/CLAUDE`, and `/GEMINI` alongside it. `README`, `AGENTS`, `CLAUDE`, and
|
|
325
|
+
`GEMINI` are now filtered from both `listResources` and `resolveResource` for every
|
|
326
|
+
kind **except `rules`**, where `AGENTS.md` *is* the resource (the composed ruleset
|
|
327
|
+
that syncs as each agent's memory file). The check tests `!entry.isDirectory()`
|
|
328
|
+
rather than `isFile()`, because a `Dirent` for a symlink reports
|
|
329
|
+
`isFile() === false` and `CLAUDE.md`/`GEMINI.md` are symlinks to `AGENTS.md` by
|
|
330
|
+
convention — a resource *directory* named `agents/` is still a real resource.
|
|
331
|
+
Verified against the real installed layers: 30 commands with `README` leaking
|
|
332
|
+
before, 29 with none after.
|
|
333
|
+
- **`agents commands list` and the command picker no longer offer a name that
|
|
334
|
+
cannot be opened.** `listCentralCommands` and `discoverCommands`
|
|
335
|
+
(`src/lib/commands.ts`) run their own `readdirSync` scans rather than going
|
|
336
|
+
through `listResources`, so they kept offering `README` while
|
|
337
|
+
`agents commands view README` answered "not found" — a listed-but-unopenable
|
|
338
|
+
name. Both now share the one exported `isDirectoryDoc` predicate, so every
|
|
339
|
+
enumerator agrees. Verified: 27 names with `README` before, 26 with none after.
|
|
340
|
+
- **`agents commands add/remove/view` no longer suggest `README` as the example
|
|
341
|
+
command name.** With `README` reserved as a directory doc, the six hardcoded
|
|
342
|
+
examples in the help text and non-interactive hints named a command that can never
|
|
343
|
+
exist. They now use `plan`, which actually ships.
|
|
344
|
+
|
|
345
|
+
- **File-backed secrets bundles no longer require `AGENTS_SECRETS_PASSPHRASE` on
|
|
346
|
+
macOS.** The encrypted file store now silently auto-provisions a stable
|
|
347
|
+
machine-local key (a 0600 file under `~/.agents/.secrets-key/`, kept outside the
|
|
348
|
+
encrypted store) on first use on **every** platform, macOS included — no prompt,
|
|
349
|
+
no Touch ID, nothing to set or remember. Previously a file-backed bundle on a Mac
|
|
350
|
+
hard-failed unless `AGENTS_SECRETS_PASSPHRASE` was exported, which blocked
|
|
351
|
+
headless reads (e.g. the `auth` bundle the usage/auth reader consults) and
|
|
352
|
+
frequently hung. Setting `AGENTS_SECRETS_PASSPHRASE` still works and takes
|
|
353
|
+
precedence — use it to hold the key off disk or to share one bundle's ciphertext
|
|
354
|
+
across boxes under a common key. Source: `apps/cli/src/lib/secrets/filestore.ts`,
|
|
355
|
+
`apps/cli/src/lib/secrets/bundles.ts`.
|
|
356
|
+
|
|
357
|
+
- **Menu-bar & daemon notifications now use the current agents-cli mark, not the
|
|
358
|
+
legacy logo.** A desktop notification from the menu-bar helper or the routines
|
|
359
|
+
daemon showed the old `assets/logo.png` gradient "A" — outdated, and blank in the
|
|
360
|
+
notification's left-hand app-icon slot. `MenubarHelper.app`'s `AppIcon.icns` is
|
|
361
|
+
now generated from the current brand mark (`assets/app-icon.svg` → `app-icon.png`:
|
|
362
|
+
the lime-tile lowercase `a` shared with the agi-cli web favicon and the menu-bar
|
|
363
|
+
glyph), which drives both the notification's right-hand `contentImage` and its
|
|
364
|
+
left-hand app icon. The installer also registers the bundle with LaunchServices
|
|
365
|
+
(`lsregister -f`) at its `~/Library/Application Support` path so the OS can resolve
|
|
366
|
+
that app icon. Source: `apps/cli/menubar/scripts/build.sh`,
|
|
367
|
+
`apps/cli/src/lib/menubar/install-menubar.ts`, `assets/app-icon.svg`.
|
|
368
|
+
|
|
369
|
+
- **The menu bar is a single instance, always.** Two copies of the helper could
|
|
370
|
+
run at once — launchd's `KeepAlive` service plus a LaunchServices/`open` launch
|
|
371
|
+
of the same `.app` — putting two agents marks in the menu bar, and the second
|
|
372
|
+
copy could hold `Cmd-Shift-V`/`Cmd-Shift-O` (`RegisterEventHotKey` is
|
|
373
|
+
first-come). The helper now takes an `flock` on
|
|
374
|
+
`~/.agents/.cache/state/menubar.lock` at launch and holds it for its lifetime;
|
|
375
|
+
a helper that cannot take the lock pops the **running** helper's menu open and
|
|
376
|
+
exits 0, since re-launching a menu-bar app means "show me the one I already
|
|
377
|
+
have". An `flock` rather than a pid file: the kernel releases it when the
|
|
378
|
+
holder dies, so a `SIGKILL`ed helper cannot leave a stale "already running"
|
|
379
|
+
that blocks every later launch. Source:
|
|
380
|
+
`apps/cli/menubar/Sources/MenubarHelper/SingleInstance.swift`,
|
|
381
|
+
`apps/cli/menubar/Sources/MenubarHelper/StatusItemController.swift`.
|
|
382
|
+
|
|
383
|
+
- **`agents menubar setup` configures the menu bar end-to-end.** One idempotent
|
|
384
|
+
command for a machine that is wrong — never configured, helper down, or showing
|
|
385
|
+
a duplicate icon. It ends every running helper, installs/refreshes the bundle,
|
|
386
|
+
checks its code signature, writes the launchd login item (`RunAtLoad` +
|
|
387
|
+
`KeepAlive`), clears a previous `agents menubar disable`, and verifies exactly
|
|
388
|
+
one helper came back up — reporting each as its own step and exiting nonzero if
|
|
389
|
+
it cannot reach that state. `--check` reports without changing; `--json` emits
|
|
390
|
+
the step list. Source: `apps/cli/src/commands/menubar.ts`,
|
|
391
|
+
`apps/cli/src/lib/menubar/install-menubar.ts`.
|
|
392
|
+
|
|
393
|
+
- **`agents menubar status` now shows a duplicate.** Live helper processes were
|
|
394
|
+
collapsed to a boolean `running`, so two copies of the *installed* bundle — the
|
|
395
|
+
duplicate a user actually sees — reported as healthy. `--json` now carries an
|
|
396
|
+
`instances` array (copies of the installed bundle) beside the existing
|
|
397
|
+
`foreignInstances`, and the text readout names every extra pid and points at
|
|
398
|
+
`agents menubar setup`. Source:
|
|
399
|
+
`apps/cli/src/lib/menubar/install-menubar.ts` (`classifyMenubarProcesses`).
|
|
400
|
+
|
|
401
|
+
- **Quick-dispatch ticket list: one-row filter + sort, and a scrollable list.**
|
|
402
|
+
The ticket controls sit on a single row of popups next to the Linear project
|
|
403
|
+
(project · filter · sort) — not a chip matrix or two-column block. Quick filter
|
|
404
|
+
options: All open, Todo, Doing, Backlog, P1 only, P2 only, Overdue. Quick sort
|
|
405
|
+
options: Urgent first, Newest, Oldest, Due date, Priority (flat list, no
|
|
406
|
+
status grouping). Filter and sort picks are remembered across summons. Ticket
|
|
407
|
+
rows scroll inside a fixed viewport so more than five matches stay reachable
|
|
408
|
+
without growing the panel. Source:
|
|
409
|
+
`apps/cli/menubar/Sources/MenubarHelper/LinearTickets.swift`,
|
|
410
|
+
`apps/cli/menubar/Sources/MenubarHelper/PromptPanel.swift`.
|
|
411
|
+
|
|
412
|
+
- **`release.sh` now takes a release lease, and refuses to bump past an
|
|
413
|
+
unpublished tag.** Releases run from whichever fleet box an agent happens to be
|
|
414
|
+
on, so two agents could enter the pipeline at once; the collision only surfaced
|
|
415
|
+
at the publish gate (`merged tree != built tree -- refusing to publish`), after
|
|
416
|
+
one run had already merged and tagged, leaving the version merged but unshipped.
|
|
417
|
+
A new `scripts/release-lease.sh` holds mutual exclusion on `origin` as an orphan
|
|
418
|
+
commit at `refs/release-lock/held` — a second claimant's push can never be a
|
|
419
|
+
fast-forward, so git's rejection *is* the failed lock acquisition. The lease is
|
|
420
|
+
claimed before the first mutation and dropped by the existing cleanup trap on
|
|
421
|
+
every exit path. Because a healthy release routinely outlives any sane
|
|
422
|
+
expiry — the CI matrix alone has run 57 minutes and release 1.20.77 took 186
|
|
423
|
+
minutes — the lease is **renewed** by a background renewer for the whole run,
|
|
424
|
+
and the squash-merge, the tag, and the publish each **verify** ownership first,
|
|
425
|
+
failing closed if it can no longer be proven. A lease that stops being renewed
|
|
426
|
+
is reclaimable after 30 minutes, and reclaiming names the dead holder instead
|
|
427
|
+
of silently overwriting it. Separately,
|
|
428
|
+
`release.sh` now refuses to cut a new version while an older `v*` tag exists
|
|
429
|
+
that npm never received, and points at the re-run that finishes it — bumping
|
|
430
|
+
past an unpublished tag is what turned a one-version gap into npm 1.20.78 vs
|
|
431
|
+
main 1.20.81. Source: `apps/cli/scripts/release-lease.sh`,
|
|
432
|
+
`apps/cli/scripts/release.sh`.
|
|
433
|
+
|
|
434
|
+
- **`agents funnel down` disables a public Funnel port from the same wrapper used
|
|
435
|
+
to enable ingress.** Webhook ingress now has a complete local receiver runbook:
|
|
436
|
+
keep GitHub/Linear signing keys in `agents secrets`, bind the receiver to
|
|
437
|
+
`127.0.0.1`, expose it with `agents funnel up`, rotate one source secret at a
|
|
438
|
+
time, and turn the public port off with `agents funnel down` before stopping or
|
|
439
|
+
moving the receiver. Source: `apps/cli/src/commands/funnel.ts`,
|
|
440
|
+
`apps/cli/src/lib/funnel.ts`, `apps/cli/docs/03-routines.md`.
|
|
441
|
+
|
|
442
|
+
- **New `agents secrets rotate-passphrase` re-keys the encrypted file store under
|
|
443
|
+
a new master passphrase, atomically (RUSH-1975).** Until now there was no
|
|
444
|
+
supported way to rotate the file-store passphrase — `rekey` only renames macOS
|
|
445
|
+
keychain service names and `rotate <bundle> <key>` replaces a single secret
|
|
446
|
+
value, so a leaked passphrase (RUSH-1968) could only be remediated by a
|
|
447
|
+
hand-rolled non-atomic script or an export-to-plaintext round-trip (the exact
|
|
448
|
+
exposure being fixed). The new command decrypts every `<item>.enc` under the
|
|
449
|
+
current key, re-encrypts under a freshly generated one, and swaps both the
|
|
450
|
+
ciphertext and the 0600 key file by directory rename after verifying every item
|
|
451
|
+
round-trips. A crash at any point self-heals on the next *rotate* run to a single
|
|
452
|
+
readable store — content-aware recovery probes which key actually decrypts the
|
|
453
|
+
live store (not merely which files are present) and classifies the WHOLE store:
|
|
454
|
+
it completes the rotation forward or rolls back only when one key opens every
|
|
455
|
+
item, and if a later `secrets set` contaminated a crashed rotation into a MIXED
|
|
456
|
+
store (items under two keys at once, or a store dir recreated by an interstitial
|
|
457
|
+
write after the crash left it absent, so its backup holds items the live dir does
|
|
458
|
+
not) it refuses with an actionable error and preserves every recovery artifact
|
|
459
|
+
rather than sweeping the only copy of a key or the backed-up ciphertext — so a
|
|
460
|
+
crash anywhere in the swap can never orphan the store, even when a write landed in
|
|
461
|
+
between. The rotation and every store write run under
|
|
462
|
+
one cross-process lock, so a `secrets set` or a second rotation can never
|
|
463
|
+
interleave with a swap in the first place. No plaintext secret or passphrase is
|
|
464
|
+
ever written to disk, argv, or a log. Items
|
|
465
|
+
that don't decrypt under the current key (orphan caches, stale test artifacts)
|
|
466
|
+
are carried through verbatim, never re-keyed. Dry-run by default (`--commit` to
|
|
467
|
+
apply). A dry run never re-keys, but it *does* heal an interrupted rotation —
|
|
468
|
+
that is how a crashed store becomes readable again without re-keying it — and it
|
|
469
|
+
says so instead of claiming nothing was written. Refuses while the secrets-agent
|
|
470
|
+
holds live unlocks or while
|
|
471
|
+
`AGENTS_SECRETS_PASSPHRASE` is exported in the environment, unless `--force`.
|
|
472
|
+
Headless-safe and Linux-first. Source: `apps/cli/src/lib/secrets/filestore.ts`,
|
|
473
|
+
`apps/cli/src/commands/secrets-rotate-passphrase.ts`.
|
|
474
|
+
|
|
475
|
+
- **`agents sessions --active --json` now reports who is watching each session.**
|
|
476
|
+
The `viewingIn` field carries the same string the table prints — `codium tab 3`,
|
|
477
|
+
`ghostty tab 2`, or `detached` for a live tmux pane with **no client attached**
|
|
478
|
+
(its terminal was closed or crashed). It is `null` both for a session that isn't
|
|
479
|
+
tmux-hosted and for one whose pane the locator could not resolve — `detached` is
|
|
480
|
+
claimed only when the pane was actually located, so absence of evidence is never
|
|
481
|
+
reported as evidence of absence. Previously the JSON path returned
|
|
482
|
+
before the locator pass ran, so the field never appeared and a machine consumer
|
|
483
|
+
could not tell a session someone is looking at from an orphaned one — which is
|
|
484
|
+
exactly what the Factory extension's `Agents: Resume` picker ranks by. The JSON
|
|
485
|
+
path resolves tmux clients only — no osascript — so scriptable output keeps the
|
|
486
|
+
cheapness the old ordering was protecting; a Ghostty-attached client resolves as
|
|
487
|
+
`ghostty` without its tab number. Peers running an older CLI that still emits the
|
|
488
|
+
`{app, tab}` object are normalized at the fan-out boundary, so a mixed-version
|
|
489
|
+
fleet sweep stays correct. Source: `apps/cli/src/lib/session/viewing-in.ts`
|
|
490
|
+
(`viewingInLabel`, `parseViewingIn`), `apps/cli/src/commands/sessions.ts`
|
|
491
|
+
(`serializeActiveSessionsForJson`, `enrichTmuxLocators`),
|
|
492
|
+
`apps/cli/src/lib/session/remote-active.ts`.
|
|
493
|
+
|
|
494
|
+
- **Webhook handlers gain `run.env` and `host` placement.** A handler can now
|
|
495
|
+
inject environment variables into the process it spawns (`run.env`), and choose
|
|
496
|
+
where that run executes (`host`). `host` takes a device name (`yosemite-s0`), or
|
|
497
|
+
`fleet` to pick any eligible online worker, or `fleet/<platform>` /
|
|
498
|
+
`<platform>/fleet` (also a bare `linux` / `macos` / `windows`) to restrict that
|
|
499
|
+
pick to one platform. A fleet expression that matches no eligible device fails
|
|
500
|
+
loudly rather than silently falling back to the local machine, so `fleet/linux`
|
|
501
|
+
can never land on a macOS box. Omitting `host` runs locally, as before.
|
|
502
|
+
Source: `apps/cli/src/lib/triggers/handlers.ts` (`resolveHandlerHost`),
|
|
503
|
+
`apps/cli/src/lib/routines-placement.ts` (`pickFleetDevice` platform filter),
|
|
504
|
+
`apps/cli/src/lib/routines.ts` (`JobConfig.env`), `apps/cli/src/lib/runner.ts`.
|
|
505
|
+
|
|
3
506
|
## 1.20.88
|
|
4
507
|
|
|
5
508
|
- **`agents doctor` redesigned into a prioritized, fleet-aware, per-version
|
package/README.md
CHANGED
|
@@ -265,6 +265,8 @@ On a terminal, `agents sessions --active` (and a bare `agents sessions`) open th
|
|
|
265
265
|
|---|---|---|
|
|
266
266
|
| `s` | search text | `--query` / positional |
|
|
267
267
|
| `r` | running only | `--active` |
|
|
268
|
+
| `f` | favorites only | `--favorites` |
|
|
269
|
+
| `*` | star / unstar the highlighted session | `agents sessions favorite <id>` |
|
|
268
270
|
| `c` | team sessions | `--teams` |
|
|
269
271
|
| `a` | agent (cycles) | `-a` |
|
|
270
272
|
| `d` | device (cycles) | `--device` |
|
|
@@ -274,6 +276,10 @@ On a terminal, `agents sessions --active` (and a bare `agents sessions`) open th
|
|
|
274
276
|
| `⏎` | resume / attach | `resume` / `focus` |
|
|
275
277
|
| `y` | copy the equivalent command | `--print-cmd` |
|
|
276
278
|
|
|
279
|
+
**Star the sessions you keep coming back to.** `*` marks the highlighted row (a `★` shows in the listing), `f` narrows to the starred ones, and `agents sessions favorite <id>` / `--favorites` do the same outside a TTY. Stars live in `~/.agents/.history/favorites.json` keyed by session id, so they survive a reindex of the session cache. They're per-machine — session sync carries transcripts, not this file.
|
|
280
|
+
|
|
281
|
+
**A session that lost its host says so.** When an editor window or an SSH connection goes down hard, the agent it owned used to simply disappear from `--active`; when an agent outlived its window in tmux, it reported a plain `idle`. Both now carry their own status: `✗ crashed` (the host went down and took the agent with it) and `◍ orphan` (still alive, but no client is attached — nothing is showing it). Read from tmux's attached-client count and the editor window's registry heartbeat, so a deliberate `agents sessions detach` is never mistaken for one, and a session that is still *working* headlessly is left alone.
|
|
282
|
+
|
|
277
283
|
Filters **stack** (they AND together), the active set shows in the header, and the highlighted row **previews below by default** (`tab` hides it) — prompt, activity, last response, plus a links line where the worked-on ticket and the PR the session opened are **clickable** (OSC 8 hyperlinks: the ticket jumps to Linear, the `PR#` to GitHub, in terminals that support them). The Linear workspace is resolved from `LINEAR_WORKSPACE` or the linear-cli config, so tickets stay plain text when it's unknown. Because every hotkey has a flag, the view you build by hand is a real command: press `y` (or run `--print-cmd`) to get the exact `ag sessions …` line — explore interactively, hand the line to an agent. Piped output, `--json`, or `--no-interactive` keep the plain listing for scripts. Peek without opening the pager with `agents sessions <id> --preview`.
|
|
278
284
|
|
|
279
285
|
| before — preview hidden | after — preview open + clickable links |
|
|
@@ -1002,16 +1008,24 @@ Other useful commands: `agents doctor` checks CLI availability and resource sync
|
|
|
1002
1008
|
On macOS, `agents-cli` puts a status item in your menu bar -- a live glance at what your agents are doing, plus a Spotlight-style bar for filing work without breaking focus.
|
|
1003
1009
|
|
|
1004
1010
|
```bash
|
|
1005
|
-
agents menubar
|
|
1011
|
+
agents menubar setup # configure end-to-end: one instance, started at login
|
|
1006
1012
|
agents menubar status # is it installed and running?
|
|
1007
1013
|
```
|
|
1008
1014
|
|
|
1015
|
+
There is only ever **one** agents mark: the helper takes a lock at launch, so a
|
|
1016
|
+
second copy surfaces the running one's menu and exits instead of adding a
|
|
1017
|
+
duplicate icon. `agents menubar setup` is the recovery command when a machine is
|
|
1018
|
+
already wrong -- it ends any duplicate, installs the bundle, wires the login
|
|
1019
|
+
item, and verifies exactly one helper came back up.
|
|
1020
|
+
|
|
1009
1021
|
The dropdown surfaces a **NEEDS YOU** queue (agents waiting on a question, a plan review, or a permission prompt), the running roster, and a routines summary -- the same live state as `agents sessions --active`, one click away.
|
|
1010
1022
|
|
|
1011
1023
|
### Quick-issue bar (⌘⇧O)
|
|
1012
1024
|
|
|
1013
1025
|
Press `Cmd-Shift-O` anywhere for a thin capture surface: type a one-line note, `Cmd-V` to paste, and attach one or more recent screenshots (double-click a thumbnail to preview it in full). Submit, and a headless agent picks the right project from your recent sessions, investigates, and files the Linear ticket itself -- you never leave what you were doing.
|
|
1014
1026
|
|
|
1027
|
+
The bar also lists the **open Linear tickets of the repo you picked**, urgent first. Switching the repo switches the Linear project; typing filters the list, so an existing ticket shows up before you file a duplicate; and clicking a row (or `⌘1`-`⌘5`) dispatches that ticket to the selected agents -- **Run** implements it, **Plan** posts a plan as a ticket comment.
|
|
1028
|
+
|
|
1015
1029
|
<p align="center">
|
|
1016
1030
|
<img src="assets/menubar-quickissue.svg" alt="The Cmd-Shift-O quick-issue bar: a one-line note with attached screenshot thumbnails that a headless agent turns into a filed Linear ticket" width="100%" />
|
|
1017
1031
|
</p>
|
package/dist/bin/agents
CHANGED
|
Binary file
|
|
@@ -35,7 +35,7 @@ Examples:
|
|
|
35
35
|
agents commands add
|
|
36
36
|
|
|
37
37
|
# Install specific commands by name
|
|
38
|
-
agents commands add --names
|
|
38
|
+
agents commands add --names plan,debug --agents codex@0.116.0
|
|
39
39
|
|
|
40
40
|
When to use:
|
|
41
41
|
- Project setup: 'agents commands add gh:team/commands' to sync everyone's workflow
|
|
@@ -90,7 +90,7 @@ Examples:
|
|
|
90
90
|
agents commands add
|
|
91
91
|
|
|
92
92
|
# Install specific commands to a single version
|
|
93
|
-
agents commands add --names
|
|
93
|
+
agents commands add --names plan,debug --agents codex@0.116.0
|
|
94
94
|
|
|
95
95
|
# Pull commands from GitHub and sync to all installed agents
|
|
96
96
|
agents commands add gh:user/repo --agents claude,codex,cursor
|
|
@@ -125,7 +125,7 @@ Examples:
|
|
|
125
125
|
else {
|
|
126
126
|
if (!isInteractiveTerminal()) {
|
|
127
127
|
requireInteractiveSelection('Selecting commands from ~/.agents/commands/', [
|
|
128
|
-
'agents commands add --names
|
|
128
|
+
'agents commands add --names plan,debug --agents codex',
|
|
129
129
|
'agents commands add gh:user/repo --agents codex',
|
|
130
130
|
]);
|
|
131
131
|
}
|
|
@@ -272,7 +272,7 @@ Examples:
|
|
|
272
272
|
.addHelpText('after', `
|
|
273
273
|
Examples:
|
|
274
274
|
# Remove a command by name
|
|
275
|
-
agents commands remove
|
|
275
|
+
agents commands remove plan
|
|
276
276
|
|
|
277
277
|
# Interactive: pick commands to remove
|
|
278
278
|
agents commands remove
|
|
@@ -305,7 +305,7 @@ Examples:
|
|
|
305
305
|
}
|
|
306
306
|
if (!isInteractiveTerminal()) {
|
|
307
307
|
requireInteractiveSelection('Selecting commands to remove', [
|
|
308
|
-
'agents commands remove
|
|
308
|
+
'agents commands remove plan',
|
|
309
309
|
]);
|
|
310
310
|
}
|
|
311
311
|
try {
|
|
@@ -422,7 +422,7 @@ Examples:
|
|
|
422
422
|
.addHelpText('after', `
|
|
423
423
|
Examples:
|
|
424
424
|
# View a specific command
|
|
425
|
-
agents commands view
|
|
425
|
+
agents commands view plan
|
|
426
426
|
|
|
427
427
|
# Interactive picker
|
|
428
428
|
agents commands view
|
|
@@ -437,7 +437,7 @@ Examples:
|
|
|
437
437
|
}
|
|
438
438
|
if (!isInteractiveTerminal()) {
|
|
439
439
|
requireInteractiveSelection('Selecting a command to view', [
|
|
440
|
-
'agents commands view
|
|
440
|
+
'agents commands view plan',
|
|
441
441
|
]);
|
|
442
442
|
}
|
|
443
443
|
try {
|
package/dist/commands/exec.js
CHANGED
|
@@ -59,7 +59,13 @@ function formatRotationBanner(result, verb = 'balanced') {
|
|
|
59
59
|
const { picked, healthy, excluded } = result;
|
|
60
60
|
const label = picked.email ? `${picked.email} · ${picked.agent}@${picked.version}` : `${picked.agent}@${picked.version}`;
|
|
61
61
|
const ratio = `${healthy.length} of ${healthy.length + excluded.length} healthy`;
|
|
62
|
-
|
|
62
|
+
// Say it when the pick was a guess. A machine whose usage refresh is failing
|
|
63
|
+
// reports old percentages with total confidence, so a silent banner reads
|
|
64
|
+
// identical whether the router knew the account had headroom or merely hoped
|
|
65
|
+
// so — and the operator only finds out when the agent answers "you've hit
|
|
66
|
+
// your weekly limit".
|
|
67
|
+
const caveat = result.usageUnverified ? ', usage unverified — no account could be refreshed' : '';
|
|
68
|
+
return `[agents] ${verb} picked ${label} (${ratio}${caveat})`;
|
|
63
69
|
}
|
|
64
70
|
/**
|
|
65
71
|
* Whether `cwd` is inside a git work tree.
|