@phnx-labs/agents-cli 1.22.69 → 1.22.71
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 +34 -0
- package/README.md +34 -9
- package/dist/bootstrap.js +4 -4
- package/dist/commands/accounts.js +23 -7
- package/dist/commands/exec.js +2 -2
- package/dist/commands/import.js +2 -2
- package/dist/commands/models.js +2 -2
- package/dist/commands/permissions.js +2 -2
- package/dist/commands/repo.js +2 -2
- package/dist/commands/rules.js +1 -1
- package/dist/commands/send.js +9 -3
- package/dist/commands/sessions-export.d.ts +5 -1
- package/dist/commands/sessions-export.js +100 -24
- package/dist/commands/sessions-import.d.ts +2 -1
- package/dist/commands/sessions-import.js +85 -21
- package/dist/commands/traces.js +1 -1
- package/dist/lib/account-capabilities.js +0 -2
- package/dist/lib/account-registry.d.ts +3 -3
- package/dist/lib/account-registry.js +25 -7
- package/dist/lib/accounting/usage-sync.d.ts +1 -1
- package/dist/lib/accounting/usage-sync.js +3 -3
- package/dist/lib/acp/client.d.ts +1 -1
- package/dist/lib/acp/client.js +12 -1
- package/dist/lib/acp/harnesses.js +1 -1
- package/dist/lib/add-dir.js +0 -2
- package/dist/lib/agent-cli-commands.js +0 -2
- package/dist/lib/agent-spec/agents.d.ts +1 -1
- package/dist/lib/agent-spec/agents.js +2 -83
- package/dist/lib/browser/ipc.d.ts +34 -0
- package/dist/lib/browser/ipc.js +149 -20
- package/dist/lib/browser/remote-control.d.ts +6 -3
- package/dist/lib/browser/remote-control.js +6 -3
- package/dist/lib/browser/service.d.ts +4 -1
- package/dist/lib/browser/service.js +7 -1
- package/dist/lib/browser/types.d.ts +3 -1
- package/dist/lib/channels/registry.d.ts +6 -0
- package/dist/lib/channels/send.d.ts +11 -2
- package/dist/lib/channels/send.js +11 -2
- package/dist/lib/cloud/rush.d.ts +10 -2
- package/dist/lib/cloud/rush.js +15 -9
- package/dist/lib/daemon/auth-sync-service.js +1 -1
- package/dist/lib/daemon/browser-task-reap-service.js +1 -1
- package/dist/lib/daemon/daemon.js +41 -11
- package/dist/lib/daemon/heartbeat-service.js +3 -3
- package/dist/lib/daemon/keychain-reap-service.js +1 -1
- package/dist/lib/daemon/runner.d.ts +18 -1
- package/dist/lib/daemon/runner.js +237 -80
- package/dist/lib/daemon/self-heal-service.js +13 -3
- package/dist/lib/daemon/self-update-service.d.ts +174 -0
- package/dist/lib/daemon/self-update-service.js +353 -0
- package/dist/lib/daemon/state-dir-check-service.js +3 -3
- package/dist/lib/daemon/usage-sync-service.js +1 -1
- package/dist/lib/daemon/watchdog-service.js +4 -4
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +5 -0
- package/dist/lib/device-config.d.ts +12 -1
- package/dist/lib/device-config.js +63 -13
- package/dist/lib/exec-bounded.d.ts +52 -0
- package/dist/lib/exec-bounded.js +113 -0
- package/dist/lib/exec.d.ts +2 -2
- package/dist/lib/exec.js +3 -34
- package/dist/lib/feed/events.d.ts +22 -14
- package/dist/lib/feed/events.js +84 -44
- package/dist/lib/feed-broadcast.d.ts +12 -29
- package/dist/lib/feed-broadcast.js +28 -27
- package/dist/lib/fleet-shared-state.d.ts +12 -5
- package/dist/lib/fleet-shared-state.js +50 -20
- package/dist/lib/fs-atomic.d.ts +11 -0
- package/dist/lib/fs-atomic.js +60 -0
- package/dist/lib/hooks/install.js +0 -87
- package/dist/lib/hosts/reconcile.d.ts +11 -4
- package/dist/lib/hosts/reconcile.js +31 -5
- package/dist/lib/installations/strategies.js +1 -1
- package/dist/lib/mcp-registry.js +0 -13
- package/dist/lib/mcp.js +2 -2
- package/dist/lib/model-tiers.js +1 -1
- package/dist/lib/models.js +0 -63
- package/dist/lib/notify.d.ts +11 -0
- package/dist/lib/notify.js +17 -4
- package/dist/lib/owner-message.d.ts +46 -3
- package/dist/lib/owner-message.js +26 -6
- package/dist/lib/permissions-registry.d.ts +0 -2
- package/dist/lib/permissions-registry.js +3 -50
- package/dist/lib/permissions.d.ts +3 -17
- package/dist/lib/permissions.js +4 -73
- package/dist/lib/project-resources.d.ts +12 -0
- package/dist/lib/project-resources.js +129 -0
- package/dist/lib/routine-process-cleanup.d.ts +2 -2
- package/dist/lib/routine-process-cleanup.js +45 -34
- package/dist/lib/rush-session.d.ts +19 -0
- package/dist/lib/rush-session.js +24 -0
- package/dist/lib/secrets/drivers/rush.js +2 -1
- package/dist/lib/secrets/reaper.d.ts +2 -2
- package/dist/lib/secrets/reaper.js +13 -10
- package/dist/lib/secrets/reserved-sync.d.ts +1 -1
- package/dist/lib/secrets/reserved-sync.js +4 -4
- package/dist/lib/self-update.d.ts +21 -8
- package/dist/lib/self-update.js +54 -31
- package/dist/lib/session/cloud.js +2 -1
- package/dist/lib/session/sync/backend.d.ts +61 -0
- package/dist/lib/session/sync/backend.js +89 -0
- package/dist/lib/session/sync/managed-config.d.ts +29 -0
- package/dist/lib/session/sync/managed-config.js +23 -0
- package/dist/lib/session/sync/managed-key.d.ts +45 -0
- package/dist/lib/session/sync/managed-key.js +128 -0
- package/dist/lib/session/sync/net-client.d.ts +65 -0
- package/dist/lib/session/sync/net-client.js +117 -0
- package/dist/lib/session/sync/provision.d.ts +19 -0
- package/dist/lib/session/sync/provision.js +38 -0
- package/dist/lib/session/sync/r2.d.ts +5 -2
- package/dist/lib/session/sync/r2.js +5 -2
- package/dist/lib/session/sync/worker-template.d.ts +6 -0
- package/dist/lib/session/sync/worker-template.js +847 -0
- package/dist/lib/sink-format.d.ts +34 -0
- package/dist/lib/sink-format.js +17 -0
- package/dist/lib/staleness/detectors/permissions.js +0 -20
- package/dist/lib/staleness/writers/commands.js +1 -1
- package/dist/lib/staleness/writers/hooks.js +2 -2
- package/dist/lib/subagents-registry.js +2 -12
- package/dist/lib/subagents.d.ts +0 -10
- package/dist/lib/subagents.js +0 -37
- package/dist/lib/tmux/orphan-reap.js +6 -4
- package/dist/lib/tmux/session.js +4 -1
- package/dist/lib/traces/classify.d.ts +8 -1
- package/dist/lib/traces/insights.d.ts +13 -1
- package/dist/lib/traces/insights.js +78 -3
- package/dist/lib/traces/sync.js +8 -3
- package/dist/lib/traces/worker-template.js +9 -5
- package/dist/lib/types.d.ts +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.22.71
|
|
4
|
+
|
|
5
|
+
- **The daemon now keeps itself current instead of running stale code for days (PHNX-3695).** The long-running daemon (`agents __daemon-run`) deliberately opted out of the interactive CLI's auto-update (`AGENTS_CLI_DISABLE_AUTO_UPDATE=1` forced on for `__daemon-run`), so new agents-cli code never reached a running daemon until a human ran `agents daemon restart` — R5 ("the installed CLI auto-updates") silently did not hold for the one process that runs unattended the longest. A new supervised `self-update` service checks npm roughly every 75 minutes, installs and byte-verifies a newer version with the same primitives `agents upgrade` already uses (never a bare `npm install -g`), best-effort pulls the `.system` companion repo and reconciles with `agents sync --local`, then exits so the OS supervisor (launchd `KeepAlive` / systemd `Restart=always`) relaunches it onto the new code — clients reconnect on their own, and the scheduler's atomic `(routine, scheduledFor)` claim dedupes any routine mid-fire across the restart. It fails closed on every step: an install or verify failure leaves the running daemon untouched and retries next tick, and it no-ops on a dev build or a shadowed install. A version-skewed browser-IPC client no longer just prints "run agents daemon restart" — `reconcileDaemonVersion` now asks the daemon to run this same fail-closed path on demand (`request-self-update`), sharing one in-flight attempt with the periodic tick so concurrent requests can't race two installs. That on-demand trigger is DECOUPLED from the install: because every version-skewed `agents browser <verb>` routes through it, the daemon kicks the self-update off in the background and answers "triggered" immediately rather than making the browser verb wait out the whole check→download→install→verify (tens of seconds, worst case ~15 min) — the daemon installs, verifies, and exits on its own, and the browser reconnects. Source: `cli/src/lib/daemon/self-update-service.ts`, `cli/src/lib/browser/ipc.ts`.
|
|
6
|
+
|
|
7
|
+
- **The daemon's event loop is no longer starved by synchronous subprocess spawns on its service ticks (PHNX-3695).** Every daemon background service runs on one Node event loop, so a synchronous `execFileSync`/`readFileSync` inside a service tick froze that loop for the whole call — and while frozen the supervisor's per-tick deadline timer could not fire and the browser IPC server could not answer, which is the "accept but never reply" wedge the trivial `version` probe (`cli/src/lib/browser/ipc.ts`) surfaces. A new async, deadline-bounded, process-group-killable exec helper (`cli/src/lib/exec-bounded.ts` `execFileBounded`) replaces the tick-reachable sync spawns. The heartbeat tick is now fully async: it reaps exited routine children through `reapExitedRunningJobs` (an async twin of the sync `monitorRunningJobs`, sharing one reconciliation core so the CLI `routines list/status` builders keep their synchronous, off-loop path) with an `execFileBounded`-bounded `ps` identity probe and an async `sshExecAsync` read of a `host:`-placed run's remote exit (a synchronous `ssh` there would freeze the loop on a host timeout), plus the async terminal-routine reaper; the self-heal and state-dir-check service ticks moved their `existsSync`/`readFileSync` to `fs/promises`. The worst halt was the event-log **file lock** (`withFileLock` → `lockfile.lockSync` + `Atomics.wait`, up to 30s), reached from every `ctx.log` on every tick as well as the watchdog and routine-reaper emits; a new `withFileLockAsync` + `emitAsync` (`cli/src/lib/fs-atomic.ts`, `cli/src/lib/feed/events.ts`) acquire it without freezing the loop. The other unconditional every-tick halts are converted the same way: usage-sync/auth-sync publish through `updateFleetSharedDeviceStateAsync`, keychain-reap's whole-process-table `ps` uses `execFileBounded`, watchdog/browser-task-reap read config via async paths, and tmux-reap's `/proc` scan uses `fs/promises`. A structural guard test (`cli/src/lib/daemon/guard-no-sync-io.test.ts`) scans every `*-service.ts` tick/start body for a synchronous fs/exec/lock call AND pins that each hot tick call site uses its async variant (failing if a fix is reverted to its sync twin). Startup and stop/uninstall lifecycle code (pid/lock, `launchctl`/`systemctl`, the `ps` daemon-identity probe) stays synchronous — it runs in a short-lived CLI process, never on the served loop. Source: `cli/src/lib/{exec-bounded,fs-atomic,device-config,fleet-shared-state}.ts`, `cli/src/lib/feed/events.ts`, `cli/src/lib/secrets/{reaper,reserved-sync}.ts`, `cli/src/lib/accounting/usage-sync.ts`, `cli/src/lib/tmux/{session,orphan-reap}.ts`, `cli/src/lib/daemon/{runner,daemon,heartbeat-service,watchdog-service,usage-sync-service,auth-sync-service,keychain-reap-service,browser-task-reap-service,self-heal-service,state-dir-check-service}.ts`, `cli/src/lib/hosts/reconcile.ts`, `cli/src/lib/routine-process-cleanup.ts`, `cli/src/lib/daemon/AGENTS.md`.
|
|
8
|
+
|
|
9
|
+
- **The required PR check no longer reinstalls 273 packages on every run.** Measured across recent Linux required runs, `bun install` took 14–22s on *every* run — whether the impact plan selected 78 test files or 2 — while a release check's actual test execution is 0.27s. The install was a fixed cost every pull request paid, and the largest single item in the required-check budget. `tests.yml` now restores `~/.bun/install/cache`, `cli/node_modules`, and `packages/session-tracker/node_modules` from an `actions/cache` keyed on **both** `cli/bun.lock` and `packages/session-tracker/bun.lock` — keying on the CLI lockfile alone would have left the session-tracker tree outside its own key. The key also carries a `node -v`/`bun -v` fingerprint, because `cli/package.json` trusts a native addon (`@homebridge/node-pty-prebuilt-multiarch`) whose prebuilt binary is Node-ABI-specific and nothing pins the runner's Node. `restore-keys` is deliberately omitted: a prefix fallback would let bun install on top of a *different* lockfile's modules, and `--frozen-lockfile` only guarantees the tree matches the lock from a clean or exact-match state — correctness beats a warmer cache when an attestation binds the tested tree to the published tarball. A dependency change misses the cache and reinstalls, which is exactly the previous behavior, so a miss can never be slower than not caching. Source: `.github/workflows/tests.yml`.
|
|
10
|
+
|
|
11
|
+
- **The CI dependency cache now actually hits.** The cache added a change earlier was dead weight in practice: GitHub scopes caches per ref — a run can restore only from its own branch or the default branch — and the required `test` job runs on `pull_request` only, so every entry it saved landed under `refs/pull/<N>/merge`, invisible to every other pull request. Measured after it shipped: `deps-cache-hit=0` with `packages installed [21.63s]` on unrelated PRs, i.e. a permanent cold miss for everyone except a re-run of the same PR. A push-to-`main` `warm-dep-cache` job now writes the identical key and paths, so the first PR after any lockfile or toolchain change pays the install once and every later PR restores it. The job is push-only and never joins the required check identity. `tests-gate.test.ts` pins the two keys equal, because drift between them would be silent — the warm job would populate an entry the required job never asks for, and the cache would look healthy while never hitting. Source: `.github/workflows/tests.yml`.
|
|
12
|
+
|
|
13
|
+
- **Managed session backup: harden the storage boundary (PHNX-3726).** Follow-up security fixes to the managed sessions Worker landed in the same ticket. The Worker is now **Phoenix-only** — the static `WRITE_TOKEN` principal is removed (and no longer provisioned): the zero-knowledge `--byo` path talks to your own R2 bucket directly and never reaches this Worker, so a token here was only a Phoenix-and-quota bypass. Mandatory encryption is now enforced at the storage boundary, not just client-side: a PUT whose body is not an encrypted bundle is rejected `422`, and a managed restore fails loud on any plaintext object instead of importing it. DELETE now refunds the per-user quota ledger via a delta CAS applied BEFORE the object is removed, and fails loud (503, object left intact and charged) when that CAS stays contended — instead of deleting first and silently dropping the refund on a contended ledger. Source: `cli/src/lib/session/sync/{worker-template,provision}.ts`, `cli/src/commands/sessions-import.ts`, `cli/docs/specifications.md`.
|
|
14
|
+
|
|
15
|
+
- **Signed-in users back sessions up off-box with NO `r2.backups` bucket to set up (PHNX-3726).** `agents sessions export --to-r2` / `import --from-r2` now default to a managed, Phoenix-gated store (`sessions.agents-cli.sh`) when you are signed in (`agents auth login`) — the same zero-setup pattern `agents artifacts share` and `agents traces sync` already use, resolved through the one shared `selectStorageBackendKind` policy. It is **managed-first**: a signed-in user backs up to managed even with a stale `r2.backups` bundle present; only `--byo` / `AGENTS_SESSIONS_BACKEND=byo` flips to your own bucket. Every managed backup is **mandatorily** AES-256-GCM encrypted under a per-account key that is minted once, cached at `~/.agents/.cache/state/sessions-backup-key.json` (mode 0600), and escrowed at the bearer-gated, immutable Worker key `<userId>/__key/backup-dek` so a fresh box signing in recovers it and decrypts prior backups with zero setup — the managed path never uploads plaintext. Both the command and Worker reject plaintext/malformed managed objects, and restore revalidates every at-rest bundle before decryption. Honest trust boundary: confidential vs a raw R2/Cloudflare bucket read (ciphertext at rest) but NOT zero-knowledge vs Phoenix (the DEK is escrowed on Phoenix infra); `--byo` keeps the zero-knowledge own-bucket path (the key stays only in your `r2.backups` bundle). The isolated Worker verifies the Phoenix bearer on every PUT/GET/LIST/DELETE, enforces `segments[0] == userId` (401/403, no public GET), and meters a per-user CAS quota (`__usage`, 413 over quota). Same-key managed PUT/DELETE operations share an expiring R2 lease; every object change is etag-conditioned, DELETE leaves a GET-404/LIST-hidden tombstone, and expired recovery fences the predecessor's etag before settling quota. The ledger retains an idempotent pending delta plus a terminal generation per path in the same CAS object, preventing late predecessor charges, replacement deletes, double refunds, stranded paths, and phantom quota. A CAS-reserved historical-path budget is consumed before persistent per-path state is created and is not refunded on DELETE, so authenticated create/delete churn cannot grow tombstones, leases, or ledger history past the hard cap. Reserved `__usage`/`__key` prefixes are never listed. The BYO object layout is byte-for-byte unchanged (the `<userId>/` prefix is added inside the client) and the Worker is integration-tested against real workerd. Source: `cli/src/lib/session/sync/{backend,managed-config,managed-key,net-client,provision,worker-template}.ts`, `cli/src/commands/sessions-export.ts`, `cli/src/commands/sessions-import.ts`, `cli/docs/{sessions,specifications}.md`.
|
|
16
|
+
|
|
17
|
+
- **Daemon: a host-placed run finishing on the heartbeat tick no longer freezes the event loop (PHNX-3727).** Fast-follow to PHNX-3695. `finalizeHostRunAsync` (the async, tick-reachable finalize path reached via `reapExitedRunningJobs`) called `applyHealedHostRun`, which ended by emitting routine-end through the **synchronous** `emitRoutineEnd` — whose event-log file lock (`withFileLock` → `lockSync` + `Atomics.wait`, up to 30s under contention) is exactly the loop-freeze PHNX-3695 removed elsewhere, on a path `guard-no-sync-io` couldn't see (it lives in `runner.ts`, not a `*-service.ts`). So a `host:`-placed routine run completing could still wedge the browser-IPC "accept but never reply" state under lock contention. `applyHealedHostRun` now takes an injected emitter: `finalizeHostRun` (sync CLI path) keeps the synchronous emit, while `finalizeHostRunAsync` (the tick) passes `emitRoutineEndAsync` (`withFileLockAsync`), so no daemon tick reaches the synchronous lock. `guard-no-sync-io` gains a call-site pin so reverting it fails loud. Source: `cli/src/lib/daemon/runner.ts`, `cli/src/lib/daemon/guard-no-sync-io.test.ts`.
|
|
18
|
+
|
|
19
|
+
- **`agents traces sync` now surfaces silent behavioral failures as ranked issues, not just a friction counter (RUSH-2988).** A session where the agent went idle after its last event and a human had to nudge it is a real failure with **no error code**, so it never clustered through `computeInsights` (which only groups `outcome === 'error'` tool calls) and was previously only a `needsAttention` severity input. The per-session silent-stall friction (`silent stall: 5-15m/15-60m/1h+`, already computed by `computeInsightFacets`) is now promoted into cross-session `FailurePattern`s with a new `behavioral` failure cause (`cli/src/lib/traces/insights.ts` `computeBehavioralPatterns`), ranked into the same top-K `failurePatterns` by wasted idle time (a bucket's midpoint, bounded by the same 30-min `MAX_GAP_ATTRIBUTION_MS` cap the tool-error path uses) and summed into `wastedMsTotal`. `TraceFailureCause` gains `behavioral` (`cli/src/lib/traces/classify.ts`); it is producer-derived and never returned by `classifyCause`, so the `failures.byCause` tool-error split reports it as `0`. The console renders the new cause in a follow-up web change.
|
|
20
|
+
|
|
21
|
+
## 1.22.70
|
|
22
|
+
|
|
23
|
+
- **`agents browser stop --profile` is now gated by the remote-control consent gate (PHNX-3317).** `stop --profile <name>` (no `--task`) is handled entirely by `bindTask`'s early return in `ipc.ts`, so it never reached `resolveOrCreateTask` — the one chokepoint every other page/close/attach verb's `assertRemoteControlAllowedForRequest` gate runs through — and `case 'stop'` called `BrowserService.stopProfile()` directly with no consent check at all. That left one destructive fleet-remote path (kills the profile's browser process, clears its runtime dir) reachable from `browser --device <device> stop --profile` even with `remote-control off` on the target. `stopProfile()` now takes the same `{ fleetRemote, actor }` opts as `start`/`showUrl`/`resolveOrCreateTask` and asserts the gate first; a local (non-fleet-remote) call is unaffected. Source: `cli/src/lib/browser/service.ts`, `cli/src/lib/browser/ipc.ts`.
|
|
24
|
+
|
|
25
|
+
- **The PHNX-2939 headless-account regression test now genuinely exercises the headless path (PHNX-3352).** The real-macOS-keychain test in `account-registry.keychain.test.ts` set `AGENTS_RUNTIME='test-headless-account-launch'`, a made-up value `isHeadlessSecretsContext()` never matches — its set is exactly `headless`/`teams`/`terminal` — so the resolve never entered the headless keychain guard and the test passed identically with OR without the PHNX-2939 biometry-ACL fix, reproducing nothing. It now sets `AGENTS_RUNTIME='headless'` and asserts `isHeadlessSecretsContext()` is `true` before resolving, so the test fails against the pre-fix code (a direct `getKeychainToken` read of the policy-`never`, no-ACL account item is rejected as if it required Touch ID) and passes only because the fix routes through the bundle path that attests `silentNoAcl`. Source: `cli/src/lib/account-registry.keychain.test.ts`.
|
|
26
|
+
|
|
27
|
+
- **`agents daemon stop` reclaims a stale `browser.sock` when the daemon vanishes mid-stop (PHNX-3618).** `stopDaemonLocked` captured the browser IPC socket's inode ONLY when a live pid resolved (`pid !== null && browserSock ? …`). If the daemon died in the window between the CLI liveness precheck (`isDaemonRunning()` was true) and the locked read, `resolveLiveDaemonPid()` returned null, so the owner was never captured — and the socket the daemon's ungraceful death left on disk hit the else branch, was reported "browser IPC socket ownership could not be verified", and was left behind, so the next daemon start raced a dead binding. The socket inode is now captured **independently of the pid**, and the stale binding is reclaimed once no live daemon — the signalled target OR any successor for this state dir — is proven to own it. PHNX-3607's never-delete-a-successor invariant is preserved: a successor that rebinds during the stop gets a new inode (identity no longer matches) or shows up as a survivor, and in either case its socket is left untouched. Source: `cli/src/lib/daemon/daemon.ts`.
|
|
28
|
+
|
|
29
|
+
- **`agents run --cloud` no longer rejects a non-expiring Phoenix bearer (PHNX-3645).** A Rush session stores `expires_at: 0` for an opaque Phoenix `pid_` bearer, which by contract means NON-EXPIRING. Three freshness checks read that `0` as an absolute timestamp — `new Date(0)` = 1970-01-01 — and refused a valid, fresh session with `Rush session expired at 1970-01-01`, so no Phoenix-authed box could dispatch a cloud run, push/pull secrets over Rush, or list cloud sessions. The `expires_at === 0` (and missing) special-case now lives in one place, `isRushSessionExpired` (`cli/src/lib/rush-session.ts`), which cloud dispatch, the secrets Rush driver, and the cloud session source all route through — mirroring rush/cli's own `isFresh` (RUSH-1310, PHNX-3637). Source: `cli/src/lib/rush-session.ts`, `cli/src/lib/cloud/rush.ts`, `cli/src/lib/secrets/drivers/rush.ts`, `cli/src/lib/session/cloud.ts`.
|
|
30
|
+
|
|
31
|
+
- **Owner alerts now render per destination — Slack gets tappable links, iMessage stays plain (PHNX-3698).** `agents notify` / `agents send --to owner` (and an important `feed post` that fans out to the owner) resolve every channel in `owner.policy.normal` and composed ONE plain string for all of them, so a Slack owner destination received a wall of prose with no `<url|label>` links and no tappable session crumb — the labeled-link support already in `composeBroadcastMessage` never fired on the path the owner actually uses. The owner fan-out (`sendToOwner`) now re-renders the body PER destination, keyed on each channel's resolved provider (the same `notify.transports` remap delivery uses): a Slack destination turns every `TEAM-N` key the post names into `<https://linear.app/<ws>/issue/<KEY>|<KEY>>` and the `Sent from claude/<id> on <host>` crumb into `<https://prix.dev/console/sessions/<full-id>|claude/<id>>` (the 8-char crumb upgraded to the full indexed id first), while iMessage / owner-scoped rush / desktop / `command:` sinks stay the plain human sentence with no URLs. Each delivery echoes the exact body it sent, so `agents notify --dry-run --json` now shows the two different bodies. Source: `cli/src/lib/notify.ts`, `cli/src/lib/owner-message.ts`, `cli/src/lib/sink-format.ts`, `cli/src/lib/feed-broadcast.ts`, `cli/src/lib/channels/send.ts`, `cli/src/commands/send.ts`.
|
|
32
|
+
|
|
33
|
+
- **A release no longer starves waiting for the tip of `main` to be attested (PHNX-3705).** `release.sh` phase 2 required an attestation for `origin/main`'s tree *as of the instant the release ran*, but `attest-main.yml` produces that by running the full suite — minutes of work. On a repo where agents merge continuously, main advances faster than attestation completes, so every release lost the race and died at `missing exact attestation key`. Measured 2026-09-01: six merges to `main` in ~30 minutes with attest-main runs still pending or superseded; the tip was never attested long enough to release from, which also makes the R2 bar (ordinary release under 60s) unreachable by construction. The release does not need *the tip* — it needs a green base to cut the release commit from and derive the release-tree attestation against (PHNX-3696), and any attested **ancestor** serves. Phase 2 now requires an attestation for the tree it is **actually releasing from** (`$BASE_SHA`) rather than the live remote tip, and `derive_release_attestation` inherits from that same commit — the release commit's real parent — instead of the tip. New `cli/scripts/release-attested-base.sh` resolves the newest attested commit at or below `origin/<default>` (bounded lookback, fails closed when none is found), `release-worktree.sh` cuts the release worktree from it and says so on stderr when it is behind the tip, and `release.sh`'s base check now requires an *ancestor* of the remote tip rather than the tip exactly. The supply-chain guarantee is unchanged: the published tarball is still bound to a tree whose suite passed, and `derive`'s allowlist still fails closed on any code file — the release simply ships from a slightly older tree than the tip, which was always true of an attested tree. Source: `cli/scripts/release-attested-base.sh`, `cli/scripts/release-worktree.sh`, `cli/scripts/release.sh`.
|
|
34
|
+
|
|
35
|
+
- **Docs: sign-in unlocks managed sharing, not only team spaces; `traces setup` marked advanced/self-host.** The README said signing in unlocks "the one thing" — team spaces — which predated managed, private-by-default sharing; it now names both hosted surfaces (team spaces AND `agents artifacts share`/`agents traces sync` on our storage with zero Cloudflare setup, private by default when signed in). The `agents traces setup` command description no longer reads as a normal user step — it is now labeled advanced/self-host, since `agents auth login` syncs to the managed endpoint with no setup. Source: `README.md`, `cli/src/commands/traces.ts`, `cli/docs/command-index.{md,json}`.
|
|
36
|
+
|
|
3
37
|
## 1.22.69
|
|
4
38
|
|
|
5
39
|
- **`agents monitors` no longer fires an action on a poll FAILURE (PHNX-3510).** An `[on-change]` monitor treated a failing poll — a `gh pr list … | jq` that intermittently printed `GraphQL: API rate limit already exceeded …` — as a value change: the empty→error→empty flap read as two changes and dispatched a full agent run each time on a premise that was false (~10 sessions burned re-verifying two already-merged PRs). A command/poll source now flags a snapshot that exited non-zero, or whose output matches a transport/auth/rate-limit error shape (caught even on exit 0, since a pipe to jq swallows the failing half's exit code), as an *observation failure*: the engine skips it, leaves watched-state untouched, does not fire, and records it as a failed check so a sustained streak escalates to the owner as a drought. `agents monitors test` labels a failed poll and reports `Would fire: no`. Note: a non-zero exit is now an observation failure rather than a watchable value — a monitor that wants to watch a command's success/failure should emit a stable token (`… && echo UP || echo DOWN`, always exit 0) so the state it cares about diffs as a value; sustained failures still reach the owner via the drought escalation. Source: `cli/src/lib/monitors/sources/failure.ts`, `cli/src/lib/monitors/sources/command.ts`, `cli/src/lib/monitors/engine.ts`, `cli/docs/automation.md`.
|
package/README.md
CHANGED
|
@@ -34,7 +34,6 @@
|
|
|
34
34
|
|
|
35
35
|
<a href="https://dev.meta.ai/docs/muse-code" title="Meta Muse Code"><picture><source media="(prefers-color-scheme: dark)" srcset="assets/harnesses/muse.svg"><img src="assets/harnesses/muse-light.svg" height="32" alt="Meta Muse Code" /></picture></a>
|
|
36
36
|
|
|
37
|
-
<a href="https://omp.sh" title="Oh My Pi"><picture><source media="(prefers-color-scheme: dark)" srcset="assets/harnesses/pi.svg"><img src="assets/harnesses/pi-light.svg" height="32" alt="Oh My Pi" /></picture></a>
|
|
38
37
|
|
|
39
38
|
<a href="https://docs.warp.dev/reference/cli" title="Warp Agent CLI (Oz)"><picture><source media="(prefers-color-scheme: dark)" srcset="assets/harnesses/warp.svg"><img src="assets/harnesses/warp-light.svg" height="32" alt="Warp Agent CLI" /></picture></a>
|
|
40
39
|
</p>
|
|
@@ -54,7 +53,7 @@ agents setup status # readiness for browser, computer, fleet,
|
|
|
54
53
|
agents run claude "explain this repo" # run any agent on your existing subscription
|
|
55
54
|
```
|
|
56
55
|
|
|
57
|
-
Everything here — and every other command in this README — is free and needs no account; the optional `agents auth login`
|
|
56
|
+
Everything here — and every other command in this README — is free and needs no account; the optional `agents auth login` unlocks the hosted surfaces — [team spaces](#sign-in) and managed, private-by-default sharing (`agents artifacts share` / `agents traces sync` with no Cloudflare setup). `agents setup` is interactive and idempotent -- safe to re-run on any machine. Once core setup exists, it opens a status-aware menu for browser, computer, secrets, fleet, share, watchdog, and device preferences; each choice delegates to the same wizard available under `agents setup <capability>`. In CI or another non-TTY, bare setup prints the checklist without prompting. The `agi-cli.sh` one-liner installs this same canonical `@phnx-labs/agents-cli` package. Prefer bun? `bun install -g @phnx-labs/agents-cli` works too.
|
|
58
57
|
|
|
59
58
|
The command surface teaches setup through `agents setup` and group-level `--help`.
|
|
60
59
|
The durable system model starts at [`cli/docs/README.md`](cli/docs/README.md).
|
|
@@ -403,6 +402,31 @@ Sharing a session uses `agents sessions render <id> -o session.md`, not the raw
|
|
|
403
402
|
|
|
404
403
|
`agents sessions share <id>` goes one step further and publishes that document as a self-contained web page on your own share endpoint, printing the link. It is **unlisted** unless you pass `--public` — a transcript carries file paths, command output, and error text that a plan does not, so it stays out of your public gallery by default, and emails are masked on top of the render's own redaction. The slug is `session-<shortId>`, so re-sharing one session updates one URL.
|
|
405
404
|
|
|
405
|
+
### Back up sessions off-box
|
|
406
|
+
|
|
407
|
+
Signed-in Phoenix users can back sessions up without creating a Cloudflare bucket or
|
|
408
|
+
an `r2.backups` secrets bundle:
|
|
409
|
+
|
|
410
|
+
```bash
|
|
411
|
+
agents sessions export --since 30d --to-r2
|
|
412
|
+
agents sessions import --from-r2 --dry-run
|
|
413
|
+
agents sessions import --from-r2
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
The managed path encrypts every transcript body locally with AES-256-GCM before
|
|
417
|
+
upload. Its per-account key is escrowed behind the same Phoenix bearer so another
|
|
418
|
+
signed-in device can restore with zero setup. This protects transcript contents from
|
|
419
|
+
a raw storage-bucket read, but it is not zero-knowledge against Phoenix because
|
|
420
|
+
Phoenix-operated infrastructure stores the recovery key. Use `--byo` with your own
|
|
421
|
+
`r2.backups` bundle and `R2_SYNC_ENC_KEY` when Phoenix must never possess the key:
|
|
422
|
+
|
|
423
|
+
```bash
|
|
424
|
+
agents sessions export --since 30d --to-r2 --byo
|
|
425
|
+
agents sessions import --from-r2 --byo
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
Both targets are on-demand backups only; neither enables background session sync.
|
|
429
|
+
|
|
406
430
|
### Resume anywhere — and stay resumed
|
|
407
431
|
|
|
408
432
|
Pick up any past conversation and drop it back into a terminal:
|
|
@@ -1090,7 +1114,7 @@ agents browser profiles create cloud \
|
|
|
1090
1114
|
|
|
1091
1115
|
## Sign in
|
|
1092
1116
|
|
|
1093
|
-
Signing in is **optional**. Every local feature — `agents run`, sessions, teams, fleet dispatch, secrets, browser, computer — works with no account.
|
|
1117
|
+
Signing in is **optional**. Every local feature — `agents run`, sessions, teams, fleet dispatch, secrets, browser, computer — works with no account. Signing in unlocks the hosted surfaces: **team spaces** (`agents auth space`) and **managed sharing** — `agents artifacts share` and `agents traces sync` publish to our storage with **zero Cloudflare setup**, and a signed-in `agents artifacts share` is **private by default** (`me`, owner-only) unless you pass `--visibility org`/`public`. `agents auth` signs this machine in to **Phoenix ID**, the Phoenix Labs account layer behind these hosted surfaces. Sign-in is Google-only and runs a device-code flow: the CLI shows a code, your browser confirms it, and the CLI picks the session up.
|
|
1094
1118
|
|
|
1095
1119
|
```bash
|
|
1096
1120
|
agents auth login # shows a code, opens a Phoenix-branded page
|
|
@@ -1262,8 +1286,13 @@ agents daemon doctor # one-shot health check; non-zero ex
|
|
|
1262
1286
|
```
|
|
1263
1287
|
|
|
1264
1288
|
Each hosted responsibility (secrets broker, browser IPC, scheduler, monitors,
|
|
1265
|
-
watchdog, device probe, self-heal, keychain reap, account-state refresh,
|
|
1289
|
+
watchdog, device probe, self-heal, self-update, keychain reap, account-state refresh,
|
|
1266
1290
|
state-dir checks) is an independent toggle in `~/.agents/daemon/services.yaml`.
|
|
1291
|
+
Self-update checks npm on its own schedule, installs + verifies a newer
|
|
1292
|
+
agents-cli with the same primitives `agents upgrade` uses, then exits so the OS
|
|
1293
|
+
supervisor relaunches the daemon onto the new code — the daemon used to run
|
|
1294
|
+
with auto-update forced off and only picked up new code on a manual
|
|
1295
|
+
`agents daemon restart`.
|
|
1267
1296
|
`agents daemon services list` shows every service; `enable|disable <id>` flips
|
|
1268
1297
|
one. Missing keys default to enabled, so upgrades are no-ops. Most services take
|
|
1269
1298
|
effect on the next daemon start; browser IPC is registered even when boot-disabled
|
|
@@ -1602,7 +1631,6 @@ Which DotAgents resources each agent CLI can load. Source of truth: [src/lib/age
|
|
|
1602
1631
|
| OpenCode | yes | no | yes | >= 1.1.1 | yes | yes | no | no | `AGENTS.md` | no |
|
|
1603
1632
|
| Copilot | yes | no | yes | no | yes | yes | no | no | `AGENTS.md` | no |
|
|
1604
1633
|
| Amp | yes | no | yes | no | yes | yes | no | no | `AGENTS.md` | no |
|
|
1605
|
-
| Kiro | yes | no | yes | >= 2.8.0 | yes | yes | no | >= 1.23.0 | `AGENTS.md` | no |
|
|
1606
1634
|
| Goose | yes | no | yes | no | no | no | no | no | `AGENTS.md` | no |
|
|
1607
1635
|
| Roo Code | yes | no | yes | no | yes | yes | no | no | `AGENTS.md` | no |
|
|
1608
1636
|
| Droid | yes | yes | yes | >= 0.57.5 | >= 0.26.0 | yes | yes | yes | `AGENTS.md` | no |
|
|
@@ -1623,17 +1651,14 @@ Which DotAgents resources each agent CLI can load. Source of truth: [src/lib/age
|
|
|
1623
1651
|
| Antigravity | -- | yes | -- |
|
|
1624
1652
|
| Copilot | -- | -- | yes |
|
|
1625
1653
|
| OpenClaw, Amp, Goose, Roo | -- | -- | -- |
|
|
1626
|
-
| Kiro | -- | -- | -- |
|
|
1627
1654
|
|
|
1628
1655
|
### Version-gated sync
|
|
1629
1656
|
|
|
1630
1657
|
| Capability | Agent | Gate |
|
|
1631
1658
|
|------------|-------|------|
|
|
1632
1659
|
| Hooks | Codex | >= 0.116.0 |
|
|
1633
|
-
| Subagents | Kiro | >= 1.23.0 |
|
|
1634
1660
|
| Skills | Droid | >= 0.26.0 |
|
|
1635
1661
|
| Permissions | Droid | >= 0.57.5 |
|
|
1636
|
-
| Permissions | Kiro | >= 2.8.0 |
|
|
1637
1662
|
| File-based commands | Codex | < 0.117.0 (0.117+ uses command-as-skill) |
|
|
1638
1663
|
| Plugins | Codex | >= 0.128.0 |
|
|
1639
1664
|
|
|
@@ -1649,7 +1674,7 @@ Claude Code, Codex CLI, Antigravity, Grok Build, and others each have their own
|
|
|
1649
1674
|
|
|
1650
1675
|
### Is it free?
|
|
1651
1676
|
|
|
1652
|
-
Yes — every feature, with no account and no signup. `agents run`, sessions, teams, fleet dispatch, secrets, browser, and computer automation all work the moment you install. The optional `agents auth login`
|
|
1677
|
+
Yes — every feature, with no account and no signup. `agents run`, sessions, teams, fleet dispatch, secrets, browser, and computer automation all work the moment you install. The optional `agents auth login` unlocks the hosted surfaces — team spaces (`agents auth space`) and managed, private-by-default sharing (`agents artifacts share` / `agents traces sync`, no Cloudflare setup); there are no paid tiers. This developer tool is entirely free because we believe developers should have the best tools — fast and robust — so they can create the best products for their users.
|
|
1653
1678
|
|
|
1654
1679
|
### Is this like `nvm` / `mise` / `asdf` for AI agents?
|
|
1655
1680
|
|
package/dist/bootstrap.js
CHANGED
|
@@ -328,7 +328,7 @@ async function installResolvedPackage(metadata) {
|
|
|
328
328
|
// every subsequent upgrade dead-ends there forever. bun does not use
|
|
329
329
|
// npm's retire-path staging scheme, so this only needs to run once, ahead
|
|
330
330
|
// of both package-manager branches below.
|
|
331
|
-
sweepStaleInstallStaging(packageRoot);
|
|
331
|
+
await sweepStaleInstallStaging(packageRoot);
|
|
332
332
|
// Upgrade with the package manager that owns this install. A bun global
|
|
333
333
|
// install lives at <bunGlobalDir>/node_modules/... (no `lib` segment), so an
|
|
334
334
|
// `npm install --prefix` would write to <bunGlobalDir>/lib/node_modules and
|
|
@@ -349,8 +349,8 @@ async function installResolvedPackage(metadata) {
|
|
|
349
349
|
/* leave it for the OS temp sweep */
|
|
350
350
|
}
|
|
351
351
|
}
|
|
352
|
-
verifyInstalledVersion(packageRoot, metadata.version);
|
|
353
|
-
refreshAliasShims(packageRoot);
|
|
352
|
+
await verifyInstalledVersion(packageRoot, metadata.version);
|
|
353
|
+
await refreshAliasShims(packageRoot);
|
|
354
354
|
// PHNX-2768: the npm install above can leave the package at the new version
|
|
355
355
|
// but the global bin links GONE — the state that stranded zion (package at
|
|
356
356
|
// 1.22.40, `/opt/homebrew/bin/{agents,ag,browser,computer}` missing, every
|
|
@@ -361,7 +361,7 @@ async function installResolvedPackage(metadata) {
|
|
|
361
361
|
// own bin shims and are out of scope.
|
|
362
362
|
if (detectPackageManager(packageRoot) !== 'bun' && process.platform !== 'win32') {
|
|
363
363
|
const prefix = deriveGlobalPrefix(packageRoot);
|
|
364
|
-
const repairs = ensureGlobalBinLinks(packageRoot, prefix);
|
|
364
|
+
const repairs = await ensureGlobalBinLinks(packageRoot, prefix);
|
|
365
365
|
const repaired = repairs.filter((r) => r.action === 'repaired');
|
|
366
366
|
const failed = repairs.filter((r) => r.action === 'failed');
|
|
367
367
|
if (repaired.length > 0) {
|
|
@@ -336,7 +336,10 @@ export async function listSwitchableAccounts(agent) {
|
|
|
336
336
|
*/
|
|
337
337
|
export function setDefaultAccount(agentRaw, name) {
|
|
338
338
|
const agent = parseHarness(agentRaw);
|
|
339
|
-
|
|
339
|
+
// Scope to the harness this default is FOR: a bare identity (`<email>`) matches
|
|
340
|
+
// every harness that identity is signed into, so an un-scoped lookup resolved
|
|
341
|
+
// the wrong row and then rejected it as "is a <other> login" just below.
|
|
342
|
+
const account = findUnifiedAccount(name, readMeta(), undefined, agent);
|
|
340
343
|
if (!account)
|
|
341
344
|
throw new Error(`Unknown account '${name}'.`);
|
|
342
345
|
if (account.kind === 'provider') {
|
|
@@ -519,14 +522,19 @@ agents run codex#work`,
|
|
|
519
522
|
.action(async (name, target, _o, command) => {
|
|
520
523
|
await runAccountsAction(command, async () => {
|
|
521
524
|
const meta = readMeta();
|
|
522
|
-
|
|
525
|
+
// Validate the target exists before mutating any binding — and resolve it
|
|
526
|
+
// FIRST so the account lookup can be scoped to the harness being attached
|
|
527
|
+
// to. `identityLabel` defaults to the login's email, so a bare identity
|
|
528
|
+
// (`muqsitnawaz@gmail.com`) matches every harness that identity is signed
|
|
529
|
+
// into; un-scoped this resolved whichever row the store ordered first and
|
|
530
|
+
// then rejected it below as "is a <other> login".
|
|
531
|
+
const t = classifyAttachTarget(target);
|
|
532
|
+
const targetAgent = t.kind === 'profile' ? t.profile.host.agent : t.agent;
|
|
533
|
+
const account = findUnifiedAccount(name, meta, undefined, targetAgent);
|
|
523
534
|
if (!account)
|
|
524
535
|
throw new Error(`Unknown account '${name}'.`);
|
|
525
536
|
if (account.kind === 'native')
|
|
526
537
|
assertNativeAccountNameable(account.agent);
|
|
527
|
-
// Validate the target exists before mutating any binding.
|
|
528
|
-
const t = classifyAttachTarget(target);
|
|
529
|
-
const targetAgent = t.kind === 'profile' ? t.profile.host.agent : t.agent;
|
|
530
538
|
if (account.kind === 'native') {
|
|
531
539
|
// A provider-backed profile injects provider env at spawn, which would run
|
|
532
540
|
// under a different credential than the native identity claims — refuse it.
|
|
@@ -572,7 +580,7 @@ agents run codex#work`,
|
|
|
572
580
|
// Provider account: it must be able to authenticate the target's harness.
|
|
573
581
|
getAccountProvider(account.provider).envFor(targetAgent, account.auth);
|
|
574
582
|
}
|
|
575
|
-
bindAccount(name, target);
|
|
583
|
+
bindAccount(name, target, targetAgent);
|
|
576
584
|
writeClaudeInteractiveOauthToken(t, targetAgent, account.kind === 'native' && account.agent === 'claude' ? account.identityLabel : undefined);
|
|
577
585
|
console.log(chalk.green(`Attached ${account.name} to ${target}.`));
|
|
578
586
|
});
|
|
@@ -581,7 +589,15 @@ agents run codex#work`,
|
|
|
581
589
|
.description('Remove one account attachment')
|
|
582
590
|
.action(async (name, target, _o, command) => {
|
|
583
591
|
await runAccountsAction(command, () => {
|
|
584
|
-
|
|
592
|
+
// Classify the target first so the unbind is scoped to the right harness
|
|
593
|
+
// for a colliding identity selector (same as the attach path).
|
|
594
|
+
let targetAgent;
|
|
595
|
+
try {
|
|
596
|
+
const t = classifyAttachTarget(target);
|
|
597
|
+
targetAgent = t.kind === 'profile' ? t.profile.host.agent : t.agent;
|
|
598
|
+
}
|
|
599
|
+
catch { /* an unresolvable target still unbinds by whatever row matches */ }
|
|
600
|
+
unbindAccount(name, target, targetAgent);
|
|
585
601
|
// With the binding gone, no setup-token resolves for this version home, so this
|
|
586
602
|
// clears any .oauth_token the attach left behind (else interactive runs would keep
|
|
587
603
|
// authenticating as the just-detached account).
|
package/dist/commands/exec.js
CHANGED
|
@@ -2244,7 +2244,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
2244
2244
|
const { pickSessionInteractive } = await import('./sessions.js');
|
|
2245
2245
|
const { buildContinuePrompt } = await import('../lib/loop.js');
|
|
2246
2246
|
// Freshen the index for this agent before any lookup (incremental, cached).
|
|
2247
|
-
// AgentId is wider than SessionAgentId (amp/
|
|
2247
|
+
// AgentId is wider than SessionAgentId (amp/goose/copilot keep no transcripts);
|
|
2248
2248
|
// those simply yield no matches and fall through to the not-found error.
|
|
2249
2249
|
const sessionAgent = agent;
|
|
2250
2250
|
if (!resolvedResumeSource)
|
|
@@ -2723,7 +2723,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
2723
2723
|
process.exit(1);
|
|
2724
2724
|
}
|
|
2725
2725
|
// Default CLI mode is the generic 'plan'. Agents without a read-only
|
|
2726
|
-
// mode (antigravity,
|
|
2726
|
+
// mode (antigravity, …) degrade via resolveMode to their
|
|
2727
2727
|
// safest native mode (modes[0], typically edit). That covers both the
|
|
2728
2728
|
// implicit default and an explicit `--mode plan`, so multi-agent
|
|
2729
2729
|
// scripts can pass a uniform plan flag without per-agent branching.
|
package/dist/commands/import.js
CHANGED
|
@@ -56,7 +56,7 @@ async function runImport(agentArg, opts) {
|
|
|
56
56
|
if (!opts.isolated) {
|
|
57
57
|
assertIsolationBoundary(agentId, 'adopt your existing install');
|
|
58
58
|
}
|
|
59
|
-
// installScript-based agents (Grok, Antigravity, Cursor,
|
|
59
|
+
// installScript-based agents (Grok, Antigravity, Cursor, Goose, Roo)
|
|
60
60
|
// don't have an npm package; their binary lives wherever the curl/brew
|
|
61
61
|
// installer dropped it. We adopt by symlinking that PATH binary directly
|
|
62
62
|
// into the version's `node_modules/.bin/`. No package.json walk.
|
|
@@ -345,7 +345,7 @@ When to use:
|
|
|
345
345
|
pointing at the existing install — nothing is copied or moved (except the
|
|
346
346
|
agent's config dir, which is moved into the version's home). Works for both
|
|
347
347
|
npm-style packages (claude, codex, opencode, openclaw) and
|
|
348
|
-
installScript-based agents (grok, antigravity, cursor,
|
|
348
|
+
installScript-based agents (grok, antigravity, cursor, goose).
|
|
349
349
|
`)
|
|
350
350
|
.action((...args) => withIsolationBoundary(() => runImport(...args)));
|
|
351
351
|
}
|
package/dist/commands/models.js
CHANGED
|
@@ -18,13 +18,13 @@ import { terminalWidth, truncateToWidth, stringWidth } from '../lib/session/widt
|
|
|
18
18
|
import { wrapJoined } from './inspect.js';
|
|
19
19
|
import { setHelpSections } from '../lib/help.js';
|
|
20
20
|
import { formatRunDefaultEntry, listRunDefaults, parseRunDefaultSelector, setRunDefault, } from '../lib/run-defaults.js';
|
|
21
|
-
const MODEL_CAPABLE_AGENTS = ['claude', 'codex', 'opencode', 'cursor', 'openclaw', 'antigravity', 'kimi', 'grok', 'droid'
|
|
21
|
+
const MODEL_CAPABLE_AGENTS = ['claude', 'codex', 'opencode', 'cursor', 'openclaw', 'antigravity', 'kimi', 'grok', 'droid'];
|
|
22
22
|
/**
|
|
23
23
|
* Agents that don't necessarily install under ~/.agents/versions (cursor ships
|
|
24
24
|
* via a curl script). For these, fall back to the PATH binary and synthesize
|
|
25
25
|
* a version label from the install path so cache keys stay stable.
|
|
26
26
|
*/
|
|
27
|
-
const PATH_ONLY_AGENTS = new Set(['cursor'
|
|
27
|
+
const PATH_ONLY_AGENTS = new Set(['cursor']);
|
|
28
28
|
/** Derive a version label from the PATH-installed binary location for agents without managed versions. */
|
|
29
29
|
function fallbackPathVersion(agent) {
|
|
30
30
|
const src = locateModelSource(agent, 'unresolved');
|
|
@@ -377,8 +377,8 @@ Examples:
|
|
|
377
377
|
//
|
|
378
378
|
// This used to hardcode `.json`/`.jsonc`/`.toml` plus a `.claude`/
|
|
379
379
|
// `.opencode`/`.codex` substring, which excluded the ten other harnesses
|
|
380
|
-
// the CLI writes -- and excluded
|
|
381
|
-
//
|
|
380
|
+
// the CLI writes -- and excluded hermes twice over, since
|
|
381
|
+
// its config is YAML. The registry already answers this by matching
|
|
382
382
|
// each harness's own declared path, so ask it (RUSH-2676).
|
|
383
383
|
const isAgentConfig = detectPermissionAgentFromPath(localPath) !== null;
|
|
384
384
|
if (isAgentConfig) {
|
package/dist/commands/repo.js
CHANGED
|
@@ -60,7 +60,7 @@ function syncMarketplacesForDefaults() {
|
|
|
60
60
|
/** Write this device's account metadata before the user repo is committed. */
|
|
61
61
|
async function publishUserRepoAccountState(provisionAuth) {
|
|
62
62
|
const { publishUsageSnapshotToSharedStore } = await import('../lib/accounting/usage-sync.js');
|
|
63
|
-
const usage = publishUsageSnapshotToSharedStore();
|
|
63
|
+
const usage = await publishUsageSnapshotToSharedStore();
|
|
64
64
|
if (usage.error)
|
|
65
65
|
console.error(chalk.yellow(`Usage snapshot: ${usage.error}`));
|
|
66
66
|
if (provisionAuth) {
|
|
@@ -73,7 +73,7 @@ async function publishUserRepoAccountState(provisionAuth) {
|
|
|
73
73
|
return;
|
|
74
74
|
}
|
|
75
75
|
const { publishReservedAuthVerdict } = await import('../lib/secrets/reserved-sync.js');
|
|
76
|
-
const auth = publishReservedAuthVerdict();
|
|
76
|
+
const auth = await publishReservedAuthVerdict();
|
|
77
77
|
if (auth.error)
|
|
78
78
|
console.error(chalk.yellow(`Auth verdict: ${auth.device}: ${auth.error}`));
|
|
79
79
|
}
|
package/dist/commands/rules.js
CHANGED
|
@@ -52,7 +52,7 @@ Project rules & @-imports:
|
|
|
52
52
|
@path imports inside AGENTS.md/CLAUDE.md are resolved at session start by the agent
|
|
53
53
|
itself, not by agents-cli. Support is per-agent:
|
|
54
54
|
Inlined natively: claude
|
|
55
|
-
Literal text: codex, cursor, opencode, copilot, amp,
|
|
55
|
+
Literal text: codex, cursor, opencode, copilot, amp, goose
|
|
56
56
|
|
|
57
57
|
For rules that need to work across all agents, inline the content rather than using
|
|
58
58
|
@-imports — the second group will load '@path/to/file.md' as a literal string.
|
package/dist/commands/send.js
CHANGED
|
@@ -3,7 +3,7 @@ import { die } from '../lib/format.js';
|
|
|
3
3
|
import { setHelpSections } from '../lib/help.js';
|
|
4
4
|
import { readMeta } from '../lib/state.js';
|
|
5
5
|
import { sendMessage, isOwnerAlias } from '../lib/channels/send.js';
|
|
6
|
-
import {
|
|
6
|
+
import { ownerMessageComposer } from '../lib/owner-message.js';
|
|
7
7
|
import { fireTraceSyncInBackground } from '../lib/run-trace-sync.js';
|
|
8
8
|
function mergeAttachments(opts) {
|
|
9
9
|
const list = [...(opts.attach ?? []), ...(opts.attachment ?? [])];
|
|
@@ -30,6 +30,7 @@ async function runSend(positionalText, opts, ownerMode) {
|
|
|
30
30
|
// the SAME composer as an important `feed post` (PHNX-3698): short-shaped body,
|
|
31
31
|
// TEAM-N keys linkified, session crumb as a tappable console URL — instead of a
|
|
32
32
|
// raw dump. A non-owner send (explicit --channel/--to) is delivered verbatim.
|
|
33
|
+
let ownerCompose;
|
|
33
34
|
if (ownerMode || isOwnerAlias(opts.to)) {
|
|
34
35
|
const flagged = opts.text?.trim() ?? '';
|
|
35
36
|
const positional = (positionalText ?? '').trim();
|
|
@@ -38,7 +39,12 @@ async function runSend(positionalText, opts, ownerMode) {
|
|
|
38
39
|
// loud with the "pass the message once" error rather than composing a guess.
|
|
39
40
|
const bothDiffer = flagged !== '' && positional !== '' && flagged !== positional;
|
|
40
41
|
if (raw && !bothDiffer) {
|
|
41
|
-
|
|
42
|
+
// Resolve the context once, then let each owner destination render its own
|
|
43
|
+
// format: the envelope carries the plain default (for --json and dry-run
|
|
44
|
+
// display), while ownerCompose hands sendToOwner the mrkdwn variant for a
|
|
45
|
+
// Slack owner destination and plain for iMessage (PHNX-3698).
|
|
46
|
+
ownerCompose = ownerMessageComposer(raw);
|
|
47
|
+
input = { ...input, text: ownerCompose('plain'), positionalText: undefined };
|
|
42
48
|
// The console URL in the composed body only resolves once this session's
|
|
43
49
|
// trace shard is uploaded; fire that now so the tapped link isn't a 404.
|
|
44
50
|
// A --dry-run resolves + composes but MUST NOT act (its documented contract),
|
|
@@ -46,7 +52,7 @@ async function runSend(positionalText, opts, ownerMode) {
|
|
|
46
52
|
fireTraceSyncInBackground({ disabled: Boolean(opts.dryRun) });
|
|
47
53
|
}
|
|
48
54
|
}
|
|
49
|
-
const out = await sendMessage(input, meta);
|
|
55
|
+
const out = await sendMessage(input, meta, ownerCompose);
|
|
50
56
|
if ('error' in out) {
|
|
51
57
|
die(out.error);
|
|
52
58
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
2
|
import type { SessionMeta } from '../lib/session/types.js';
|
|
3
|
+
import { type SessionsBackupClient } from '../lib/session/sync/net-client.js';
|
|
3
4
|
import { type BundleHeader, type BundleRecord } from '../lib/session/bundle.js';
|
|
4
5
|
export declare function registerSessionsExportCommand(sessionsCmd: Command): void;
|
|
5
6
|
interface GlobalSelection {
|
|
@@ -10,6 +11,7 @@ interface GlobalSelection {
|
|
|
10
11
|
redact?: boolean;
|
|
11
12
|
encrypt?: boolean;
|
|
12
13
|
toR2?: boolean;
|
|
14
|
+
byo?: boolean;
|
|
13
15
|
output?: string;
|
|
14
16
|
stdout?: boolean;
|
|
15
17
|
host?: string[];
|
|
@@ -36,7 +38,9 @@ interface GlobalSelection {
|
|
|
36
38
|
* its test drive the exact same decision.
|
|
37
39
|
*/
|
|
38
40
|
export declare function r2ExportGateError(g: Pick<GlobalSelection, 'toR2' | 'host'>, isConfigured: boolean): string | null;
|
|
39
|
-
export declare function uploadToR2(header: BundleHeader, records: BundleRecord[]): Promise<void>;
|
|
41
|
+
export declare function uploadToR2(header: BundleHeader, records: BundleRecord[], resolvedClient?: SessionsBackupClient): Promise<void>;
|
|
42
|
+
/** Fail-closed guard at the managed transport boundary, not only at record construction. */
|
|
43
|
+
export declare function managedUploadEncryptionError(header: BundleHeader, records: BundleRecord[]): string | null;
|
|
40
44
|
/** R2 object key for one record — dir-shaped agents key by relKey, file-shaped by session. */
|
|
41
45
|
export declare function r2KeyForRecord(rec: BundleRecord): string;
|
|
42
46
|
/**
|