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