@phnx-labs/agents-cli 1.22.29 → 1.22.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +44 -5
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/accounts.d.ts +13 -0
  5. package/dist/commands/accounts.js +32 -0
  6. package/dist/commands/daemon.d.ts +18 -0
  7. package/dist/commands/daemon.js +581 -0
  8. package/dist/commands/exec.js +66 -20
  9. package/dist/commands/focus.d.ts +4 -1
  10. package/dist/commands/focus.js +19 -4
  11. package/dist/commands/routines.js +29 -11
  12. package/dist/commands/secrets.d.ts +37 -0
  13. package/dist/commands/secrets.js +86 -105
  14. package/dist/commands/sessions-bookmark.d.ts +20 -0
  15. package/dist/commands/{sessions-favorite.js → sessions-bookmark.js} +42 -42
  16. package/dist/commands/sessions-browser.d.ts +10 -8
  17. package/dist/commands/sessions-browser.js +61 -32
  18. package/dist/commands/sessions-picker.d.ts +33 -1
  19. package/dist/commands/sessions-picker.js +102 -27
  20. package/dist/commands/sessions-stats.js +1 -1
  21. package/dist/commands/sessions.d.ts +21 -8
  22. package/dist/commands/sessions.js +328 -74
  23. package/dist/commands/view.d.ts +11 -0
  24. package/dist/commands/view.js +56 -29
  25. package/dist/index.js +37 -2
  26. package/dist/lib/account-labels.d.ts +24 -0
  27. package/dist/lib/account-labels.js +72 -0
  28. package/dist/lib/agents.d.ts +32 -1
  29. package/dist/lib/agents.js +96 -31
  30. package/dist/lib/daemon-health.d.ts +24 -0
  31. package/dist/lib/daemon-health.js +84 -0
  32. package/dist/lib/daemon-ticks.d.ts +81 -0
  33. package/dist/lib/daemon-ticks.js +190 -0
  34. package/dist/lib/daemon.d.ts +68 -18
  35. package/dist/lib/daemon.js +303 -338
  36. package/dist/lib/device-config.d.ts +10 -0
  37. package/dist/lib/device-config.js +27 -0
  38. package/dist/lib/exec.d.ts +27 -0
  39. package/dist/lib/exec.js +49 -2
  40. package/dist/lib/hosts/dispatch.d.ts +4 -0
  41. package/dist/lib/hosts/dispatch.js +4 -0
  42. package/dist/lib/hosts/remote-cmd.js +1 -0
  43. package/dist/lib/hosts/run-target.d.ts +1 -0
  44. package/dist/lib/hosts/run-target.js +1 -0
  45. package/dist/lib/import.js +7 -6
  46. package/dist/lib/memory-cache.d.ts +19 -0
  47. package/dist/lib/memory-cache.js +31 -0
  48. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  49. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  50. package/dist/lib/migrate.d.ts +1 -1
  51. package/dist/lib/migrate.js +13 -2
  52. package/dist/lib/picker.d.ts +6 -3
  53. package/dist/lib/picker.js +7 -2
  54. package/dist/lib/routine-activation.d.ts +2 -0
  55. package/dist/lib/routine-activation.js +16 -0
  56. package/dist/lib/runner.d.ts +18 -0
  57. package/dist/lib/runner.js +52 -0
  58. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  59. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  60. package/dist/lib/secrets/agent.d.ts +19 -1
  61. package/dist/lib/secrets/agent.js +32 -6
  62. package/dist/lib/secrets/scope.d.ts +3 -3
  63. package/dist/lib/secrets/scope.js +3 -3
  64. package/dist/lib/secrets/session-store.d.ts +0 -4
  65. package/dist/lib/secrets/session-store.js +0 -5
  66. package/dist/lib/session/{favorites.d.ts → bookmarks.d.ts} +15 -15
  67. package/dist/lib/session/{favorites.js → bookmarks.js} +23 -23
  68. package/dist/lib/session/db.d.ts +15 -0
  69. package/dist/lib/session/db.js +90 -15
  70. package/dist/lib/session/discover.js +91 -39
  71. package/dist/lib/session/parse.d.ts +63 -0
  72. package/dist/lib/session/parse.js +165 -20
  73. package/dist/lib/session/session-cache.d.ts +9 -6
  74. package/dist/lib/session/session-cache.js +23 -6
  75. package/dist/lib/shims.js +12 -0
  76. package/dist/lib/startup/command-registry.d.ts +15 -1
  77. package/dist/lib/startup/command-registry.js +49 -0
  78. package/dist/lib/usage-refresh.js +3 -2
  79. package/dist/lib/usage.d.ts +12 -10
  80. package/dist/lib/usage.js +63 -144
  81. package/package.json +4 -1
  82. package/dist/commands/sessions-favorite.d.ts +0 -20
