@phnx-labs/agents-cli 1.20.73 → 1.20.76
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 +989 -0
- package/README.md +36 -10
- package/dist/bin/agents +0 -0
- package/dist/commands/apply.js +21 -3
- package/dist/commands/attach.d.ts +10 -0
- package/dist/commands/attach.js +41 -0
- package/dist/commands/computer.js +3 -3
- package/dist/commands/detach-core.d.ts +46 -0
- package/dist/commands/detach-core.js +61 -0
- package/dist/commands/detach.d.ts +5 -0
- package/dist/commands/detach.js +171 -0
- package/dist/commands/doctor.js +31 -1
- package/dist/commands/exec.js +31 -3
- package/dist/commands/export.d.ts +11 -0
- package/dist/commands/export.js +215 -0
- package/dist/commands/feed.d.ts +7 -1
- package/dist/commands/feed.js +47 -4
- package/dist/commands/go.d.ts +9 -2
- package/dist/commands/go.js +16 -6
- package/dist/commands/harness.d.ts +14 -0
- package/dist/commands/harness.js +145 -0
- package/dist/commands/import.js +87 -9
- package/dist/commands/inspect.js +5 -2
- package/dist/commands/profiles.d.ts +20 -0
- package/dist/commands/profiles.js +68 -21
- package/dist/commands/repo.js +14 -0
- package/dist/commands/routines.js +15 -2
- package/dist/commands/secrets.js +44 -21
- package/dist/commands/sessions-browser.js +1 -1
- package/dist/commands/sessions-export.js +19 -5
- package/dist/commands/sessions-migrate.d.ts +29 -0
- package/dist/commands/sessions-migrate.js +596 -0
- package/dist/commands/sessions-picker.js +16 -1
- package/dist/commands/sessions.d.ts +36 -13
- package/dist/commands/sessions.js +210 -40
- package/dist/commands/setup-computer.js +2 -2
- package/dist/commands/setup.js +6 -1
- package/dist/commands/ssh.js +71 -35
- package/dist/commands/status.js +10 -2
- package/dist/commands/versions.js +51 -11
- package/dist/commands/view.d.ts +5 -0
- package/dist/commands/view.js +99 -31
- package/dist/commands/watchdog.d.ts +7 -2
- package/dist/commands/watchdog.js +73 -57
- package/dist/index.js +59 -7
- package/dist/lib/activity.d.ts +14 -4
- package/dist/lib/activity.js +17 -5
- package/dist/lib/actor.d.ts +50 -0
- package/dist/lib/actor.js +166 -0
- package/dist/lib/agent-spec/provider.js +2 -1
- package/dist/lib/agent-spec/resolve.js +19 -5
- package/dist/lib/agent-spec/types.d.ts +9 -1
- package/dist/lib/agents.d.ts +56 -0
- package/dist/lib/agents.js +133 -7
- package/dist/lib/cloud/codex.d.ts +2 -0
- package/dist/lib/cloud/codex.js +14 -3
- package/dist/lib/cloud/session-index.d.ts +32 -0
- package/dist/lib/cloud/session-index.js +58 -0
- package/dist/lib/cloud/store.d.ts +7 -0
- package/dist/lib/cloud/store.js +25 -0
- package/dist/lib/config-transfer.d.ts +16 -0
- package/dist/lib/config-transfer.js +60 -0
- package/dist/lib/daemon.js +18 -2
- package/dist/lib/devices/connect.d.ts +15 -1
- package/dist/lib/devices/connect.js +15 -1
- package/dist/lib/devices/fleet.d.ts +21 -0
- package/dist/lib/devices/fleet.js +26 -0
- package/dist/lib/devices/health-report.d.ts +38 -0
- package/dist/lib/devices/health-report.js +214 -0
- package/dist/lib/devices/health.js +4 -1
- package/dist/lib/devices/reachability.d.ts +31 -0
- package/dist/lib/devices/reachability.js +40 -0
- package/dist/lib/devices/registry.d.ts +33 -0
- package/dist/lib/devices/registry.js +37 -0
- package/dist/lib/devices/resolve-target.d.ts +15 -27
- package/dist/lib/devices/resolve-target.js +63 -102
- package/dist/lib/devices/sync.d.ts +18 -0
- package/dist/lib/devices/sync.js +23 -1
- package/dist/lib/devices/tailscale.d.ts +3 -0
- package/dist/lib/devices/tailscale.js +1 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/exec.d.ts +66 -0
- package/dist/lib/exec.js +195 -6
- package/dist/lib/export.d.ts +72 -0
- package/dist/lib/export.js +269 -0
- package/dist/lib/feed-post.d.ts +63 -0
- package/dist/lib/feed-post.js +204 -0
- package/dist/lib/fleet/apply.d.ts +32 -2
- package/dist/lib/fleet/apply.js +97 -10
- package/dist/lib/fleet/types.d.ts +11 -0
- package/dist/lib/fs-walk.d.ts +13 -0
- package/dist/lib/fs-walk.js +16 -7
- package/dist/lib/hooks.d.ts +1 -0
- package/dist/lib/hooks.js +156 -0
- package/dist/lib/hosts/dispatch.d.ts +7 -0
- package/dist/lib/hosts/dispatch.js +2 -0
- package/dist/lib/hosts/passthrough.js +2 -0
- package/dist/lib/hosts/reconnect.d.ts +78 -0
- package/dist/lib/hosts/reconnect.js +127 -0
- package/dist/lib/hosts/registry.d.ts +75 -14
- package/dist/lib/hosts/registry.js +205 -30
- package/dist/lib/hosts/remote-cmd.js +1 -0
- package/dist/lib/hosts/run-target.d.ts +13 -5
- package/dist/lib/hosts/run-target.js +26 -10
- package/dist/lib/hosts/session-index.d.ts +18 -1
- package/dist/lib/hosts/session-index.js +37 -2
- package/dist/lib/hosts/session-marker.d.ts +33 -0
- package/dist/lib/hosts/session-marker.js +51 -0
- package/dist/lib/hosts/tasks.d.ts +8 -4
- package/dist/lib/import.d.ts +25 -0
- package/dist/lib/import.js +107 -2
- package/dist/lib/isolation-boundary-report.d.ts +10 -0
- package/dist/lib/isolation-boundary-report.js +35 -0
- package/dist/lib/menubar/install-menubar.d.ts +20 -0
- package/dist/lib/menubar/install-menubar.js +45 -1
- package/dist/lib/migrate.d.ts +16 -0
- package/dist/lib/migrate.js +36 -0
- package/dist/lib/models.d.ts +27 -0
- package/dist/lib/models.js +54 -1
- package/dist/lib/overdue.d.ts +7 -2
- package/dist/lib/overdue.js +18 -11
- package/dist/lib/picker.js +4 -1
- package/dist/lib/platform/process.d.ts +17 -0
- package/dist/lib/platform/process.js +70 -0
- package/dist/lib/profiles-presets.js +9 -7
- package/dist/lib/profiles.d.ts +31 -0
- package/dist/lib/profiles.js +70 -0
- package/dist/lib/project-resources.js +20 -5
- package/dist/lib/pty-server.d.ts +2 -10
- package/dist/lib/pty-server.js +4 -38
- package/dist/lib/registry.d.ts +1 -1
- package/dist/lib/registry.js +48 -8
- package/dist/lib/rotate.d.ts +18 -0
- package/dist/lib/rotate.js +28 -0
- package/dist/lib/routines.d.ts +26 -0
- package/dist/lib/routines.js +19 -2
- package/dist/lib/runner.js +104 -7
- package/dist/lib/secrets/agent.d.ts +66 -1
- package/dist/lib/secrets/agent.js +154 -90
- package/dist/lib/secrets/bundles.d.ts +34 -0
- package/dist/lib/secrets/bundles.js +179 -19
- package/dist/lib/secrets/index.d.ts +15 -1
- package/dist/lib/secrets/index.js +21 -3
- package/dist/lib/secrets/rc-hygiene.d.ts +55 -0
- package/dist/lib/secrets/rc-hygiene.js +154 -0
- package/dist/lib/secrets/remote.d.ts +7 -4
- package/dist/lib/secrets/remote.js +7 -4
- package/dist/lib/secrets/sync-commands.d.ts +21 -0
- package/dist/lib/secrets/sync-commands.js +21 -0
- package/dist/lib/self-update.d.ts +23 -2
- package/dist/lib/self-update.js +86 -5
- package/dist/lib/session/active.d.ts +114 -7
- package/dist/lib/session/active.js +323 -52
- package/dist/lib/session/cloud.js +2 -1
- package/dist/lib/session/db.d.ts +48 -0
- package/dist/lib/session/db.js +238 -14
- package/dist/lib/session/detached.d.ts +30 -0
- package/dist/lib/session/detached.js +92 -0
- package/dist/lib/session/discover.d.ts +465 -2
- package/dist/lib/session/discover.js +1648 -468
- package/dist/lib/session/fork.js +2 -1
- package/dist/lib/session/hook-sessions.d.ts +43 -0
- package/dist/lib/session/hook-sessions.js +135 -0
- package/dist/lib/session/linear.d.ts +6 -0
- package/dist/lib/session/linear.js +55 -0
- package/dist/lib/session/migrate-targets.d.ts +65 -0
- package/dist/lib/session/migrate-targets.js +94 -0
- package/dist/lib/session/migrations.d.ts +37 -0
- package/dist/lib/session/migrations.js +60 -0
- package/dist/lib/session/parse.d.ts +18 -0
- package/dist/lib/session/parse.js +130 -30
- package/dist/lib/session/pid-registry.d.ts +34 -2
- package/dist/lib/session/pid-registry.js +49 -2
- package/dist/lib/session/render.d.ts +7 -2
- package/dist/lib/session/render.js +22 -10
- package/dist/lib/session/short-id.d.ts +17 -0
- package/dist/lib/session/short-id.js +20 -0
- package/dist/lib/session/state.js +6 -2
- package/dist/lib/shims.d.ts +27 -1
- package/dist/lib/shims.js +88 -6
- package/dist/lib/sqlite.d.ts +3 -2
- package/dist/lib/sqlite.js +27 -4
- package/dist/lib/staleness/detectors/commands.js +1 -1
- package/dist/lib/staleness/detectors/workflows.js +13 -2
- package/dist/lib/staleness/writers/commands.js +7 -7
- package/dist/lib/staleness/writers/workflows.d.ts +4 -2
- package/dist/lib/startup/command-registry.d.ts +2 -0
- package/dist/lib/startup/command-registry.js +5 -0
- package/dist/lib/state.d.ts +1 -1
- package/dist/lib/state.js +15 -36
- package/dist/lib/teams/agents.d.ts +19 -10
- package/dist/lib/teams/agents.js +40 -39
- package/dist/lib/types.d.ts +48 -2
- package/dist/lib/uninstall.js +1 -45
- package/dist/lib/usage.d.ts +33 -1
- package/dist/lib/usage.js +172 -12
- package/dist/lib/versions.d.ts +22 -2
- package/dist/lib/versions.js +98 -10
- package/dist/lib/watchdog/log.d.ts +43 -0
- package/dist/lib/watchdog/log.js +69 -0
- package/dist/lib/watchdog/routine.d.ts +44 -0
- package/dist/lib/watchdog/routine.js +69 -0
- package/dist/lib/watchdog/runner.d.ts +51 -7
- package/dist/lib/watchdog/runner.js +239 -64
- package/dist/lib/watchdog/watchdog.d.ts +1 -1
- package/dist/lib/watchdog/watchdog.js +31 -16
- package/dist/lib/workflows.d.ts +16 -0
- package/dist/lib/workflows.js +110 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,994 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.20.76
|
|
4
|
+
|
|
5
|
+
- **The routines daemon can read a `never`/no-ACL secrets bundle headlessly again — fixes a false "no Claude credential" alert.** The headless secrets guard (`readAndResolveBundleEnv`'s `agentOnly` branch) threw for every keychain-backed bundle absent from the broker, but a `never`/no-ACL bundle carries no biometry ACL — its reads raise no Touch ID sheet, so blocking it served no purpose. That wrongly blocked the automation-only `claude` bundle the routines daemon reads at startup (`readDaemonClaudeOAuthToken`), leaving every scheduled Claude routine token-less, and — on the new auth-failure alert path — firing "no Claude credential" on each daemon start even when the bundle was configured correctly. The guard now exempts `never`/no-ACL bundles (policy learned via a prompt-less metadata read), matching the existing file-backend exemption. Source: `apps/cli/src/lib/secrets/bundles.ts`.
|
|
6
|
+
|
|
7
|
+
- **`release.sh` can now cut the next patch when main is ahead of an unpublishable
|
|
8
|
+
version.** The catch-up guard refuses to publish a merged release PR whose squash pulled
|
|
9
|
+
in concurrent main commits — correctly, since the tree that would ship is not the tree CI
|
|
10
|
+
tested (the hole that let 1.20.58 publish before its Windows matrix failed). Its refusal
|
|
11
|
+
advises cutting the next patch through the normal release PR flow, but the version
|
|
12
|
+
validator measured patch+1 from the REGISTRY, so with main at 1.20.75 and npm at 1.20.74
|
|
13
|
+
both 1.20.75 (blocked) and 1.20.76 (read as a skipped version) were rejected — leaving no
|
|
14
|
+
patch-level path forward and a minor bump as the only escape. A new `patch-from-main`
|
|
15
|
+
case accepts the version one patch above `package.json` when main is ahead of the
|
|
16
|
+
registry; it grants no bypass, and the release still earns its own release PR, full
|
|
17
|
+
cross-platform matrix, merge, tag, and publish. The decision moved out of `release.sh`
|
|
18
|
+
into `scripts/validate-bump.sh` so it can be tested directly — `release.sh` itself
|
|
19
|
+
cannot be run in a test, since it demands a clean main plus npm and gh auth long before
|
|
20
|
+
it reaches the bump decision, which is why this arithmetic had no coverage at all. The
|
|
21
|
+
rejection message now also lists the main-ahead options only when main really is ahead,
|
|
22
|
+
instead of advising a version the script would then refuse. Source:
|
|
23
|
+
`apps/cli/scripts/validate-bump.sh`, `apps/cli/scripts/release.sh`.
|
|
24
|
+
|
|
25
|
+
- **Teams now run local teammates under one frozen actor.** The orchestrator was
|
|
26
|
+
spawning each local teammate through a raw shell without the actor env, so every
|
|
27
|
+
teammate's inner `agents run` re-resolved the actor independently instead of
|
|
28
|
+
inheriting the orchestrator's — contradicting the "resolve once, whole tree
|
|
29
|
+
shares one actor" contract. The local spawn env now carries `actorEnv(resolveActor())`
|
|
30
|
+
(process env < actor < `--env` overrides), so all teammates inherit the single
|
|
31
|
+
frozen actor. Teammate records also carry an `actor` field, persisted to
|
|
32
|
+
`meta.json` and emitted in the status dict. Remote teammates inherit the fix at
|
|
33
|
+
the dispatch layer. Source: `apps/cli/src/lib/teams/agents.ts`.
|
|
34
|
+
|
|
35
|
+
- **`agents sessions <full-session-id>` no longer answers with an unrelated
|
|
36
|
+
session.** A complete id that was not in the local index fell through to the
|
|
37
|
+
FTS content search, which tokenizes the UUID and matches every transcript that
|
|
38
|
+
merely *mentions* it. The miss surfaced as up to ten unrelated sessions under
|
|
39
|
+
`Multiple sessions match "<id>"` plus the advice `Pass a longer ID to narrow it
|
|
40
|
+
down` — impossible to follow, since a full id is already the longest form. The
|
|
41
|
+
same fallthrough made `--preview` render a different session's transcript, let
|
|
42
|
+
an 8-char short id lose to a content hit, and made `agents sessions export
|
|
43
|
+
<id>` bundle every transcript that mentions the id (14 unrelated sessions
|
|
44
|
+
written into an archive meant to be handed to someone else). A query that is a
|
|
45
|
+
whole session id now resolves by id alone: it reports `No session with id <id>
|
|
46
|
+
on this machine.` and points at `--device <host>` for the fleet. Short-id
|
|
47
|
+
prefixes and text searches are unchanged. The recognized shapes are the ones
|
|
48
|
+
the index actually holds — a bare UUID, `session_` + UUID (kimi, rush), and
|
|
49
|
+
`ses_` + 26-char ULID (opencode); routine run ids and cloud execution ids stay
|
|
50
|
+
out of scope and keep today's search behavior. Source:
|
|
51
|
+
`apps/cli/src/lib/session/discover.ts` (`isCompleteSessionId`),
|
|
52
|
+
`apps/cli/src/commands/sessions.ts` (`resolveSessionQuery`), and
|
|
53
|
+
`apps/cli/src/commands/sessions-export.ts` (`selectSessions`).
|
|
54
|
+
|
|
55
|
+
## 1.20.75
|
|
56
|
+
|
|
57
|
+
- **Wire native file-based slash commands for Grok (RUSH-1851).** Grok >= 0.2.111
|
|
58
|
+
discovers commands from the cross-agent `~/.agents/commands/` dir (plus the
|
|
59
|
+
legacy `~/.claude/commands/` symlink). `agents sync grok` now writes native
|
|
60
|
+
`.md` command files there instead of converting commands to skills, so `agents
|
|
61
|
+
view grok` and `agents commands list grok` report Grok as commands-capable.
|
|
62
|
+
Source: `apps/cli/src/lib/agents.ts`.
|
|
63
|
+
|
|
64
|
+
- **Document that Droid Factory Missions are invoke-only (RUSH-1864).** Probed
|
|
65
|
+
droid v0.177.0 (self-updating; ticket cited v0.161.0) and Factory docs:
|
|
66
|
+
Missions run via `/missions` or `droid exec --mission` (optional `-f` is a
|
|
67
|
+
prompt file, not a named template). `~/.factory/missions/<sessionId>/` is
|
|
68
|
+
runtime state only — no auto-discovery dir agents-cli can populate — so
|
|
69
|
+
`workflows` stays `false` with an evidence comment rather than inventing a
|
|
70
|
+
writer target. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/tests/agents.test.ts`.
|
|
71
|
+
|
|
72
|
+
- **Reading state no longer writes `agents.yaml`, which silently deadlocked
|
|
73
|
+
`agents repo pull` (RUSH-1925).** Registry presets from `SEEDED_REGISTRIES` (today
|
|
74
|
+
`skill.hermes`) were seeded on the state **read** path, which wrote the registry entry
|
|
75
|
+
plus a `seededPresets` marker into `agents.yaml`. That file is git-tracked in the user's
|
|
76
|
+
DotAgents repo, so the write left the working tree dirty and every subsequent
|
|
77
|
+
`agents repo pull` aborted with `Working tree has uncommitted changes` — naming neither
|
|
78
|
+
the file nor the cause. Because *every* `agents` invocation reads state, the dirt
|
|
79
|
+
reappeared the instant it was cleared: `git checkout -- agents.yaml && agents repo pull`
|
|
80
|
+
re-seeded before the pull ran, so the loop could not be escaped through the CLI at all.
|
|
81
|
+
On a host with several live agent sessions even a raw `git pull --rebase` lost the race,
|
|
82
|
+
and one machine sat 27 commits behind for weeks as a result. Seeded presets are now
|
|
83
|
+
resolved in memory by `getRegistries` — the same way `DEFAULT_REGISTRIES` has always
|
|
84
|
+
worked — so nothing is written and no later write can flush them into the file.
|
|
85
|
+
`seededPresets` becomes a removal tombstone: `agents registry remove skill hermes`
|
|
86
|
+
records the key and the preset stops being offered, which is the behaviour the marker
|
|
87
|
+
existed to protect. Files seeded by the old code carry both the tombstone and an explicit
|
|
88
|
+
entry in their own `registries:` block, and the explicit entry still wins, so upgrading
|
|
89
|
+
changes nothing for them. `setRegistry` also falls back to
|
|
90
|
+
`SEEDED_REGISTRIES` when merging a partial update, so `registry disable/enable/config`
|
|
91
|
+
on a never-materialized preset can no longer persist a stripped entry that drops `url`.
|
|
92
|
+
Source: `apps/cli/src/lib/registry.ts`,
|
|
93
|
+
`apps/cli/src/lib/state.ts`, `apps/cli/src/lib/registry.seeds.test.ts`,
|
|
94
|
+
`apps/cli/src/lib/state.test.ts`.
|
|
95
|
+
|
|
96
|
+
- **`agents fleet status` no longer hangs on an unreachable box (RUSH-1964).** The cheap
|
|
97
|
+
stats probe (~2.5s) already learns whether each box is reachable, but the dead-box skip
|
|
98
|
+
that spares the expensive `agents --version` (15s) + `agents doctor --json` (30s) dials
|
|
99
|
+
was gated behind `--refresh` — so a default run still spent up to 45s per genuinely
|
|
100
|
+
unreachable box, and one down box could stall the whole matrix. The default path now
|
|
101
|
+
gates those dials on the same reachability verdict: a box the stats probe found
|
|
102
|
+
unreachable short-circuits straight to an `unreachable` row with zero further SSH
|
|
103
|
+
round-trips. Measured on a single blackholed target, `fleet status` dropped from 22.8s
|
|
104
|
+
to 2.8s. (VPN-first transport and SSH key provisioning remain deferred.) Source:
|
|
105
|
+
`apps/cli/src/lib/devices/fleet.ts` (`fleetHealthSkip`), `apps/cli/src/commands/ssh.ts`
|
|
106
|
+
(`runFleetStatus`).
|
|
107
|
+
|
|
108
|
+
- **Fleet reachability reflects the live probe (RUSH-1965).** `agents devices` and `agents fleet status` now persist the live SSH probe's `{reachable, via, checkedAt}` verdict to the registry and read the online/offline word from it — freshest signal wins: a live stat this run, then the written-back verdict, then the cached `tailscale.online` snapshot. A reachable box (including a `via:"manual"` device with no Tailscale peer) no longer renders "offline" while its live load/mem sit one column over. Source: `apps/cli/src/lib/devices/reachability.ts`, `apps/cli/src/lib/devices/registry.ts`.
|
|
109
|
+
|
|
110
|
+
- **`agents fleet status` output redesigned — rollup + NEEDS ATTENTION, quiet when healthy
|
|
111
|
+
(RUSH-1966).** The old grid buried "is my fleet OK?" under duplicated columns and glyph
|
|
112
|
+
soup (a "Health" column that just repeated Load/Mem, `stale · cold` counts across every
|
|
113
|
+
orphan version, an `●5 ·8 ◐3` auth cell, and warnings that re-listed all 12 devices three
|
|
114
|
+
times). The default view now leads with a one-line rollup (`● N online · ○ M offline`),
|
|
115
|
+
then a short **NEEDS ATTENTION** list where every item names its fix command — offline →
|
|
116
|
+
`check the box`, config drift or a stark CLI gap → `agents apply <box>`, version skew →
|
|
117
|
+
`agents upgrade --fleet` — then quiet per-device rows grouped by OS (macOS / Linux /
|
|
118
|
+
Windows) showing `name · capacity · load/mem · version`, with this machine flagged
|
|
119
|
+
`▸ … ← this machine`. Drift is reported on the active version only (not orphan versions);
|
|
120
|
+
orphaned versions are demoted to a one-line `agents prune` nudge in the footer; the
|
|
121
|
+
freshness footer names the cache age and what `--live` / `--verbose` add. A healthy fleet
|
|
122
|
+
reads in a few lines. The full per-device auth/CLI/sync/version grid moves behind the new
|
|
123
|
+
`--verbose` flag; `--json` is unchanged. Source: `apps/cli/src/lib/devices/health-report.ts`
|
|
124
|
+
(`renderFleetSummary`, `buildFleetAttentionItems`), `apps/cli/src/commands/ssh.ts`
|
|
125
|
+
(`runFleetStatus`).
|
|
126
|
+
|
|
127
|
+
- **`agents doctor` now flags credentials exported from shell rc files (RUSH-1968).** A
|
|
128
|
+
secret exported from `~/.zshenv`/`~/.zshrc`/`~/.bashrc`/`~/.profile` is inherited by every
|
|
129
|
+
process the login shell spawns and is readable from `/proc/<pid>/environ` by any same-user
|
|
130
|
+
process — `.zshenv` is sourced even by non-interactive `ssh host 'cmd'`, so the value lands
|
|
131
|
+
in essentially everything the box runs. The doctor overview now scans the current user's rc
|
|
132
|
+
files and prints a `Secrets in shell config` warning that names each credential-shaped
|
|
133
|
+
export by `file:line` and variable name (never the value), with the file-store master key
|
|
134
|
+
`AGENTS_SECRETS_PASSPHRASE` called out separately — its off-env home is
|
|
135
|
+
`~/.agents/.secrets-key/passphrase` (chmod 600), and other credentials should move to
|
|
136
|
+
`agents secrets` and inject via `agents secrets exec`. The scanner reads only the variable
|
|
137
|
+
name and line number, so a finding is safe to print or log. Source:
|
|
138
|
+
`apps/cli/src/lib/secrets/rc-hygiene.ts` (`scanUserRcFiles`, `scanRcExports`,
|
|
139
|
+
`rcSecretWarningLines`), wired into `apps/cli/src/commands/doctor.ts`
|
|
140
|
+
(`renderRcHygieneAdvisory`).
|
|
141
|
+
|
|
142
|
+
- **`agents devices` no longer forces a Touch ID prompt on a password-auth box
|
|
143
|
+
(RUSH-1970).** The read-only stats probe's live SSH to an uncached
|
|
144
|
+
`auth.method === 'password'` device used to drive the askpass shim to resolve
|
|
145
|
+
the SSH password through the biometry-gated Keychain sheet under a TTY, popping
|
|
146
|
+
Touch ID during what should be a silent probe. The probe now threads a
|
|
147
|
+
broker-only signal (`AGENTS_SSH_AGENT_ONLY`) so it resolves from an
|
|
148
|
+
already-unlocked broker or degrades to an unreachable row — never a biometric
|
|
149
|
+
prompt. Source: `apps/cli/src/commands/ssh.ts`,
|
|
150
|
+
`apps/cli/src/lib/devices/health.ts`, `apps/cli/src/lib/devices/connect.ts`.
|
|
151
|
+
|
|
152
|
+
- **`agents sessions migrate` (alias `relocate`) relocates a RUNNING session onto
|
|
153
|
+
another machine, then stops the source here (RUSH-1977).** `--auto` scores the
|
|
154
|
+
fleet and picks a target, `--host <name>` names one explicitly, and `--lease`
|
|
155
|
+
provisions a fresh ephemeral box; `--mode resume|rehydrate` chooses whether the
|
|
156
|
+
target resumes the native transcript or replays it via `/continue`. Every
|
|
157
|
+
migration is written to an append-only ledger, viewable with `agents sessions
|
|
158
|
+
migrations`. Load-bearing invariant: the source session is never stopped until
|
|
159
|
+
the transcript is confirmed live on the target, so a failed hop leaves the
|
|
160
|
+
original running. (Not to be confused with `agents sessions detach`/`attach`,
|
|
161
|
+
the unrelated background/foreground pair.) Source:
|
|
162
|
+
`apps/cli/src/commands/sessions-migrate.ts`,
|
|
163
|
+
`apps/cli/src/lib/session/migrate-targets.ts`,
|
|
164
|
+
`apps/cli/src/lib/session/migrations.ts`.
|
|
165
|
+
|
|
166
|
+
- **`agents sessions --active --json` now emits flat `ticketId` and `project`
|
|
167
|
+
keys on every row.** A supervising watcher joins active sessions on ticket +
|
|
168
|
+
project, but the raw row nested the ticket under `ticket.id` and carried no
|
|
169
|
+
`project` at all, so a naive join silently dropped every session. Each row now
|
|
170
|
+
carries top-level `ticketId` (from the detected ticket) and `project` (the
|
|
171
|
+
basename of the session's cwd — the same derivation the historical `--json`
|
|
172
|
+
listing uses), both always present and `null` when unknown. The existing raw
|
|
173
|
+
fields are unchanged. Source: `apps/cli/src/commands/sessions.ts`
|
|
174
|
+
(`serializeActiveSessionsForJson`).
|
|
175
|
+
|
|
176
|
+
- **Actor provenance — agent git commits are now credited to the human who
|
|
177
|
+
started the run, not the shared account.** One account across a shared fleet
|
|
178
|
+
meant every commit, from anyone who SSH'd into a box, showed up as the same
|
|
179
|
+
author. `resolveActor()` now identifies who is behind a run: over SSH it
|
|
180
|
+
`tailscale whois`es the client IP to the connecting tailnet identity (name +
|
|
181
|
+
login email); locally it stays honest with `UNRESOLVED@<host>` and claims no
|
|
182
|
+
identity. The resolved actor rides the agent's process env as `AGENTS_ACTOR` /
|
|
183
|
+
`AGENTS_ACTOR_KIND` (inherited by the whole spawn tree, so it resolves once),
|
|
184
|
+
and for a resolved human it also injects `GIT_AUTHOR_*` / `GIT_COMMITTER_*` — so
|
|
185
|
+
the agent's own `git commit` is attributed to the person. An unresolved actor
|
|
186
|
+
injects no git identity, so local runs keep their ambient git config unchanged.
|
|
187
|
+
Source: `apps/cli/src/lib/actor.ts`, wired into `buildExecEnv`
|
|
188
|
+
(`apps/cli/src/lib/exec.ts`).
|
|
189
|
+
|
|
190
|
+
- **New optional `actors:` map in `agents.yaml`.** Keyed by a short slug, each
|
|
191
|
+
entry (`kind` / `name` / `email` / `github` / `login`) enriches or overrides
|
|
192
|
+
what `tailscale whois` resolves — pin a preferred git email, add a GitHub
|
|
193
|
+
handle, override the display name, or mark an entry as an agent rather than a
|
|
194
|
+
human. Entirely optional: a tailnet SSH identity already resolves without it.
|
|
195
|
+
Source: `apps/cli/src/lib/types.ts` (`ActorConfig`, `Meta.actors`).
|
|
196
|
+
|
|
197
|
+
- **`agents run antigravity "prompt"` now works headless without an explicit
|
|
198
|
+
`--headless`.** Antigravity's `--print` flag was gated on the raw `--headless`
|
|
199
|
+
flag, which defaults to `false` at the CLI layer — but headless is inferred from
|
|
200
|
+
prompt presence. So a bare `agents run antigravity "do X"` built `agy <prompt>`
|
|
201
|
+
with no `--print`, launching the interactive TUI and dying with
|
|
202
|
+
`bubbletea: could not open TTY: /dev/tty` in any non-terminal shell (headless
|
|
203
|
+
runs, teams, routines, `--host`). Print flags are now gated on the resolved
|
|
204
|
+
headless state, matching the documented "`--headless` auto-enabled when a prompt
|
|
205
|
+
is provided" contract and the behavior of every other agent. Antigravity was the
|
|
206
|
+
only agent affected — it is the sole harness whose prompt is a bare positional
|
|
207
|
+
with no headless subcommand and no `-p` print alias. Source:
|
|
208
|
+
`apps/cli/src/lib/exec.ts`.
|
|
209
|
+
|
|
210
|
+
- **Routine auth-failures are now detected, not silent.** When a routine's agent is logged
|
|
211
|
+
out or its token is revoked, the run is classified `failed` with an `auth_failed:` /
|
|
212
|
+
`auth_preflight:` reason instead of a generic non-zero exit. The login-error text is no
|
|
213
|
+
longer written into `report.md`, and `{last_report}` now only injects the last *completed*
|
|
214
|
+
run's report — so a single logged-out run can no longer poison every subsequent run's
|
|
215
|
+
prompt. Classification uses the Claude stream-json markers (`error:"authentication_failed"`
|
|
216
|
+
and a `result` event with `is_error:true`), which is the reliable signal — `terminal_reason`
|
|
217
|
+
is `"completed"` on a logged-out run. Rate-limit still classifies first, so a 429 keeps
|
|
218
|
+
triggering failover rather than being mistaken for an auth failure. Source:
|
|
219
|
+
`apps/cli/src/lib/exec.ts`, `apps/cli/src/lib/runner.ts`, `apps/cli/src/lib/routines.ts`.
|
|
220
|
+
- **`agents routines run` now exits non-zero when a run doesn't complete.** A run that ends
|
|
221
|
+
in `failed`, `timeout`, or an auth failure returns exit code 1 and `--json { "ok": false, … }`
|
|
222
|
+
with the reason, instead of exiting 0 with `ok:true` — so cron wrappers, `&&` chains, and
|
|
223
|
+
`--json` consumers actually see the failure. (Exit code is set via `process.exitCode` so the
|
|
224
|
+
JSON payload flushes fully to a pipe first.) Source: `apps/cli/src/commands/routines.ts`.
|
|
225
|
+
- **Auth preflight before dispatch.** A routine whose (agent, version) has a cached
|
|
226
|
+
`revoked` auth verdict fails fast with `auth_preflight: revoked` without spawning a
|
|
227
|
+
doomed agent. Fails open on any other verdict, so a stale/absent probe or a network blip
|
|
228
|
+
never blocks a run, and agents with no live probe (codex/gemini/grok) are never blocked.
|
|
229
|
+
Source: `apps/cli/src/lib/runner.ts`, reusing `apps/cli/src/lib/auth-health.ts`.
|
|
230
|
+
- **The routines daemon no longer starts silently token-less.** When no Claude OAuth token
|
|
231
|
+
is available (e.g. a headless macOS daemon whose keychain was locked at start), the daemon
|
|
232
|
+
now logs a `WARN` and fires a desktop notification instead of quietly spawning Claude
|
|
233
|
+
routines that all fail auth. Source: `apps/cli/src/lib/daemon.ts`.
|
|
234
|
+
|
|
235
|
+
- **`agents sessions` indexes again from the standalone binary.** Every session
|
|
236
|
+
write went through a named-parameter bind (`INSERT ... VALUES (@id, @short_id,
|
|
237
|
+
...)` in `apps/cli/src/lib/session/db.ts`), and `bun:sqlite` matches such an
|
|
238
|
+
object only when its keys carry the SQL sigil — bare keys bound nothing, so all
|
|
239
|
+
columns landed NULL and `sessions.short_id` (NOT NULL) rejected the row. The
|
|
240
|
+
shims exec the Bun-compiled `dist/bin/agents`, so no session reached the index
|
|
241
|
+
from the CLI: `agents sessions` printed `Warning: skipped unindexable session
|
|
242
|
+
<id>: NOT NULL constraint failed: sessions.short_id` per session and then
|
|
243
|
+
listed only rows indexed earlier by the Node entrypoint. The suite runs under
|
|
244
|
+
Node (vitest), where `node:sqlite` accepts bare keys, which is why CI stayed
|
|
245
|
+
green. `apps/cli/src/lib/sqlite.ts` now opens the DB with `strict: true` under
|
|
246
|
+
Bun, so the bare-key call shape this codebase uses works on both runtimes (the
|
|
247
|
+
edges still differ — the module doc lists what strict changes). `sqlite.test.ts`
|
|
248
|
+
covers both the bind and a full `agents sessions` scan in a real `bun`
|
|
249
|
+
subprocess.
|
|
250
|
+
|
|
251
|
+
- **The compiled binary no longer reports itself as a phantom `/$bunfs` install, and can self-upgrade again.**
|
|
252
|
+
Since the standalone executable started shipping (1.20.53), the running copy located
|
|
253
|
+
its own package root as `<__dirname>/..`. Under a Bun standalone binary `__dirname`
|
|
254
|
+
is the embedded virtual filesystem, so that resolved to `/$bunfs` — a path that
|
|
255
|
+
exists nowhere. Two symptoms followed on every machine running the compiled binary:
|
|
256
|
+
the multi-install check reported one install as two (`/$bunfs (running)` alongside
|
|
257
|
+
the real npm root, with the misleading advice to uninstall a stale copy that did not
|
|
258
|
+
exist), and `agents upgrade` failed closed with `/$bunfs is not an npm-managed
|
|
259
|
+
install` because no global prefix can be derived from a virtual path. A new
|
|
260
|
+
`resolveRunningPackageRoot()` resolves the real on-disk root by walking up from
|
|
261
|
+
`process.execPath` to the directory whose `package.json` names this package, and
|
|
262
|
+
both sites use it. The PATH scan also recognizes `<root>/dist/bin/agents` as an
|
|
263
|
+
entrypoint, so a shim pointing at the compiled binary — typically first on PATH, and
|
|
264
|
+
the copy that actually runs — resolves to the same root as its sibling npm bin
|
|
265
|
+
instead of being invisible. Genuine multi-install warnings still fire, now naming a
|
|
266
|
+
real, actionable path. Source: `apps/cli/src/lib/self-update.ts`,
|
|
267
|
+
`apps/cli/src/index.ts`, `apps/cli/src/lib/self-update.test.ts`.
|
|
268
|
+
|
|
269
|
+
- **Reading Claude usage no longer rotates the token and logs your fleet out.**
|
|
270
|
+
`getClaudeUsageInfo` refreshed the OAuth token just to read the usage endpoint
|
|
271
|
+
(`getClaudeAccessToken`, `usage.ts`) — and Claude's refresh token is single-use
|
|
272
|
+
and rotates server-side, so with one account signed into several machines that
|
|
273
|
+
background refresh (fired by the stale-while-revalidate usage cache and by
|
|
274
|
+
`agents run`'s default "balanced" rotation on every unpinned run) invalidated
|
|
275
|
+
every other box's copy, dropping the fleet to "run /login". This is the
|
|
276
|
+
RUSH-1822 stampede, which was fixed for the 3-minute health probe but left live
|
|
277
|
+
in the usage/run hot path. Usage reads are now strictly read-only: a new pure
|
|
278
|
+
`claudeUsageAccessTokenNoRefresh` uses the stored access token and, when it is
|
|
279
|
+
within the refresh leeway, reports "no usage right now" instead of rotating —
|
|
280
|
+
exactly mirroring `probeClaudeStatus`. The single legitimate refresh stays on
|
|
281
|
+
the real `claude` run, never a usage read. Source: `apps/cli/src/lib/usage.ts`,
|
|
282
|
+
`apps/cli/src/lib/usage.test.ts`.
|
|
283
|
+
|
|
284
|
+
- **No more Touch ID storm from the usage view.** On macOS, the usage-bar fetch
|
|
285
|
+
(`agents view`, the Factory watchdog that polls `agents view --json` every 60s
|
|
286
|
+
per agent) and the daemon's every-3-min auth-health probe each read Claude's own
|
|
287
|
+
ACL-bound `Claude Code-credentials-<hash>` keychain item on every refresh — each
|
|
288
|
+
read popping a Touch ID prompt, so several running Claude agents meant a
|
|
289
|
+
biometric prompt every couple of minutes. Those two access-token-only, high-
|
|
290
|
+
frequency callers now opt into a device-local **no-ACL** access-token cache (the
|
|
291
|
+
prompt-free mechanism `secrets/session-store.ts` uses for unlocked bundles),
|
|
292
|
+
bounded by the token's own expiry, so the ACL-gated read happens at most once per
|
|
293
|
+
token lifetime and every agent process reads the cache silently. The cache is
|
|
294
|
+
opt-in and caches only the short-lived access token — callers that need the full
|
|
295
|
+
credential (`isClaudeAuthValid`'s refresh, `readClaudeCredentialsBlob`'s Rush
|
|
296
|
+
Cloud export) still take the ACL read with the refresh token intact. Source:
|
|
297
|
+
`apps/cli/src/lib/usage.ts`.
|
|
298
|
+
|
|
299
|
+
- **The daemon no longer silently repoints your default agent version.** The unattended
|
|
300
|
+
6-hourly launch-health pass (`healBrokenDefaultLaunches` → `ensureAgentRunnable`) now runs
|
|
301
|
+
with `allowDefaultSwitch: false`: it still repairs the *current* default in place, but if
|
|
302
|
+
that default can't be repaired it no longer adopts another installed version or installs
|
|
303
|
+
`latest` and pins it. A background default switch installs a fresh version home, which for
|
|
304
|
+
Claude is a fresh, empty credential scope (macOS keychain keyed off `CLAUDE_CONFIG_DIR`;
|
|
305
|
+
Linux per-version token file) — i.e. an "unprovoked logout" at a time uncorrelated with
|
|
306
|
+
anything you did, and a leading cause of routine auth-failures on unattended machines. The
|
|
307
|
+
daemon now logs a `WARN` naming the version to pick instead; interactive callers
|
|
308
|
+
(`agents run`, `agents add`) are unchanged and still repoint as before. Source:
|
|
309
|
+
`apps/cli/src/lib/versions.ts`, `apps/cli/src/lib/daemon.ts`.
|
|
310
|
+
|
|
311
|
+
- **`agents sessions detach` / `agents sessions attach` — send a running agent to the
|
|
312
|
+
background and back.** `agents sessions detach <id>` stops a live session's
|
|
313
|
+
interactive process (killing the tmux session when tmux-hosted, else SIGTERM'ing the
|
|
314
|
+
pid) and continues it **headless**, detached, via the existing version-pinned
|
|
315
|
+
`agents run --resume` path — so it drives its task to completion without holding a
|
|
316
|
+
terminal. The resumed run carries a nudge that tells the now-unwatched agent it is
|
|
317
|
+
headless and to make the call rather than stall on a confirmation nobody can answer.
|
|
318
|
+
`agents sessions attach <id>` stops that headless continuation and **resumes the
|
|
319
|
+
session interactively** in the current terminal (`resumeSessionInPlace`) — the same
|
|
320
|
+
session and full history, including whatever the background run did. They sit under
|
|
321
|
+
`sessions` next to `focus`/`resume` (the session-lifecycle verbs); the Factory
|
|
322
|
+
extension exposes them as **Agents: Detach** (`Cmd/Ctrl+K B`) and **Agents: Attach**
|
|
323
|
+
(`Cmd/Ctrl+K A`). Both verbs are agent-agnostic (native resume for Claude/Codex,
|
|
324
|
+
`/continue` replay for the rest). A session on **another host** is detached there over SSH rather than
|
|
325
|
+
killed locally; **cloud and team sessions are refused** (they have their own
|
|
326
|
+
lifecycles); the interactive process is fully awaited before the headless resume
|
|
327
|
+
starts (no transcript race); and the background run's output is captured to
|
|
328
|
+
`~/.agents/.cache/logs/detach-<shortid>.log` so a crash after detach is
|
|
329
|
+
debuggable. `agents sessions --active --json` now carries a `presence` field
|
|
330
|
+
(`attached` / `background` / `parked`), folded onto every row from a per-session
|
|
331
|
+
detach record, so the menu bar and Factory show where each agent is. Source:
|
|
332
|
+
`apps/cli/src/commands/detach.ts`, `apps/cli/src/commands/attach.ts`,
|
|
333
|
+
`apps/cli/src/lib/session/detached.ts`.
|
|
334
|
+
(`agents sessions migrate`'s old `detach` alias is renamed to `relocate` to free
|
|
335
|
+
the `detach` name for the background/foreground verb — `migrate` and `relocate`
|
|
336
|
+
both still work.)
|
|
337
|
+
|
|
338
|
+
- **`agents devices sync` no longer auto-registers tailnet nodes another user
|
|
339
|
+
shared into the tailnet.** `tailscale status` includes ShareeNode peers (for
|
|
340
|
+
example a tagged relay shared in by a teammate); the parser ignored that flag,
|
|
341
|
+
so bootstrap registered them as your own boxes and they surfaced in `agents
|
|
342
|
+
fleet ls`. Parsing now carries a `sharee` flag, `runDeviceSync` filters those
|
|
343
|
+
peers out of auto-registration and suggestions, and the interactive picker
|
|
344
|
+
leaves them unchecked (labeled `shared`). Deliberate paths — `devices
|
|
345
|
+
register`/`add` and a `fleet:` manifest — still reach shared nodes when you name
|
|
346
|
+
them. Source: `apps/cli/src/lib/devices/sync.ts`,
|
|
347
|
+
`apps/cli/src/lib/devices/tailscale.ts`.
|
|
348
|
+
|
|
349
|
+
- **Faster `agents sessions` on large / unchanged session trees.** Session
|
|
350
|
+
discovery re-walked and re-`stat`'d every transcript directory on every
|
|
351
|
+
`agents sessions` / `output` / `view` / `teams` call — for a heavy user the
|
|
352
|
+
immutable version-home and backup roots dominated the cost yet never changed. A
|
|
353
|
+
new `dir_ledger` (SQLite, schema v14) caches each leaf transcript directory's
|
|
354
|
+
`(mtime, entry_count)`; when both match, the per-file `stat` of that directory
|
|
355
|
+
is skipped and its unchanged files are served straight from the DB, so those
|
|
356
|
+
immutable roots cost one dir stat each instead of hundreds of per-file stats.
|
|
357
|
+
Append safety is preserved: a file under the agent's live `~/.<agent>` root, or
|
|
358
|
+
scanned within the last 10 minutes, is always re-`stat`'d (a parent-dir mtime
|
|
359
|
+
bumps on create/delete/rename but NOT on an in-place append), so a growing live
|
|
360
|
+
session is never missed; a create / delete / rename bumps the dir mtime and
|
|
361
|
+
forces a full re-walk of that dir exactly as before. Wired into the Claude and
|
|
362
|
+
Gemini scanners (the biggest win); the other scanners keep the existing
|
|
363
|
+
per-file path. Set `AGENTS_SESSIONS_NO_DIR_LEDGER=1` to disable the
|
|
364
|
+
short-circuit and force the old full per-file walk. Source:
|
|
365
|
+
`apps/cli/src/lib/session/db.ts`, `apps/cli/src/lib/session/discover.ts`.
|
|
366
|
+
|
|
367
|
+
- **`agents feed post` — agents announce progress without opening a “needs you”
|
|
368
|
+
block.** Free-text status posts append a `status.posted` milestone to the
|
|
369
|
+
per-session activity log (same stream as `agents activity` and the feed’s
|
|
370
|
+
recent-activity lane). Session/agent/host/runtime/pid/launch identity is
|
|
371
|
+
auto-stamped from the process env and the per-pid launch registry — no
|
|
372
|
+
domain-specific flags (tickets, URLs). Managed runs export `AGENT_SESSION_ID`
|
|
373
|
+
/ `AGENTS_AGENT_NAME` / `AGENTS_CWD` so a Bash tool call needs no extra
|
|
374
|
+
wiring. Source: `apps/cli/src/lib/feed-post.ts`, `apps/cli/src/commands/feed.ts`,
|
|
375
|
+
`apps/cli/src/lib/activity.ts`, `apps/cli/src/lib/exec.ts`.
|
|
376
|
+
|
|
377
|
+
- **PID-reuse protection now works on Windows, from one implementation.**
|
|
378
|
+
`captureProcessStartTime()` existed twice — in `pty-server.ts` and `teams/agents.ts` —
|
|
379
|
+
and neither copy had a Windows branch: both fell through to `ps`, which does not exist
|
|
380
|
+
there, so the function always returned `null` and every caller silently skipped the
|
|
381
|
+
guard. A dead session whose PID the OS had recycled read as alive, and
|
|
382
|
+
`agents teams stop` could signal an unrelated process group. Both copies now delegate to
|
|
383
|
+
a single implementation in `platform/process.ts` that reads `CreationDate` from
|
|
384
|
+
`Win32_Process` as a culture-independent FILETIME, memoizes per PID (the listing path
|
|
385
|
+
probes one PID per row), and bounds the spawn with a timeout. Source:
|
|
386
|
+
`apps/cli/src/lib/platform/process.ts` (`captureProcessStartTime`).
|
|
387
|
+
|
|
388
|
+
- **A session's recorded working directory is no longer rebased onto the local drive.**
|
|
389
|
+
`normalizeCwd()` ran `path.resolve()` over a cwd read out of a transcript, which may name
|
|
390
|
+
a directory on another machine. On Windows that grafted the current drive onto a POSIX
|
|
391
|
+
path (`/Users/me` became `D:\Users\me`), inventing a location that never existed. A
|
|
392
|
+
foreign path is now normalized with POSIX rules and never realpath'd; local paths still
|
|
393
|
+
normalize and resolve symlinks as before. Source:
|
|
394
|
+
`apps/cli/src/lib/session/discover.ts` (`normalizeCwd`).
|
|
395
|
+
|
|
396
|
+
- **`agents cloud`'s task database can now be closed.** `cloud/store.ts` opened `tasks.db`
|
|
397
|
+
and exported no closer, so nothing could release the handle — on Windows that leaves the
|
|
398
|
+
file un-unlinkable. Adds `closeStore()`, the mirror of `closeDB()` in `session/db.ts`.
|
|
399
|
+
Source: `apps/cli/src/lib/cloud/store.ts` (`closeStore`).
|
|
400
|
+
|
|
401
|
+
- **Collapse indistinguishable worker processes into one active-session row.** A daemon
|
|
402
|
+
that spawns many agent binaries (an OpenClaw gateway running `codex app-server`) produced
|
|
403
|
+
one `sessions --active` row per process, because a row with no session id and no
|
|
404
|
+
transcript file skipped dedupe entirely — the Factory Floor showed ~40 identical
|
|
405
|
+
`.openclaw · bg · 0s ago` rows that buried every real session. Dedupe now falls back to
|
|
406
|
+
the cloud/run handle and then to the worker's identity (agent binary + context + working
|
|
407
|
+
directory), so N indistinguishable workers become one row carrying `pidCount: N`.
|
|
408
|
+
Source: `apps/cli/src/lib/session/active.ts`.
|
|
409
|
+
|
|
410
|
+
- **`sessions --active` now stamps a start and last-activity time on every
|
|
411
|
+
interactive session.** Terminal, tmux, and headless agents discovered by the
|
|
412
|
+
process scan carried no `startedAtMs` — so the Factory Floor rendered every
|
|
413
|
+
running agent as "0s ago" even when its transcript, topic, and progress had
|
|
414
|
+
resolved. The scan now stamps `startedAtMs` (the SessionStart hook's own
|
|
415
|
+
timestamp, else the transcript's creation time) and a new `lastActivityMs` (the
|
|
416
|
+
transcript's last-write) on each row, and the Floor renders "Xs ago" off the
|
|
417
|
+
real last-activity instead of the session's age. Source:
|
|
418
|
+
`apps/cli/src/lib/session/active.ts`, `apps/cli/src/lib/session/hook-sessions.ts`.
|
|
419
|
+
|
|
420
|
+
- **`agents sessions` now classifies Grok transcripts into real events, not a
|
|
421
|
+
one-line stub.** Grok sessions were indexed (title, timestamps, message count)
|
|
422
|
+
but opening one showed a single placeholder `session_start` event — `parseGrok`
|
|
423
|
+
was a stub. It now reads the session's `chat_history.jsonl` and normalizes every
|
|
424
|
+
line into the shared `SessionEvent` shape: `user`/`assistant` messages,
|
|
425
|
+
`reasoning` → thinking, `assistant.tool_calls[]` → tool_use (with `path` and
|
|
426
|
+
`command` surfaced), and `tool_result` correlated back to its call by
|
|
427
|
+
`tool_call_id` (an `Error:`-prefixed result becomes an error event). The scanner
|
|
428
|
+
records `summary.json` as the session path, so the parser resolves
|
|
429
|
+
`chat_history.jsonl` beside it; per-line timestamps aren't stored, so each event
|
|
430
|
+
carries the session's `created_at` (falling back to the transcript mtime).
|
|
431
|
+
Verified end-to-end against a real Grok session (30 events: messages + thinking +
|
|
432
|
+
tool_use + tool_result). Source: `apps/cli/src/lib/session/parse.ts`.
|
|
433
|
+
|
|
434
|
+
- **`agents harness` — name a (host CLI + model) combo and run it like a native agent
|
|
435
|
+
type.** `agents harness add spark --host opencode --model meta/muse-spark-1.1` writes
|
|
436
|
+
`~/.agents/profiles/spark.yml`, and `agents run spark` then dispatches OpenCode pinned to
|
|
437
|
+
that model; `--model` at run time still overrides it. A harness is a profile under the
|
|
438
|
+
hood (same YAML, same run resolution, same `agents repo push user` device sync), so
|
|
439
|
+
`agents profiles` is unchanged; `harness` adds the host+model one-shot (no preset
|
|
440
|
+
required), owns its own `--host` (never remote-routed, unlike `profiles --host`), and
|
|
441
|
+
`agents harness list` shows custom harnesses, addable presets, and the native harness
|
|
442
|
+
registry in one view. The model lands on the host's model env var
|
|
443
|
+
(`OPENCODE_MODEL`/`ANTHROPIC_MODEL`/`GROK_MODEL`/`GEMINI_MODEL`). Source:
|
|
444
|
+
`apps/cli/src/commands/harness.ts`, `apps/cli/src/lib/profiles.ts`,
|
|
445
|
+
`apps/cli/src/lib/hosts/passthrough.ts`.
|
|
446
|
+
- **Fixed the Spark presets, which never ran.** `claude-spark`, `opencode-spark`, and the
|
|
447
|
+
`opencode` preset help all named `meta/claude-spark-1.1` — a model neither OpenRouter nor
|
|
448
|
+
OpenCode serves; the live id is `meta/muse-spark-1.1`. Separately, an `authOptional`
|
|
449
|
+
preset (opencode) still wrote a keychain `auth` block that `resolveProfileEnv` always
|
|
450
|
+
read, so `agents run opencode-spark` died with "Keychain item not found" even though
|
|
451
|
+
OpenCode uses its own login. `resolveProfileEnv` now skips optional auth when no token is
|
|
452
|
+
stored, so those presets run on the host's own credentials. Source:
|
|
453
|
+
`apps/cli/src/lib/profiles-presets.ts`, `apps/cli/src/lib/profiles.ts`.
|
|
454
|
+
|
|
455
|
+
- **`agents sessions` now heals any pre-existing empty-`shortId` rows on upgrade.**
|
|
456
|
+
The prior fix stopped *producing* empty shortIds (bare-prefix ids like a `session_`
|
|
457
|
+
directory stripped to `''`), but a row already poisoned in the index did not
|
|
458
|
+
self-heal — an empty shortId is not re-parsed unless its transcript changes, and an
|
|
459
|
+
orphaned row whose file is gone never re-parses at all, so it stayed unaddressable in
|
|
460
|
+
the `short_id LIKE ?` picker lookups. A one-time schema migration (v16) repairs every
|
|
461
|
+
such row in place (`short_id = substr(id, 1, 8)`), so upgrading users get a clean
|
|
462
|
+
index without a full rescan. Source: `apps/cli/src/lib/session/db.ts`.
|
|
463
|
+
|
|
464
|
+
- **Host and cloud runs are now mappable in `agents sessions` for every agent, not
|
|
465
|
+
just Claude.** A `--host` dispatch forced a session id only for Claude (the sole
|
|
466
|
+
agent that accepts `--session-id`); every other agent's remote run coined its own
|
|
467
|
+
id that the launcher never learned, so the run was orphaned in `agents sessions`
|
|
468
|
+
and couldn't be resumed by id. The remote run now prints its resolved session id
|
|
469
|
+
as a one-line stdout sentinel (via a new internal `--emit-session-id` flag the
|
|
470
|
+
dispatch forwards); the launcher parses it out of the followed log and stamps it
|
|
471
|
+
on the host task, so `agents sessions`/resume-by-id work for Codex, Gemini, and
|
|
472
|
+
the rest. Source: `apps/cli/src/lib/hosts/session-marker.ts`,
|
|
473
|
+
`apps/cli/src/lib/hosts/session-index.ts`, `apps/cli/src/lib/hosts/run-target.ts`,
|
|
474
|
+
`apps/cli/src/lib/exec.ts`.
|
|
475
|
+
|
|
476
|
+
- **`agents cloud run` reconciles into the session index at dispatch.** The cloud
|
|
477
|
+
task store (`tasks.db`) and the session index were disjoint: a cloud run wrote only
|
|
478
|
+
the store, and `agents sessions` learned of it only later, via a proxy discovery.
|
|
479
|
+
Now every cloud dispatch (and every status poll) registers a session row keyed by
|
|
480
|
+
the real execution id with a `[cloud/<status>]` label, so a launch is mappable to a
|
|
481
|
+
session immediately. Source: `apps/cli/src/lib/cloud/session-index.ts`,
|
|
482
|
+
`apps/cli/src/lib/cloud/store.ts`.
|
|
483
|
+
|
|
484
|
+
- **Codex Cloud dispatch no longer fabricates a task id.** When `codex cloud exec`
|
|
485
|
+
didn't print a parseable id, the provider minted a synthetic `codex-<timestamp>` —
|
|
486
|
+
an id that could never match the real execution, silently breaking status, list,
|
|
487
|
+
and session reconcile. It now also scans stderr for the id and, on a genuine miss,
|
|
488
|
+
fails loud pointing at `agents cloud list` rather than persisting a bogus id.
|
|
489
|
+
Source: `apps/cli/src/lib/cloud/codex.ts`.
|
|
490
|
+
|
|
491
|
+
- **`agents import <agent> --as <version>` — the version flag now actually works.** The
|
|
492
|
+
option was declared as `--version <version>`, which the program-level `.version(VERSION)`
|
|
493
|
+
claims globally: `agents import codex --version 1.2.3` printed the CLI's own version and
|
|
494
|
+
exited without importing. It had been unreachable since it was introduced, and the
|
|
495
|
+
"could not determine version" error advised passing it. Renamed to `--as`, which reaches
|
|
496
|
+
the command. This also makes `agents import <agent> --isolated --as <version>` re-seed an
|
|
497
|
+
*existing* isolated copy from your current local config, instead of only ever creating a
|
|
498
|
+
new copy at whatever version happens to be installed locally.
|
|
499
|
+
- **Fixed a silent no-op in config copying under the compiled binary.** `fs.cpSync` defaults
|
|
500
|
+
to `force: true`, but Bun drops that default when a `filter` is supplied — so copies that
|
|
501
|
+
strip symlinks left existing destination files untouched. `dist/bin/agents` is
|
|
502
|
+
bun-compiled, so this affected a shipped path, not just tests. Now passed explicitly in
|
|
503
|
+
`config-transfer.ts` and `import.ts`.
|
|
504
|
+
|
|
505
|
+
- **`agents import --isolated` no longer misdescribes itself, chokes on codex, or copies
|
|
506
|
+
your session history.** Three defects found by using it: (1) the confirmation summary
|
|
507
|
+
printed `config: ~/.codex (will be moved into version home)` even under `--isolated` —
|
|
508
|
+
announcing the exact adoption the flag exists to prevent, though the code correctly
|
|
509
|
+
copied; it now reads `will be COPIED — your original stays put`. (2) Seeding failed
|
|
510
|
+
outright for codex with `Cannot overwrite non-directory`, because its version home is a
|
|
511
|
+
SUN_LEN-safe symlink to `~/.agents/.codex-homes/<version>/.codex` rather than a real
|
|
512
|
+
directory; the seeder now follows the link and writes the home the agent actually reads.
|
|
513
|
+
(3) The seed copied the whole config dir including sessions, logs, caches and sqlite —
|
|
514
|
+
757MB on a real machine, 349MB of it `sessions` — so runtime state is now skipped and
|
|
515
|
+
reported (33MB for the same install), with `--all` to include it. Also skips the config
|
|
516
|
+
copy when `~/.<agent>` is itself a managed symlink, which is another version's home
|
|
517
|
+
rather than the user's real settings. Source: `apps/cli/src/lib/import.ts`,
|
|
518
|
+
`apps/cli/src/commands/import.ts`.
|
|
519
|
+
|
|
520
|
+
- **Incremental Claude transcript parsing on the live scan path.** When an active
|
|
521
|
+
Claude session grows, `agents sessions` (and every consumer that scans:
|
|
522
|
+
`output` / `view` / `teams` / the watcher) now re-parses only the newly-appended
|
|
523
|
+
bytes instead of re-reading the whole transcript from the top. The scan persists
|
|
524
|
+
a resumable continuation (`parser_state` + `content_text`, schema v15) in the
|
|
525
|
+
`scan_ledger`; the next scan resumes from the saved byte offset when the file
|
|
526
|
+
merely grew and its mtime did not go backwards, and falls back to a full reparse
|
|
527
|
+
from byte 0 on a cold start, a truncation / rewrite (size shrank), or a clock
|
|
528
|
+
rewind. Both paths run through one shared reducer, so the indexed row an append
|
|
529
|
+
produces is identical, field for field, to a from-scratch full reparse — token
|
|
530
|
+
counts, cost, duration, topic/title, PR + ticket refs, and FTS content all match
|
|
531
|
+
even when a signal straddles two scans (a `gh pr create` in one write and its URL
|
|
532
|
+
in the next). Only the Claude scanner is wired for now (Codex / Kimi are
|
|
533
|
+
follow-ups); the other scanners are unchanged. Source:
|
|
534
|
+
`apps/cli/src/lib/session/discover.ts`, `apps/cli/src/lib/session/db.ts`.
|
|
535
|
+
|
|
536
|
+
- **Incremental Codex + Kimi transcript parsing on the live scan path.** Following
|
|
537
|
+
the Claude incremental parse, the Codex rollout scanner and the Kimi wire.jsonl
|
|
538
|
+
scanner now re-parse only the newly-appended bytes when an active session grows,
|
|
539
|
+
instead of re-reading the whole file from the top every scan (`agents sessions`
|
|
540
|
+
and every consumer that scans: `output` / `view` / `teams` / the watcher). Each
|
|
541
|
+
persists a resumable continuation in the `scan_ledger` (`parser_state`, reusing
|
|
542
|
+
the schema v15 columns): the next scan resumes from the saved byte offset when
|
|
543
|
+
the file merely grew and its mtime did not go backwards, and falls back to a full
|
|
544
|
+
reparse from byte 0 on a cold start, a truncation / rewrite (size shrank), or a
|
|
545
|
+
clock rewind. Both branches run through one shared reducer per scanner, so the
|
|
546
|
+
indexed row an append produces is identical, field for field, to a from-scratch
|
|
547
|
+
full reparse. For Codex that covers messageCount, the last-wins cumulative token
|
|
548
|
+
snapshot (tokenCount / outputTokens / cost), duration, topic, and PR + ticket +
|
|
549
|
+
team signals that straddle two scans (a `gh pr create` function_call in one write
|
|
550
|
+
and its URL in the next). For Kimi it covers the additive message + token
|
|
551
|
+
counters. Both incremental paths apply only newline-terminated lines and defer a
|
|
552
|
+
complete-but-unterminated trailing record to the next pass, so a record written
|
|
553
|
+
before its `'\n'` is flushed is never double-counted. Grok is out of scope (it
|
|
554
|
+
reads a whole `summary.json`, not an append-only JSONL); Claude / Gemini and the
|
|
555
|
+
shared helpers are unchanged. Source: `apps/cli/src/lib/session/discover.ts`.
|
|
556
|
+
|
|
557
|
+
- **Isolated installs now resume sessions and resolve `@default` like any other install.**
|
|
558
|
+
Two places still assumed a managed version is reachable on PATH — which an isolated
|
|
559
|
+
install deliberately is not. (1) `agents sessions` resume looked up
|
|
560
|
+
`<cli>@<version>` with a plain PATH lookup, never found it (the shims dir is
|
|
561
|
+
intentionally off PATH under `--isolated`), concluded the version was uninstalled, and
|
|
562
|
+
fell back to spawning `<cli> "/continue <id>"` — a slash command neither CLI has, so
|
|
563
|
+
the session simply never resumed. It now resolves the versioned alias by absolute path,
|
|
564
|
+
the way `agents run` already did, and the fallback is the agent's real resume verb
|
|
565
|
+
against the current version rather than `/continue`. (2) The agent-spec resolver behind
|
|
566
|
+
`--agents` / `@default` / `@pinned` read only the global default, so an isolated-only
|
|
567
|
+
agent threw "No default version set" even after an explicit `agents use` —
|
|
568
|
+
`resolveVersion` had gained the isolated-default fallback but this resolver had not.
|
|
569
|
+
Both now consult it, and report `isolated-default` as the source rather than claiming a
|
|
570
|
+
global default. `opencode` resume stays deliberately un-pinned, since its sessions are
|
|
571
|
+
shared across versions. Source: `apps/cli/src/commands/sessions.ts`,
|
|
572
|
+
`apps/cli/src/lib/agent-spec/`.
|
|
573
|
+
|
|
574
|
+
- **Menu bar: prune orphan attention sentinels; group `NEEDS YOU`; end silent
|
|
575
|
+
truncation.** `LocalState.attentionMarks` now takes the caller's live-session
|
|
576
|
+
set and unlinks sentinels whose `sessionId` is not alive — the
|
|
577
|
+
`06-attention-sentinel.sh` hook already clears on `Stop`/`UserPromptSubmit`,
|
|
578
|
+
but leaks when a terminal is killed hard, a Claude version has no hook, or the
|
|
579
|
+
`sessionId` doesn't round-trip; the reader is the only layer with `pidAlive`
|
|
580
|
+
ground truth. Verified on mac-mini: 6 stale sentinels aged 1–22 days pruned to
|
|
581
|
+
0 on one dump run. `addNeedsAttention` groups blocked sessions by
|
|
582
|
+
`(agent, repo)` and collapses groups of 2+ into a single
|
|
583
|
+
`<Agent> · <repo> · N waiting · oldest <t> ›` row + submenu, dropping the
|
|
584
|
+
generic `— Claude is waiting for your input` filler when the Notification
|
|
585
|
+
message is empty. `addActive` collapses the `"other"` bucket to a single
|
|
586
|
+
clickable `ACTIVE · other · N idle ›` row when idle-only, and replaces the
|
|
587
|
+
silent 3-cap on idle rows with an explicit `+ N more idle ›` row + submenu so
|
|
588
|
+
the header count always matches visible + explicit-hidden. No new session
|
|
589
|
+
state — closed = hidden, as before.
|
|
590
|
+
|
|
591
|
+
- **The menu-bar helper no longer crash-loops on macOS 26.** npm's pack/extract
|
|
592
|
+
strips the ad-hoc signature the release bakes into `MenubarHelper.app`, leaving
|
|
593
|
+
it `code object is not signed at all`. macOS 26's code-signing monitor SIGKILLs
|
|
594
|
+
an unsigned binary at launch (`SIGKILL (Code Signature Invalid)`), so under the
|
|
595
|
+
launchd `KeepAlive` service it restarted forever, and its unstable identity made
|
|
596
|
+
the Accessibility grant (needed for the clip→paste keystroke in `Clip.swift`)
|
|
597
|
+
re-prompt every time. The install path now re-signs the copied bundle ad-hoc and
|
|
598
|
+
verifies it before bootstrapping the service, so every machine gets a valid
|
|
599
|
+
signature the kernel accepts — and a bundle that can't be made valid is skipped
|
|
600
|
+
instead of spun in a crash loop. A Developer-ID-signed helper (which survives
|
|
601
|
+
npm) is left untouched. Source: `apps/cli/src/lib/menubar/install-menubar.ts`,
|
|
602
|
+
`apps/cli/menubar/scripts/build.sh`.
|
|
603
|
+
|
|
604
|
+
- **The configured model now shows wherever an agent is displayed.** `agents
|
|
605
|
+
view`, `use`, `add`, `status`, and `inspect` surface the model an agent+version
|
|
606
|
+
actually runs with, beside the version (the identity cluster reads `agent ·
|
|
607
|
+
version · model · account`). The model is resolved agents.yaml `run.defaults` →
|
|
608
|
+
the native `settings.json` → the built-in default, and `agents view --json`
|
|
609
|
+
gains a `configuredModel { model, source }` field so downstream tools can read
|
|
610
|
+
both the value and where it came from. Source: `apps/cli/src/lib/models.ts`,
|
|
611
|
+
`apps/cli/src/commands/view.ts`.
|
|
612
|
+
|
|
613
|
+
- **`agents repo pull` now reloads the routines daemon so device pins refresh.**
|
|
614
|
+
The scheduler froze each routine's config — device pins included — in memory at
|
|
615
|
+
daemon start. A `repo pull` rewrites the synced routine YAML on disk (a routine
|
|
616
|
+
re-pinned to another host, say), but without a reload the daemon kept firing the
|
|
617
|
+
pre-pull pins, so a routine moved to another device still fired on the old host
|
|
618
|
+
too — a phantom double-fire across the fleet. A successful pull now SIGHUPs the
|
|
619
|
+
running daemon (`scheduler.reloadAll()`), re-reading the YAML so pins refresh. A
|
|
620
|
+
no-op when the daemon isn't running or on Windows (no SIGHUP). Source:
|
|
621
|
+
`apps/cli/src/commands/repo.ts`.
|
|
622
|
+
|
|
623
|
+
- **Routines can pin a Claude account by identity to stop the OAuth-rotation
|
|
624
|
+
logout storm.** Unpinned `claude` routines pick an account with the default
|
|
625
|
+
`balanced` (stateless weighted-random) strategy, so two concurrent unattended
|
|
626
|
+
runs — on one box or across the fleet — can land on the same account; Claude's
|
|
627
|
+
refresh token is single-use and rotates server-side, so the second refresh
|
|
628
|
+
revokes the first run's token mid-flight (`401 OAuth access token has been
|
|
629
|
+
revoked`). Across ~20 routines waking in one morning window that is a
|
|
630
|
+
self-inflicted logout storm (RUSH-1957). A routine may now set `account:` (a
|
|
631
|
+
login email or account key) to pin the run to the version slot holding that
|
|
632
|
+
account — no rotation, no usage-read refresh, no failover onto other accounts —
|
|
633
|
+
so each routine (or each device's routines) refreshes one credential nobody else
|
|
634
|
+
touches. Prefer it over `version:`, which pins a version *number* that is GC'd on
|
|
635
|
+
the next upgrade, silently dropping the routine back to `balanced`. An account
|
|
636
|
+
that is not signed in on the box warns and falls back to the strategy rather than
|
|
637
|
+
refusing to run. Source: `apps/cli/src/lib/routines.ts`,
|
|
638
|
+
`apps/cli/src/lib/rotate.ts`, `apps/cli/src/lib/runner.ts`.
|
|
639
|
+
|
|
640
|
+
- **Balanced account rotation now works for scheduled Claude routines.** The
|
|
641
|
+
routines daemon injects one `CLAUDE_CODE_OAUTH_TOKEN` into its environment so a
|
|
642
|
+
token-less default account still authenticates (RUSH-1759). But Claude — and the
|
|
643
|
+
Linux shim's own `-z CLAUDE_CODE_OAUTH_TOKEN` guard — both prefer that env var
|
|
644
|
+
over a pinned account's `CLAUDE_CONFIG_DIR`, so once balanced rotation pinned a
|
|
645
|
+
specific account the injected token shadowed it: the whole pool was inert and
|
|
646
|
+
every fire authenticated as (and eventually 401'd on) the one token. A routine
|
|
647
|
+
spawn now drops the injected token when the rotated account holds its own on-disk
|
|
648
|
+
credential, so it authenticates as that account; when the account has no on-disk
|
|
649
|
+
credential (the RUSH-1759 default) the injected token is kept. Source:
|
|
650
|
+
`apps/cli/src/lib/runner.ts` (`buildRoutineSpawnEnv`),
|
|
651
|
+
`apps/cli/src/lib/agents.ts` (`claudeHomeHasOwnCredential`).
|
|
652
|
+
|
|
653
|
+
- **No more Touch ID prompt on every new agent session.** Bundle metadata (names,
|
|
654
|
+
descriptions, variable names + references, and non-sensitive `--value` literals)
|
|
655
|
+
is now stored WITHOUT the biometry ACL at every prompt-policy tier, not just
|
|
656
|
+
`never`. Metadata is non-sensitive by contract — real secret values live in
|
|
657
|
+
separate `agents-cli.secrets.*` items that keep the bundle's policy ACL — so
|
|
658
|
+
enumerating bundles no longer needs a keychain unlock. This kills the recurring
|
|
659
|
+
Touch ID prompt that fired on every new Claude/agent terminal: a SessionStart
|
|
660
|
+
hook runs `agents devices list`, which scans bundle metadata through crabbox, and
|
|
661
|
+
that scan used to pop Touch ID once per broker window (~7 days) on every cold
|
|
662
|
+
launch. `agents secrets list` is now silent too. Reading a bundle's actual
|
|
663
|
+
values (run injection, `view --reveal`) still prompts. Existing bundles are
|
|
664
|
+
migrated automatically and once: the first metadata scan after upgrade re-homes
|
|
665
|
+
each bundle's metadata item no-ACL (reusing the read it already did, so it adds
|
|
666
|
+
no extra prompt), and every scan after that is prompt-free. Source:
|
|
667
|
+
`apps/cli/src/lib/secrets/bundles.ts`, `apps/cli/src/lib/secrets/index.ts`.
|
|
668
|
+
|
|
669
|
+
- **Wire workflows support for Grok (RUSH-1863).** Grok Build ships native Workflows as of v0.2.111 (on by default): file-defined Rhai orchestration scripts in `~/.grok/workflows/<name>.rhai` (user-global, under `GROK_HOME`) and `.grok/workflows/` (repo-level). agents-cli now declares `workflows: { since: 0.2.111 }` for grok, projects central `WORKFLOW.md` bundles into managed Rhai scripts via `transformWorkflowForGrok` (with an `// agents_workflow: <name>` marker so user-authored scripts are never overwritten), and registers the writer + detector so `agents workflows add --agents grok@…` / sync land files Grok can invoke as `/<name>`. Distinct from the grok *commands* gap (RUSH-1851). Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/workflows.ts`, `apps/cli/src/lib/staleness/{writers,detectors}/workflows.ts`, `apps/cli/src/lib/versions.ts`.
|
|
670
|
+
|
|
671
|
+
- **Dispatch-bar screenshots now upload via `linear create --image` instead of
|
|
672
|
+
landing as dead local paths.** The menu-bar helper previously injected
|
|
673
|
+
screenshot paths into the ticket-agent prompt, and the model echoed them into
|
|
674
|
+
the issue description as `/Users/…` text. The agent now returns ticket fields
|
|
675
|
+
as JSON, and the helper itself runs `linear create` with `--image <path>` for
|
|
676
|
+
each selected screenshot, so paths pass through Swift argv and survive spaces
|
|
677
|
+
or `@` in CleanShot filenames. Coordinates with the `linear create --image`
|
|
678
|
+
support added in `phnx-labs/linear-cli#28`. Source:
|
|
679
|
+
`apps/cli/menubar/Sources/MenubarHelper/AgentsCLI.swift`,
|
|
680
|
+
`apps/cli/menubar/Sources/MenubarHelper/IssueSelfTest.swift`.
|
|
681
|
+
|
|
682
|
+
- **Native helper bundles now ship the agents-cli icon and the computer helper is
|
|
683
|
+
branded "Agents Computer".** MenubarHelper.app, ComputerHelper.app, and the
|
|
684
|
+
keychain `Agents CLI.app` previously had no `CFBundleIconFile`/`.icns`, so
|
|
685
|
+
Notification Center and System Settings → Privacy & Security showed a blank
|
|
686
|
+
square. Each build script now generates `AppIcon.icns` from `assets/logo.png`
|
|
687
|
+
and adds `CFBundleIconFile` to the bundle `Info.plist`. The computer helper
|
|
688
|
+
display name changed from "Computer Helper" to "Agents Computer" while keeping
|
|
689
|
+
its bundle id and on-disk path, so existing Accessibility/Screen Recording
|
|
690
|
+
grants remain valid. Source: `apps/cli/menubar/scripts/build.sh`,
|
|
691
|
+
`native/computer-mac/scripts/build.sh`, `apps/cli/scripts/build-keychain-helper.sh`,
|
|
692
|
+
`apps/cli/src/commands/setup-computer.ts`, `apps/cli/menubar/Sources/MenubarHelper/PromptPanel.swift`.
|
|
693
|
+
|
|
694
|
+
- **One resolver for `--host` / `--device` — every subcommand now dials the same box
|
|
695
|
+
(RUSH-1967).** A host token used to resolve through two disagreeing code paths:
|
|
696
|
+
`run --host` (and the generic passthrough, teams placement, doctor, funnel, remote
|
|
697
|
+
secrets) let a `~/.ssh/config` stanza win and dialed its bare name, while
|
|
698
|
+
`sessions --host`, session bundles, and `agents ssh` dialed the device Tailscale
|
|
699
|
+
`user@dnsName`. The same name could reach two different machines, and because the two
|
|
700
|
+
emitted different target strings they never shared a multiplexed SSH connection.
|
|
701
|
+
Resolution is now a single merged lookup (`matchHost`): the live devices registry
|
|
702
|
+
supplies address/OS/presence, the agents.yaml overlay supplies capability tags, and
|
|
703
|
+
ssh_config supplies hosts Tailscale has never seen — merged per-field, not one
|
|
704
|
+
shadowing another. Fallout fixed with it: an enrolled device address always comes from
|
|
705
|
+
the live registry (so `agents devices sync` takes effect without re-enrolling, no more
|
|
706
|
+
frozen route), an enrolled device keeps its presence and `dispatchable` flags, a
|
|
707
|
+
password-auth device cannot be made dispatchable by shadowing it with an inline entry,
|
|
708
|
+
and a host present only in `~/.ssh/config` is now visible to the `sessions --host`
|
|
709
|
+
fan-out.
|
|
710
|
+
|
|
711
|
+
- **`agents run --device`/`--host` now auto-reconnects when the network drops.** A
|
|
712
|
+
remote interactive agent runs in a detached tmux session on the peer, so an SSH
|
|
713
|
+
blink kills only the local client — the agent keeps running. Previously the local
|
|
714
|
+
side exited with ssh's connection-layer code (255) and you had to notice, find the
|
|
715
|
+
session id, and `agents sessions focus` by hand. Now, when a tmux-hosted run with a
|
|
716
|
+
known session id drops (exit 255), the client re-attaches the live remote pane
|
|
717
|
+
automatically over SSH — reusing the peer's own `agents sessions focus <id> --local
|
|
718
|
+
--attach-only` (a live join, not a resumed copy) — with bounded exponential backoff
|
|
719
|
+
(2s→30s, up to 6 attempts, and the budget refills after a genuinely live
|
|
720
|
+
reconnection). A clean detach (Ctrl-b d, exit 0) or a real agent exit (any non-255
|
|
721
|
+
code) is left alone; `--raw`/no-tmux runs, which don't survive a drop, are not
|
|
722
|
+
retried. This covers Claude and resumed runs today; capturing a resumable id for
|
|
723
|
+
other agents on the `--device` path is tracked in RUSH-2007. Source:
|
|
724
|
+
`apps/cli/src/lib/hosts/reconnect.ts`, `apps/cli/src/commands/exec.ts`.
|
|
725
|
+
|
|
726
|
+
- **The secrets broker cache now actually works on the shipped macOS binary — one Touch ID per bundle per hold window, not one per read.** The three synchronous broker clients (`agentGetSync`, `agentReachableSync`, `agentEvictSync`) spawned `process.execPath -e <inline node program>`, which is only correct when `process.execPath` is node. Since 1.20.53 the macOS `agents` is a bun-compiled Mach-O, so `process.execPath` is the CLI itself and the spawn became `agents -e …` — rejected with `error: unknown option '-e'` and a non-zero exit. Each client then took its own failure path (`null` / `false` / no-op), which the caller reads as "broker down" and falls through to a real keychain read. Net effect: on every standalone install the hot cache was never hit, so the `daily` policy's one-prompt-per-7d never applied and **every bundle read re-popped Touch ID** — `agents secrets status` would report the broker running while holding nothing but explicitly `unlock`ed bundles (the durable session-store path, the only client that never spawned). Same defect class as the broker launch fixed in 1.20.56 and the PTY sidecar in 1.20.72; these three sites were simply never converted. They now spawn top-level `__secrets-get` / `__secrets-ping` / `__secrets-lock` tokens built by the shared `getCliLaunch` primitive and intercepted in `index.ts` before commander — alongside `__daemon-run` and `__vault-age-helper`, and deliberately above the line where every normal command runs `checkForUpdates()` and forks a detached background sync, which would otherwise fire on every cache hit. Source: `apps/cli/src/lib/secrets/agent.ts`, `apps/cli/src/index.ts`.
|
|
727
|
+
|
|
728
|
+
- **Let a bundle whose passphrase is lost be deleted, so the name can be recovered.**
|
|
729
|
+
A file-backed bundle that no longer decrypts bricked its own name: `view`, `add`,
|
|
730
|
+
`delete`, and both `import --from icloud` and `import --from 1password` all called
|
|
731
|
+
`readBundle()` first, so none of them could touch it — including the two commands that
|
|
732
|
+
exist to restore it from a valid iCloud Keychain or 1Password copy. `delete` now uses
|
|
733
|
+
the new `readBundleIfDecryptable()` and proceeds without the plaintext, reporting that
|
|
734
|
+
the bundle's keychain items cannot be enumerated for purging instead of claiming a
|
|
735
|
+
clean purge. The `view` hint no longer points at `import --from icloud` for a bundle
|
|
736
|
+
that is still on disk — that command fails identically — and names the delete-then-import
|
|
737
|
+
sequence that actually works. Only a genuine decrypt failure counts as deletable: a
|
|
738
|
+
bundle that is merely locked for the run (headless macOS with no
|
|
739
|
+
`AGENTS_SECRETS_PASSPHRASE`) still fails loudly and is left in place, so
|
|
740
|
+
`secrets delete <name> --yes` from a cron/launchd run that forgot to export the
|
|
741
|
+
passphrase can't silently destroy a healthy bundle. Source:
|
|
742
|
+
`apps/cli/src/lib/secrets/bundles.ts`, `apps/cli/src/commands/secrets.ts`.
|
|
743
|
+
|
|
744
|
+
- **Session tab titles no longer go missing or stale.** A rescan that carried an
|
|
745
|
+
empty or whitespace-only label used to clobber a good stored label, because
|
|
746
|
+
`upsertSession`/`upsertSessionsBatch` wrote `label = excluded.label`
|
|
747
|
+
unconditionally on conflict. The `ON CONFLICT` clause now preserves an existing
|
|
748
|
+
non-empty label and only overwrites when a real label arrives, so a `/rename`,
|
|
749
|
+
agent-generated title, or `--name` handle survives later rescans. Headless runs
|
|
750
|
+
launched with `--name` now also surface that name as the session label (matching
|
|
751
|
+
the terminal path) instead of showing only the topic. Source:
|
|
752
|
+
`apps/cli/src/lib/session/db.ts`, `apps/cli/src/lib/session/active.ts`.
|
|
753
|
+
|
|
754
|
+
- **`agents sessions --active` no longer shows zombie sessions from recycled pids.**
|
|
755
|
+
Liveness was a bare `process.kill(pid, 0)` existence check, so once the OS handed a
|
|
756
|
+
dead session's pid to an unrelated process, that session kept showing as alive — and
|
|
757
|
+
the registry GC never pruned it. `isPidAlive` now takes the session's recorded
|
|
758
|
+
`startedAtMs` and, when a start time is available, verifies the process at that pid did
|
|
759
|
+
not begin meaningfully after the session started (a 60s window): a process that started
|
|
760
|
+
later is a reused pid, so the session is dead. The start time is read once via
|
|
761
|
+
`ps -o lstart=` (macOS + Linux); Windows and any unreadable start time fall back to the
|
|
762
|
+
existence check, never worse than before. Applied to every registry-backed liveness
|
|
763
|
+
path — the live-terminals filter, the terminal listing, the tmux-pane resolver, and the
|
|
764
|
+
pid-registry prune. Source: `apps/cli/src/lib/session/active.ts`.
|
|
765
|
+
|
|
766
|
+
- **`agents sessions` no longer corrupts the index with empty `shortId` rows.**
|
|
767
|
+
Session ids that are only a known prefix — a bare `session_` Rush directory, an
|
|
768
|
+
id of exactly `api-` (Hermes) or `ses_` (OpenCode) — used to strip to `''`
|
|
769
|
+
(`'session_'.replace(/^session_/, '').slice(0, 8) === ''`). An empty `shortId`
|
|
770
|
+
passes the `short_id TEXT NOT NULL` constraint (empty string is not NULL) yet
|
|
771
|
+
matches nothing in the `short_id LIKE ?` picker lookups, so the row was silently
|
|
772
|
+
unaddressable. All shortId derivation is now routed through one helper,
|
|
773
|
+
`deriveShortId`, that guarantees a non-empty result by falling back to the
|
|
774
|
+
unstripped id when the strip empties it. Every producer — the twelve parsers in
|
|
775
|
+
`discover.ts`, `session/cloud.ts`, `cloud/session-index.ts`, `hosts/session-index.ts`,
|
|
776
|
+
`session/fork.ts`, and `commands/go.ts` — uses it, replacing the duplicated inline
|
|
777
|
+
`.slice(0, 8)` (some with a `.replace(prefix, '')`). Source:
|
|
778
|
+
`apps/cli/src/lib/session/short-id.ts`.
|
|
779
|
+
|
|
780
|
+
- **Honest live status: report `unknown` instead of a fake `idle` (RUSH-1976).** `agents
|
|
781
|
+
sessions --active` now reports an explicit `unknown` status (`◌`) for a live agent whose
|
|
782
|
+
activity it cannot introspect — a running gemini/droid/cursor/opencode whose transcript
|
|
783
|
+
format is not parsed — instead of the misleading `idle` it showed before. Status resolution
|
|
784
|
+
is standardized in one place (`resolveFallbackStatus`): a vanished transcript file no longer
|
|
785
|
+
flips to a false `running`, an unanswered prose question with no mtime signal no longer
|
|
786
|
+
sticks as "waiting on you" forever (the RUSH-1522 null-mtime hole), and the `ps`/`lsof`
|
|
787
|
+
probes behind the scan now have hard timeouts so a hung syscall can't silently drop live
|
|
788
|
+
sessions. Source: `apps/cli/src/lib/session/active.ts` (`resolveFallbackStatus`),
|
|
789
|
+
`apps/cli/src/lib/session/state.ts`, `apps/cli/src/commands/sessions.ts`.
|
|
790
|
+
|
|
791
|
+
- **Interactive session browser: preview-by-default with clickable ticket + PR
|
|
792
|
+
links.** In `agents sessions` / `agents sessions --active`, the highlighted
|
|
793
|
+
row's preview is now open by default (`tab` toggles it off), and the preview's
|
|
794
|
+
links line renders the ticket and PR as OSC 8 terminal hyperlinks — the ticket
|
|
795
|
+
resolves to its Linear URL (workspace slug resolved config-first) and `PR#`
|
|
796
|
+
resolves to its GitHub URL — so they are click-through in terminals that support
|
|
797
|
+
them. Source: `apps/cli/src/lib/picker.ts`,
|
|
798
|
+
`apps/cli/src/commands/sessions-browser.ts`,
|
|
799
|
+
`apps/cli/src/lib/session/render.ts`.
|
|
800
|
+
|
|
801
|
+
- **`agents sessions` now lists what agents-cli manages, not your own installs.** Discovery
|
|
802
|
+
scans the union of your real `~/.<agent>` and every managed version home, so once you had
|
|
803
|
+
managed versions the listing mixed both — most visibly after `agents add --isolated`, where
|
|
804
|
+
keeping the two apart was the whole point. Listing is now scoped to managed versions
|
|
805
|
+
(isolated or not); `--unmanaged` brings your own installs back, and every render path prints
|
|
806
|
+
what it hid (`N sessions from your own unmanaged installs hidden`) so nothing disappears
|
|
807
|
+
silently. A user who has never run `agents add` sees exactly what they saw before — with
|
|
808
|
+
nothing managed there is nothing to scope to. Scoping happens at query time rather than by
|
|
809
|
+
narrowing the scan, so the index stays complete, `--unmanaged` needs no re-scan, and
|
|
810
|
+
watchdog / `--roots` / the Factory watcher are unaffected. Source:
|
|
811
|
+
`apps/cli/src/lib/session/discover.ts`, `apps/cli/src/commands/sessions.ts`.
|
|
812
|
+
|
|
813
|
+
- **Attribute split-spawned agents on the authoritative tmux source (RUSH-1976).** `agents
|
|
814
|
+
sessions --active` now attributes each tmux pane to the agent actually running in it: an
|
|
815
|
+
agent bare-spawned into a split of an existing shared-socket session (where `$TMUX` is
|
|
816
|
+
already set, so no new session meta is stamped) is surfaced by the authoritative tmux
|
|
817
|
+
source with its own exact identity and pane, instead of being dropped there and left to the
|
|
818
|
+
weaker `ps`-scan fallback. Attribution reads the per-pane launch registry (the id recorded
|
|
819
|
+
at launch, or the SessionStart-hook join by launchId for a non-Claude agent), gated on the
|
|
820
|
+
pid still being alive so a dead agent's pane can't linger. Source:
|
|
821
|
+
`apps/cli/src/lib/session/active.ts` (`resolvePaneIdentity`, `listTmuxAgentSessions`),
|
|
822
|
+
`apps/cli/src/lib/session/pid-registry.ts` (`listPidSessionEntries`).
|
|
823
|
+
|
|
824
|
+
- **Watchdog v2 — the always-on watchdog now has judgment and delivers correctly
|
|
825
|
+
into VS Codium.** The 2-minute `agents watchdog --nudge` routine no longer
|
|
826
|
+
hard-skips a session that stopped to ask a question. The deterministic pass is
|
|
827
|
+
now a cheap pre-filter (clearly-complete → skip, clear promise-without-toolcall →
|
|
828
|
+
nudge) that ESCALATES the judgment-heavy cases — a session parked on a question,
|
|
829
|
+
or an ambiguous stall — to a smart brain. The brain drives the agent to finish
|
|
830
|
+
end-to-end when it asked a needless / already-authorized question or paused with
|
|
831
|
+
work left, and leaves it for the human only for genuine cases (credentials/auth,
|
|
832
|
+
an irreversible or outward-facing action, a real ambiguous product decision, or a
|
|
833
|
+
finished task). Nudge messages restate the goal, tell the agent to use best
|
|
834
|
+
judgment, and give one concrete next step. The brain is a customizable
|
|
835
|
+
`watchdog` workflow: drop a `watchdog` WORKFLOW.md in your project or user
|
|
836
|
+
`workflows/` to override the prompt and pick the `model:`; absent one, the
|
|
837
|
+
improved built-in prompt runs via `agents run … --mode plan`. Source:
|
|
838
|
+
`apps/cli/src/lib/watchdog/watchdog.ts`, `apps/cli/src/lib/watchdog/runner.ts`.
|
|
839
|
+
- **Watchdog delivery routes through the answer-router with the VS Codium rail
|
|
840
|
+
working.** A running agent is steered via its mailbox; a parked-on-question agent
|
|
841
|
+
is answered into its EXACT split — including a VS Codium / Cursor / VS Code
|
|
842
|
+
integrated terminal, which the answer-router's own resolver could not address —
|
|
843
|
+
or, when headless, re-entered via resume; a parked agent with no addressable rail
|
|
844
|
+
is flagged, never a guessed target.
|
|
845
|
+
- **Watchdog precedence + concurrency fixes.** A long-idle (>15m) open question is
|
|
846
|
+
no longer blindly force-nudged — waiting-on-user and completion now win over the
|
|
847
|
+
15-minute force-review short-circuit. The per-session cooldown ledger is written
|
|
848
|
+
under a file lock (fresh-read + merge + atomic write), closing a lost-update race
|
|
849
|
+
between concurrent ticks.
|
|
850
|
+
- **Watchdog decisions are logged to `~/.agents/.cache/logs/watchdog.log`** in the
|
|
851
|
+
JSONL shape the Factory Floor watchdog card reads, so it keeps working after the
|
|
852
|
+
extension-side watchdog is retired.
|
|
853
|
+
|
|
854
|
+
- **The always-on watchdog is now a daemon-fired routine, not a hand-rolled loop + sentinel.**
|
|
855
|
+
`agents watchdog enable` used to flip a private `~/.agents/.cache/state/watchdog/enabled`
|
|
856
|
+
sentinel that only meant anything to a manually-launched `agents watchdog --watch` loop —
|
|
857
|
+
so the auto-nudge only ran while some shell was babysitting it. It now creates and enables a
|
|
858
|
+
plain `watchdog` command routine (`agents watchdog --nudge`, every 2 minutes) and reloads
|
|
859
|
+
the daemon, so the always-on watchdog is fired by the same scheduler that runs every other
|
|
860
|
+
routine: it survives reboots, catches up if the daemon was down, and shows up in
|
|
861
|
+
`agents routines list`. `disable` pauses that routine; `status` reports whether it is
|
|
862
|
+
enabled. The Swift menu-bar toggle and `watchdog status --json` are unchanged. Bare
|
|
863
|
+
`agents watchdog` (dry) and `agents watchdog --watch` (now dry unless `--nudge`) still work
|
|
864
|
+
for ad-hoc runs. If you had already opted in under the old build, a one-shot migration
|
|
865
|
+
folds that state forward — you stay enabled, now as the routine. Source:
|
|
866
|
+
`apps/cli/src/lib/watchdog/routine.ts`, `apps/cli/src/commands/watchdog.ts`,
|
|
867
|
+
`apps/cli/src/lib/migrate.ts`.
|
|
868
|
+
|
|
869
|
+
- **Wire hooks support for OpenCode through generated plugins (RUSH-1850).** `hooks.yaml` entries now compile into `~/.config/opencode/plugins/agents-cli-hooks.ts`, mapping tool, prompt, and session lifecycle events to OpenCode's native plugin API and executing managed scripts with Bun's `$` shell primitive. OpenCode hooks are capability-gated to v0.3.130 and newer.
|
|
870
|
+
|
|
871
|
+
## 1.20.74
|
|
872
|
+
|
|
873
|
+
- **`agents apply --agent claude@all --device <box>` — replicate this machine's
|
|
874
|
+
exact version set.** The fleet roster is agent-granular, so a fresh box only ever
|
|
875
|
+
got one `claude@latest` — losing a multi-version setup (e.g. several claude
|
|
876
|
+
versions, one per Max account, to spread rate-limit quota). The new `--agent
|
|
877
|
+
<specs...>` flag overrides the roster for the targeted device(s): `claude@all`
|
|
878
|
+
expands source-side to every version installed here (`claude@2.1.170`,
|
|
879
|
+
`claude@2.1.207`, …) and installs each missing one on the target; a pinned
|
|
880
|
+
`claude@2.1.207` installs that exact version even if another claude is present.
|
|
881
|
+
Version-pinned specs diff against a per-device `agents view --json` probe, so the
|
|
882
|
+
plan installs only what's missing and login still propagates once per agent.
|
|
883
|
+
Source: `apps/cli/src/commands/apply.ts`, `apps/cli/src/lib/fleet/apply.ts`.
|
|
884
|
+
|
|
885
|
+
- **Project resource manifests are now portable across Windows and POSIX.** The
|
|
886
|
+
managed-resource manifest `.agents-managed.json` recorded its paths with the
|
|
887
|
+
host's native separator, so a sync run on Windows wrote entries like
|
|
888
|
+
`skills\myskill`. That file lives in the version-controlled project `.agents`
|
|
889
|
+
dir and travels between machines, and the cleanup pass matches manifest entries
|
|
890
|
+
with `path.sep` — so a manifest written on Windows silently failed to match on
|
|
891
|
+
macOS or Linux and left previously managed files behind on the next sync (and
|
|
892
|
+
vice versa). Manifest paths are now normalized to POSIX separators on write and
|
|
893
|
+
on read, which also repairs manifests written by earlier Windows builds. Source:
|
|
894
|
+
`apps/cli/src/lib/project-resources.ts`.
|
|
895
|
+
|
|
896
|
+
- **`agents import <agent> --isolated` — bring your existing setup into a sandbox.**
|
|
897
|
+
Isolation was a cold start: a new isolated copy began empty, and the only way to get
|
|
898
|
+
settings into it was by hand. A plain `agents import` is the opposite of what is
|
|
899
|
+
wanted here — it *adopts*, moving `~/.<agent>` into a version home, symlinking the
|
|
900
|
+
original away, setting the global default and creating a shim (and is now refused
|
|
901
|
+
outright for an isolated-only agent). `--isolated` copies instead: your settings land
|
|
902
|
+
in the isolated home, your real config stays exactly where it is, and the version is
|
|
903
|
+
finalized the way `agents add --isolated` does — versioned alias and marker, no
|
|
904
|
+
default, no bare shim, no config symlink. Credentials are skipped by default and
|
|
905
|
+
named in the output rather than silently included, since an isolated copy signs in as
|
|
906
|
+
its own principal; `--with-auth` opts in. Symlinks into `~/.agents` are dropped so the
|
|
907
|
+
copy does not depend on the CLI's tree. Source: `apps/cli/src/lib/import.ts`,
|
|
908
|
+
`apps/cli/src/commands/import.ts`.
|
|
909
|
+
|
|
910
|
+
- **`agents use <agent>@<isolated>` now works, and a bare `agents run <agent>` reaches
|
|
911
|
+
your isolated copy.** Isolated installs were unreachable by name: `resolveVersion`
|
|
912
|
+
ended at the global default, and an isolated install deliberately never becomes one —
|
|
913
|
+
so `agents use` refused, and an isolated-only user had to type the full
|
|
914
|
+
`agents run codex@0.144.6` every time while a bare `agents run codex` fell through to
|
|
915
|
+
whatever `codex` meant on PATH. `use` now records an **isolated default** instead of
|
|
916
|
+
refusing, and resolution falls back to it (`project pin -> global default -> isolated
|
|
917
|
+
default`). Strictly a fallback, so nothing changes for anyone who has a global
|
|
918
|
+
default. The pointer lives in `isolatedAgents:` in `agents.yaml`, never in the global
|
|
919
|
+
`agents:` map — that separation is what keeps `getGlobalDefault` incapable of
|
|
920
|
+
returning an isolated version, and with it the launcher, bare shim, config symlink and
|
|
921
|
+
self-heal `shadowing` check all stay out of reach. It is verified on read and
|
|
922
|
+
re-pointed (or cleared) on removal, so it can never resolve to a version that is gone.
|
|
923
|
+
`agents view` labels it `(isolated default)`. Source: `apps/cli/src/lib/versions.ts`,
|
|
924
|
+
`apps/cli/src/commands/versions.ts`, `apps/cli/src/commands/view.ts`.
|
|
925
|
+
|
|
926
|
+
- **`agents export <agent>[@<version>]` — take an isolated install's config with you.**
|
|
927
|
+
`--isolated` was a one-way door: it builds a self-contained home under the version
|
|
928
|
+
dir and nothing ever brings that work back, so a user who configured a sandboxed copy
|
|
929
|
+
for a week had to copy files by hand to promote it — or to leave. Export is additive
|
|
930
|
+
by default: it copies only paths you don't already have, and a collision is **not**
|
|
931
|
+
silently skipped — the incoming file is written beside yours as
|
|
932
|
+
`<name>.from-agents-cli` so you can `--diff` it and take the parts you want. Your
|
|
933
|
+
files are never modified. `--replace` promotes a sandbox wholesale (yours is moved to
|
|
934
|
+
`backups/<agent>/<ts>`, and it is the only mode that asks for confirmation);
|
|
935
|
+
`--staged` dumps the tree into `~/.<agent>/.agents-export-<ts>/` and activates
|
|
936
|
+
nothing. Every mode strips symlinks pointing back into `~/.agents` so the result
|
|
937
|
+
keeps working after agents-cli is gone, keeps your own symlinks, and writes a receipt
|
|
938
|
+
to `~/.<agent>/.agents-cli-export.json` recording exactly what came from the export —
|
|
939
|
+
which makes "which of these files are mine?" answerable and the whole thing
|
|
940
|
+
reversible. A `~/.<agent>` that agents-cli already adopted is refused, since writing
|
|
941
|
+
there would mutate that version's home rather than your config. File *contents* are
|
|
942
|
+
never auto-merged: the TOML parser here drops comments across parse+stringify, so
|
|
943
|
+
unioning keys would silently delete them. Source: `apps/cli/src/lib/export.ts`,
|
|
944
|
+
`apps/cli/src/commands/export.ts`, `apps/cli/src/lib/config-transfer.ts`.
|
|
945
|
+
|
|
946
|
+
- **An isolated-only agent can no longer be adopted by anything.** `--isolated` used to
|
|
947
|
+
be defined by what it *doesn't* do — no global default, no bare shim, no config
|
|
948
|
+
symlink, no PATH edit — which meant every code path that could adopt an agent had to
|
|
949
|
+
remember to check first. It leaked three times that way. Protection is now derived
|
|
950
|
+
from the `.isolated` markers on disk (`isIsolationProtected`: at least one installed
|
|
951
|
+
version, and every one isolated) and enforced inside the five primitives that can
|
|
952
|
+
cross the boundary — `setGlobalDefault`, `createShim`, `switchConfigSymlink`,
|
|
953
|
+
`switchHomeFileSymlinks`, `adoptShadowingLauncher` — so refusal is a property of the
|
|
954
|
+
code rather than a convention. There is no mode to set and none to forget: installing
|
|
955
|
+
with `--isolated` *is* the opt-in, it is per-agent, and the escape hatch is inherent
|
|
956
|
+
(remove the isolated copies and the agent is ordinary again). `agents add`,
|
|
957
|
+
`agents import` and `doctor --adopt` refuse with guidance rather than a stack trace —
|
|
958
|
+
`import` is additionally checked at its entry point, because it registers the adopted
|
|
959
|
+
install as a normal version *before* adopting, which would otherwise un-protect the
|
|
960
|
+
agent underneath the primitive gate. Clearing a global default stays allowed, since
|
|
961
|
+
removal legitimately clears one as an agent becomes isolated-only. A completeness test
|
|
962
|
+
pins the primitive list and scans for any new ungated mutator. Source:
|
|
963
|
+
`apps/cli/src/lib/shims.ts`, `apps/cli/src/lib/versions.ts`,
|
|
964
|
+
`apps/cli/src/lib/isolation-boundary-report.ts`.
|
|
965
|
+
|
|
966
|
+
- **`agents view` no longer hides your own CLI behind an isolated install.** The listing
|
|
967
|
+
was either/or per agent: any managed version at all suppressed the "Not Managed by
|
|
968
|
+
Agents CLI" block, so a single `agents add <agent>@<v> --isolated` made the user's
|
|
969
|
+
globally-installed CLI disappear from the one command they'd run to confirm
|
|
970
|
+
`--isolated` had left it alone. Nothing on disk was ever touched — the isolation
|
|
971
|
+
boundary holds — but the report read exactly like the damage it was supposed to rule
|
|
972
|
+
out. Isolated copies now render alongside the global install and are tagged
|
|
973
|
+
`9.9.4 (isolated)`; a normal (non-isolated) version still takes the launcher over and
|
|
974
|
+
still suppresses the global row, since that row would just be our own shim. The global
|
|
975
|
+
row is also resolved from PATH now (`getUnmanagedCliState`) instead of from the version
|
|
976
|
+
dirs, which could otherwise report an isolated copy — deliberately unreachable from
|
|
977
|
+
PATH — as `(global)`. Source: `apps/cli/src/commands/view.ts`, `apps/cli/src/lib/agents.ts`.
|
|
978
|
+
|
|
979
|
+
- **`agents sessions --active` now resolves the exact session id for non-Claude and
|
|
980
|
+
user-typed agents.** Previously only Claude (launched with a known `--session-id`) got an
|
|
981
|
+
exact id; every other agent fell back to "newest `.jsonl` in the cwd", which collapses
|
|
982
|
+
co-located agents onto one row. `ag run` now mints a launch id and exports it as
|
|
983
|
+
`AGENT_LAUNCH_ID` on every launch path (bare spawn, tmux, and the Windows shim); the
|
|
984
|
+
agent's own SessionStart hook already records that id, so the active-scan reconciles a
|
|
985
|
+
`ps`-discovered process to the hook's authoritative session id by `launchId` (robust even
|
|
986
|
+
when the hook runs under a different pid — a tmux pane leaf or `cmd.exe` wrapper), falling
|
|
987
|
+
back to `terminalId` and pid. This also attributes agents `ag run` never launched (you
|
|
988
|
+
typing `claude` in a terminal). No on-disk directory moved — the CLI reads the existing
|
|
989
|
+
hook state files read-only, so old installed hooks and a new CLI coexist safely. Source:
|
|
990
|
+
`apps/cli/src/lib/exec.ts`, `apps/cli/src/lib/session/{pid-registry,hook-sessions,active}.ts`.
|
|
991
|
+
|
|
3
992
|
## 1.20.73
|
|
4
993
|
|
|
5
994
|
- **`agents cli install <binary-cli>` no longer hardcodes `/usr/local/bin` (#1103).**
|