package/CHANGELOG.md CHANGED
@@ -1,7 +1,95 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.22.31
4
+
5
+ - **`agents secrets unlock --keys` folds in the scoped-hold surface; the `secrets lease`/`leases`/`revoke` commands are deleted (RUSH-2350).** `unlock` now takes `--keys K1,K2` to hold ONLY that subset of a bundle behind its own expiry instead of the whole bundle — the one capability `lease` had that `unlock` did not, now on the command agents already reach for. Without `--keys`, `unlock` behaves exactly as before (whole bundle). An unknown or empty key subset fails closed (`Unknown secret lease key(s): …` / `requires at least one key`), `--keys` scopes exactly one local bundle (rejected with `--all`/`--host`), and `secrets status` now names the held keys of a scoped hold; `secrets lock <name>` releases it. The duplicate `secrets lease`/`secrets leases`/`secrets revoke` trio is gone — it had zero consumers, and its jobs (list holdings, release one) are already `secrets status` and `secrets lock`. No alias or deprecation shim (per the repo's no-unasked-shims rule). The underlying lease model (`src/lib/secrets/lease.ts`) is unchanged — `unlock --keys` reuses it. Source: `apps/cli/src/commands/secrets.ts` (`scopeHeldEnv`, the `unlock`/`status` actions), `apps/cli/src/lib/secrets/{agent,session-store}.ts`, `apps/cli/src/commands/secrets.scope.test.ts`, `apps/cli/src/commands/secrets.flags.test.ts`.
6
+
7
+ - **Session previews use bookmark and focus as separate actions (RUSH-2373).** The shared interactive browser for ordinary, team, and routine sessions now uses `*` to bookmark the highlighted row, `b` / `--bookmarks` to filter bookmarks, and `f` to focus the highlighted session through the same attach-or-recover flow as `agents sessions focus`. Enter still resumes. The non-TTY surface is now `agents sessions bookmark`; its flag, JSON keys, code symbols, and durable store are bookmark-only, with a one-time migration from `~/.agents/.history/favorites.json` to `bookmarks.json`. The retired favorite command and flag are not aliases. Source: `apps/cli/src/commands/sessions-browser.ts`, `sessions-bookmark.ts`, `focus.ts`, `apps/cli/src/lib/session/bookmarks.ts`, `apps/cli/src/lib/migrate.ts`.
8
+
9
+ ## 1.22.30
10
+
11
+ - **`agents secrets import --force` now repairs a bundle whose metadata record is
12
+ undecryptable (#2305).** A file store whose key was lost or rotated out from
13
+ under it leaves bundles present but unreadable — exactly the state provisioning
14
+ exists to fix. `agents secrets export <bundle> --host <box> --remote-backend
15
+ file --force` drives the remote's own `import`, which died on the unreadable
16
+ record (`Bundle 'x': failed to decrypt`) and wrote nothing, so the only route
17
+ left was deleting the record by hand on an already-degraded store. With
18
+ `--force`, an undecryptable record is now treated as absent and recreated.
19
+
20
+ Still refused **without** `--force`: recreating unconditionally would destroy a
21
+ healthy bundle for someone who merely forgot to set `AGENTS_SECRETS_PASSPHRASE`.
22
+ Only `BundleUndecryptableError` qualifies — a locked keychain or logged-out
23
+ vault still throws, so a recoverable state is never mistaken for a lost key.
24
+ Source: `apps/cli/src/commands/secrets.ts` (`resolveImportBundle`).
25
+
26
+ - **Documented the routine reliability contract (RUSH-2290).** `docs/specifications.md` now specifies routines normatively: a new **§Routine execution & readiness** section (RT-1..RT-11) pins that `projects` (plural) is grouping metadata only while a singular `project`/`--project-anchor` is the execution anchor, that a routine's working directory resolves on the *execution target* (with a canonical cwd-resolution table — rootless project or bare relative cwd anchors at the target `$HOME`), that a proven readiness blocker saves the routine **paused** with a stable code (`project_not_found`, `cwd_missing`, `codex_workspace_untrusted`, `agent_auth_failed`, `execution_context_missing`, …), that run history owns attempts while sessions/logs/reports are optional children, and that `repo` is an external Git/cloud/webhook identity, not a local cwd. §Scheduling & execution singularity gains SING-11..SING-13 (one scheduled fire launches at most once; the slot claim and the active-run claim are separate; a routine never overlaps itself), and run statuses gain `blocked`/`skipped`. Most of the contract is marked `[Intended]` (RT-GAP-1, SING-GAP-3) — the target the reliability work implements — while the landed guarantees (daemon singleton + catch-up consolidation, run-first history, definition/activation split, menu bar read-only) are marked Current. `docs/03-routines.md` documents the same model for users. Docs-only; no runtime change. Source: `apps/cli/docs/specifications.md`, `apps/cli/docs/03-routines.md`.
27
+
28
+ - **Daemon: one instance per state dir via last-wins takeover (RUSH-2352).** A second
29
+ `agents __daemon-run` for the same state dir — from ANY install path (homebrew,
30
+ `.local` dev, nvm, npx) sharing one `~/.agents` — now evicts the incumbent and takes
31
+ over, instead of exiting and leaving it running. This inverts the old first-wins
32
+ refusal, an owner product decision: a restart always replaces the previous daemon.
33
+ `claimDaemonInstance` SIGTERMs the live pid-file owner of its own state dir, waits for
34
+ its graceful shutdown to release the secrets broker socket and browser IPC binding
35
+ (hard timeout → `killTree` → bind), and never binds before that release — closing the
36
+ two-brokers-on-one-socket orphan. In-flight detached routine children survive and are
37
+ adopted by the new daemon, never killed. A daemon serving a DIFFERENT state dir (a
38
+ separate `HOME`, a test fixture) is never a takeover target. Source:
39
+ `apps/cli/src/lib/daemon.ts`.
40
+
41
+ - **Daemon housekeeping ticks are now routines, not a second scheduling concept (RUSH-2353).** `watchdog`, `device-probe`, `tmux-reconcile`, `launch-health`, `fleet-cache-warm`, `session-cache-warm`, `usage-refresh`, and `auto-dispatch` were hardcoded `setInterval` timers inside `runDaemon()` — undeclared, no run history, no pause, no device pin. They are now shipped system routines (`gh:phnx-labs/.agents-system` `routines/*.yml`) that invoke the same tick body one-shot via `agents __daemon-tick <name>` (`apps/cli/src/lib/daemon-ticks.ts`), fired by the same pid-claimed `JobScheduler` as every other routine. Same schedules, same effects; each tick now shows up in `agents routines list`, accumulates history in `agents routines runs`/`stats`, and can be individually paused. `auto-dispatch` — polling Linear for delegated tickets — is the one that mattered most: it previously fired unpinned on every daemon in a fleet with no coordination, and can now be pinned to a single device with `agents routines devices auto-dispatch --set <device>`. Requires the paired `phnx-labs/.agents-system` release that ships the new routine YAML. Source: `apps/cli/src/lib/daemon.ts`, `apps/cli/src/lib/daemon-ticks.ts`, `apps/cli/src/index.ts`.
42
+ - Existing device activation lists automatically retain all eight formerly always-on daemon ticks during the routine migration.
43
+
44
+ - **Restored `agents daemon` — runtime, hosted-service, and failure visibility for the always-on daemon (RUSH-2354).** `agents daemon` had been deliberately removed while every mechanism it needs (`getDaemonStatus`, `startDaemon`, `stopDaemon`, `readDaemonLog`, `signalDaemonReload`) stayed implemented and unreachable. It's back as `status` (identity — state/pid/uptime/heartbeat/owning install — plus every duplicate `__daemon-run` process on the box, plus per-service health), `start`/`stop`/`restart`, `enable`/`disable` (a new persisted `daemon.enabled` device kill switch — `disable` stops `routines add`/`routines start`/`routines catchup`/webhook auto-start; `agents daemon start` still starts it explicitly), `reload` (SIGHUP), `services`, `logs` (`-n`/`-f`/`--level`/`--since`/`--json`), and `doctor`. A new persisted per-subsystem health record (`apps/cli/src/lib/daemon-health.ts`) tracks the secrets broker and browser IPC server's consecutive-failure streaks, so a subsystem failure no longer just scrolls out of the daemon log. There is still no `agents daemon jobs` — scheduled work stays `agents routines`; `status`/`doctor` point failures at `agents routines stats`. Source: `apps/cli/src/commands/daemon.ts`, `apps/cli/src/lib/daemon-health.ts`, `apps/cli/src/lib/device-config.ts`.
45
+
46
+ - **Daemon: `stop` asserts its postcondition instead of assuming it (RUSH-2355).**
47
+ `stopDaemon` used to fire SIGTERM and report success without checking anything, so a
48
+ stop that silently left a resource bound still read as "stopped". It now waits for
49
+ shutdown, escalates via `killTree` on a wedged daemon, then verifies each resource
50
+ released — the secrets broker socket, the browser IPC binding, and no surviving
51
+ `__daemon-run` for this state dir — reclaiming any stale socket the ungraceful exit
52
+ left behind. `agents daemon stop` prints what released vs what survived, exits non-zero
53
+ when a resource could not be released, and carries a structured result under `--json`.
54
+ In-flight detached routine children survive a stop deliberately and are reported, never
55
+ killed. It never reports success on an unverified stop. Source:
56
+ `apps/cli/src/lib/daemon.ts`, `apps/cli/src/commands/daemon.ts`.
57
+
58
+ - **OpenCode sessions are no longer pruned from every listing (RUSH-2357).** OpenCode keeps all sessions in one SQLite file, so the index stores a composite `file_path` (`opencode.db#ses_<id>`). The staleness gate split that string with dirname/basename and looked the composite basename up as a directory entry — it never matched, so every OpenCode row was classified as a deleted file and dropped from `agents sessions`. A second gate then hid it as an "unmanaged" install because its single shared DB is never under a version home. Both now key off the composite FORM: existence is tested against the CONTAINER file, and a single-DB session is treated as managed (there is one canonical store, not a per-install dotfile). Any future single-shared-DB harness inherits the fix. Source: `apps/cli/src/lib/session/parse.ts`, `apps/cli/src/lib/session/db.ts`, `apps/cli/src/lib/session/discover.ts`.
59
+
60
+ - **OpenCode session reads are bounded again, and one bad row no longer hides every OpenCode session (RUSH-2358).** `parseOpenCode` now projects a tool part to just the fields it reads, dropping `state.attachments` and capping `state.output` / an oversized `state.input`: on a real `opencode.db` the largest tool part is 1.3 MB of base64 image attachment that the previous output-only truncation left whole, inflating one session's loaded payload 6.4×. Every `json_extract` in the OpenCode scan and parse queries is now guarded by `json_valid` — SQLite aborts the entire query on a malformed value, so a single unparseable `part` or `message` row previously dropped **all** OpenCode sessions from `agents sessions`, with no message in a non-TTY run. The `todo` read is gated by a `sqlite_master` row count instead of a blanket catch, so a locked or corrupt database no longer reads as "no todos" — and the count avoids the `undefined`-vs-`null` split between `node:sqlite` and `bun:sqlite`, which a sentinel check would have inverted on the shipped Bun binary. Source: `apps/cli/src/lib/session/parse.ts`, `apps/cli/src/lib/session/discover.ts`.
61
+ - **`agents sessions`' OpenCode `account` field now resolves from `auth.json`, the same source `agents view`/`agents doctor` already use (RUSH-2358).** The prior resolver queried `opencode.db`'s `account`/`account_state`/`control_account` tables, which are permanently empty on a real, actively-used install (verified: yosemite-s1, OpenCode 1.16.0, 35 applied migrations, zero rows in all three) — so `account` always read null regardless of login state. `resolveOpenCodeAccountId` (new export, `apps/cli/src/lib/agents.ts`) is now the single source of truth for both surfaces: the sorted, `+`-joined provider ids holding a valid `auth.json` credential. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/session/discover.ts`.
62
+
63
+ - **OpenCode sessions now carry the same usage/burn fields as every other harness (RUSH-2358).** `agents sessions` reads what OpenCode's own SQLite DB already records: `input_tokens`, `cache_read_tokens`, `cache_write_tokens` (siblings of the existing `output_tokens`), `cost_usd` and `model` from the session row, `duration_ms` from its timestamps, `tool_call_count` from its tool parts, `todos` from its `todo` table, `recent_directories_touched` from the files it edited, and `worktree_slug` from its cwd. `parseOpenCode` now truncates only a tool part's output (not the whole part), so large `edit` parts are no longer dropped. Newer `cost`/`model` columns and the `todo` table are probed, so an older `opencode.db` still scans. `account` stays null when no signed-in account is recorded, and `account_key`/`account_org`/`git_branch`/`cost_usd_nocache` remain not-applicable for OpenCode. Source: `apps/cli/src/lib/session/discover.ts`, `apps/cli/src/lib/session/parse.ts`.
64
+
65
+ - **`agents sessions preview <uuid-or-8-char-id>` resolves rich previews across the fleet (RUSH-2370).** ID-shaped lookups use the SQLite ID index rather than scanning recent history; full UUIDs can stop at the first exact owner, while short prefixes wait for every selected peer and surface ambiguity or unreachable peers. The owning device renders remote cards. A bounded `lru-cache` L1 handles process-local reuse, normalized transcript-derived preview facts are cached durably by actual file mtime + size, and live status stays outside that cache behind a 15-second TTL. Text output now includes plugin provenance alongside skills, hooks, errors, tests, messages, tokens, timing, and artifacts; `--json` emits a versioned envelope. The existing detailed `agents sessions <id>` renderer and `--preview` spelling remain unchanged. Source: `apps/cli/src/{commands/{sessions,sessions-picker}.ts,lib/{memory-cache.ts,session/{db,session-cache}.ts}}`.
66
+
67
+ - **Full command index doc — `docs/command-index.md` + `docs/command-index.json`.** Every `agents` command and subcommand with its argument names and one-line description, on one scannable page, plus a machine-readable tree. Generated deterministically from the CLI's own command tree (`npm run gen:index`), regenerated on release, and gated in CI (`npm run verify:index`, in the cli-preflight and cli-docs jobs) so it can never drift from the shipped surface.
68
+
69
+ - **`agents secrets` drops the vague `--for` flag: duration is `--ttl`/`--until`, harness narrowing is `--agent`.** `--for` was doing two unrelated jobs on sibling commands — `secrets unlock --for <agent>` meant a HARNESS while `secrets lease --for <duration>` meant a DURATION — so an agent that learned the flag from one command passed it wrongly to the other, and `unlock --for 8h` silently read `8h` as a harness name. `unlock` keeps its existing `--ttl`/`--until` and now narrows with `--agent <agent>`; `lease` now takes `--ttl <duration>` (matching `unlock`) alongside its existing `--agent`. `--for` no longer exists anywhere under `secrets`, and a test asserts Commander's real option tree so it cannot come back. Breaking for anyone scripting `secrets unlock --for <agent>` — no consumer existed in this repo or the fleet's own tooling, so no alias was added (per the repo's no-unasked-shims rule). Source: `apps/cli/src/commands/secrets.ts`, `apps/cli/src/commands/secrets.flags.test.ts`.
70
+
71
+ - **Cursor launches no longer hang in a blank terminal after agents-cli adopts its launcher (RUSH-2345).** Native launcher discovery now follows symlinks and rejects any candidate that resolves back into the agents shim, using the durable adopted-original record instead. Imports store the immutable native target, startup migration repairs existing two-hop loops, and generated shims refuse to recursively execute themselves. Cursor is also restored to prompt-less interactive selection because current builds open a native TUI with no arguments. The same centralized repair fixes `agents run cursor` and Factory Cursor tabs.
72
+
73
+ - **`agents view` and `agents run` keep custom harnesses independent from their native host.** The unfiltered view still lists each fork as its own custom harness block, while `agents view claude` (and its filtered JSON form) shows only Claude versions. Exact custom names are resolved before native harness names, so a fork remains viewable and runnable through its configured host even when its name matches a native or hard-deprecated id. Source: `apps/cli/src/commands/view.ts`, `apps/cli/src/commands/exec.ts`.
74
+
75
+ - **Fixed a Touch ID storm from version-skewed clients evicting the daemon's secrets broker.** When the always-on daemon hosts the secrets broker on one installed version and a differently-versioned client invokes (e.g. a `~/.local` dev build alongside the Homebrew/npm install), the client tore the daemon's broker down at zero held bundles. Because teardown only recognizes the standalone broker's pid claim, it unlinked the daemon's socket without stopping the daemon — which then refused to re-host and was orphaned until restart, leaving every secrets read to cold-start a one-off broker and re-prompt Touch ID. `ensureAgentRunning` now defers to a live daemon (`shouldClientEvictSkewedBroker`) and never evicts a daemon-hosted broker; daemon version upgrades are handled by the postinstall restart, and the wire protocol is already version-checked, so a code-skewed daemon broker stays wire-compatible. Source: `apps/cli/src/lib/secrets/agent.ts`.
76
+
77
+ - **The daemon is now one process per device, whatever launch entry started it — stops a duplicate-daemon pile-up that double-fired every routine.** One install exposes two launch entries (the compiled `dist/bin/agents` binary and the `node <shim>` JS entry) with different `process.argv[1]`, and the stray-daemon reaper keyed on that path — so the two never reaped each other, duplicates accumulated (78 live `__daemon-run` observed on one box), and each scheduled routine fired from many daemons at once (measured: a once-per-hour routine spawning 10+ times per tick, once-per-day Claude routines spawning ~8 concurrent runs — burning account tokens and colliding into `failed`). The reaper now enumerates the device singleton from an on-disk **instance registry** under `<daemonDir>/instances/` (keyed by the daemon dir, which is per device — `AGENTS_DAEMON_DIR` ?? `<HOME>/.agents/.cache/helpers/daemon`), so every daemon of one device registers in the same place and the survivor reaps the rest regardless of how each was launched; a genuinely separate install/home or test fixture resolves elsewhere and is left alone. Identity rides the shared registry rather than reading another process's environment, which hardened macOS hides from `ps`. Source: `apps/cli/src/lib/daemon.ts` (`registerDaemonInstance`, `reapStrayDaemons`).
78
+
79
+ - **`agents run <agent>` now says the harness is not installed instead of exiting 127 (RUSH-2339).** On a machine without that harness the launch used to exec the bare CLI name and die with `sh: 1: exec: cursor-agent: not found`, behind a `⚠ <agent> looks logged out` banner that was also wrong. `agents run` now probes the executable it is about to spawn and exits `1` with `agents: <agent> is not installed on this machine.` plus the `agents add <agent>` fix, before any spawn. The probe is existence-based, so a harness you installed yourself (Homebrew, a vendor `curl | sh`, a distro package) with no agents-cli version home still launches, and a machine with managed versions but no pinned default still gets the shim's own `agents use <agent> <version>` guidance rather than a wrong "not installed".
80
+
81
+ - **The Claude usage/auth-health probe no longer reads your interactive Claude Code login — the fix for repeated Anthropic logouts.** The daemon's usage (~60s) and auth-health (~3min) warms authenticated with a file-based setup-token when one was provisioned, but otherwise fell through to reading Claude Code's interactive OAuth token from the keychain / `.credentials.json` and firing it at `api.anthropic.com/api/oauth/usage`. Anthropic sees an interactive login used programmatically from a background loop and revokes it (the fleet-wide-logout class, RUSH-1822). `loadClaudeOauth`'s read-only `accessTokenCache` path now returns nothing when no setup-token is provisioned — it never reads the interactive login, in the keychain or in a file — matching the "interactive/rotating login is untouchable" invariant in docs/design/credential-management.md. The now-obsolete no-ACL access-token cache is removed with it. An account without a minted setup-token shows "usage pending" in `agents view` (seed one via the mint-auth path) but still runs normally; account rotation is unaffected. Source: `apps/cli/src/lib/usage.ts`.
82
+
83
+ - **Sort `agents view <agent>` accounts by email after the default.** Managed installs previously followed semantic-version order after the default, forcing users with several Claude, Codex, or other harness accounts to scan a shuffled email column. Rows without an email remain deterministic in version-descending order, and `--json` keeps its existing default-then-version order for automation compatibility. Source: `apps/cli/src/commands/view.ts`.
84
+
85
+ - **`agents sessions` accepts version filters in the same forms as other harness commands (RUSH-2363).** An installed positional selector such as `agents sessions claude@2.1.181` now routes to the structured agent/version filter instead of searching for that literal text and returning no matches. The equivalent split form, `agents sessions --agent claude --version 2.1.181`, is also supported; unknown or uninstalled positional pairs remain ordinary free-text queries. Source: `apps/cli/src/commands/sessions.ts`.
86
+
3
87
  ## 1.22.29
4
88
 
89
+ - **Name provider accounts once and find their installed versions automatically (#2300).** After completing a harness's normal login, `agents accounts` discovers distinct signed-in accounts across every installed version and `agents accounts name work` names one through a picker (`--from claude@2.1.220` is the non-interactive form). The synced registry stores only the harness id and a SHA-256 identity fingerprint—no OAuth material, email, provider id, per-device binding, or cross-harness account group. `agents run <agent> --account <label>` live-scans candidates and chooses a healthy matching version; it fails instead of falling back to another identity.
90
+
91
+ - **Sessions: one "favorite" vocabulary, no more "star".** The interactive browser (`agents sessions`) footer read `f favorites · * star`, presenting two names for one mechanism — `*` toggles a favorite, `f` filters to favorited, and both write the single store in `lib/session/favorites.ts`. The `*` action, the `--favorites` flag help, the `agents sessions favorite` command help, and the docs now all say "favorite"; the `★`/`☆` glyphs and the `favorites`/`favorite` JSON keys are unchanged. Source: `apps/cli/src/commands/sessions-browser.ts`, `sessions-favorite.ts`, `sessions.ts`.
92
+
5
93
  - **Menu bar: collapsible DEVICES roster, a Focus action on sessions, and richer routine submenus.** The dropdown now has a **DEVICES** section near the bottom — the full registered fleet as one accordion, folded by default so the long list never walls the menu, each row `<name> · <platform>` with live load% merged from the warm fleet cache (never a faked online/offline) and a Copy `agents ssh <name>` action; **NEW DEVICES** moved to sit just above it. Each live-session `›` submenu leads with **▶ Focus session** (attaches locally or SSHes to the owning box via `agents sessions focus`). Each routine submenu now leads with a last-run line — `● running now` (server-verified), `✓ completed · ran 45s · 2h ago`, or `✕ failed exit 1` — plus the next fire. The device roster rides the existing 3-minute `menubar snapshot --json` poll (a cheap local registry read, `MenubarSnapshot.devices`); everything else renders from fields the snapshot already carried, so no new timer and no per-open shell-out. Source: `apps/cli/src/lib/menubar/snapshot.ts`, `apps/cli/menubar/Sources/MenubarHelper/{StatusItemController,Models,LocalState}.swift`.
6
94
 
7
95
  - Fix `agents view` and `agents usage` labeling Codex's weekly or monthly quota as session usage when the native CLI publishes the long-duration limit in its `primary` rate-limit window. Codex windows are now labeled from their reported duration (`S`, `W`, or `M`) instead of their primary/secondary position.
package/README.md CHANGED
@@ -338,8 +338,9 @@ On a terminal, `agents sessions --active` (and a bare `agents sessions`) open th
338
338
  |---|---|---|
339
339
  | `s` | search text | `--query` / positional |
340
340
  | `r` | running only | `--active` |
341
- | `f` | favorites only | `--favorites` |
342
- | `*` | star / unstar the highlighted session | `agents sessions favorite <id>` |
341
+ | `b` | bookmarks only | `--bookmarks` |
342
+ | `*` | bookmark / unbookmark the highlighted session | `agents sessions bookmark <id>` |
343
+ | `f` | focus the highlighted session | `agents sessions focus <id>` |
343
344
  | `c` | team sessions | `--team` (alias: `--teams`) |
344
345
  | `a` | agent (cycles) | `-a` |
345
346
  | `d` | device (cycles) | `--device` |
@@ -349,17 +350,17 @@ On a terminal, `agents sessions --active` (and a bare `agents sessions`) open th
349
350
  | `⏎` | resume / attach | `resume` / `focus` |
350
351
  | `y` | copy the equivalent command | `--print-cmd` |
351
352
 
352
- **Star the sessions you keep coming back to.** `*` marks the highlighted row (a `★` shows in the listing), `f` narrows to the starred ones, and `agents sessions favorite <id>` / `--favorites` do the same outside a TTY. Stars live in `~/.agents/.history/favorites.json` keyed by session id, so they survive a reindex of the session cache. They're per-machine — session sync carries transcripts, not this file.
353
+ **Bookmark the sessions you keep coming back to.** `*` marks the highlighted row (a `★` shows in the listing), `b` narrows to bookmarks, and `agents sessions bookmark <id>` / `--bookmarks` do the same outside a TTY. Press `f` to focus the highlighted row through the same attach-or-recover flow as `agents sessions focus <id>`; Enter keeps its existing resume behavior. Bookmarks live in `~/.agents/.history/bookmarks.json` keyed by session id, so they survive a reindex of the session cache. They're per-machine — session sync carries transcripts, not this file.
353
354
 
354
355
  **A session that lost its host says so.** When an editor window or an SSH connection goes down hard, the agent it owned used to simply disappear from `--active`; when an agent outlived its window in tmux, it reported a plain `idle`. Both now carry their own status: `✗ crashed` (the host went down and took the agent with it) and `◍ orphan` (still alive, but no client is attached — nothing is showing it). Read from tmux's attached-client count and the editor window's registry heartbeat, so a deliberate `agents sessions detach` is never mistaken for one, and a session that is still *working* headlessly is left alone.
355
356
 
356
- Filters **stack** (they AND together), the active set shows in the header, and the highlighted row **previews below by default** (`tab` hides it) — prompt, activity, last response, plus a links line where the worked-on ticket and the PR the session opened are **clickable** (OSC 8 hyperlinks: the ticket jumps to Linear, the `PR#` to GitHub, in terminals that support them). The Linear workspace is resolved from `LINEAR_WORKSPACE` or the linear-cli config, so tickets stay plain text when it's unknown. Because every hotkey has a flag, the view you build by hand is a real command: press `y` (or run `--print-cmd`) to get the exact `ag sessions …` line — explore interactively, hand the line to an agent. Piped output, `--json`, or `--no-interactive` keep the plain listing for scripts. Peek without opening the pager with `agents sessions <id> --preview`.
357
+ Filters **stack** (they AND together), the active set shows in the header, and the highlighted row **previews below by default** (`tab` hides it) — prompt, activity, last response, plus a links line where the worked-on ticket and the PR the session opened are **clickable** (OSC 8 hyperlinks: the ticket jumps to Linear, the `PR#` to GitHub, in terminals that support them). The Linear workspace is resolved from `LINEAR_WORKSPACE` or the linear-cli config, so tickets stay plain text when it's unknown. Because every hotkey has a flag, the view you build by hand is a real command: press `y` (or run `--print-cmd`) to get the exact `ag sessions …` line — explore interactively, hand the line to an agent. Piped output, `--json`, or `--no-interactive` keep the plain listing for scripts. Peek without opening the pager with `agents sessions preview <uuid-or-8-char-id>`; it resolves across the fleet and supports `--json`. The older `agents sessions <id> --preview` spelling remains available.
357
358
 
358
359
  | before — preview hidden | after — preview open + clickable links |
359
360
  | --- | --- |
360
361
  | ![sessions browser, preview hidden](assets/demos/sessions-preview-before.png) | ![sessions browser, preview open with a links line](assets/demos/sessions-preview-after.png) |
361
362
 
362
- Each live session resolves to `working`, `waiting_input` (with why -- a question, a plan review, or a permission prompt), `idle`, or a lifecycle state such as `orphaned`, `crashed`, `closed`, `abandoned`, `queued`, or `unknown`. Pass the matching flag (`--working`, `--idle`, `--waiting`, `--orphan`, `--crashed`, `--closed`, `--abandoned`, `--queued`, `--unknown`) directly; each implies `--active`, and several flags form a union. The fleet fan-out is already the default; `--local` opts out. `--all` instead widens historical directory and time scope. Rows also carry badges for the PR, worktree, and ticket. `agents sessions focus [selector]` accepts the same agent/version, device, time, team, project, skill/plugin, favorite, and live-state filters as the session browser. A unique id focuses directly; an agent/version or text selector always opens the preview picker. Immediately before attach it checks the tmux pane process: a living pane is joined in place, while a dead/missing pane enters recovery instead of showing tmux's `Pane is dead` screen.
363
+ Each live session resolves to `working`, `waiting_input` (with why -- a question, a plan review, or a permission prompt), `idle`, or a lifecycle state such as `orphaned`, `crashed`, `closed`, `abandoned`, `queued`, or `unknown`. Pass the matching flag (`--working`, `--idle`, `--waiting`, `--orphan`, `--crashed`, `--closed`, `--abandoned`, `--queued`, `--unknown`) directly; each implies `--active`, and several flags form a union. The fleet fan-out is already the default; `--local` opts out. `--all` instead widens historical directory and time scope. Rows also carry badges for the PR, worktree, and ticket. `agents sessions focus [selector]` accepts the same agent/version, device, time, team, project, skill/plugin, bookmark, and live-state filters as the session browser. A unique id focuses directly; an agent/version or text selector always opens the preview picker. Immediately before attach it checks the tmux pane process: a living pane is joined in place, while a dead/missing pane enters recovery instead of showing tmux's `Pane is dead` screen.
363
364
 
364
365
  Landing on a session cold? `agents sessions <id>` prints a catch-up digest: an inferred title, files changed grouped by directory (created / modified / deleted), a histogram of which tools did the work (including parsed Bash commands -- `git`, `npm`, `ffmpeg`, `ssh`, and so on), and the last test verdict -- the signals to reload a task in seconds.
365
366
 
@@ -496,6 +497,9 @@ agents add claude@2.0.65 # Install a specific version
496
497
  agents add codex@latest # Install latest
497
498
  agents add codex@oldest # Install the oldest published version
498
499
  agents view # See everything installed
500
+ agents accounts # Discover signed-in accounts across installed versions
501
+ agents accounts name work # Name one account through a picker
502
+ agents run claude --account work
499
503
  ```
500
504
 
501
505
  ---
@@ -1033,6 +1037,41 @@ agents funnel up yosemite-s0 --local-port 8787 --port 443 # public HTT
1033
1037
  ```
1034
1038
 
1035
1039
  Jobs run sandboxed -- agents only see directories and tools you explicitly allow.
1040
+ `--project` tags a routine into a project **group** for listings only -- it never
1041
+ decides where the body runs. The reliability contract (execution anchor + `--cwd`,
1042
+ readiness that saves a blocked routine paused, and the `blocked`/`skipped` run
1043
+ statuses) is specified in
1044
+ [docs/specifications.md §Routine execution & readiness](apps/cli/docs/specifications.md#routine-execution--readiness);
1045
+ some of it is planned (RUSH-2290), and the section marks what is landed vs intended.
1046
+
1047
+ ### Daemon
1048
+
1049
+ Routines, the secrets broker, browser IPC, and the watchdog pass all run inside
1050
+ one always-on daemon per device. `agents daemon` is its runtime surface:
1051
+
1052
+ ```bash
1053
+ agents daemon # identity + duplicates + per-service health (same as status)
1054
+ agents daemon status --json # machine-readable, for scripts / Factory
1055
+
1056
+ agents daemon start # start it (bypasses daemon.enabled -- the deliberate override)
1057
+ agents daemon stop # stop it
1058
+ agents daemon restart # stop then start
1059
+
1060
+ agents daemon disable # persist daemon.enabled: false -- nothing auto-starts it
1061
+ agents daemon enable # clear the kill switch
1062
+
1063
+ agents daemon reload # SIGHUP -- reload jobs, re-evaluate scheduler.enabled, no restart
1064
+ agents daemon services # just the two hosted services (secrets broker, browser IPC)
1065
+ agents daemon logs -f --level warn --since 1h
1066
+ agents daemon doctor # one-shot health check; non-zero exit on problems
1067
+ ```
1068
+
1069
+ There is no `agents daemon jobs` -- scheduled work is always `agents routines`
1070
+ (see `agents routines stats` for per-routine failure detail). `disable` is a
1071
+ device-local kill switch: with it set, `routines add`/`routines start`/
1072
+ `routines catchup`/webhook triggers stop auto-starting the daemon, mirroring
1073
+ `systemctl disable` -- `agents daemon start` still works as the explicit
1074
+ override.
1036
1075
 
1037
1076
  ---
1038
1077
 
package/dist/bin/agents CHANGED
Binary file
@@ -0,0 +1,13 @@
1
+ import type { Command } from 'commander';
2
+ import type { AgentId } from '../lib/types.js';
3
+ import { listInstalledVersions } from '../lib/versions.js';
4
+ import { discoverAccounts } from '../lib/account-labels.js';
5
+ export declare function fingerprintFromSource(raw: string, deps?: {
6
+ installedVersions?: typeof listInstalledVersions;
7
+ discover?: typeof discoverAccounts;
8
+ }): Promise<{
9
+ agent: AgentId;
10
+ fingerprint: string;
11
+ versions: string[];
12
+ }>;
13
+ export declare function registerAccountsCommand(program: Command): void;
@@ -0,0 +1,32 @@
1
+ import chalk from 'chalk';
2
+ import { select } from '@inquirer/prompts';
3
+ import { resolveAgentName, formatAgentError } from '../lib/agents.js';
4
+ import { listInstalledVersions } from '../lib/versions.js';
5
+ import { discoverAccounts, nameAccount, removeAccountLabel, renameAccountLabel } from '../lib/account-labels.js';
6
+ import { setHelpSections } from '../lib/help.js';
7
+ function parseSource(raw) { const at = raw.lastIndexOf('@'); if (at < 1 || at === raw.length - 1)
8
+ throw new Error(`Expected <agent>@<version>, got '${raw}'.`); const name = raw.slice(0, at); const agent = resolveAgentName(name); if (!agent)
9
+ throw new Error(formatAgentError(name)); return { agent, version: raw.slice(at + 1) }; }
10
+ export async function fingerprintFromSource(raw, deps = {}) {
11
+ const { agent, version } = parseSource(raw);
12
+ if (!(deps.installedVersions ?? listInstalledVersions)(agent).includes(version))
13
+ throw new Error(`${raw} is not installed.`);
14
+ const account = (await (deps.discover ?? discoverAccounts)([agent])).find(candidate => candidate.versions.includes(version));
15
+ if (!account)
16
+ throw new Error(`${raw} has no stable signed-in account. Run it and complete its normal login first.`);
17
+ return { agent, fingerprint: account.fingerprint, versions: account.versions };
18
+ }
19
+ async function printAccounts(json) { const accounts = await discoverAccounts(); if (json)
20
+ return console.log(JSON.stringify(accounts, null, 2)); if (!accounts.length)
21
+ return console.log(chalk.gray('No signed-in accounts found. Run an installed agent and complete its normal login first.')); console.log(chalk.bold('Signed-in accounts\n')); for (const account of accounts)
22
+ console.log(` ${account.label ? chalk.cyan(account.label) : chalk.gray('(unnamed)')} ${account.agent} ${account.display}\n ${account.versions.length} installed version${account.versions.length === 1 ? '' : 's'}: ${account.versions.join(', ')}`); }
23
+ async function chooseAccount() { const accounts = await discoverAccounts(); if (!accounts.length)
24
+ throw new Error('No signed-in accounts found. Run an installed agent and complete its normal login first.'); return select({ message: 'Which signed-in account do you want to name?', choices: accounts.map(account => ({ name: `${account.agent} ${account.display} (${account.versions.length} version${account.versions.length === 1 ? '' : 's'})${account.label ? ` currently “${account.label}”` : ''}`, value: account })) }); }
25
+ export function registerAccountsCommand(program) {
26
+ const accounts = program.command('accounts').description('Browse and name signed-in harness accounts').option('--json', 'Machine-readable discovered accounts').action(async (o) => printAccounts(!!o.json));
27
+ accounts.command('list').description('Alias for accounts').option('--json').action((o) => printAccounts(!!o.json));
28
+ accounts.command('name <label>').description('Name one signed-in account; matching installed versions are found automatically').option('--from <agent@version>', 'Non-interactive identity source').action(async (label, o) => { const picked = o.from ? await fingerprintFromSource(o.from) : await chooseAccount(); nameAccount(label, picked.agent, picked.fingerprint); console.log(chalk.green(`Named the ${picked.agent} account '${label}'.`)); console.log(chalk.gray(`Found it in ${picked.versions.length} installed version${picked.versions.length === 1 ? '' : 's'}: ${picked.versions.join(', ')}`)); });
29
+ accounts.command('rename <old> <new>').action((oldLabel, newLabel) => renameAccountLabel(oldLabel, newLabel));
30
+ accounts.command('remove <label>').action((label) => removeAccountLabel(label));
31
+ setHelpSections(accounts, { examples: `agents accounts\nagents accounts name work\nagents accounts name work --from claude@2.1.220\nagents run claude --account work`, notes: 'First run the harness and complete its normal login. A label names one provider account; every matching installed version is discovered automatically. OAuth credentials are never copied or shared.' });
32
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * `agents daemon` — runtime, hosted services, and failure visibility for the
3
+ * always-on daemon (RUSH-2354).
4
+ *
5
+ * The daemon holds the routines scheduler, the secrets broker, the browser IPC
6
+ * server, and the watchdog pass — but until this command group existed it had
7
+ * no user-facing surface: no way to see it, restart it, or turn it off.
8
+ * `daemon.ts` (the runtime) has always implemented every mechanism this file
9
+ * wires up; nothing here is new machinery, only the missing CLI surface.
10
+ *
11
+ * There is deliberately no `agents daemon jobs` — scheduled work is
12
+ * `agents routines`, always (see RUSH-2353, which migrates the daemon's
13
+ * hardcoded timers onto routines). `status`/`services` point at
14
+ * `agents routines stats` for per-routine failure detail instead of
15
+ * duplicating it.
16
+ */
17
+ import type { Command } from 'commander';
18
+ export declare function registerDaemonCommand(program: Command): void;