Changelog
1.20.60
- Fix Goose skill sync status for its native central-storage path.
agents skills list goose@<version>now reports skills under~/.agents/skills/as installed instead of falsely requiring a per-version.config/goose/skills/copy that Goose never reads. Source:apps/cli/src/lib/skills.ts. - Correct the documented
autoand ACPskipsemantics. The README and bundledrunskill now distinguish Kimi's interactive--autofrom its already-auto-approved headless-ppath, document Droid's native--auto high, and explain that ACPskipprefersallow_alwaysbut falls back to the first permission option offered by the server. Documentation only; runtime behavior is unchanged. Source:README.md,skills/run/SKILL.md. - Wire Antigravity subagents and Kimi workflow sync (RUSH-1548, RUSH-1581). Antigravity now receives subagents as custom-agent Markdown under
~/.gemini/config/agents/<name>/agent.mdwith the>= 1.0.16version gate enforced during sync. Kimi receives workflows as managedtype: flowskills under.kimi-code/skills/<name>/SKILL.md, using the canonical slug as the flow name and anagents_workflowmarker so native user-owned flows are not overwritten or removed. Antigravity workflows are wired separately in RUSH-1580 (they target a shared HOME-global dir, not a version home). Source:apps/cli/src/lib/agents.ts,apps/cli/src/lib/subagents.ts,apps/cli/src/lib/workflows.ts. - Release retries rebuild the exact merged, CI-tested release tree even after
mainadvances. A registry/auth failure after the release PR merged previously stranded that version because the catch-up guard required the release merge to remain currentmain. The local release script now validates the original PR head and full green matrix, verifies the SHA-pinned keychain helper, rebuilds the unpinned menu-bar helper from historical source in a detached temporary worktree, rejects mismatched remote tags, and tags/publishes that exact merge without including later commits. Source:apps/cli/scripts/release.sh. - Wire Gemini plugins/subagents and Goose workflows/allowlists. Gemini now syncs plugin bundles as Gemini extensions (
.gemini/extensions/<name>/gemini-extension.json) from CLI 0.8.0+ and subagents as.gemini/agents/*.mdfrom CLI 0.36.0+. Goose now syncs workflows as recipe/subrecipe YAML under.config/goose/recipes/and permission groups into.config/goose/permission.yaml. Source:apps/cli/src/lib/agents.ts,apps/cli/src/lib/plugins.ts,apps/cli/src/lib/subagents.ts,apps/cli/src/lib/workflows.ts,apps/cli/src/lib/permissions.ts. (RUSH-1568, RUSH-1569, RUSH-1574, RUSH-1582) - Wire Gemini permissions/allowlist support (RUSH-1567). Gemini permission groups now sync Bash allow/deny rules into
.gemini/settings.jsonastools.core/tools.excludeentries with per-commandShellTool(...)patterns; non-Bash canonical permissions remain unsupported by Gemini's native tool grammar and are skipped. Flipallowlist: true, register the permission writer/detector through the capability table, and replace the dormant legacytools.allowedserializer. Source:apps/cli/src/lib/agents.ts,apps/cli/src/lib/permissions.ts,apps/cli/src/lib/resources/permissions.ts,apps/cli/src/lib/staleness/detectors/permissions.ts. - Fix:
agents run <agent>@<version> --host <host>now forwards the version pin and most run flags to the remote host. Previously the--hostbranch stripped@versionand ignored--strategy,--effort,--add-dir,--json,--verbose,--timeout,--yes, and--acp, so the remote host applied its own defaults. The local CLI now parsesagent@versionverbatim, normalizes--strategy/--balanced, makes--add-dirpaths remote-portable, and forwards all of these flags to the remoteagents runinvocation.--add-dirportability uses the same~/$HOMEre-rooting that--cwdalready uses, so a Linux remote resolves home paths against its own/home/<user>. Source:apps/cli/src/lib/hosts/dispatch.ts,apps/cli/src/commands/exec.ts,apps/cli/src/lib/hosts/dispatch.test.ts. - Fix: routine edits no longer rewrite the whole YAML file, so
~/.agentsstays clean andagents repo pullcan sync routines across the fleet.writeJobpreviously re-emitted the entire document viayaml.stringifyon every mutation (pause/resume,routines devices --set, add), restyling untouched scalars — unquotingschedule, re-wrapping the foldedpromptblock — which left the git-backed user repo perpetually dirty. That made cross-deviceagents repo pullrefuse ("uncommitted changes"), so adevices:pin set on one machine never reached the others andDevices: allroutines kept firing on every box. A newserializeJobedits only the changed keys via the YAML Document API, preserving byte-for-byte formatting of untouched nodes; new/unparseable/non-mapping files fall back to canonical stringify. Source:apps/cli/src/lib/routines.ts,apps/cli/src/lib/__tests__/routines.serialize.test.ts.
1.20.59
- Fix: remote secrets now choose the Windows PowerShell wrapper from the original
--hostname, not the resolveduser@ipSSH target. Inline enrolled Windows hosts resolve to address-based SSH targets, but the OS registry is keyed by the host name;agents secrets view/list/exec --host <windows>,agents run --secrets bundle@<windows>, and remote secrets unlock/export paths now pass that original name into command construction so Windows hosts no longer fall back tobash -lc. Source:apps/cli/src/lib/secrets/remote.ts,apps/cli/src/commands/secrets.ts,apps/cli/src/commands/exec.ts. (RUSH-1431) - Wire Droid skills support (RUSH-1397). Droid loads skills from
.factory/skills/(since 0.26.0). Flipskills: { since: '0.26.0' }, register generic skills writer/detector. Source:apps/cli/src/lib/agents.ts,apps/cli/src/lib/__tests__/capabilities.test.ts. - Wire skills support for Goose CLI (RUSH-1394). Goose reads skills directly from
~/.agents/skills/via the Summon extension (block-goose-cli >= 1.25.0). Flipskills: { since: '1.25.0' }, register generic skills writer/detector. Source:apps/cli/src/lib/agents.ts,apps/cli/src/lib/staleness/writers/commands.ts. - Wire Droid allowlist support (RUSH-1396). Droid stores allow/deny in
.factory/settings.json(commandAllowlist/commandDenylist). Flipallowlist: truesince 0.57.5, addconvertToDroidFormat, wire writer/detector. Source:apps/cli/src/lib/agents.ts,apps/cli/src/lib/permissions.ts. - Wire Codex permissions/allowlist support (RUSH-1566). Codex stores allow/deny in
.codex/config.toml(approval_policy,sandbox_mode). Flipallowlist: truesince 0.128.0, addconvertToCodexFormat, wire writer/detector. Source:apps/cli/src/lib/agents.ts,apps/cli/src/lib/permissions.ts. - OpenCode permissions write to the loaded config path (RUSH-1623). Global config is
~/.config/opencode/opencode.jsonc(not~/.opencode/); project config isopencode.jsoncat the project root. Source:apps/cli/src/lib/permissions.ts,apps/cli/src/lib/agents.ts. - Catch-up publishes now require the exact CI-tested release tree and its full green matrix. A package version already present on
mainno longer counts as release validation by itself:release.shresolves the mergedrelease/v<version>PR, requires its merge commit to be currentmain, fetches the PR head that ran CI, requires that head tree to equal currentmain, and rechecks every expected CI context before publishing. This closes the path that let 1.20.58 reach npm before its tag-triggered Windows matrix exposed failures. Source:apps/cli/scripts/release.sh. - Windows release validation now matches portable path behavior. Home-relative project paths normalize native separators to
/before they are stored as~/…, and the systemd-manifest and remote-shell assertions now compare the escaped/quoted forms that the runtime deliberately emits. This restores the Windows Node 22/24 release matrix without weakening command escaping. Source:apps/cli/src/lib/project-root.ts,apps/cli/src/lib/{project-root,daemon}.test.ts,apps/cli/src/lib/hosts/dispatch.test.ts. - Wire subagents support for Kiro CLI. Kiro custom agents are JSON files under
~/.kiro/agents/*.json(introduced in kiro-cli v1.23.0). Flip Kiro'ssubagents: { since: '1.23.0' }, addtransformSubagentForKiro, and wire the subagents writer, detector, install/remove, and orphan-detection paths. Source:apps/cli/src/lib/agents.ts,apps/cli/src/lib/subagents.ts,apps/cli/src/lib/staleness/{writers,detectors}/subagents.ts. (RUSH-1393)
1.20.58
Self-updating agent CLIs are represented as one live installation.
agents viewno longer invents version-home rows for single-binary installers such as Droid, Grok, Cursor, Kiro, Goose, and Hermes; it reports the version returned by the installed binary and folds away stale per-version directories.agents add <agent>@<version>now installs or keeps that agent's current release instead of rejecting an unsupported pinned install. Source:apps/cli/src/lib/agents.ts,apps/cli/src/lib/versions.ts,apps/cli/src/commands/{versions,view}.ts. (RUSH-1321)Stopped teammate resumes are transactional from launch through persistence. If a local or remote resume fails, the existing teammate record, directory, runtime metadata, stdout mirror, and log cursor are restored; any replacement wrapper and its descendants are terminated as one process group. A successful resume whose log was truncated restarts parsing at byte zero, and a secondary restore-write failure retains the original launch error as its cause. Source:
apps/cli/src/lib/teams/agents.ts,apps/cli/src/lib/hosts/dispatch.ts. (#1104, #1108)Wire allowlist support for Cursor CLI. Cursor agent CLI stores allow/deny in
~/.cursor/cli-config.json(permissions.allow/denywith Shell/Read/Write/WebFetch/Mcp). Flipallowlist: true, addconvertToCursorFormat(Bash→Shell), and write viaapplyPermissionsToVersion+ detector. Source:apps/cli/src/lib/agents.ts,apps/cli/src/lib/permissions.ts. (RUSH-1387)GitHub Copilot CLI subagents now sync (RUSH-1390). Installed subagents flatten into GitHub Copilot custom-agent profiles at
~/.copilot/agents/<name>.agent.md(the Droid custom-droid format), gated to Copilot CLI ≥ 0.0.353.agents subagents list/viewnow surfaces synced Copilot agents andagents subagents removesoft-deletes their.agent.mdfiles to trash — both previously skippedcopilotentirely. Source:apps/cli/src/lib/subagents.ts(listSubagentsForAgent,removeSubagentFromVersion,transformSubagentForCopilot),apps/cli/src/lib/staleness/writers/subagents.ts,apps/cli/src/lib/staleness/detectors/subagents.ts,apps/cli/src/lib/agents.ts.Menu-bar Quick Dispatch preserves typed drafts when focus is stolen (RUSH-1592). If another app activates while the
Cmd-Shift-Ocapture panel is open, the panel can hide without destroying the note; the next summon restores the draft text plus selected screenshots, action, and agents. Return submits and clears the draft; Escape clears without dispatching. Source:apps/cli/menubar/Sources/MenubarHelper/PromptPanel.swift,apps/cli/docs/menubar.md.Menu-bar ticket agents now carry every selected screenshot into the Linear issue (RUSH-1668).
Cmd-Shift-Oalready passed selected file paths to the ticket agent, but the prompt only asked it to inspect them, so agents could create text-only issues and stop. The brief now identifies every selected path as user-provided ticket material, requires each file to be uploaded, supplies the existinglinear update <id> --proof <path>path as a reliable default, and leaves description/comment/other placement to the agent's judgment. Source:apps/cli/menubar/Sources/MenubarHelper/{AgentsCLI,IssueSelfTest}.swift,apps/cli/docs/menubar.md.agents sessions --active --jsonnow carries session attachment metadata for Factory previews (RUSH-1524). Claude and Droid prompt image/document blocks that reference local files are preserved as{ path, name, mediaType, sizeBytes }, and the active-session state dedupes them intoattachmentsso consumers can render screenshot thumbnails and open the original files instead of only seeing an attachment count. Source:apps/cli/src/lib/session/parse.ts,apps/cli/src/lib/session/state.ts,apps/cli/src/lib/session/active.ts.Retired the standalone
com.phnx-labs.agents-secrets-agentlaunchd service — the always-on daemon is now the sole broker host (#416, step 2).ensureAgentRunning()no longer installs a separate launchd service: it retires any leftover plist via the newretireLegacySecretsAgentService()and relies on the daemon (Path 0), with a one-off detached broker as the only fallback. The upgrade migration (scripts/postinstall.js→healLongRunningProcesses) nowlaunchctl bootouts the legacy service first, then (re)starts the daemon so it takes over the broker socket, instead of kickstarting the old service onto new code.agents secrets startis now a thin alias that brings the daemon up (and waits for the broker to answer);agents secrets stoplocks all bundles and retires any leftover legacy service while leaving the always-on daemon running;agents secrets statusreports broker reachability (daemon-hosted vs standalone) rather than "service installed". The stale broker teardown (version-skew self-heal) retires the legacy service instead of kickstarting it. Source:apps/cli/src/lib/secrets/agent.ts(retireLegacySecretsAgentService,ensureAgentRunning,teardownStaleBroker,uninstallSecretsAgentService; removedinstallSecretsAgentService/kickstartSecretsAgentService/generateServicePlist),apps/cli/scripts/postinstall.js(healLongRunningProcesses),apps/cli/src/commands/secrets.ts(start/stop/status).Clarify the native escape hatch behind
--mode skip. The README and bundledrunskill now discourageskip, list its exact direct-exec per-harness flag mappings and ACPallow_alwaysbehavior, replace an older recommendation of unsafefullfor ordinary writes, and distinguish Codexauto(sandboxededit, which can still prompt) from Codexskip(--dangerously-bypass-approvals-and-sandbox, equivalent to unsandboxed--yolo). Documentation only; runtime behavior is unchanged. Source:README.md,skills/run/SKILL.md.Wire allowlist support for Kiro CLI. Kiro 2.8.0+ permission groups now sync into
~/.kiro/settings/permissions.yamlas v3 capability rules for shell, filesystem, and web access; existing user-authored rules are preserved and duplicate generated rules are removed. Source:apps/cli/src/lib/agents.ts,apps/cli/src/lib/permissions.ts,apps/cli/src/lib/staleness/detectors/permissions.ts. (RUSH-1392)Fix: remote
agents secrets view <bundle>@host --revealno longer leaves a 60-second SSH control master behind. The interactive-ttreveal path now opts out of default SSH multiplexing (multiplex: false), matching the transport guidance for one-shot commands that must not keep aControlPersistsocket open after a Touch ID/passphrase reveal. Source:apps/cli/src/lib/secrets/remote.ts(remoteSecretsRaw).agents run --host <host> --cwd <dir>now sets the working directory ON the host (and a new--projectshorthand jumps to a project by name). Previously--cwdwas silently dropped for--hostruns — only the separate--remote-cwdflag worked — soagents run claude --host s1 --cwd ~/src/foolanded in the remote login-shell's default directory with no warning.--cwdis now forwarded as the host working directory, and a home-anchored path (~/…,$HOME/…, or a local-home absolute the shell already expanded like/Users/me/…) is re-rooted at the remote$HOMEso it resolves correctly across machines with different home paths (/Users/me→/home/me).--remote-cwdremains as the explicit override. New-P, --project <slug>[@worktree]resolves a bare project name against your projects root (e.g.~/src/github.com/<user>) — auto-inferred from the repo you launch inside and cached inagents.yaml, or set/shown withagents defaults project-root [path];--project foo@fixtargets thefixgit worktree. Works for local and--hostruns. Verified end-to-end:agents run claude --host yosemite-s1 --project agents-cliruns the remote agent withpwd=/home/muqsit/src/github.com/muqsitnawaz/agents-cli. Source:apps/cli/src/lib/project-root.ts(new),apps/cli/src/lib/hosts/dispatch.ts(remoteCdPrefix),apps/cli/src/commands/exec.ts(--project/--cwdhost wiring),apps/cli/src/commands/defaults.ts(project-root).Fix daemon crash-looping when its pinned Node version is pruned (fleet-wide). The routine daemon's launchd/systemd manifest hardcoded
~/.nvm/versions/node/v24.0.0/binon PATH, and launched the CLI entry bare when it was an extension-less shim or abin/agents → dist/index.jssymlink (an extension check on the link name missed it). The moment that exact nvm patch was upgraded away, the shim's#!/usr/bin/env nodeshebang fell through to an ancient system node (Node 18 →SyntaxError: node:util has no export 'styleText'from@inquirer/core), and the service crash-looped at import — observed at 100k+ restarts on Linux workers, silently killing all scheduled routines.getDaemonLaunchnow detects Node-script entries by resolving symlinks and sniffing the shebang (not just the.js/.cjs/.mjsextension), so it pins them toprocess.execPath; and the generated PATH now leads withpath.dirname(process.execPath)— the Node that installed the service — instead of a hardcoded nvm version, so both the shim and child routine processes always resolve a working runtime. Source:apps/cli/src/lib/daemon.ts(getDaemonLaunch,isNodeScriptEntry,daemonNodeBinDir,generateSystemdUnit,generateLaunchdPlist).Fix global npm upgrades restarting the routines daemon through
scripts/postinstall.js. The postinstall process is itselfprocess.argv[1], so its daemon self-heal could stampnode scripts/postinstall.js daemon _runinto launchd. Daemon startup now accepts an explicit CLI entry and postinstall passes the resolved signed native binary (or JavaScript entrypoint), with the same value threaded through launchd, systemd, and detached startup. Source:apps/cli/scripts/postinstall.js,apps/cli/src/lib/daemon.ts.Fix a standalone secrets service stealing the daemon-hosted broker socket during postinstall. The standalone and hosted brokers now bind through one race-safe owner arbitration path: an existing reachable broker wins without its socket being unlinked, a persistent losing service stays quiescent instead of triggering launchd restart churn, takes over if the owner stops, and releases its standby PID on service shutdown; only an unreachable stale socket is reclaimed. This covers the release ordering where postinstall restarts the daemon first and then kickstarts an installed standalone service. Source:
apps/cli/src/lib/secrets/agent.ts(bindBrokerSocket,runSecretsAgent,startHostedBroker).
1.20.57
agents teams resume/agents teams message— resume a stopped teammate with a follow-up message. A teammate that ended its turn with more to do (PR open awaiting review, headless turn cap, a redirect after the fact) could not be reached:agents messageresolves only live sessions, so a completed/stopped/failed teammate had no path back short of finishing the work by hand or spawning a fresh, context-less teammate.teams resume <team> <teammate> <message>re-enters the teammate's own session with the message as the next user turn, re-launching through the same backend (local process or remote host) in its original worktree and flipping it back torunningsoteams statustracks it live.teams messageis the same command with automatic routing by reconciled status: a running teammate is steered via its mailbox (delivered at its next tool call, no re-launch); a stopped one is resumed; a pending one is refused with a pointer toteams start. Works for every harness — the resume delegates toagents run --resume, inheriting native resume for Claude/Codex and the universal/continuereplay for the rest (OpenCode, Grok, Kimi, …); the resume target is the teammate's captured underlying session id (remoteSessionId ?? agentId), and a non-Claude teammate that died before emitting a session id is refused with a clear error rather than resumed into a fresh run. This also makes good onteams stop's long-standing "can be restarted later" promise, which no code implemented. Source:apps/cli/src/commands/teams.ts(message/resumesubcommands,decideTeamMessageRoute),apps/cli/src/lib/teams/agents.ts(AgentManager.resumeTeammate, resume-awarebuildRunArgv/buildCommand/launchProcess/launchRemoteProcess).- The always-on daemon now hosts the secrets broker (socket-first) — one supervised backbone instead of a separate service (#416, step 1).
runDaemon()binds the broker via the newstartHostedBroker()before the scheduler and the heavy browser/session-sync services, soagents secretsresolves within ms of daemon start. It serves the same socket + wire protocol as the standalone broker (noPROTOCOL_VERSIONbump —agentGetSync/agentPing/agentAutoLoadSyncare unchanged), but is daemon-safe: no pid-guard, noprocess.exit/signal handlers/self-heal-exit (which would take the daemon down), TTL-eviction only.ensureAgentRunning()gains a Path 0 that prefers the daemon and falls back to the standalonecom.phnx-labs.agents-secrets-agentlaunchd service, and the daemon only hosts when no broker is already reachable, so a live standalone broker is never orphaned. Retiring the standalone service (a gatedlaunchctl bootoutmigration) and child-spawning the heavy services are the follow-on (#417). Source:apps/cli/src/lib/secrets/agent.ts(startHostedBroker,ensureAgentRunningPath 0,agentPingexported),apps/cli/src/lib/daemon.ts(runDaemonbroker host + shutdown). - Clarified
agents secrets listPOLICY column labels. The column previously mixed policy names, runtime state, and implementation jargon (daily · 7d left,always ask,never · NO ACL). It now uses a consistentpolicy · stateform:daily,daily · held 7d,always · prompt, andnever · no prompt. Source:apps/cli/src/commands/secrets.ts(renderPolicyCol).
1.20.56
- Fix native routine schedulers rejecting the published CLI as a Bun virtual path. Bun's standalone runtime reports the embedded
/$bunfs/root/agentsentry as existing atprocess.argv[1], while the real physical executable lives atprocess.execPath. Daemon resolution now substitutes that physical executable before generating launchd/systemd manifests or detached launches; the existing virtual-path guard still rejects any virtual path that reaches supervision. Source:apps/cli/src/lib/daemon.ts. - Fix:
agents teams,agents message, andagents profiles checkwork again on the signed standalone binary (regression from #315). Whenagentsresolves to the bun-compiled Mach-O (shipped since 1.20.53), three self-spawn sites relaunched the CLI as[process.execPath, process.argv[1], …]— but under a bun standalone executableprocess.argv[1]is the virtual entry/$bunfs/root/agents, so the child died withunknown command '/$bunfs/root/agents'(or/bin/sh: /$bunfs/root/agents: No such file or directory). Every teammate spawned by a compiled-binary install failed in 0s. New sharedgetAgentsInvocation(subArgs)(apps/cli/src/lib/daemon.ts) resolves the real on-disk binary — mapping the/$bunfs/root/…virtual path toprocess.execPath, running a.jsentry under node, and a native binary directly — andteams/agents.ts,commands/message.ts, andcommands/profiles.tsroute through it. Verified end-to-end: a teammate spawned by the freshly-compiled binary runs tocompletedwith no$bunfserror. Source:apps/cli/src/lib/daemon.ts(getAgentsInvocation),apps/cli/src/lib/teams/agents.ts,apps/cli/src/commands/{message,profiles}.ts.
1.20.55
- Routine scheduler health is now observable and self-healing.
agents routines statusdistinguishesrunning,wedged, andstopped, and reports the daemon binary plus heartbeat age. Routine listing/status opportunistically finalize orphaned runs; PID reuse checks and a 24-hour wall-clock limit prevent stalerunningrecords; daemon startup rejects bun virtual paths and warns about worktree binaries that can disappear. Source:apps/cli/src/lib/daemon.ts,apps/cli/src/lib/runner.ts,apps/cli/src/commands/routines.ts. - Built-in Open-Claude and OpenCode profiles.
agents profilesnow shipsopen-claudeandclaude-sparkfor Claude Code through OpenRouter, plusopencode,opencode-spark, andopencode-qwenpresets for the OpenCode harness. Source:apps/cli/src/lib/profiles-presets.ts,apps/cli/docs/profiles.md. - Fix: exiting a user split inside an
agents runtmux session reliably closes just that split (de-flakes CI #965). The guardedpane-diedhook's else-branch was a barekill-pane, which relies on the hook context supplying an implicit "current pane" — nondeterministic on a loaded detached server, so the dead split intermittently survived as a husk (the same failure the flakysession.test.tspane-died tests reproduced in CI). An intermediate externaltmux -S <socket>self-client still raced the server under Linux load. The else-branch now runsrun-shell -C "kill-pane -t #{hook_pane}", which format-expands the event pane and executes the targeted command inside tmux's own server queue. Interactive tmux-backed runs now require tmux 3.2+, the release that introducedrun-shell -C.AGENT_HOOK_SCHEMAbumps to 4; the daemon reconcile retrofits live sessions automatically and only stamps the marker after tmux accepts the hook, so a transient failure stays retryable. Source:apps/cli/src/lib/tmux/session.ts(agentPaneDiedHook,AGENT_HOOK_SCHEMA),apps/cli/src/lib/tmux/binary.ts,apps/cli/src/lib/exec.ts. agents devices syncpins the login user on Windows too.os.userInfo().usernamereturnsCOMPUTER\user/DOMAIN\useron Windows, which failed the safe-charset guard, so Windows boxes synced with no pinned user and--host <device>fell back to the wrong local account.sanitizeLoginUsernow strips the domain prefix to the bare ssh account before the guard. Also folds the duplicateuser@hostsplitter (parseTargetinssh.ts) into the canonicalsplitUserHostso there is one parser. Source:apps/cli/src/lib/devices/sync.ts,apps/cli/src/commands/ssh.ts.- Menu bar ACTIVE section now shows every local session, not just extension-registered terminals. The dropdown's session source was
live-terminals.json, which only carries terminals the Factory extension registers — a machine with 25 live sessions (tmux, ghostty, headless) renderedACTIVE · 1 running. The helper now feeds triage + ACTIVE fromagents sessions --active --local --json(the session engine's authoritative view, issue #741 contract) on the same warm-cache pattern as routines (30s TTL, refreshed off the click path; the cheap file still covers cold start and the 10s badge poll). Blocked sessions outside the extension's view now surface in NEEDS YOU too. Idle rows cap at 3 per repo group — the group header carries the true counts — so a big idle fleet can't wall the menu. Source:apps/cli/menubar/Sources/MenubarHelper/{StatusItemController,LocalState,AgentsCLI,Models}.swift. - Daemon service manifests pin JavaScript installs to the current Node runtime. launchd and systemd now invoke
process.execPath <entry> daemon _run, matching the detached launcher, instead of executing the JS entrypoint through#!/usr/bin/env node. Linux user services therefore stop falling back to an obsolete system Node (observed as Node 18 failing onnode:util.styleText) when the CLI was installed under Node 22/24. Nativeagentslaunchers remain direct executables. Source:apps/cli/src/lib/daemon.ts. - Routines support a
devices:allowlist so multiple machines each fire the same job independently. Routine YAMLs sync fleet-wide via the user repo, so without a restriction an enabled routine fires on every device running the scheduler. Adevices: [yosemite-s0, mac-mini]allowlist makes each listed machine run the job independently on schedule; omitting the field (or--clear) leaves the job unrestricted. A single-entry listdevices: [yosemite-s0]replaces the legacy singulardevice:pin — v12 migration converts any existingdevice: XYAML automatically todevices: [X]. All automatic paths (cron scheduler, webhook triggers, overdue/catchup, daemon nags, detached runner fires, one-shot--at) skip devices outside the allowlist; attempting to run a job on an ineligible host errors with the allowed device names and a ready-to-paste--hosthint.routines add --devices yosemite-s0,mac-minisets the list at creation (validated against the registered fleet);routines devices <name>opens a preselected multi-select picker;--set <csv>and--clearupdate it non-interactively and are mutually exclusive.routines listgains a Devices column;--jsongainsdevicesarray andrunsHere.--host <device>(alias:--device) routes anyroutinessubcommand to a remote machine over SSH. Source:apps/cli/src/lib/routines.ts,apps/cli/src/lib/scheduler.ts,apps/cli/src/lib/overdue.ts,apps/cli/src/lib/triggers/webhook.ts,apps/cli/src/lib/runner.ts,apps/cli/src/lib/hosts/passthrough.ts,apps/cli/src/lib/migrate.ts,apps/cli/src/commands/routines.ts. - Routines now default to
--mode autoinstead ofplan(RUSH-1595). A routine created without an explicitmodenow runs under the smart classifier (auto) rather than read-onlyplan, so unattended jobs can create PRs, write files, and run tests end-to-end without every user opting in —automaps to--permission-mode auto(claude), workspace-write + network (codex),--auto high(droid), and kimi's default headless run (which had no read-only mode and previously errored atplan). Opt down tomode: planfor read-only monitoring/reporting.JOB_DEFAULTS.mode, theagents routines add --modeflag default, and the file-add default all move toauto;writeJobnow omitsmodewhen it equalsauto. Source:apps/cli/src/lib/routines.ts,apps/cli/src/commands/routines.ts. agents publish— a self-hosted, zero-infrastructure skill registry that round-trips withagents search/agents install. Publish walks a git repo'sskills/directory, records a sha256 of everySKILL.md, and writes a flatskills-index.json(SkillIndexDocumentshape) at the repo root, then commits + pushes it and prints theraw.githubusercontent.comURL plus the exactagents registry add skill <name> <url>command to share. No hosted aggregator: the index is just a file in your GitHub repo, consumed directly by the existingfetchSkillIndex/searchSkillRegistriespath. Targets your~/.agentsrepo by default or an extra repo via--repo <alias>(--dry-runpreviews without pushing). Each index entry carriessha256, threaded throughSkillEntry/normalizeSkillEntry, andagents installnow verifies the freshly clonedSKILL.mdagainst it — a mismatch aborts with a clear error rather than trusting a tampered artifact. This is the self-hosted/git-index slice of #336; global no-URL discovery (a hosted aggregator) remains future work. Source:apps/cli/src/commands/packages.ts(publishsubcommand + install-time verify),apps/cli/src/lib/registry.ts(buildSkillIndex,verifySkillIntegrity,sha256OfFile,parseOwnerRepoFromRemote,SkillIndexEntry.sha256),apps/cli/src/lib/types.ts(SkillEntry.sha256). (#336)
1.20.54
- Unified fleet target resolution for
agents ssh+sessions --host.agents sshnow accepts the full target grammar the fan-out already used — a registeredname, auser@device(same device, login user overridden, still dialed via its Tailscale route rather than raw LAN DNS), and an ad-hocuser@host/hostliteral — instead of only an exact device name (agents ssh muqsit@mac-minino longer errors "Unknown device"). A bare unregistered alias still reports "Unknown device".sessions --host user@devicenow resolves the host part through the registry too, so it stops silently diverging onto the non-Tailscale route. NewresolveDeviceTarget;resolveSshTargetshares one host-part matcher. Source:apps/cli/src/lib/devices/resolve-target.ts,apps/cli/src/commands/ssh.ts. agents sessions --hostsearches the peer's whole index, not its login cwd. A remote listing runs in the peer's SSH-login home dir and was silently cwd-scoped, sosessions --host <box>read as empty (No sessions found for /home/<user>) even when the box's index was full.--hostnow defaults to whole-index (--all) scope; an explicit path query /--project/--since/--agentfilter still narrows on top. It also runs the peer once, for itself (AGENTS_SESSIONS_LOCAL=1), so it no longer re-sweeps the fleet and prints a spurious<this-machine>: unreachable. Source:apps/cli/src/lib/session/remote.ts,apps/cli/src/commands/sessions.ts.agents devices syncpins each device's login user. Tailscale status carries a node's OS + address but not the account you ssh in as, so sync now materializes the local operator's username onto newly-synced devices (never clobbering a user you pinned). This makes--host <device>dial the same account no matter which machine launches the fan-out, instead of leaning on ssh's implicit local-username default. Source:apps/cli/src/lib/devices/sync.ts.
1.20.53
agents add <agent>@latestresolves to a concrete version before installing (no install race).latest(likeoldest) is now resolved vianpm viewup front and installed as a pinned spec directly intoversions/<agent>/<version>/. Previouslylatestinstalled into a shared, well-knownversions/<agent>/latest/scratch dir and was renamed to the real version only after npm finished — so a concurrentagents viewreconcile (reconcileStaleLatestForAgent) or a secondlatestinstall could rename that dir out from under npm mid-extraction, corrupting the install withENOENTon the seededpackage.json. A concrete dir per version has no shared name to race on. Source:apps/cli/src/lib/versions.ts.Native memory sync preserves unmanaged Markdown files (RUSH-1621). Sync tracks managed fact names in
.agents-cli-memory.jsonand only deletes those; user-authored*.mdunder the agent memory dir survive. Source:apps/cli/src/lib/memory.ts.Feed high-consequence authz uses the canonical operator registry (RUSH-1618).
recordAnswerno longer looks foroperators.yamlunder the feed root; it resolves operators from~/.agents/vialoadOperators(). Source:apps/cli/src/lib/feed.ts.Menu bar groups worktree sessions under the real repo name (RUSH-1635). Paths under
.agents/worktrees/<slug>use the enclosing repository directory as the grouping key instead of the worktree slug. Source:apps/cli/menubar/.../LocalState.swift.PR outcome keys include repository identity (RUSH-1630). Full GitHub pull URLs normalize to
owner/repo#Nso two repos' PR #10 no longer collide underpr:#10. Source:apps/cli/src/lib/feed-outcome.ts.Urgent OpenClaw notifications use
--targetand--message(RUSH-1620).openclaw message sendrequires a destination and the--messageflag (not--text); without--targetthe send was invalid. Source:apps/cli/src/lib/notify.ts.High-consequence answers require env-proven operator identity (RUSH-1619).
agents message --as <id>alone is not verification;AGENTS_OPERATOR_IDmust match the claimed id (and the id must be inoperators.yaml). Source:apps/cli/src/lib/operator.ts,apps/cli/src/commands/message.ts.Hermes and ForgeCode are first-class install targets (RUSH-559).
AgentIdnow includeshermesandforge, with resource capability metadata for skills, rules, and MCP. MCP sync writes Hermesmcp_serversYAML in~/.hermes/config.yamland ForgeCodemcpServersJSON in~/.forge/.mcp.json, so registering these targets no longer requires their CLIs to be installed first. Source:apps/cli/src/lib/agents.ts,apps/cli/src/lib/mcp.ts,apps/cli/src/lib/resources/mcp.ts.Cloud tasks can now report a resumable
idlestatus (RUSH-601). Provider-side stopped states such as Rushidle/paused/needs_review, Codexpaused/needs_review, and Antigravityidle/pausednormalize to canonicalidle; stream output,agents sessions, andagents cloud list/statusrender idle as an idle state instead of falling through to queued or unknown-status output. Source:apps/cli/src/lib/cloud/types.ts,apps/cli/src/lib/cloud/stream.ts,apps/cli/src/lib/session/active.ts,apps/cli/src/commands/cloud.ts.OpenCode plugin install only writes loader-visible direct
.ts/.jsfiles (RUSH-1617). Drop nested and.mjs/.cjsinstalls that OpenCode never scans; multi-module plugins flatten into~/.config/opencode/plugins/. Source:apps/cli/src/lib/plugins.ts.Routine credit-failover scans only the current attempt's log (RUSH-1616). Each failover spawn writes
stdout.attempt-N.logand rate-limit detection uses that file alone; prior attempts still append intostdout.logfor the continuous trail. Source:apps/cli/src/lib/runner.ts.Cursor hook sync drops stale managed entries when matcher/event change (RUSH-1615). GC keys managed hooks by
event|command|matcherinstead of command path alone, so a matcher or event edit no longer leaves dead entries. Source:apps/cli/src/lib/hooks.ts.Stop advertising Goose SubagentStart/SubagentStop hooks (RUSH-1613). Goose does not emit those events; drop them from
GOOSE_EVENT_MAPso sync no longer installs dead entries. Source:apps/cli/src/lib/hooks.ts.Mailbox delivery receipts are monotonic (RUSH-1614).
recordMessageReceiptno longer lets a latequeuedwrite overwrite an already-recordedconsumed/continuedwhen enqueue races the drain. Source:apps/cli/src/lib/feed.ts.Per-session rate-limit detection + feed badge (RUSH-1523). The session state engine flags rate/usage-limit text in the transcript (
detectRateLimited);ActiveSession.rateLimitedflows through remote fan-out into Factory'sFloorAgent.rateLimited, which renders a rate limited pill on the feed card. Source:apps/cli/src/lib/session/state.ts,apps/factory/.../floorAdapter.ts,FeedItem.tsx.Kiro launches with
--v3so standalone hooks actually fire (RUSH-1612). Agents-cli writes Kiro hooks as v3 standalone files under~/.kiro/hooks/*.json, but those only load on the v3 engine.AGENT_COMMANDS.kiro.basenow includes--v3soagents run kiroopts into the engine that reads them. Source:apps/cli/src/lib/exec.ts.Ask classifier + stall suppression for the agent feed (RUSH-1477). Every open block is classified as Decision / Approval / Clarification / Stall / Fyi. Workflow-stalls ("should I…?", "what's next?", "looks good?") are auto-answered and removed so they never render as cards; Decisions and Approvals still surface.
agents feedreports a digest (N stalls auto-resolved by policy);--allshows suppressed items;--jsonstamps each block with itsaskclassification. Agent-taggedblockClass: decisionis never auto-suppressed. Source:apps/cli/src/lib/ask-classifier.ts,apps/cli/src/commands/feed.ts.Parked-agent answer router: PTY-select / resume / mailbox by runtime (RUSH-1474).
agents messageno longer always enqueues to the mailbox. When the target is parked on an open feed question, delivery routes by runtime: tmux/iterm/pty rails get keystrokes that select the matching option label (or free-text via Other); headless parked runs resume viaagents run --resume <id> -- <answer>; running agents still use the mailbox. Wrong-state delivery is refused with a clear error instead of silently rotting in the spool. Source:apps/cli/src/lib/answer-router.ts,apps/cli/src/commands/message.ts.Wire subagents support for OpenCode. OpenCode loads agent markdown from
~/.config/opencode/agents/with frontmattermode: subagent. Flipsubagents: true, addtransformSubagentForOpenCode, wire writer/detector/list/diff/remove. Source:apps/cli/src/lib/agents.ts,apps/cli/src/lib/subagents.ts. (RUSH-1386)agents feedgroups by outcome (ticket/PR/worktree), not by agent (RUSH-1479). The default human view collapses open blocks under the deliverable they serve — Linear ticket first, then PR, then worktree/epic, then a shared Unassigned bucket — so a 1,100-agent fleet reads as dozens of initiatives (RUSH-1125 · 4 agents · 1 needs you). Each block is attributed to exactly one outcome; live session meta fills missing ticket/PR/worktree at list time;--flatrestores the per-agent list;--jsonstamps each block with itsoutcomeref. Factory Floor's Group control gains an Outcome axis (the new default). Source:apps/cli/src/lib/feed-outcome.ts,apps/cli/src/commands/feed.ts,apps/factory/ui/settings/components/mission-control/floorModel.ts.Menu bar dropdown redesigned around triage: attention floats up, context groups down. The dropdown used to stack ~11 flat sections at equal weight, so a session waiting on you sat as loud as setup noise. Now: a ⚠ NEEDS YOU strip on top, sorted by wait-time across all projects (most-stalled first), each row carrying the actual question the session is waiting on plus how long it's waited (
Claude · agents-cli — Claude needs your permission to use Bash · 2h 25m); live work grouped by repo below (ACTIVE · <repo>headers, rich rows show the session's own title inline); ROUTINES expanded into a glanceable section (next few upcoming + any failing routine inline,All routines…for the rest); RECENT TICKETS and RECENT stay dedicated sections; Setup + Auto-nudge collapse into one System row (submenu keeps the doctor items and the auto-nudge toggle). A density toggle in the footer cycles Auto → Rich → Compact — compact folds rows to one-liners and tucks Recent behind a submenu; Auto (default) is rich while something needs you, compact on a calm machine (menubarDensityin UserDefaults,MENUBAR_DENSITYenv override for dump probes). The question text + wait-time come from the attention sentinel: the Notification hook now writes the notification message as the sentinel content (phnx-labs/.agents-system#74), and the helper reads content + mtime (LocalState.attentionMarks); an empty sentinel still renders as "awaiting input".Sessiongainedtitle/question/attentionSinceMs; terminal rows group by working-dir name and carry the live-terminal label as the title. Source:apps/cli/menubar/Sources/MenubarHelper/StatusItemController.swift,apps/cli/menubar/Sources/MenubarHelper/LocalState.swift.Wire allowlist support for OpenCode. OpenCode stores per-tool allow/ask/deny rules in
opencode.json/opencode.jsoncunderpermission(bash patterns etc.; present since ~1.1.1). Flipallowlist: { since: '1.1.1' }so the existingconvertToOpenCodeFormat/applyPermissionsToVersion/ detector path actually runs. Source:apps/cli/src/lib/agents.ts,apps/cli/src/lib/permissions.ts. (RUSH-1385)Wire subagents support for Grok CLI. Grok discovers agent definitions as Claude-compatible
.mdfiles under~/.grok/agents/(docs: user-guide/16-subagents.md). Flipsubagents: true, reuse the Claude flatten transform for install/writer paths, and register a detector plus list/diff/remove. Source:apps/cli/src/lib/agents.ts,apps/cli/src/lib/subagents.ts,apps/cli/src/lib/staleness/writers/subagents.ts,apps/cli/src/lib/staleness/detectors/subagents.ts. (RUSH-1384)Wire subagents support for Kimi CLI. Kimi Code loads custom agents as YAML under
~/.kimi-code/agents/*.yamlwith a sibling*.system.mdreferenced viasystem_prompt_path(Kimi has no inlinesystem_promptfield) and a managed parent_agents-cli.yamlthat declaresagent.subagentsfor--agent-file. Flipsubagents: true, addtransformSubagentForKimi/writeKimiSubagentFiles, list/diff/remove paths, and wire the subagents writer + detector (underscore-prefixed parent excluded from the installed name list). Source:apps/cli/src/lib/agents.ts,apps/cli/src/lib/subagents.ts,apps/cli/src/lib/staleness/writers/subagents.ts,apps/cli/src/lib/staleness/detectors/subagents.ts. (RUSH-1383)Grok + Antigravity now register dir-form subrule hooks. The hooks writer excluded
grokfromregisterHooksToSettings, so subrule-bundled guards (absolute paths outside the central hooks copy set) never reached~/.grok/hooks/hooks.json. Grok is now in the gate. Antigravity entries carrymatcherso guards are tool-scoped. Source:apps/cli/src/lib/staleness/writers/hooks.ts,apps/cli/src/lib/hooks.ts. (RUSH-1353)Menu-bar Quick Dispatch can now pick agents and fan out autonomous fixes from the screenshot panel.
Cmd-Shift-Ostill supports filing one Linear ticket, but the panel now has a File Ticket / Fix mode control plus a roster picker sourced from the menu-bar agent list. File Ticket runs the selected ticket agent; Fix dispatches every selected agent withagents run <agent> --mode auto --name quick-<agent>-<timestamp>, carrying the typed note and selected screenshots into a repo-discovery prompt so those runs surface in normal session/tray views instead of hidden background work.AGENTS_QUICK_DISPATCH_ROSTER=claude,codexfilters visible agents andAGENTS_QUICK_DISPATCH_AGENTS=claude,codexpreselects them. Source:apps/cli/menubar/Sources/MenubarHelper/{LocalState,PromptPanel,AgentsCLI,IssueSelfTest}.swift,apps/cli/docs/menubar.md. (RUSH-1416)Menu-bar Quick Dispatch keeps immediate typing in the capture field. The
Cmd-Shift-Oquick-capture panel now uses an activating borderless window, orders it front, and waits briefly for the field editor to become ready before returning from summon, so notes typed immediately after summon no longer lose their leading characters to the previously focused app. Source:apps/cli/menubar/Sources/MenubarHelper/PromptPanel.swift. (RUSH-1591)Internal: consolidated the drifted provider status-normalizers and git-root helpers (#753). The three copies of
mapStatusincloud/{rush,codex,antigravity}.ts— which had drifted (different vocabularies and defaults) — collapse into one exportednormalizeProviderStatus(provider, wireStatus)incloud/types.ts, with provider-specific defaults still explicit (rush defaultrunning, codex defaultrunning, antigravityundefined-safe defaultcompleted); factory's structurally-differentmapResultStatusis left in place.getGitRootmoves tolib/git.tsandcommands/worktree.tsnow calls it instead of a privategitRootcopy; the two divergentisGitRepovariants (synchronous root-only ingit.tsvs async worktree-correct inteams/worktree.ts) are documented and deliberately not merged. Source:apps/cli/src/lib/cloud/{types,rush,codex,antigravity}.ts,apps/cli/src/lib/git.ts,apps/cli/src/lib/teams/worktree.ts,apps/cli/src/commands/worktree.ts. (#753)macOS installs now run a Developer-ID-signed + notarized
agentsbinary — the primary fix for EDR (CrowdStrike Falcon) blocking the CLI. The resolvedagentson macOS was a node-shebang JS file in a user-writable path; unsigned and spawned by editor/Electron children, it matched Falcon's post-exploitation profile and the sessions feature silently died on EDR-enabled Macs (mitigation 1 of #315; the behavioral hardening, mitigations 2-4, shipped earlier). Releases now build a standalone arm64 Mach-O withbun build --compile(scripts/build-bin.sh), sign it with Developer ID + hardened runtime + the JIT entitlement bun's JavaScriptCore needs under the hardened runtime (scripts/sign-cli-binary.sh,scripts/bun-jit-entitlements.plist), notarize it withnotarytool, and ship it in the npm tarball atdist/bin/agents.postinstallpoints the alias shims and the~/.local/bin/agents/aglinks at the signed binary — with a run-probe that falls back loudly to the JS entrypoint if the binary is missing, wrong-arch, or blocked — and repoints links an earlier install left at the JS shim. Aprepackgate (scripts/verify-cli-binary.sh) refuses to pack unless the binary matches its sign-run sha pin, embeds the release version, and (on macOS) passescodesign --verifywith a Developer ID authority. Linux-driven releases build + sign the binary on the mac sign host viascripts/remote-sign-mac.sh. Intel Macs and non-mac platforms keep the JS entrypoint. (#315)Codex multi-file apply_patch now surfaces every path.
parseCodexused.match()on the patch body so only the first*** Update/Add/Delete File:path became a tool_use; files 2+ were invisible to artifact discovery. NowapplyPatchTargetPathsusesmatchAlland emits one Edit event per file. Source:apps/cli/src/lib/session/parse.ts. (RUSH-1410)memoryis a first-class top-level resource (distinct fromrules).agents memory list|add|remove|view|syncmanages portable knowledge facts under~/.agents/memory/(project > user > system;MEMORY.mdindex + one<slug>.mdper fact). The legacyagents memory→rulestombstone is gone. Capable agents (claude, codex, openclaw, grok) get facts fanned into version homes onsyncResourcesToVersion/agents memory sync. Plugins can ship amemory/dir (surfaced inpluginResourceGroups). Note: internalResourceSelection.memorystill means the composed rules file — rename torulesis a follow-up. Source:apps/cli/src/lib/memory.ts,apps/cli/src/commands/memory.ts,apps/cli/src/lib/resources/memory.ts,apps/cli/src/lib/versions.ts,apps/cli/src/lib/plugins.ts. (RUSH-1330)agents sessions --active --jsonnow carriestokPerSec, andagents sessions --roots --jsonemits the session-scan directories — one CLI contract the Factory extension consumes instead of re-implementing (issue #741). Every active row gainedtokPerSec: live output-token throughput over a rolling 60s window from the transcript tail (Claude assistantoutput_tokens; Codextoken_countoutput + reasoning; Gemini output + thoughts), absent when the session is idle or its format reports no usage. The newagents sessions --roots --jsonprints, per on-disk agent, the exact directories the CLI scans for transcripts (every version home + backup mirror), so an external watcher (the Factory Floor'sfs.watch) tracks the same paths the CLI does instead of hardcoding~/.claude|.codex|.gemini— add an on-disk agent to discovery and every consumer watches it automatically. The throughput math and the roots list are now the single source of truth; the extension used to keep parallel copies. Source:apps/cli/src/lib/session/throughput.ts(computeTokPerSec),apps/cli/src/lib/session/active.ts(ActiveSession.tokPerSec,computeLiveSignals),apps/cli/src/lib/session/tail.ts(readSessionTailWithRaw),apps/cli/src/lib/session/discover.ts(getSessionRoots),apps/cli/src/commands/sessions.ts(--roots).Fix:
agents sessions --activeno longer attaches stale Codex transcripts to live processes. Codex session lookup now sorts by indexedlast_activityand refuses to borrow a transcript outside an explicit 24-hour freshness bound, so a desktop app service or unrelated long-lived process cannot light up a months-old session asrunning. Source:apps/cli/src/lib/session/active.ts,apps/cli/src/lib/session/db.ts. (RUSH-1489)Startup shim self-heal stays silent by default, with
--verbosediagnostics on stderr. The unified shim/shadow/PATH repair path no longer pollutes command stdout, includingagents sessions --active --json;agents --verbose <cmd>now prints a concise startup self-heal summary to stderr for debugging. Source:apps/cli/src/index.ts,apps/cli/src/lib/shim-heal.ts. (RUSH-1533)Wire subagents support for Codex CLI. Codex custom agents are standalone TOML under
~/.codex/agents/*.toml(required:name,description,developer_instructions; multi-agent plumbing since 0.117.0). Flipsubagents: { since: '0.117.0' }, addtransformSubagentForCodex, and wire the subagents writer + install/remove paths. Source:apps/cli/src/lib/agents.ts,apps/cli/src/lib/subagents.ts,apps/cli/src/lib/staleness/writers/subagents.ts. (RUSH-1382)Routines use the same version/account selection and credit failover as
agents run. Scheduled jobs used to spawn a bareclaude/codexname under a sandbox HOME, which could surface asagents: no version of claude configuredeven when installs existed, and never walked past a credit-exhausted default. The runner now resolves a healthy install via the configured run strategy (defaultbalanced), pins the absolute binary, injects per-version config dirs + the daemon'sCLAUDE_CODE_OAUTH_TOKENinto sandboxed spawns, and on foregroundagents routines runre-dispatches to the next healthy same-agent account when a mid-run rate/usage limit is detected (daemon detached fires use the pre-flight pick only). Diagnostic lines log the pick, skipped accounts, and each failover hop. Source:apps/cli/src/lib/runner.ts(resolveRoutineLaunch,pinJobBinary,buildRoutineSpawnEnv),apps/cli/src/lib/sandbox.ts(OAuth allowlist),apps/cli/src/commands/routines.ts(help). (RUSH-1016)Wire plugin support for Goose. Goose loads Open Plugins from
$HOME/.agents/plugins/<name>/(same layout as agents-cli). Flipplugins: trueand copy each selected plugin into the version home under.agents/plugins/. Source:apps/cli/src/lib/agents.ts,apps/cli/src/lib/plugins.ts(installGoosePlugin). (RUSH-1339)Wire plugin support for Cursor CLI. Cursor agent plugins use
.cursor-plugin/plugin.json(re-enabled 2026-05). Flipplugins: true, setpluginManifestDir: '.cursor-plugin', and reuse the centralized marketplace mirror path under~/.cursor/plugins/. Source:apps/cli/src/lib/agents.ts. (RUSH-1338)Wire plugin support for OpenCode. OpenCode loads JS/TS plugin modules from
$HOME/.config/opencode/plugins/(not Claude marketplace layout). Flipplugins: true, install modules from a plugin'sopencode/orplugins/dir (or root) into the version home, and track install/remove viaisPluginSynced/removePluginFromVersion. Source:apps/cli/src/lib/agents.ts,apps/cli/src/lib/plugins.ts(installOpenCodePlugin,openCodePluginsDir). (RUSH-1336)Wire hooks support for Cursor CLI. Cursor agent CLI (
cursor-agent) gained lifecycle hooks on 2026-01-16 (~/.cursor/hooks.json,{ "version": 1, "hooks": {…} }). Flip the capability, map canonical events to Cursor camelCase (SessionStart→sessionStart,UserPromptSubmit→beforeSubmitPrompt,Stop→stop, …), and merge managed entries intohooks.jsonwhile preserving user-authored commands outside managed prefixes. Source:apps/cli/src/lib/agents.ts,apps/cli/src/lib/hooks.ts(registerHooksForCursor,CURSOR_EVENT_MAP),apps/cli/src/lib/staleness/writers/hooks.ts. (RUSH-1326)Wire hooks support for Goose. Goose (
block-goose-cli≥ 1.34.0) auto-discovers Open Plugins hooks at$HOME/.agents/plugins/<name>/hooks/hooks.json. FlipsupportsHooksand gatehooks: { since: '1.34.0' }, write a managed plugin (agents-cli-hooks) under the version home with Claude-shaped event groups, and leave user plugins alone. Source:apps/cli/src/lib/agents.ts,apps/cli/src/lib/hooks.ts(registerHooksForGoose,GOOSE_EVENT_MAP),apps/cli/src/lib/staleness/writers/hooks.ts. (RUSH-1325)Wire hooks support for Kiro CLI. Kiro CLI v3 stores standalone hooks under
~/.kiro/hooks/*.json({ "version": "v1", "hooks": [...] }with command/agent actions); PreToolUse/PostToolUse firing was fixed in kiro-cli 0.10. FlipsupportsHooksand gatehooks: { since: '0.10.0' }, map canonical events to Kiro PascalCase triggers, and write a single managedagents-cli-hooks.jsonon every sync (user-authored sibling files untouched). Source:apps/cli/src/lib/agents.ts,apps/cli/src/lib/hooks.ts(registerHooksForKiro,KIRO_EVENT_MAP),apps/cli/src/lib/staleness/writers/hooks.ts. (RUSH-1324)Wire hooks support for GitHub Copilot CLI. Copilot GA (
@github/copilot≥ 1.x) ships a real hooks system (~/.copilot/hooks/*.json, schema{ "version": 1, "hooks": {…} }), but agents-cli still declaredhooks: falseso nothing ever installed. Flip the capability, map canonical events to Copilot camelCase (SessionStart→sessionStart,PreToolUse→preToolUse,Stop→agentStop, …), and write a single managed file (agents-cli-hooks.json) on every sync so GC is a rewrite and user-authored sibling JSON files are never touched. Source:apps/cli/src/lib/agents.ts,apps/cli/src/lib/hooks.ts(registerHooksForCopilot,COPILOT_EVENT_MAP),apps/cli/src/lib/staleness/writers/hooks.ts. (RUSH-1323)agents run --leasenow shows live progress instead of a 30-90s blank terminal. After the runtime picker + consent, the lease used to go completely silent while the box provisioned (crabboxWarmupwas a blockingspawnSync, so nothing could animate), then dump crabbox's raw sync/bootstrap log interleaved with the agent's output. Now a spinner animates each previously-silent phase —Leasing a hetzner box… (Ns)→✔ Box <slug> ready (<ip>) · Ns→Setting up box — <latest step>→✔ Box provisioned — <agent> output:— and the agent's own output then prints verbatim. The box bootstrap echoes a marker (LEASE_AGENT_MARKER) right before the agent run;createLeaseOutputRoutersplits the crabbox stream on it so setup noise feeds the spinner while the agent output streams clean, and a setup failure dumps the captured setup log so errors are never swallowed.crabboxWarmupis now async so the event loop stays free to animate. The spinner is a purpose-builtcreateSpinner(NOTora): ora hooksstream.writeand re-renders on every external write while spinning, which ballooned to multi-GB output when a lease streamed past a live spinner —createSpinnerwrites exactly one short line per fixed tick and nowhere else (a unit test asserts 100kupdate()calls produce zero writes), and on a non-TTY prints each phase label once and stays silent on update (no piped/CI flood). Verified live on Hetzner: animated warmup counter, cleanLOGIN_OK, box auto-destroyed, 43KB total output. Source:apps/cli/src/lib/crabbox/progress.ts(createSpinner,createLeaseOutputRouter,LEASE_AGENT_MARKER),apps/cli/src/lib/crabbox/{lease,cli}.ts,apps/cli/src/commands/exec.ts.Fix: Grok hooks now respect their
matcherand no longer run twice per event. The Grok hook registrar (registerHooksForGrok) dropped the manifestmatcherwhen writing~/.grok/hooks/, so a matcher-scoped hook fired on every tool call — the plan-presentation hook (meant forExitPlanModeonly) hard-blocked whole Grok sessions (exit 2 = explicit deny). It also double-registered every hook into BOTHhooks.jsonAND a per-event file (pretooluse.json, …); Grok merges all of~/.grok/hooks/*.json, so each hook ran twice. Now the registrar emitsmatcheronly for the events Grok accepts it on (PreToolUse,PostToolUse,Notification— lifecycle events reject it), groups hooks one-per-distinct-matcher like the Claude writer, translates tool names Grok doesn't auto-alias (ExitPlanMode→ExitPlanMode|exit_plan_mode, since Grok's plan tool isexit_plan_mode), and writes a singlehooks.json, pruning the stale per-event files an older build left behind so already-synced installs stop double-running. Source:apps/cli/src/lib/hooks.ts(registerHooksForGrok,isManagedGrokHookFile,GROK_MATCHER_EVENTS,GROK_MATCHER_ALIASES).Agent feed replies now have delivery confirmation and atomic first-answer-wins closure (RUSH-1476).
agents messageties a local reply to the agent's current open feed block; if any surface already answered that block, the second attempt is rejected with the surface that won. The feed block records queued → consumed → continued receipts:queuedwhenagents messageenqueues,consumedwhen the mailbox drain archives the message, andcontinuedonce the agent continues past the block. A human answer typed directly in the terminal is reconciled via theUserPromptSubmithook, which records a terminal answer and removes the visible block within one feed poll cycle. Source:apps/cli/src/lib/feed.ts(recordAnswer,recordMessageReceipt,recordContinued, answered-marker lifecycle),apps/cli/src/lib/mailbox.ts(blockIdonMailboxMessage, consumed-receipt surfacing),apps/cli/src/commands/message.ts(open-block lookup +--surface),apps/cli/src/commands/feed.ts(receipt rendering).24/7 multi-operator controls for agent feed (RUSH-1480). Blocks can now carry
blockClass(approval/decision),consequence(normal/high),allowedOperators,timeoutMinutes,safeDefault, andcostOfDelay; the feed-publish hook captures these from the agent'sAskUserQuestiontool_input. High-consequence answers require a verified operator id known to~/.agents/operators.yaml(agents message --as <operator>).agents feed --dispatchevaluates default-on-no-answer policy loaded from~/.agents/feed-policy.yaml: approval blocks resolve to their safe default after the timeout, decision blocks are hard-parked. Urgent blocks (costOfDelayat or above the phone threshold) are paged once via the OpenClaw Telegram gateway (openclaw message send --channel telegram --account default). Source:apps/cli/src/lib/operator.ts,apps/cli/src/lib/feed-policy.ts,apps/cli/src/lib/notify.ts,apps/cli/src/lib/feed.ts(block metadata + authz + parked/defaulted/notified timestamps),apps/cli/src/commands/message.ts(--as),apps/cli/src/commands/feed.ts(--dispatch, metadata rendering).Mailbox TTL + liveness/GC so messages never rot in dead-agent inboxes (RUSH-1475).
enqueueaccepts an optionalttlSeconds; expired messages are archived toconsumed/withdropped: expiredinstead of being returned bydrain/peek.agents feed --dispatchruns a liveness sweep againstgetActiveSessions(): boxes whose owning agent is no longer alive are treated as dead, their pending messages are archived withdropped: dead, and any feed block tied to that mailbox is removed so the operator never answers a ghost. Oldconsumed/entries are pruned after 24 hours by default. Source:apps/cli/src/lib/mailbox.ts(expiresAt,isExpired,sweepExpired),apps/cli/src/lib/mailbox-gc.ts,apps/cli/src/commands/feed.ts(--dispatchliveness sweep).Keychain service names are no longer silently enumerable — items are stored under opaque HMAC-hashed names (macOS). The helper's
listnever decrypts and never prompts (that's what keepsagents secrets listsnappy), which meant the service names themselves —agents-cli.secrets.<bundle>.<KEY>,agents-cli.bundles.<name>,agents-cli.<provider>.token— were readable metadata: any same-user process could silently inventory your bundles, keys, and providers before ever popping Touch ID. Everyagents-cli.*item now lives under an opaque name (agents-cli.h.<ns>.mfor bundle metadata,agents-cli.h.<ns>.k.<kh>for values,agents-cli.h.o.<ih>otherwise) keyed by a per-machine random HMAC key (agents-cli.hmackey, no-ACL so silent operations stay silent). The structured shape keeps a bundle's items under one hashed prefix, sosecrets exec/run --secretsstill resolve metadata + all values behind a single Touch ID. A one-time re-key migrates existing items — automatically on the first interactive keychain use, or via the newagents secrets rekey(--statusto inspect, exit 4 on a cancelled Touch ID; re-running resumes). It is crash-safe end to end: values are batch-read once, hashed copies are written and verified before ANY original is deleted, activation is all-or-nothing, and an interrupted delete phase is finished silently by the next run. A--prefix-restricted (partial) run resolves each value item's bundle tier directly from the keychain — scoping anever-policy bundle's value items without its metadata item keeps the silent no-ACL tier intact. The signed Swift helper is untouched (it already treats service names as opaque), so no re-notarization or sha re-pin. Caveat: an older agents-cli on the same machine writes/reads cleartext names and won't see re-keyed items — keep all installs current. Source:apps/cli/src/lib/secrets/index.ts(hashedServiceName,rekeyServiceNames),apps/cli/src/lib/secrets/bundles.ts,apps/cli/src/commands/secrets-migrate.ts,apps/cli/docs/secrets.md. (GitHub #316, Finding 1)
1.20.52
- Re-pinned the keychain helper to a freshly notarized build carrying the new iCloud verbs. The #904 session changed
keychain-helper.swift(legacy iCloud/synchronizable item verbs) and re-pinned the sha, but the notarized binary it pinned lived only in that session's since-removed worktree — the release prepack gate (verify-keychain-helper.sh) then failed on every machine (SHA256 mismatch) because no reachablebin/Agents CLI.appmatched the pin. Rebuilt the helper from current source on the release machine (universal, signed, notarized: GatekeeperNotarized Developer ID, submission accepted) and pinned that binary. Same class of fix as #835. - Fix: version-skewed CLI invocations no longer wipe the secrets-agent's hot cache — the recurring Touch ID storm on version-churning machines is closed.
ensureAgentRunningtore down a reachable broker whenever the broker's running version differed from the caller's on-disk version — unconditionally, vialaunchctl kickstart -k, wiping every unlocked bundle. On a machine where installed versions churn (dev builds stamp a fresh0.0.0-dev.<sha>per install; an npm copy and a dev copy invoke in turn), that meant constant wipes, and the next read of each held bundle popped a fresh Touch ID prompt — the exact storm #435 fixed on the server side, reintroduced client-side. The client now accepts a protocol-compatible, version-skewed broker while it holds real unlocks (shouldTeardownVersionSkewedBroker); the broker's own sweep still adopts new code at the next quiet moment (store empty), so upgrades land without ever costing a re-prompt. Source:apps/cli/src/lib/secrets/agent.ts(ensureAgentRunning,shouldTeardownVersionSkewedBroker). - Fix: mutating a bundle now evicts the broker-held copy —
rotateno longer serves the old secret for up to 7 days. Onlysecrets policyinvalidated the secrets-agent after a write;add,rotate,remove,rename,delete, andimportupdated the keychain while a broker-held snapshot kept serving the pre-write values for the rest of the ~7d hold — a rotated credential silently kept injecting the OLD value into every run.writeBundleanddeleteBundlenow evict the bundle from the broker after every mutating write (agentEvictSync, a synchronous socket client mirroring the read fast-path), so the next read re-resolves fresh from the keychain (one prompt) and re-caches. The usage-telemetry stamp (stampLastUsed, fired on every broker HIT) opts out — evicting there would make the cache destroy itself on first use — and the eviction honorsAGENTS_SECRETS_NO_AGENTplus the test-backend override so suites never evict a user's real unlocks (shouldEvictAfterBundleWrite). Thepolicycommand's bespoke eviction is superseded by the chokepoint. Source:apps/cli/src/lib/secrets/bundles.ts(writeBundle,deleteBundle,shouldEvictAfterBundleWrite),apps/cli/src/lib/secrets/agent.ts(agentEvictSync),apps/cli/src/commands/secrets.ts. agents secrets importgains a unified--from <source>and recovers bundles stranded in the iCloud Keychain. One axis for every source: a .env path (-reads stdin),1password:<vault>(the boolean--from-1password --vault <name>pair still works as a hidden deprecated alias), and the newicloud. Bundles created in the pre-biometry era were synced via iCloud Keychain; the device-local cutover pinned every query tokSecAttrSynchronizable: false, which orphaned those items — visible in Keychain Access under iCloud, invisible tosecrets listandmigrate-acl.agents secrets import --from iclouddiscovers them (bundle metadata and bare per-key secret items whose metadata never synced), offers an interactive multi-select (or takes an explicit bundle name for non-interactive use), re-imports them as normal device-local biometry-gated bundles, and with--purgedeletes only the iCloud copies whose value provably lives locally — imported this run or already present in the local bundle; an unreadable item, a key the modern store refuses by policy (reserved/loader env names the pre-cutover store accepted — reported asreserved, not importableinstead of aborting the bundle), and the metadata item of a partially-recovered bundle all survive.secrets view <name>on a missing bundle now points at the recovery command when an iCloud copy of that name exists. Three new keychain-helper verbs (list-synced,get-batch-synced,delete-synced) match synchronizable items exclusively, so the live device-local store is untouchable from the recovery path. Source:apps/cli/src/lib/secrets/icloud-import.ts,apps/cli/src/lib/secrets/keychain-helper.swift,apps/cli/src/commands/secrets.ts(parseImportSource).agents feedsurfaces every top-level agent block that is waiting on the user across the fleet.AskUserQuestionand waitingNotificationhooks publish atomic open-block records with the full question/options or notification message, mailbox/session identity, host, and runtime; answer/resume/stop hooks remove resolved blocks, and Task subagents are gated out so internal questions do not flood the operator view. A bareagents feedmerges local blocks with every registered online device in parallel,--host/--devicescopes the fleet,--localskips SSH, and--jsonreturns the same merged view. Runtime-managed hooks install from the CLI-writable user layer without dirtying the auto-pulled system repository. Source:apps/cli/src/commands/feed.ts,apps/cli/src/lib/feed.ts,apps/cli/src/lib/remote-agents-json.ts, runtime labelling inapps/cli/src/lib/exec.tsandapps/cli/src/lib/teams/agents.ts. (RUSH-1473)- Browser-profile credentials: account identity,
secrets get <bundle> <KEY>, andbrowser type --secretfor leak-free login.agents browser profiles loginsnow shows, per profile, the account signed into each live service (plaintext username from ChromiumLogin Data— never decrypts the encrypted password) and whether login creds are declared in the profile's secrets bundle (columnsSERVICE | ACCOUNT | CREDS);profiles showgains aLogins:block.agents secrets get <bundle> <KEY>prints one resolved value from a bundle (arg-count overload of the existing rawget <item>; ungated like it, and thesecrets.getaudit event fires inside the resolver).agents browser type <ref> --secret <bundle>/<KEY>resolves a credential in-process and types it into the page — the value never crosses stdout or the agent transcript — so an agent can drive a login by composingprofiles logins→browser start <loginUrl>→refs→type --secret→screenshot, handling 2FA/selectors itself (no fragile CLI auto-login engine; Google/X block automation anyway). A profile's--secretsbundle is the credential store, keyed by the<PREFIX>_USERNAME/<PREFIX>_PASSWORDconvention (per-service prefixes inAUTH_SIGNATURES);profiles create --secretsnow warns if the bundle doesn't exist yet. Cookie-persistence-first remains the headline (thebrowserskill's credential guidance was corrected — the bundle only injected env vars into the browser process before, inert for web login). Source:apps/cli/src/lib/browser/login-detection.ts,apps/cli/src/lib/browser/secret-ref.ts,apps/cli/src/commands/browser.ts,apps/cli/src/commands/secrets.ts. agents logs audit/agents logs stats/agents logs rotate— user-facing audit trail viewer. The append-only local event log (~/.agents/events.jsonl) is now a first-class audit surface.agents logs auditqueries events with filters (--module,--command,--event,--agent,--caller,--level,--since,--limit,--json) and--followfor live tailing;agents logs statsshows aggregate breakdowns by level, event type, module, and user;agents logs rotateprunes old numbered archives (--days, default 7). Events carrylevel(audit/warn/info/debug) and an environment-derivedcaller(claude-code, Factory agent kind, terminal, or script). Sensitive flag values, secret-shaped payload fields, token-like strings, and raw prompts are redacted before append. Security-relevant operations (secrets, teams lifecycle, cloud dispatch) auto-classify asaudit. At 10 MB the active file rotates losslessly throughevents.1.jsonl.gz,events.2.jsonl.gz, and so on;query()reads every archive transparently. New instrumentation incloud.ts,factory.ts,teams.ts,secrets.ts,mcp.ts, androtate.ts. Source:apps/cli/src/lib/events.ts,apps/cli/src/commands/logs.ts, instrumentation call sites. (RUSH-460)agents run claude --leasenow runs the box logged-in — the Claude OAuth token ships alongside the config. The lease copied~/.claude.json(config/account-metadata) but never the OAuth token, so Claude booted "Not logged in" on every leased box. The token lives in the macOS Keychain (hash-suffixed service for an agents-cli managed home, bare for a default install) and on Linux at~/.claude/.credentials.json.resolveClaudeCredentialsBlob()now reads the raw wrapped Keychain payload silently (/usr/bin/security … -w— Claude's item trusts it, no Touch ID): bare service first, then enumerate installed version homes, preferring the account whose email matches the copied config; off-darwin it reuses the existing.credentials.jsonfile branch.buildCredentialScriptwrites that blob to~/.claude/.credentials.json(0600) via the same quoted-heredoc that carries every other cred (the box's~/.claudeis a symlink into the versioned home, so it lands exactly where the shim'sCLAUDE_CONFIG_DIRreads it); it is shredded after the run regardless of--keep-box. Resolved in the command layer after the existing per-run consent prompt, whose text now names the token explicitly. Scope is Claude-only — Codex/Grok already ship their token in the copied auth file. Verified live on Hetzner: a leased box ranagents run claudeand returned a real model reply (LOGIN_OK, exit 0), and the token file was absent (shredded) afterward. Source:apps/cli/src/lib/crabbox/runtimes.ts(resolveClaudeCredentialsBlob,buildCredentialScript),apps/cli/src/lib/crabbox/lease.ts,apps/cli/src/commands/exec.ts.agents browser profiles set-default <name>picks the profile a bareagents browser startuses — so agents stop opening a logged-out Chrome. With no--profile,startused to auto-detect the first installed Chromium-family browser (Chrome first on macOS) and save it asdefault, ignoring a profile you'd actually logged into. Nowstartresolves in order: (1) your configured default, (2) an existingdefaultprofile, (3) auto-detect. The configured default ALSO re-points an explicit--profile default, so an agent that hardcodesdefaultstill lands on your chosen profile. The setting is device-local — stored in~/.agents/devices/<machine>/agents.yaml, never synced to other machines (the target profile may hold machine-local logins).profiles list/showmark it;set-default --unsetreverts to auto-detect; a missing target warns and falls back rather than hard-failing. Source:apps/cli/src/lib/browser/profiles.ts(ensureDefaultBrowserProfile,getConfiguredDefaultProfileName),apps/cli/src/lib/state.ts(writeMetaUnlocked,overlayMachineLocal),apps/cli/src/lib/types.ts(Meta.defaultBrowserProfile),apps/cli/src/commands/browser.ts.agents browsernow warns when a task opens a login-gated site on a logged-out profile — grounded in real session state. Newapps/cli/src/lib/browser/login-detection.tsreads a profile's Chromium cookie store (presence only — never decrypts the Keychain-encrypted values, and filters expiry in SQL so Chromium's >2^53 microsecond timestamps never tripnode:sqlite's integer range) to tell which login-gated services (LinkedIn, Google, X, GitHub, Reddit) have a live session.agents browser start --url <login-gated>prints a stderr hint likeprofile "default" has no linkedin.com session. logged in elsewhere: comet-local. try: --profile comet-localwhen the chosen profile is logged out; it never blocks or slows start.agents browser profiles loginsshows a profile-by-service table. Source:apps/cli/src/lib/browser/login-detection.ts,apps/cli/src/commands/browser.ts.- Fix: a finished session that signed off with a trailing "?" no longer reads as
input_requiredforever (RUSH-1522). The session state engine's prose-question heuristic (last assistant message ends with a question) now decays after 30 minutes without a session write: an unanswered prose question older than that classifies asidle, notwaiting_input— soagents sessions --activeand the Factory Floor's NEEDS YOU lane stop surfacing long-finished sessions as needing input. The structural signals are exempt and never decay: a genuinely pendingExitPlanMode(plan review) orAskUserQuestionstill classifies aswaiting_inputat any age. Source:apps/cli/src/lib/session/state.ts(inferActivity,PROSE_QUESTION_FRESH_MS). - The post-upgrade "What's new" summary shows the release notes again. The summary parser only recognized the old changelog format (standalone
**Heading**lines with sub-bullets); every release since the changelog moved to single-line- **Title.** prose…entries rendered as a bare version header with zero bullets, so upgrades looked like they shipped nothing. The parser now extracts the bold heading from both formats (prose still dropped — full notes stay in the changelog). Verified against the real changelog: the 1.20.49 → 1.20.50 range renders all four 1.20.50 entry titles. Source:apps/cli/src/lib/whats-new.ts. - Fix: daemon no longer crash-loops when started from the bare
browserorcomputershim. Daemon launch resolution now maps installed sibling shims to theagentslauncher and compiled shims toindex.jsbefore generating launchd/systemd commands, and fails clearly if that invariant is broken. Headless auto-start reads the long-lived Claude token only from an already-unlocked secrets-agent snapshot, so it cannot hang on a biometric prompt nobody can answer; an interactive start can still prompt normally. Source:apps/cli/src/lib/daemon.ts(getAgentsBinPath,readDaemonClaudeOAuthToken),apps/cli/src/lib/secrets/bundles.ts(agentOnly). (RUSH-1527)
1.20.51
Fix:
agents run --leasebootstraps a fresh crabbox image and no longer leaks the box after the run. Three failures compounded on a stock Hetzner lease (Ubuntu 24.04, no node preinstalled): (1) the bootstrap'snpm install -g @phnx-labs/agents-cliran with no node/npm on the box and swallowed the failure with|| true, so every run died deep in the script withagents: command not found(exit 127) and no hint why; (2) even with the CLI installed, a fresh install refusesagents runwith "agents-cli is not set up" untilagents setuphas run; (3) teardown calledcrabbox stop --id <slug>, but crabbox'sstoptakes a positional target (unlikestatus/run/ssh) and died withflag provided but not defined: -id— silently, becausecrabboxStopis best-effort — so every one-shot lease box was kept, billed, and left carrying the run's working data until someone noticed (Box … keptinstead of destroyed). The bootstrap now: exports~/.local/binonto PATH, installs node user-level from the officiallatest-v22.xtarball when missing (arch-aware, satisfiesengines.node >=22.5.0, no sudo needed), points the npm prefix at~/.local, fails loud with exit 96 and a diagnostic when the CLI still isn't runnable, and runsagents setupbehind the same[ ! -d ~/.agents/.system ]first-run guard the hosts bootstrap uses;crabboxStoppasses the slug positionally. Verified live on a fresh Hetzner cpx62 by the run's own progression across builds: the pre-fix lease exited 127 (agents: command not found) withBox … kept; after the node/npm fix it reachedagents-cli is not set up; after the setup fix it reached the agent's login check (Not logged in); and every post-fix run ends withBox <slug> destroyed.instead of leaking. Source:apps/cli/src/lib/crabbox/lease.ts(ENSURE_AGENTS_CLI,buildBootstrapScript),apps/cli/src/lib/crabbox/cli.ts(crabboxStop). Known follow-up: leasing a Claude runtime from a Mac whose Claude Code credential lives in the login Keychain (the default install, and any agents-cli managed home — service name is hash-suffixed) still lands "Not logged in" on the box, because the picker copies~/.claude.json(config/state) but not the OAuth token, and extracting the token from the Keychain needs an interactive ACL approval; tracked separately.agents repo pull user <git-url>now git-backs a plain~/.agentsinstead of silently skipping it — fixing config sync on Windows/fresh machines. Setup only ever git-clones the system repo (~/.agents/.system/); the user repo is created as a bare directory (state.ts ensureAgentsDir), so~/.agentsis git-backed only where it was cloned by hand as a dotfiles step. On a box where that never happened (a fresh install, or Windows),agents repo pulljust printeduser: not a git repo, skippingand the machine silently fell out of config sync — norules/, noagents syncof shared resources. Now, passing your config remote once —agents repo pull user git@github.com:you/.agents.git— adopts the existing directory in place: it clones your remote and moves the.gitin without deleting anything, materializes the tracked resources it was missing, and backs up any locally-modified tracked file (e.g. a machine-specificagents.yaml) to a sibling~/.agents.pre-adopt-backup/before overwriting it. Untracked runtime state (.cache/,.history/,.system/— all gitignored) is never touched. Every subsequentagents repo pull/agents syncis plain (the remote is noworigin). No new command; the URL is only needed the first time. SSH transport is preserved (agit@…URL clones over SSH, not a rewritten https that would hang on a private-repo credential prompt), and git never prompts (GIT_TERMINAL_PROMPT=0). Source:apps/cli/src/lib/git.ts(adoptRepo),apps/cli/src/commands/repo.ts.agents runnow warns when a headless run leaves committed-but-unpushed work, instead of stranding it silently. A headlessagents runin a writable mode (edit/skip/auto) could end with the agent having committed on a branch but never pushed it — the run's exit path did no git work, so those commits sat invisible in a worktree until someone audited the box (exactly how a batch dispatch loop can quietly lose a verified fix). After a non-interactive, writable run the CLI now inspects the cwd for commits on the current branch that haven't reached any remote (git log HEAD --not --remotes, correct even when no upstream is set — work already on anorigin/*ref is not flagged) and prints a loud stderr warning naming the branch, the unpushed commits, and the exactgit push/gh pr createcommands. Advisory only: it never pushes, never mutates the repo, and never throws (a 5s git timeout plus full error-swallowing guarantee it can't delay or break the run's exit). The check is wired into every headless exit path — single run,--loop,--acp,--resume-checkpoint, and the crash/catch path — and gated byshouldWarnUnpushed(mode, interactive)so it stays silent for interactive runs (the human sees their shell) and read-onlyplanmode. Source:apps/cli/src/lib/warn-unpushed.ts,apps/cli/src/commands/exec.ts. (#868)Codex mode flags now match what the mode names promise — only
--mode skipis yolo.--mode editused to append--dangerously-bypass-approvals-and-sandbox(Codex's--yolo) alongside--sandbox workspace-write, and the bypass flag wins — so "edit" silently ran Codex with no sandbox and no approvals, verified against codex 0.142.5's own session banner (sandbox: danger-full-access). And--mode planmapped toworkspace-write(writable!) because the template predated Codex'sread-onlysandbox. Now:plan→--sandbox read-only,edit→--sandbox workspace-write -c sandbox_workspace_write.network_access=true(sandboxed writes, network on so git/gh/installs keep working, no approval bypass),skip→--dangerously-bypass-approvals-and-sandbox(unchanged — skip IS the gnarly mode, equivalent tocodex --yolo). Same fix in routine jobs (runner.ts) and in headlesscodex exec resume, which used to get the bypass for ANY non-plan resume — it now maps plan/edit through-c sandbox_mode=…and reserves the bypass for skip; interactivecodex resumenow carries the mode's sandbox flags instead of none. Verified live per mode against codex 0.142.5 session banners: skip =approval: never / sandbox: danger-full-access, edit =sandbox: workspace-write (network access enabled), plan =sandbox: read-only. Source:apps/cli/src/lib/exec.ts(AGENT_COMMANDS.codex, resume block),apps/cli/src/lib/runner.ts(buildJobCommand).--add-diris now forwarded to Codex (it was silently dropped).agents teamspasses--add-dir ~/.agentsso Codex teammates can runagents teams add, butbuildExecCommandemitted--add-dirfor Claude only — the grant never reached Codex, masked until now by edit mode's accidental sandbox bypass. Codex takes--add-dirnatively (widens the workspace-write sandbox); it is now forwarded for fresh runs and skipped on resume (codex exec resumerejects it). Source:apps/cli/src/lib/exec.ts.Fix: the documented
agents run <agent> [prompt] -- <native flags>passthrough works again. commander ≥13 rejects excess operands by default, so any post---token (e.g.agents run codex -- --yolo) died withtoo many argumentsbefore the run started. The run command now allows excess operands, re-derives the--boundary from argv (a post---token can never be mis-parsed as the prompt —agents run codex -- --yololaunches the TUI with--yolo, it doesn't headless-run the "prompt"--yolo), and still errors, with a hint to quote the prompt, on excess operands NOT behind--. Verified live:agents run codex "…" -- --yoloforwards--yoloand codex reportssandbox: danger-full-access. Source:apps/cli/src/commands/exec.ts.Fix: grok launch shims resolve the binary from the versioned home before the global
~/.grok/downloads, so a pinned grok that installed into the versioned home no longer dies with "grok@not installed." Grok ships a native binary (not an npm package), and it lands in the versioned home's.grok/downloadswhenever the installer runs withGROK_HOMEset — via the shim, a correctagents add grok, or a grok self-update from within the shim. Both generated shims (the dispatcher ingenerateShimScriptand thegrok@<version>versioned alias ingenerateVersionedAliasScript) checked only$HOME/.grok/downloads, which was often empty, so they fell through to the "not installed" error even though the binary existed in the versioned home.getBinaryPathalready checked the versioned home first, soagents viewand the shims disagreed. Both shim blocks now check$VERSION_DIR/home/.grok/downloadsfirst and fall back to the global$HOME/.grok/downloadsfor pre-fix installs, then the existing adopted-launcher/PATH last resort. BumpsSHIM_SCHEMA_VERSION25→26 andVERSIONED_ALIAS_SCHEMA_VERSION12→13 so existing on-disk grok shims regenerate. Supersedes the pre-monorepo #830. Source:apps/cli/src/lib/shims.ts(generateShimScriptgrok dispatcher block,generateVersionedAliasScriptbinaryResolution).Fix:
browser stop --host <windows>tree-kills the remote browser — relaunches never wedge on a staleSingletonLock. The kill script usedStop-Processon the CDP port owner only; orphaned Chromium child processes survived, kept the profile'sSingletonLockheld, and the nextbrowser start --hostagainst the same profile exited immediately as a second instance. The script now usestaskkill /PID <owner> /T /Fto take down the whole process tree. Source:apps/cli/src/lib/browser/drivers/ssh.ts(buildWindowsKillScript). (GitHub #561)Fix:
agents browser start --host <windows>actually serves CDP now — the remote browser launches in the user's interactive session instead of session 0. The Windows launch used WMIWin32_Process.Create(chosen so the browser outlives the ssh session), but a WMI-created process lands in session 0, where Edge binds the debugging port yet its DevTools server never initializes — every/json/versionprobe hung forever andDevToolsActivePortwas never written, sobrowser start --hostfailed with a connection error on every attempt. The launch is now a one-shot scheduled task registered and started by the logged-on user: it survives ssh disconnect the same way, runs in the interactive session where DevTools comes up normally, and is unregistered immediately after start. The launch args also gained the same automation-modal suppressors the local launcher has (--no-first-run --no-default-browser-check --hide-crash-restore-bubble --disable-session-crashed-bubble) — without them a relaunch against a previously hard-killed profile triggers session-restore churn that closes the CDP page target mid-command. Verified live against win-mini (Edg/150). Source:apps/cli/src/lib/browser/drivers/ssh.ts(buildWindowsLaunchScript). (GitHub #561)Fix: remote CDP no longer dies on large payloads — screenshots of content-rich pages over
browser --hostwork. The CDP client rode the platform (undici) WebSocket, which enforces a non-configurable max decompressed message size; aPage.captureScreenshotresponse for a content-rich page blew past it and the socket closed with 1006 ("Max decompressed message size exceeded") while the command was pending, surfacing as "CDP connection closed". The websocket transport now uses thewsclient (no permessage-deflate offer by default, explicit 256MBmaxPayload); the local pipe transport is unchanged. Source:apps/cli/src/lib/browser/cdp.ts. (GitHub #561)agents sessions <id> --jsonnow exposes the ExitPlanMode plan markdown as a top-level field, and the shape changed from a bare event array to{ session, events }. The session-state engine already detected plan-review (awaitingReason: 'plan_review') off a trailingExitPlanModetool call, but the plan markdown itself was dropped on the floor — forcing every consumer that wanted it (the Factory NEEDS-YOU panel viaparsePlanFromClaudeJsonl, external dashboards) to re-open the raw JSONL and scan for the same tool call. That "extension re-implements the session engine" gap now closes at the source: the state engine surfacesstate.planalongsideawaitingReason, the Claude scanner captures the plan text at scan time and persists it tosessions.db(schema v11, additive, rescan-on-migrate), it's exposed asplanonSessionMetain everyagents sessions --jsonrow, andagents sessions <id> --jsonnow emits{ session: SessionMeta, events: SessionEvent[] }so the plan is one top-leveloutput.session.planread instead of a needle-in-haystack scan. Verified live against a real Claude session with an ExitPlanMode event:agents sessions 74464df7 --jsonprints the plan markdown at.session.plan. Source:apps/cli/src/lib/session/{state,discover,db,render,types}.ts,apps/cli/src/lib/session/active.ts,apps/cli/src/commands/sessions.ts. (issue #743 / RUSH-1505)agents computerWindows parity: scoped screenshots,get-text --max-chars,status/reload --host, and honest--background/--require-frontmosthandling (#548). Four params the CLI already sent were silently ignored by the Windows daemon. (1) Screenshots are now pid-scoped like macOS —screenshot --listenumerates the target pid's top-level windows (window_idis the Win32 HWND, the same idraise --window-idtakes), the default capture crops to the pid's largest on-screen window,--window-idshoots one window, and--displaycaptures the display the app is on; previously every capture was the whole virtual desktop. Verified live on win-mini: window capture 2097x984/28KB vs full display 2560x1440/380KB. (2)get-text --max-charsis honored (default stays 20k, ceiling 200k like macOS) —--max-chars 100now returns exactly 100 chars. (3)status --host <device>andreload --host <device>— status reports the recorded tunnel plus a live daemon probe (previously it misreported macOS-local install state for a remote Windows daemon); reload restarts the daemon's scheduled task (the way to pick up a freshly pushed exe) and confirms it answers through the tunnel. (4)--require-frontmostis enforced on Windows —SendInputlands in the focused window, sotype-text/keynow reportfrontmost(feeding the existing CLI warning) and the flag hard-fails withnot_frontmostwhen the target isn't foreground;--backgroundis rejected withaction_unsupportedinstead of silently no-oping (macOS postToPid delivery has no Win32 analogue — element-mode clicks via UIA patterns are the focus-safe path). Source:native/computer-win/{Screenshot,Automation}.cs,apps/cli/src/commands/computer.ts,apps/cli/docs/computer.md.The
neverprompt-policy is now live — the signed keychain helper was rebuilt, re-notarized, and re-pinned.agents secrets create --policy never --i-understandstores bundle values with no biometry ACL (kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, device-local, non-synchronizable) so headless automation can read them with zero Touch ID prompts. The Swiftset-no-aclpath shipped in #682 but the pinned helper binary predated it, so the policy failed against the shipped helper; the helper is rebuilt from current source, notarized (Apple submissiona2373c91-7fc2-4894-a801-b37c111597aa, status Accepted, stapled, GatekeeperNotarized Developer ID), andscripts/Agents CLI.app.sha256re-pinned to the new binary. (GitHub #421)Consolidated ~30 copy-pasted terminal-formatting helpers into one shared
apps/cli/src/lib/format.ts, fixing three user-visible drifts at the source.die,truncate,relTime,humanDuration,visibleWidth,padRight/padVisible,isJsonMode,readStdinSync, andtermLinkhad drifted into per-command copies with different behavior; every consumer now imports the single canonical version. Three normalizations are user-visible: (1) the truncation ellipsis is now the single glyph…everywhere —agents cloudtask lists,agents sessionsoverflow, and session prompt/tool summaries previously showed ASCII...or a bare.; (2)agents cloudrelative timestamps switch from the long "5 minutes ago" form to the compact "5m ago" form already used byagents teams; (3) theagents teamspicker duration cell gains a space ("2h5m" → "2h 5m") to match the sessions/browser pickers. It also fixes a latent bug:agents repo's divergence-table column alignment used avisibleWidthregex missing its\x1bescape, so ANSI-colored cells were mis-measured and columns could misalign — the canonicalvisibleWidthstrips the full SGR sequence.lib/events.ts'struncate(a distinct nullable, exported helper that truncates persisted event payloads) and the domain-specificstatusColorcopies (different status vocabularies with conflicting color assignments) are deliberately left in place. Internal refactor plus the noted string normalizations; no other behavior change. Source:apps/cli/src/lib/format.tsand consumers acrossapps/cli/src/commands/andapps/cli/src/lib/. (GitHub #753 / RUSH-1515)agents computer setup --hostnow works from a plainnpm i -ginstall — the Windows helper exe downloads on demand from GitHub releases. The ~157MBcomputer-helper-win.exenever shipped in the npm tarball, so setup died with "Windows helper exe not built. Run: bash scripts/build-win.sh" for anyone without a repo checkout. Onv*tags thecomputer-helper-win.ymlworkflow now builds the self-contained exe, smoke-tests it, and uploads it plus a.sha256as GitHub release assets;setup --hostresolves a local build first, then downloads the asset for the exact running CLI version, verifies its sha256 against the published checksum, and caches it under~/.agents/.cache/computer/win-helper/v<version>/. A tag with no asset is a hard error naming that tag — never a silent fallback to a different release. (GitHub #547) Source:apps/cli/src/lib/ssh-tunnel.ts(ensureWinHelperExe,downloadWinHelperExe),.github/workflows/computer-helper-win.yml(release-exe).registerMcpHTTP transport now routes through the capability table instead of an inline agent-id allowlist. MCP-over-HTTP support and MCP-header support were gated by hardcodedagentId !== 'claude' && agentId !== 'codex' && agentId !== 'gemini'/agentId !== 'claude'checks inapps/cli/src/lib/agents.ts, bypassing thecapabilities.ts/supports()table that is the single source of truth for "which agent supports what." A newly-added agent would silently get the wrong HTTP-MCP behavior with no compile-time signal. Two new capabilities land on theAgentConfigmatrix —mcpHttp(Claude/Codex/Gemini today) andmcpHeaders(Claude only) — and both inline allowlists are replaced withsupports(agentId, cap)calls. Pure refactor:capableAgents('mcpHttp')is['claude','codex','gemini']andcapableAgents('mcpHeaders')is['claude'], matching the pre-change behavior exactly. Source:apps/cli/src/lib/{agents,capabilities,types}.ts,apps/cli/src/lib/{agents,capabilities}.test.ts,apps/cli/src/lib/__tests__/capabilities.test.ts. (issue #742 / RUSH-1504)Hook
matches:predicates are now enforced at fire time — the documented gating was inert. A hook manifest entry could declarematches:predicates (prompt_contains/prompt_matches/tool_name/tool_args_match/cwd_includes/project_has/git_dirty) to gate when it fires, and the docs described the gate ("all predicates AND together; an empty block always fires"), butshouldFire()(the evaluator insrc/lib/hooks/match.ts) had zero runtime callers: the agent execs the registered command directly and nothing evaluatedmatches:, so any hook with amatches:block fired unconditionally. A hook that declaresmatches:(with or withoutcache:) is now registered as a generated wrapper shim that evaluates the predicates against the event JSON on stdin before running the script — a non-matching event exits 0 without running the hook body (logged ascache:"skip"), a matching event runs it. Matches-only hooks (nocache:) get a gate-only pass-through shim; cached hooks apply the gate before the cache. The shim gate is a faithful port ofshouldFire()(same AND semantics, same ReDoS guard) and is pinned to it by a 20-case conformance test so the two can't drift. Gating is fail-open: a garbled predicate runs the hook rather than silently disabling a safety hook (e.g.git-guard). No installed or bundled hook currently declaresmatches:, so this changes no existing hook's behavior — it activates a documented feature for authors who add one. Verified end-to-end by generating a shim from amatches: { tool_name: Bash, tool_args_match: "rm -rf" }manifest and firing it: aReadevent and aBash+lsevent were skipped, aBash+rm -rfevent ran the body. Source:apps/cli/src/lib/hooks/cache.ts(renderShimgate + pass-through tail),apps/cli/src/lib/hooks.ts(resolveHookCommand),apps/cli/docs/hooks.md. (RUSH-1506)Browser-over-SSH no longer hangs on an unreachable remote host — it fails fast (~10s). The raw-
sshspawns in the browser SSH driver (ensureRemoteBrowser,runSSHCommand) passed only-o BatchMode=yes, with noConnectTimeout, so a dropped SYN to a down host stalled on the OS default TCP timeout (~127s) instead of erroring. Both call sites now compose the shared hardened baselineSSH_OPTSfromssh-exec.ts(BatchMode+ConnectTimeout=10+ServerAlivekeepalive) rather than re-listing options — the same baselinesshExecand the-Ltunnel already use. The options now also precede the target (matchingsshExec); on macOS/BSDgetoptan option placed after the target is swallowed into the remote command instead of applied. Verified against TEST-NET203.0.113.1(guaranteed unreachable):-o BatchMode=yesalone was still hanging at a 30s cap (en route to ~127s), while theSSH_OPTSset failed in10.04swithconnect to host 203.0.113.1 port 22: Connection timed out. Source:apps/cli/src/lib/browser/drivers/ssh.ts. (RUSH-1508)Fix: OpenCode sessions now load on Windows. Reading OpenCode sessions shelled out to the
sqlite3CLI at three call sites —parseOpenCode(transcript parse) plus the session scan and active-account lookup in discovery — and that binary is absent on Windows, so OpenCode sessions silently never appeared inagents sessionsthere. All three now read through the same runtime-aware node/bunDatabasewrapper the Antigravity parser already uses (bun:sqlite/node:sqlite, no native addon, no CLI), and the OpenCode transcript query binds the session id as a parameter instead of interpolating it. No behavior change on macOS/Linux. Source:apps/cli/src/lib/session/parse.ts(parseOpenCode),apps/cli/src/lib/session/discover.ts(scanOpenCodeIncremental,getOpenCodeAccount). (RUSH-1513)agents sessions --active --jsonnow carries the agent's actual decision, not a truncated status line. A session waiting on you used to collapse everything to a one-linepreview— anAskUserQuestionbecame the generic"Asked you a question"(throwing away the options that are already in the tool input), and a trailing thinking block masked the real turn as"thinking…". The state engine now emits a structuredquestionobject ({ text, reason, options: [{ label, description, key }] }) for every waiting path —AskUserQuestion(with each option's 1-based select key), plan review, permission (Approve=1/ Deny=esc), and a trailing prose question — plus a short assistanttailfor context, andpreviewno longer degrades to"thinking…"when a real turn exists. Every consumer (the Factory NEEDS-YOU panel, teams, cloud) now gets the real "what does it want from me" instead of re-deriving it from prose. Verified live: the blocked session in the screenshot now reportsawaitingReason: questionwith the real question text. Source:apps/cli/src/lib/session/state.ts(structuredQuestionFromAsk,inferActivity),apps/cli/src/lib/session/active.ts. (RUSH-453)
1.20.50
- Distributed agent teams: teammates can now run on different machines across your fleet, not just the box running
teams start. A single team can place the backend teammate on a Linux box and the UI teammate on a Mac while one orchestrator still drives the DAG, polls status, and cleans up. One vocabulary, all optional (omit it and teams stay 100% local as before):teams create --devices a,b,c(alias--hosts) declares a pool the team may auto-schedule onto,--repo <url|path>(defaults to the local checkout'sorigin) says how each device gets the code, andteams add --device X(alias--host) pins one teammate to a host — which needs no pool, so "send just one teammate elsewhere" is zero-setup. Placement resolves top-down at launch: explicit--devicepin → single-device pool (whole team there) → multi-device pool (least-loaded auto-schedule) → local. Remote teammates dispatch over SSH via the existingagents devices/host machinery (a third teammate backend beside local and cloud), are monitored by offset-tailing the remote log +.exitsentinel, and get the repo auto-provisioned per device (reuse an existing checkout, else clone into~/.agents/repos/<team>) with an optional per-teammate git worktree on the host.teams status/teams logsshow each teammate's host and stream its output back with the local mirror capped (~512KB rolling tail) so a 10+-teammate fleet can't blow up the orchestrator. POSIX hosts only in v1 (Windows rejected with a clear message). Source:apps/cli/src/lib/teams/{scheduler,remoteWorktree,agents,api,supervisor,registry}.ts,apps/cli/src/lib/hosts/{progress,passthrough}.ts,apps/cli/src/commands/teams.ts,apps/cli/docs/teams.md. - NEW:
agents doctor --devicesshows a cross-device agent-readiness matrix.agents doctorcould already run on one remote machine via--host, but checking the whole fleet meant running the command once per box.--devicesfans outagents teams doctor --jsonto every registered device (plus the local machine), renders a device × agent matrix, and emits a stable JSON contract with--json.--device <name>or--host <name>scopes the same matrix to a single machine. The remote probe now bootstrapsPATHwith the canonical shim directories before running, so login shells that haven't sourced interactive rc files no longer report false "not installed" negatives. Source:apps/cli/src/commands/doctor.ts,apps/cli/src/lib/teams/agents.ts,apps/cli/src/lib/hosts/{passthrough,remote-cmd}.ts. agents run codex/agents teamsnow honor your configured Codex model instead of silently defaulting togpt-5.3-codex. Codex runs under a per-versionCODEX_HOME, and yourmodelpreference (~/.codex/config.toml) lives only in the version-home that was active when you set it. A dispatch pinned to a different version read a home with no top-levelmodel, so Codex fell back to its built-in default — which a ChatGPT-tier account isn't entitled to use, so the run died with400: The 'gpt-5.3-codex' model is not supported when using Codex with a ChatGPT accountbefore doing any work, even thoughag viewreported Codex "signed in". When no explicit--modelis passed, the model is now defaulted (for Codex) to the top-levelmodelin your active~/.codex/config.tomland forwarded via--model; it's read-only (no file writes), so fanning out many parallel runs to one version-home can't race. Verified live on a box where Codex was 100% unusable: the request model changedgpt-5.3-codex→gpt-5.5and codex@0.142.0 returned successfully. Source:apps/cli/src/lib/exec.ts,apps/cli/src/lib/shims.ts(readCodexConfiguredModel).- Fix:
agents add claude@<version>now produces a runnable install — it no longer ships a half-built binary that dies with "claude native binary not installed."installVersionrunsnpm install --ignore-scripts(the right posture for the dependency tree — never run arbitrary transitive postinstalls), but that also skipped the agent package's OWN postinstall, which for@anthropic-ai/claude-codeis a required step: the package ships a ~500-byte stub atbin/claude.exeplus per-arch native binaries as optional deps, and itspostinstall(node install.cjs) is what copies the correct ~231 MB native binary over the stub. Skipped, every launch died withError: claude native binary not installed. The existing launch-health self-heal (#764, and its Windows/daemon extension) couldn't save it on two counts: the stub reports its breakage politely rather than with a rawENOENT, so the probe's missing-binary signature didn't match and the gutted install read as healthy; and the repair path (ensureAgentRunnable→ clean reinstall) re-ran the same--ignore-scriptsinstall, so it never copied the binary either.installVersionnow runs the first-party package's declaredpostinstallafter the npm install (scoped to that one package — never the dependency tree, never claude-code'sexit 1prepareguard), best-effort, before the integrity gate. BecauseinstallVersionis the single choke point foragents add, config refresh, run-time heal, and the daemon's proactive heal, this also revives the repair path for the whole class.isMissingBinarySignaturewas additionally widened to recognize the stub's polite phrases (native binary not installed,postinstall did not run,optional dependency was not downloaded) so the self-heal catches this failure mode if a postinstall ever silently no-ops. Verified end-to-end on linux-arm64:installVersion('claude','2.1.186')into a clean HOME runs the postinstall automatically, lands the 231,782,112-byte binary (not the stub), andclaude.exe --versionreturns2.1.186 (Claude Code)— with no manualinstall.cjsstep. Source:apps/cli/src/lib/versions.ts(installVersion,isMissingBinarySignature).
1.20.49
agents run --mode planno longer hard-fails on agents without a read-only mode (antigravity, cursor, kiro, …). Those agents have no plan flag, so an explicit or default--mode planused to abort withdoes not support 'plan' mode— breaking multi-agent scripts that pass a uniform plan flag, and diverging fromagents teams add(default modeedit).resolveModenow degrades unsupportedplanto the agent's safest native mode (capabilities.modes[0], typicallyedit), matching the existingauto→editdegrade. The CLI prints a yellow warning when the user explicitly asked for plan (gray for the implicit default) so the elevation is never silent.skipstill hard-fails when unsupported. Source:apps/cli/src/lib/exec.ts,apps/cli/src/commands/exec.ts.agents cloud cancelnow actually cancels paused runs.RushProvider.cancel()issuedDELETE /api/v1/cloud-runs/{id}, which the backend doesn't implement — it 404s — soagents cloud cancel(and the Factory Floor's cancel affordance) silently failed on any run that wasn't actively running:queued,needs_review, andinput_requiredruns stayed stuck (e.g. a 14-day-old input-required run lingering in the Floor's "NEEDS YOU" bucket forever). Switched to the cancel action endpointPOST /api/v1/cloud-runs/{id}/cancel, which the backend implements and which cancels paused runs too. Verified live againstapi.prix.dev(the POST returned{"ok":true,"status":"cancelled"}and the stuck run transitionedneeds_review→cancelled). Source:apps/cli/src/lib/cloud/rush.ts.
1.20.48
Menu-bar helper: a RECENT TICKETS section shows the issues you filed via the quick-issue bar, each clickable to open in Linear. The completion notification is transient, so the tickets the
Cmd-Shift-Obar creates now also persist to a small local ledger (~/.agents/.history/menubar/recent-tickets.json, newest-first, deduped by id, capped at 10) that the menu-bar dropdown surfaces below RECENT sessions — click a row to open the ticket. The dispatch records the id + note + Linear URL on a successful create; the section renders nothing when the ledger is empty. Source:apps/cli/menubar/Sources/MenubarHelper/{RecentTickets,StatusItemController,AgentsCLI,IssueSelfTest}.swift.Menu-bar helper: the quick-issue completion notification now deep-links to the created ticket, and the helper self-heals onto the install you actually run. Two fixes from dogfooding the
Cmd-Shift-Obar. (1) Clickable notification — the "Created RUSH-####" banner carried no click target, so there was no way to open the ticket. The ticket agent now also prints the issue'sURL:line, the helper parses it, and clicking the notification (or its Open button) opens the ticket in Linear (via anNSUserNotificationCenterDelegate; the banner is also force-presented so it can't be silently swallowed when the accessory app is frontmost). (2) Dual-install self-heal — the helper bakes the node interpreter + CLI entry into its launchd plist so a GUI process can findagentswithout a login PATH, but the staleness check only re-baked on a version change. With two installs present (e.g. an nvm copy and a bun copy), the plist kept pointing at whichever copy first wrote it, so the menu data and the quick-issue dispatch ran on a stale install even afteragents upgrade. The startup self-heal now also re-points when the plist's bakedAGENTS_ENTRY/AGENTS_NODEno longer match the install currently runningagents(a null active entry — a dev/tsx run — never churns the plist). Source:apps/cli/menubar/Sources/MenubarHelper/{PromptPanel,AgentsCLI,IssueSelfTest}.swift,apps/cli/src/lib/menubar/install-menubar.ts.The npm release can now be driven from a Linux box by offloading the Mac-only helper signing to a remote sign host. The tarball bundles two signed macOS
.apphelpers a Linux runner can't build —bin/Agents CLI.app(the keychain helper:swiftcuniversal → codesign with entitlements + embedded provisioning profile →notarytool→ staple) andbin/MenubarHelper.app(the menu-bar status item:swift build→ codesign, no notarization) — which is the only reason publishing was macOS-pinned. Newscripts/remote-sign-mac.sh(invoked automatically byrelease.shwhen it runs on a non-macOS host and the signed apps are absent, or on any host withFORCE_REMOTE_SIGN=1) rsyncs the build inputs to${SIGN_HOST:-mac-mini}, runs both Mac build scripts there under the appliance's headless signing creds (unlocksrush-signing.keychain-db, injects Apple notary creds via theapple.comsecrets bundle), then pulls the signedbin/*.appback and re-verifies the keychain sha locally. Thebuildscript now copies the helpers intodist/on a presence gate ([ -d 'bin/…' ]) instead of[ "$(uname)" = 'Darwin' ], so a Linux box that pulled the pre-signed bundles packages them, andprepack's sha gate usesshasumorsha256sum(whichever is present) so it works on Linux too. Override the sign host withSIGN_HOSTand its checkout withSIGN_HOST_REPO. Source:apps/cli/scripts/remote-sign-mac.sh,apps/cli/scripts/release.sh,apps/cli/scripts/verify-keychain-helper.sh,apps/cli/package.json.The shim self-heal now repairs shims that point at a removed install and prunes orphaned command shims. A dispatch shim bakes its
AGENTS_BIN(the agents-cli entrypoint it execs) at generation time, so when that install moves or is deleted — a dev build under~/.local/agents-cli-dev, an old npm-global under/opt/homebrew, a rotated version dir — the shim keeps pointing at the dead path. Agent shims survive it via their runtime self-recovery block, but the previous self-heal only compared the schema marker, so a schema-current shim aimed at a removed install read as healthy and was never repaired. Two additions to theshimsself-heal check (daemon + interactive startup): (1) drift repair — an agent shim whose bakedAGENTS_BINnames a different, now-missing install is force-regenerated to the current install (shimPointsAtLiveInstall); a shim pointing at another install that still exists is left alone, so two live installs sharing the shims dir can't ping-pong. (2) orphan prune — legacy standalone command shims (browser/secrets/sessions/teams/pty) that a removed install left in the shims dir, which the current source never regenerates and which either die withexit 127or shadow the real package bin on PATH, are removed when their baked install is gone (pruneOrphanedCommandShim); useragents aliasshims and any shim whose install still exists are spared. Verified end-to-end against a real machine carrying a deleted dev build + a removed Homebrew install: the agent shims repoint to the live install and five dead command shims are pruned. Source:apps/cli/src/lib/shims.ts(shimPointsAtLiveInstall,pruneOrphanedCommandShim,listShimFileNames),apps/cli/src/lib/self-heal/checks/shims.ts.
1.20.47
- Quick-issue bar (
Cmd-Shift-O):Cmd-Vnow pastes into the note field, and double-clicking a screenshot thumbnail opens it in Preview. Two fixes from dogfooding the new bar. (1) The panel is a borderless.accessorywindow with no main menu, so the standard clipboard key-equivalents (Cmd-V/C/X/A) were never dispatched to the field editor — paste silently did nothing.PromptPanel.performKeyEquivalentnow routes them through the responder chain so the text field handles them. (2) Thumbnails are small, so there was no way to confirm which screenshot you were attaching: single click still toggles selection, double click opens the full image in the default viewer (Preview). The single-click toggle is deferred by the double-click interval so a double-click previews without also flipping the selection, and the bar suppresses its own click-outside dismissal while Preview takes focus (so summoning Preview never closes the bar or drops your typed note; it re-arms when the bar regains focus). Source:apps/cli/menubar/Sources/MenubarHelper/PromptPanel.swift. - Fix: the headless file-store fallback no longer silently shadows the OS keyring; NEW
agents secrets import-keyringmigrates stranded secrets into it. On headless Linux/Windows the encrypted-file store is sticky — once any item is on disk,preflight()routed every op to the file store and never consulted GNOME Keyring / Windows Credential Manager again, so a secret written earlier into the native store (e.g. while a desktop keyring was unlocked) read back empty with no hint. This stranded real Linear CLI credentials in a locked keyring while other bundles lived in the file store, silently breaking the SessionStart hook. Two fixes: (1)get/hasnow read through to the native store on a file-store miss (the fast path and the non-fallback keychain-first path are untouched — the file store is still checked first), emitting a one-time stderr notice pointing atimport-keyring; once a locked/1312error is seen the store is marked unreachable so it stops re-probing a known-dead store. (2) NEWagents secrets import-keyring— the Linux/Windows analogue of the macOSmigrate-acl/orphan sweep — enumeratesagents-cliitems in the native store and copies them into the encrypted file store (the durable, passwordless headless backend). Dry-run by default;--commitwrites; existing file-store items are never overwritten; Windows enumeration is floored to theagents-cli.namespace since Credential Manager targets have no service scoping. macOS is unaffected (it has no file fallback and keepsmigrate-acl). Source:apps/cli/src/lib/secrets/{fallback,linux,windows,index}.ts,apps/cli/src/commands/{secrets-import,secrets}.ts,apps/cli/docs/secrets.md. - Launch-health self-heal now covers Windows, and the daemon repairs a gutted install proactively — before your next
agents run. #764 gaveagents runan install/run-time self-heal (probe<binary> --version; clean-reinstall in place, else fall back to another installed version that launches), but it skipped the probe on Windows —verifyInstalledBinaryLaunchesreturned healthy onwin32unconditionally, because probing the extensionless.bin/<cli>wrapper would ENOENT even on a healthy install. So the exact Windows failure the self-heal was built for went unhealed: a vendor auto-update renames the nativeclaude.exetoclaude.exe.old.<epochMs>and never lands the replacement, leaving the shim chain intact but pointing at a missing file, and every launch dies with'…claude.exe' is not recognized. The probe now runs on Windows against the real launch target — the npm.cmdwrapperagents runactually execs (getBinaryPath + '.cmd', resolved viacmd.exe), which chains to the native.exe— so a gutted install trips the existing missing-binary signature (is not recognized) and is repaired by the sameensureAgentRunnablemachinery; a missing.cmd(a non-npm/global agent likedroid.exe) is still treated as healthy so a good install is never destroyed. Separately, the daemon now runs a proactive launch-health pass (healBrokenDefaultLaunches) ~90s after startup and every ~6h: it probes each agent's default version and, if it won't launch, repairs it in the background — so a gutted install is fixed before the nextagents runhits the ENOENT, not at spawn time (the run-timeensureAgentRunnableonly fires once a run is already starting). Verified end-to-end on a real Windows host: renamingclaude.exeto.oldmakes the.cmdprobe emitis not recognized; restoring it returns2.1.191 (Claude Code). Source:apps/cli/src/lib/versions.ts(verifyInstalledBinaryLaunches,healBrokenDefaultLaunches),apps/cli/src/lib/daemon.ts.
1.20.45
1.20.46
- NEW:
Cmd-Shift-Oopens a Spotlight-style quick-issue bar in the menu-bar helper — type a sentence, attach recent screenshots, and an agent files the Linear ticket for you. The menu-bar helper already turned a screenshot into a<host>:<path>token withCmd-Shift-V(clip capture), but there was no path from "I see a bug" to "a triaged ticket exists." The new chord summons a borderless panel (a thin capture surface, not another form): you type a one-line note, optionally toggle one or more recent screenshots (from the system screencapture folder, CleanShot's export path, or the clip history) as a thumbnail strip (the newest is pre-selected when it's fresh), and hit Return. It then dispatches a headless agent (agents run claude --mode auto, isolated behind oneAgentsCLI.dispatchTicketAgentcall so a cloud pod is a later swap) that reads the screenshots, runsagents sessionsto identify which repo/project this concerns, does a brief investigation for real context, and files the ticket via~/.agents/skills/linear/scripts/linear createwith an honest priority + arepo:<name>label — no preview step, the panel closes immediately and a notification reports the createdRUSH-####. Focus is handled for a no-Dock.accessoryapp (NSApp.activate→makeKeyAndOrderFront→makeFirstResponder, with a borderlessNSPaneloverridingcanBecomeKey; click-outside dismissal is armed only after the summon settles so the activation race can't self-dismiss the panel). TheCmd-Shift-Vclip hotkey is unchanged — the Carbon hotkey manager now demultiplexes both chords byEventHotKeyID.idthrough one installed handler. Self-test:MENUBAR_ISSUE_TEST=1 MenubarHelperexercises screenshot selection, ticket-id parsing, and the meta-prompt contract;MENUBAR_PROMPT_PREVIEW=1renders the panel without the global hotkey for QA. Source:apps/cli/menubar/Sources/MenubarHelper/{PromptPanel,Hotkey,AgentsCLI,main,IssueSelfTest,Clip}.swift. - NEW: a unified self-heal subsystem — the shim/PATH "repair" notice no longer nags on every terminal, and the daemon now heals shim drift in the background. agents-cli had accumulated ~37 separate repair routines scattered across the daemon, every CLI startup, and a handful of commands, each hand-rolling its own detect+fix on its own trigger. The most visible symptom: the interactive shim bootstrap (
maybeBootstrapShimIntegration) regenerated shims, adopted shadowing launchers, and offered to add the shims dir to PATH in the foreground on every invocation, suppressed only by aprocess.ppid-keyed temp sentinel — so a new terminal re-ran the whole detect-and-nag, and the underlying condition was never permanently fixed. This lands a singleHealCheckregistry (lib/self-heal/) with one runner (runSelfHeal) driven by two front doors — the daemon (on its existing ~30s-after-start + ~6hsafe-mode cycle) and the interactive startup — sharing the same checks:shims(regenerate stale shims/aliases),shadowing(adopt symlink launchers; report real-binary shadows),path(add the shims dir to PATH once), andresources(the existingheal()engine, wrapped unchanged). The daemon's heal cycle now runs all four insafemode (low-risk fixes silently; risky ones reported), replacing the resource-onlyheal()call — and drops the desktop toast for background heals (the log is the record). The interactive startup now heals silently and prints at most a persistent, once-per-condition notice (lib/shim-heal.ts, keyed to a signature of the actionable state under~/.agents/.cache/state/shim-notice.json) for what a machine genuinely can't fix for you — a real native binary shadowing the shim — instead of re-nagging every shell. What changes is where the repairs run (background/silent) and how often you hear about them (once, not every terminal). Source:apps/cli/src/lib/self-heal/(new),apps/cli/src/lib/shim-heal.ts(new),apps/cli/src/lib/daemon.ts,apps/cli/src/index.ts,apps/cli/src/lib/shims.ts(isShimCurrentexported).
1.20.45
- NEW:
agents run <agent> --host <name>without a prompt forwards your TTY over SSH and runs the agent interactively on the remote host. Previously--hostruns required a prompt and were always headless (agents run <agent> "<task>" --host <name>). Now, omitting the prompt takes the interactive path: when local stdin is a TTY, the local CLI SSHes with-tt, runsagents run <agent>on the host, and lets the remote machine'sagentsstart its normal tmux wrapper. The tmux session lives on the remote box, so detaching (Ctrl-b d) ends the SSH connection but keeps the agent running; you can reattach from the host or resume by session id. Session ids for Claude are still minted up front soagents sessionscan surface and resolve the remote run.--no-followis rejected for interactive host runs (it is meaningless for an attached TTY), and--mode,--model,--name, passthrough args after--, and--raw/--no-tmuxare forwarded to the remote invocation. Source:apps/cli/src/commands/exec.ts,apps/cli/src/lib/hosts/dispatch.ts,apps/cli/src/lib/hosts/session-index.ts,apps/cli/docs/hosts.md. agents secrets export --hostnow works against Windows targets, and a newagents secrets unlock --hostunlocks a bundle on a remote machine. The export push was POSIX-only (bash -lc,--from /dev/stdin,create … || true,IFS= read), so a Windows remote died with'true' is not recognized … cannot find the path specified. Two changes fix it:agents secrets importnow accepts--from -(read the.envfrom stdin, replacing the POSIX-only/dev/stdin), and the push is platform-aware —bash -lcon POSIX,powershell -EncodedCommandon Windows, with the target's OS taken from the device registry. Because the npmagents.ps1shim does not forward ssh-piped stdin to the underlying node process (a raw--from -read hangs), the Windows keychain push bridges the piped.envthrough PowerShell into a temp file and imports--from <file>(deleted afterwards). File-backend export to a Windows target is refused cleanly rather than emitting broken PowerShell. Verified end-to-end:agents secrets export linear.app --host win-miniimported all 13 keys. Separately,agents secrets unlock --host <machine> <bundle>runs the unlock ON the remote overssh -tt, so a file-backed bundle's passphrase prompt surfaces on your terminal — the "unlock the Mac from the road with its password" path; keychain/biometry bundles are GUI-only (a local Touch-ID/passcode sheet can't cross SSH) and can't be remote-unlocked.unlock's--hostis single-valued so it never swallows the positional bundle name. Source:apps/cli/src/commands/secrets.ts,apps/cli/src/lib/hosts/remote-cmd.ts.- A session now has ONE name, not two.
--nameseeds the session label instead of a parallel column. Shippingagents run --name(1.20.43) as a separate immutablenamecolumn created two look-alike fields — an unshown, frozennameand the shown, searchablelabel— that both resolvedagents sessions <ref>and forced tie-break bookkeeping nobody could keep straight. They unify into one field.--nameis now the universal way to seed thelabelat launch — the same field an agent-generated title (Claude's/rename) later refines andagents sessionsdisplays and searches — and it works consistently across interactive, headless,--host, and teams teammate runs (a teammate's friendly name now seeds its session label; before, teammate sessions had no name at all). Priority is a plain fallback chain resolved at scan time, no stored winner: an agent-generated title wins, else the--nameseed, else the listing falls back totopic. So a Claude run's--nameshows until Claude titles it (your seed, then refined); a non-Claude run keeps its--nameas the label (it has no auto-title). The seeded name is now fuzzy-searchable in FTS (the oldnamecolumn was not).agents hosts logs <name>is unchanged — it resolves against the host-task sidecar, not the session column. Schema v10 folds any existingnameintolabel(where the label was empty), mirrors it into the FTS row, then drops thenamecolumn; the run-name sidecars re-seed every scan (seedLabelsFromNames), so no rescan is needed. Reworks the 1.20.43--namedesign (partly reverts its separate-column approach). Source:apps/cli/src/lib/session/{db,discover,run-names,types}.ts,apps/cli/src/lib/hosts/session-index.ts,apps/cli/src/lib/teams/agents.ts,apps/cli/src/commands/exec.ts,apps/cli/docs/{05-sessions,hosts}.md. - NEW:
agents teams add/startwarns when a version-pinned teammate is on a throttled or signed-out account. The 1.20.43balanced-default fix keeps bare teammates off rate-limited accounts (they route through bareagents run, which rotates), but a version-pinned (claude@2.1.112) or profile teammate spawnsagents run <agent>@<version>/agents run <profile>, and a pin/profile deliberately bypasses rotation — so it would launch straight onto a maxed account and 429 on the first request, with no mid-run failover either (that only arms when a non-pinned strategy actually rotated).agents teams add(at add time) andagents teams start(per staged teammate, deduped byagent@version) now pre-check a version-pinned teammate's account and print an advisory when it's rate-limited, out of credits, or not signed in — reusing the router's exact eligibility gate (checkRunAccountReadiness→hasUsageAvailable, the same session-inclusive signal theagents viewbadge uses), so the warning can never disagree with what the spawn would actually do. It warns, never blocks (mirroring the existing "may not be signed in" advisory);--forcesilences it. Scoped to version-pinned teammates on purpose: bare teammates are already handled by rotation, and a profile injects its own auth (a different account than the version home carries) that isn't locally checkable — so no unreliable profile warning is emitted. Source:apps/cli/src/lib/rotate.ts(readinessFromCandidate,checkRunAccountReadiness,rotate.test.ts),apps/cli/src/commands/teams.ts.
1.20.44
- Every
logscommand is concise by default; the token-heavy raw dump is now opt-in behind--full. Agents that spin up agents on other machines or add teammates were pulling whole transcripts just to glance at status —agents logs <session>printed the full markdown transcript, andagents hosts logs/agents teams logs/agents routines logseachcat'd their entire captured stdout, because each subsystem had hand-rolled its own "cat the log" verb over its own storage. All four now default to a bounded, concise view, with-m/--fullfor the raw log:agents logs <session>renders the same summary digest asagents sessions <id>(a real session shrank 92% — 29.9 KB → 2.6 KB);agents routines logs <name>shows a status header + the extracted report (a real run shrank 99.5% — 386 KB → 1.8 KB), falling back to a bounded stdout tail when no report was extracted;agents teams logs <teammate>renders the teammate's session summary (its agentId is the session id), with-n <lines>/--fullfor raw stdout;agents hosts logs <id>shows a bounded tail of the captured stdout (tailLines, with a "… N earlier lines hidden — pass --full" note) instead of the whole log.renderSessionLognow takes a mode and defaults to'summary';agents sessions <id>was already summary-by-default and is unchanged. Regression-tested:tailLinestruncation/elision math (hosts/logs.test.ts) andformatRunDurationhuman-time formatting (routines-logs.test.ts). Source:apps/cli/src/commands/{logs,sessions,hosts,teams,routines}.ts,apps/cli/src/lib/hosts/logs.ts. Scoped follow-up (not in this PR): host-task and sandboxed-routine runs write their real transcript on the remote / in an overlay HOME, sologscan't yet resolve them to the fullrenderSummary— making those runs discoverable is a separate change; until then the bounded tail / extracted report is the safe concise default. - The daemon now self-heals the
pane-diedhook on already-runningagents runsessions. The v1.20.42 fix that stops exiting a split from kicking you out of tmux is installed once, at session creation — so sessions already alive under the long-lived shared tmux server keep the old, unconditionaldetach-clienthook until they exit or the server is recycled. On a machine that's never "between sessions," that meant hand-repairing live sessions. The daemon now runsreconcileSessionHooks()~20s after startup and every ~5 min: it walks the managedag-sessions on the shared socket and retrofits the#{hook_pane}-guarded hook onto any whose hook predates the current schema. It is strictly non-destructive —set-hookonly, never akill-paneordetach-client— so it is safe to run against sessions you're attached to; a per-session@ag_hook_schemamarker makes steady-state a no-op. The hook string is now built in one place (agentPaneDiedHook) shared by the spawn-wrap and the reconcile so they can't drift. Source:apps/cli/src/lib/tmux/session.ts,apps/cli/src/lib/daemon.ts,apps/cli/src/lib/exec.ts. - NEW:
agents runself-heals a gutted install instead of crashing withENOENT. The recurring failure: an npm agent whose native binary ships as an optional per-arch dependency (codex →@openai/codex-<platform>) can have that tarball extract partially — the platform package'spackage.jsonlands, itsvendor/<triple>/…/codexbinary does not (an interrupted or concurrently-racedagents addinto the same version dir). The CLI's wrapperrequire.resolves the platform package, finds thepackage.json, and sails straight past its own "missing optional dependency" guard into aspawn(binaryPath)that dies with a rawENOENT.agents runnow probes the version it's about to launch and, if the binary can't run, repairs it in place (a clean reinstall — the partialnode_modulesis wiped first, because npm treats the present-but-gutted platform package as already installed and would otherwise skip re-fetching it), then falls back to another installed version that launches (re-pinning it as the default so the shim path heals too), then to installinglatest— only erroring if nothing can be made runnable.installVersiongained a{ clean }option for the wipe-then-reinstall. Source:apps/cli/src/lib/versions.ts(ensureAgentRunnable),apps/cli/src/commands/exec.ts. - Fix: a broken agent install no longer launches into a silent
[detached]— the real crash is surfaced. When an interactiveag run <agent>wrapped the agent in tmux and the agent died the instant it spawned (e.g. a gutted install crashing withspawn … ENOENT, a bad flag, a startup crash), thepane-diedhook detached the client before you could read anything — you got a bare[detached (from session …)]with zero indication of why.runInTmuxnow recaps the dead pane's last output (read from scrollback viacapture-pane -S -200, since the pane's visible screen is just the "Pane is dead" banner) plus the exit code to stderr, and points you at--no-tmux. Fast failures (dead before attach) always recap; a post-attach nonzero exit recaps too (a clean exit or a manual detach stays quiet). Source:apps/cli/src/lib/exec.ts,apps/cli/src/lib/tmux/session.test.ts. - NEW:
--no-tmux/--disable-tmuxonagents run. The interactive tmux wrapper (which gives%paneaddressing + re-attach) already had an opt-out, but it was hidden behind the opaquely-named--raw.--no-tmux(and its alias--disable-tmux) spawn the agent directly with full stdio inherited — the fastest way to see an agent's real startup output when a launch is failing. Same effect as--rawandAGENTS_NO_TMUX=1. Source:apps/cli/src/commands/exec.ts. - Fix:
agents add <agent>@<version>no longer records a gutted install as healthy (root cause of the ENOENT crash + a broken default pin). npm packages that ship their native binary via an optional per-arch dependency (e.g. codex →@openai/codex-<platform>) can land the JS wrapper atnode_modules/.bin/<cli>while the real platform binary is missing (interrupted install, omitted optional dep,--ignore-scripts).getBinaryPath()only checked the wrapper, so the broken version read as installed, got pinned as the default, and got picked to run — then died with ENOENT.installVersionnow probes<binary> --version(under the version's isolated HOME) after install and fails the install if the binary can't launch, so a broken version is never silently pinned. The check is deliberately narrow — only the missing-binary signature (ENOENT/"no such file"/"command not found") fails it; a plain nonzero exit or a timeout is treated as healthy, so a well-behaved agent that dislikes--versionis never false-failed. Source:apps/cli/src/lib/versions.ts,apps/cli/src/lib/versions-integrity.test.ts. - Security fix: the routines daemon log no longer leaks GitHub / AWS / npm tokens.
daemon.tscarried its own privateredactSecrets(used by everylog()write tologs.jsonl) that predated and diverged from the canonicalredact.ts— it caughtsk-,eyJ…,Bearer …, and a narrowNAME=valuelist, but notghp_(GitHub PAT),AKIA…(AWS access key), ornpm_(npm token), so any of those appearing in a daemon message (a git push URL, a bundle-env dump, an error string) was written to the log in the clear. The private copy is deleted;log()now routes through the canonicalredactSecretsinredact.ts, which covers all of those classes with a stronger quote-awareNAME=valuepattern. The one pattern the daemon copy had and the canonical lacked —Bearer <token>— is added toredact.ts, so the shared redactor (also used by session-transcript export insession/render.ts) is now a strict superset. Newredact.test.tspins every token class as a regression guard. Source:apps/cli/src/lib/daemon.ts,apps/cli/src/lib/redact.ts,apps/cli/src/lib/redact.test.ts. - Fix:
agents teams doctortells the truth, a version fallback never spawns an unspawnable literal, and shims survive a vanished dispatcher (completing this release's self-heal series). Three gaps remained after theagents runself-heal above. (1)agents teams doctorlied — it reportedinstalled: truewhenever a shim file existed, never checking the real binary, so a stub or gutted-native install (the exact codex/kimi failure) showed "ready" and thenENOENT'd at spawn.checkCliAvailablenow verifies the resolved default version is actually installed, and doctor additionally launch-probes each installed agent (verifyInstalledBinaryLaunches) and flips a gutted-native one to not-installed with a repair hint. (2) A version fallback spawned an unspawnable literal — when a specific version was requested (agents run kimi@0.19.2, the path every version-pinned teammate takes) and no versioned shim existed on disk, the launch left the bare<agent>@<version>name asargv[0], which is not on PATH, so it died withspawn kimi@0.19.2 ENOENT; it now resolves the version's real binary (getBinaryPath) instead, falling back to the literal only when no binary exists at all. (3) A shim couldn't survive its dispatcher vanishing — when the bakedAGENTS_BIN(often a dev build under~/.local/agents-cli-dev) was removed, moved, or went stale, the shim exited 127 and bricked every managed launch; it now self-recovers to whateveragentsresolves to on PATH before erroring (SHIM_SCHEMA_VERSION→ 25). Also drops a stale, npm-unrecoverablecodex 0.116.0pin from the repo's ownagents.yamlso codex resolves to the machine default instead of self-healing on every run. Source:apps/cli/src/lib/{exec,shims}.ts,apps/cli/src/lib/teams/agents.ts,apps/cli/src/commands/teams.ts,agents.yaml.
1.20.43
- NEW:
agents run --name <slug>— a durable, human/agent-friendly handle for any run. An agent that dispatches another agent had no cheap status handle: the host-task id was never even printed (the--no-followtip showed a literal<id>placeholder), and only Claude's session id is known up front (pre-minted--session-id) — every other agent's id is discovered later by scanning transcripts, so callers fell back toagents logs, which dumps the raw, token-heavy transcript.--nameis chosen at launch, agent-agnostic, and stored on the structures that already back these views: a first-classnamecolumn onsessions.db(schema v9, additive, no rescan) parallel tolabel—agents sessions <ref>resolves against both name and label; the HostTask sidecar (forwarded to the remote run, soagents hosts psgains a NAME column andagents hosts logs <name>resolves by name); and a run-name sidecar (~/.agents/.cache/run-names/) that joins a local run's name onto the index by id every scan viasyncNames— the same idempotent pattern as/renamelabel sync. Thenamecolumn is deliberately left out of the upsertON CONFLICT … SETclause, so a discovery rescan can never null an existing name (regression-tested indb.names.test.ts). Omitting--nameis a strict no-op:namestays unset and every id-based path is unchanged. The--no-followdispatch tip now prints the real handle and steers to the compactagents sessionsdigest over the raw log. Source:apps/cli/src/commands/exec.ts,apps/cli/src/lib/session/{db,run-names,discover}.ts,apps/cli/src/lib/hosts/{dispatch,tasks}.ts. - New terminals (and teammates) no longer launch into a rate-limited account;
balancedis now the default run strategy. Two coupled fixes. (1) A bareagents run <agent>— every new agent terminal the extension spawns, and every non-version-pinnedagents teams add/startteammate, since both route through bareagents run— used to default to theavailablestrategy, which prefers the pinned default version when it looks healthy. But "healthy" was judged by the router'sgetRoutingUsedPercent, which excluded the 5-hour session window and looked at weekly usage only. So a session-maxed account with weekly headroom (e.g. session 100% / week 60%) was deemed eligible and kept getting launched — whileagents viewshowed it "rate-limited" (its badge,deriveUsageStatusFromSnapshot, counts the session window). The router and the badge disagreed. NowhasUsageAvailableshares the badge's exact signal: an account maxed on any blocking window (session or weekly) is ineligible and skipped by bothavailableandbalanced— you never spin up an agent on an account that can't serve the next request. Capacity weighting still ranks eligible accounts by weekly headroom, so a brief session spike doesn't distort long-run routing. (2) The default strategy is nowbalanced(wasavailable): a bare run spreads load across all healthy accounts by remaining headroom instead of sticking to the pinned default. Override per-workspace withrun.<agent>.strategyinagents.yaml, or per-invocation with--strategy/-b. Source:apps/cli/src/lib/rotate.ts,apps/cli/src/lib/usage.ts,apps/cli/src/commands/exec.ts. - [browser] Logins survive browser restarts: sandboxed profiles keep memory-only session cookies, without restoring tabs. Sites that issue login cookies with
expires=-1(idealista, many banking/classifieds sites) logged the profile out on every browser restart, because Chromium purges memory-only session cookies at startup unless the session-restore preference is set — a constraint that had already leaked into agent designs as "sessions can't survive restarts". Every launch now pinssession.restore_on_startup: 1("continue where you left off") in the profile'sDefault/Preferences, which is the switch Chromium's cookie purge actually keys off — and pairs it with--no-startup-windowso the visible side of restore never happens: no window exists at startup for restore to fill, no ghost tabs from the last task reopen, and the task flow creates its own tab over CDP exactly as before. Verified live on Windows/Comet: a memory-only cookie planted pre-restart was still present after a full stop/start, with OS-level window enumeration confirming a single window and zero restored tabs. The Preferences patch runs pre-spawn (browser down, so Chromium can't overwrite it on exit), stamps the profile name only on first launch, skips malformed files untouched, and is a no-op when already set. Electron profiles keep the old name-only seeding — they manage their own storage and need their startup window (the CDP driver binds to it). Bareagents browser start(no--url) recreates the old startup-window affordance by opening a blank page target when none exists, unregistered on the task like the startup window always was. Server-side session TTLs still apply — this removes the restart logout, not the site's own expiry. Source:apps/cli/src/lib/browser/chrome.ts(ensureProfilePreferences, launch args),apps/cli/src/lib/browser/service.ts. - Security fix:
agents sessions --host <target>no longer accepts a leading-dash target (SSH argv-flag smuggling).session/remote.tscarried its own copy ofassertValidSshTargetthat omitted thehost.startsWith('-')guard every other SSH path enforces, so a bare flag like-lor-F/path— which passes the character allowlist — was handed straight tosshas an argument (-oProxyCommand=…-class injection) before any connection. The duplicate validator (and itsSSH_TARGET_RE) is deleted;runRemoteSessionsnow routes through the canonicalassertValidSshTargetinssh-exec.ts, whose dash guard is already regression-tested (ssh-exec.test.ts). Source:apps/cli/src/lib/session/remote.ts.
1.20.42
- Fix: exiting a split pane inside an interactive
ag runsession kicked you out of tmux entirely. When you split the window of an interactive agent session (ag run claude) with Ctrl-b"/%and thenexited your split, the whole tmux client detached and dumped you back to the parent shell — even though the agent was still running in the other pane. Cause:runInTmuxinstalled a session-widepane-diedhook (detach-client) meant to fire only when the AGENT pane exits (so the attach returns and the exit status is read), but with no#{hook_pane}guard it fired for any pane's death. The hook is now scoped to the agent pane; a user split that exits is closed in place (kill-pane, no lingering dead husk) and the agent keeps running full-window. Source:apps/cli/src/lib/exec.ts,apps/cli/src/lib/tmux/session.test.ts. - Every secret-value read is now audited, not just the ones that flowed through the resolver.
agents events --module secrets(or--event secrets.get) is meant to show "every secret accessed or revealed", but several paths read plaintext values without going throughreadAndResolveBundleEnv(the only place that emittedsecrets.get), so they were invisible:secrets push(which reads the whole bundle to upload it — the most sensitive silent read),secrets view --reveal, the rawsecrets get <item>,secrets set <item>(a raw write, nosecrets.set), and the initiating side ofsecrets exec --host/run --secrets bundle@host(only the remote host logged it). Each now emits with asourcetelling you HOW it was read —keychain,agent(served from the unlocked broker),reveal,raw-item,sync-push, orremote(with the targethost) — alongside the bundle, caller, keyCount, and OS-user/host/transport. The resolved value is never written to the log, only names and counts. Allsecrets.*events are now taggedmodule: 'secrets'so--module secretsactually surfaces the value reads (previously it matched only the coarse command events). Note: the event log has a 7-day retention, so export what you need for long-term records. Source:src/lib/secrets/bundles.ts,src/lib/secrets/sync.ts,src/lib/secrets/remote.ts,src/commands/secrets.ts,docs/06-observability.md. - Fix:
sessions --activeshowed the SAME preview + topic for every co-located session. Multiple Claude sessions in one cwd (e.g. several editor tabs, or two worktree siblings) all rendered identical activity — they looked like duplicate cards.findClaudeSessionFilefell back to the newest.jsonlin the cwd whenever a session's<id>.jsonlwasn't found, so every distinct session collapsed onto ONE file's preview/topic. The stale-id trigger: an editor caches the launch uuid inlive-terminals.json, but Claude rotates its transcript uuid on resume/compact, so the cached id no longer matches any file. Now the terminal path resolves each tab's EXACT id from the pid registry (mirroring the headless path), the newest-file fallback is gated to the no-id case (pickSessionFile), and an unresolvable file reads asidlerather thanrunning. Source:apps/cli/src/lib/session/active.ts. - Fix: one malformed Kimi session blanked the WHOLE
agents sessionslisting. A Kimistate.jsonwith neithercreatedAtnorupdatedAtmadereadKimiMetareturn anundefinedtimestamp, which bindsNULLinto thetimestamp TEXT NOT NULLcolumn and aborts the entire batch index — so a single bad session took down the listing for every session, not just itself. Two layers:readKimiMetanow coerces the timestamp to never-null, falling back to thestate.jsonmtime (matching how the listing already ranks Kimi vialast_activity, like every other parser); andupsertSessionsBatchwraps each row in a per-row guard so a future constraint-violating row skips itself (ledger deliberately not stamped, so the next scan re-tries it) instead of rolling back the whole batch. Source:apps/cli/src/lib/session/discover.ts,apps/cli/src/lib/session/db.ts.
1.20.41
- NEW:
agents sessions focus [id]— one command to get back to a session, however it's reachable. It attaches a live session in place (tmuxswitch-client/attach-session, a remote tmux overssh -tt, or a Ghostty tab — joining the live process without forking); where there's no live terminal to attach, it opens a new tab and resumes the session — locally, or on the remote peer over SSH (runOnPeer, so the peer resolves the version-pinned binary). No id opens the rich live-session picker (this-machine first). Reuses the live-session detection and the terminal launch engine (openSurfaces), and foldsgo's attach paths in. Source:src/commands/focus.ts,src/commands/go.ts. --deviceis now a first-class alias of--hoston every host-routable command (sessions,run, …), registered centrally onaddHostOptionso a local fall-through no longer errors. Source:src/lib/hosts/.agents computersteers Electron/webview targets over CDP instead of reporting a fake success when the native-automation path can't reach them (#716).- Secrets: the "remember" policy hold now lasts 7 days and survives screen-lock, instead of re-prompting after every lock/sleep; stale copies are evicted when a policy is tightened. Source:
src/lib/secrets/. - Fixes: shim
machine_id()normalizes to matchnormalizeHost(), and shim resolution honors the per-device default pin (not just the centralagents.yaml). agents sessions gois retired as a deprecated alias foragents sessions focus --attach-only.gowas already a strict subset offocus— its only unique behavior was "attach the live terminal or refuse, never fork/resume." That behavior is now a first-class--attach-onlyflag onfocus(focus.ts:selectFallback()picksrefuseFallbackunder--attach-only, else the resume-in-a-new-tab fallback).gonow prints a one-line deprecation notice and delegates tofocusAction(id, { attachOnly: true }); the shared reach engine (jumpTo/gatherLiveTargets/pickLiveTarget/refuseFallback) still lives ingo.tsand is imported byfocus.ts. Source:src/commands/go.ts,src/commands/focus.ts.agents sessions --json --host <h>now emits a clean JSON array of recent (non-active) sessions instead of the legacy per-host raw banner stream, so a UI can fetch a remote device's recent sessions when it has no live agents.serializeSessionsJson()is shared by the local and remote--jsonpaths;runRemoteSessionsJson()reuses the existinggatherRemoteListSSH fan-out. The non-JSON banner path and--activeare unchanged (#711).
1.20.36
[windows] agents sessions --active detects sessions on Windows, and shim launches carry cwd + session identity everywhere
The active listing found nothing on Windows: the headless scan shelled out to
ps -Aand per-pidlsof— both POSIX-only, both failing silently into "No active agent sessions" with a dozen liveclaude.exeprocesses running. The process table now comes from one CIM query on win32 (powershell.exe Get-CimInstance Win32_Process;wmicis removed on current Windows 11) parsed into the same pid/ppid/comm rows, agent-kind matching strips the.exeimage suffix case-insensitively (POSIX comms stay exact-match — macOS's Claude desktop app process is namedClaudeand must not be listed), and the ancestry walk recognizes Windows terminal hosts (Code.exe,Cursor.exe,VSCodium.exe,Windsurf.exe,WindowsTerminal.exe). Where no cwd can be recovered (nolsofon Windows), same-kind child agent processes — Claude runs subagents and its bundled ripgrep as childclaudeprocesses — fold onto their root candidate (foldSubordinateAgents) instead of printing one row per fork; on POSIX those children collapsed via shared-cwd session dedupe, which now accumulates pre-folded pid counts instead of resetting them. Verified live: 6 rootclaude.exeprocesses render as 6 rows (previously zero). Source:src/lib/session/active.ts.Those Windows rows grouped under
unknownwith no topic because onlyag runrecorded a pid → session/cwd registry entry. The transparent shim delegate (execShimPassthrough) — the path everyclaude/codextyped into a terminal actually takes — now writes the sameby-pidregistry entry at spawn: agent, launch cwd, and the exact session id when the caller passed--session-id(extractSessionIdArg; whole-arg match only, a uuid inside a prompt never counts). On the win32.cmdshell path the recorded pid is the cmd.exe intermediary rather than the agent binary, so the active scan resolves entries by walking a candidate's ancestors (readAncestorSessionEntry), accepting only a matching agent kind — a claude session shelling out to codex can't hand codex its identity — and the fork-fold keeps a descendant with a wrapper entry below its fold target as its own row (a claude launched from inside another claude session is a real second session, not a fork). Net effect: Windows shim launches list with their project directory, exact session id, and topic instead ofunknown. (POSIX bash shimsexecthe binary directly without the delegate, so they are unchanged and keep relying on lsof-recovered cwd + newest-jsonl.) Source:src/lib/exec.ts,src/lib/session/pid-registry.ts,src/lib/session/active.ts. [windows]browser profiles createno longer hands out a port an already-running browser is listening onfindFreeProfilePortprobed candidate ports by shelling out tolsof, which doesn't exist on Windows — the ENOENT was swallowed by the "assume free" catch, so every port in 9222–9399 scanned as free. The first profile created without--endpointwas assignedcdp://127.0.0.1:9222, and if the user's own browser was running with--remote-debugging-port=9222(a common Comet/Chrome setup), the new profile silently attached to that browser instead of launching its own sandboxed instance — tabs then opened in whatever profile the user had on screen. The scan now routes throughisPortInUse(chrome.ts, newly exported), the same platform-aware probe the launcher already used:lsofon POSIX,netstat -anoon Windows. Regression-tested with a real bound socket, no mocks, so the probe is exercised per-platform in CI (src/lib/browser/chrome.test.ts). Source:src/lib/browser/profiles.ts,src/lib/browser/chrome.ts.
[windows] Background spawns no longer flash console windows while agents run
- Every background chain root — the scheduler daemon, the auto-pull worker, the PTY sidecar server, the routine runner's job spawns, detached ssh tunnels — was spawned
detached: truewithoutwindowsHide. On Windowsdetachedmaps toDETACHED_PROCESS, under which CreateProcess ignoresCREATE_NO_WINDOWand the child runs console-less, so every console-subsystem descendant (powershell.exe for a Credential Manager read, git, node, a.cmdshim's cmd.exe wrapper) allocated its own visible console window — the "PowerShell windows popping up and closing while I type" bug. The worst repeat offender: the console-less daemon resolves secrets bundles throughpowershell.exeon every session-sync cycle (90s). The newbackgroundSpawnOptions()(src/lib/platform/process.ts) is the single place that decides the pattern: POSIX keepsdetached: true(own process group, group-kill still works); Windows switches towindowsHide: truewith no detach — the child owns a hidden console that all descendants inherit (nothing down the tree can flash) and that no launcher console-close event can reach, preserving the #556 daemon-teardown fix. Verified with a live Win32 probe (GetConsoleWindow+IsWindowVisible): the old pattern yieldsVISIBLE=True, the new pattern allocates no console window at all. Leaf spawns of console tools reachable from a console-less parent (powershell insecrets/windows.ts+platform/winpath.ts, tasklist/netstat/taskkill in the browser runtime probes,tailscale status, ssh, ffmpeg) now passwindowsHide: trueas defense in depth for callers this release can't re-parent (e.g. an already-running daemon). Source:src/lib/platform/process.ts,src/lib/daemon.ts,src/lib/auto-pull.ts,src/lib/pty-client.ts,src/lib/runner.ts,src/lib/ssh-tunnel.ts, plus the leaf call sites. - Fallout the hidden console surfaced (caught by the real-advapi32 round-trip test): the Credential Manager driver's
setread the secret from stdin as text via[Console]::In.ReadToEnd(), decoded with the console codepage — correct only when the caller's console happened to be UTF-8 (Windows Terminal). Under a fresh hidden console (OEM cp437), or any console-less caller like the daemon, non-ASCII secrets corrupted on write (café ☕stored ascaf├⌐ Γÿò). The static PS script now reads stdin as raw bytes ([Console]::OpenStandardInput()→MemoryStream) and pins[Console]::OutputEncodingto UTF-8, so the round-trip is codepage-independent in every calling context. Source:src/lib/secrets/windows.ts. - Correction for the fd-redirected roots (daemon, runner, PTY server):
windowsHideis inert whenever a stdio slot is redirected to an fd — libuv skipsCREATE_NO_WINDOWif any stdio fd is inherited, and log-file redirection counts. A non-detached daemon therefore shared its launcher's console and died on the launcher's console-close event the momentagents daemon startreturned (the #556 failure, reproduced live: childalive-after-launcher-exit=falseunder{detached:false, windowsHide:true}with fd stdio,trueunder{detached:true, …}).backgroundSpawnOptions({ fdStdio: true })now keepsDETACHED_PROCESSfor these roots — the child runs console-less and windowless, and its console-tool spawns stay invisible via the leafwindowsHidefixes above. Fully-piped/'ignore'roots (auto-pull, detached ssh tunnels) keep the hidden-console pattern, which the Win32 probe validated. Regression-tested: a hidden-console launcher spawns an fd-redirected child and exits; the child must survive (src/lib/platform/process.test.ts).
[teams] agents teams pr-watch <team> — autonomous PR lifecycle: CI-fix waves + review-comment routing (Closes #338)
- A team's teammates open PRs;
pr-watchwatches them and reacts without a human in the loop. Each poll it resolves the PRs the team opened (from each teammate'spr_url, else thegh pr createdetected in the session it ran), snapshots CI + review comments via theghCLI (gh pr checks --json,gh api …/pulls/{n}/comments), and decides follow-ups: RED CI spawns a fix teammate--afterthe one that failed, with the failing-run logs (gh run view --log-failed) injected so it pushes a follow-up commit to the same PR branch; a new review comment routes abugfixteammate (the existingTaskType)--afterthe source, with the comment body injected. Both slot into the team DAG the supervisor already drains — the loop callsstartReadyeach pass so staged fixers launch when their source completes — and every reaction is visible inagents teams status. Dedupe is by check-run id / comment id, persisted topr-watch-<team>.json, so the same failure or comment never spawns twice across restarts. The decision logic (decidePrActions) is a pure function over injected snapshot data (unit-tested insrc/lib/teams/pr-watch.test.ts, no network); theghcollectors and thehandleSpawn-backed reactor sit on top. Deferred (documented follow-up): the event-driven path from #331's webhook receiver —pollPrSnapshotis the seam where acheck_run/pull_request_review_commentpayload plugs in, producing the samePrSnapshotthe pure decider already consumes. Source:src/lib/teams/pr-watch.ts,src/commands/teams.ts.
[hosts] --host/--device now resolves registered devices and ad-hoc user@host — one concept, one flag
- Offloading a run no longer needs a machine enrolled in two registries.
agents run --host <name>(and the new--device <name>alias, plusteams add --host, and every other--hostconsumer via the shared resolver) now resolves in order: theagents hostsregistry (unchanged), then theagents devicesregistry, then an ad-hocuser@host. A machine registered once withagents devices syncis reachable immediately — previously it erroredUnknown hostunless you also ranagents hosts add. The fall-through lives in one place (resolveHost), so it's not a per-command band-aid. A bare unknown name still returns null so capability-tag routing (--host gpu) is unaffected; only a name containing@is treated as an ad-hoc target (validated byassertValidSshTarget). A device that authenticates by password can't offload over the BatchMode ssh path, so it throws a typed, actionableDeviceOffloadUnsupportedError(switch to key auth or enroll as a host) instead of dispatching a run that would hang. Source:src/lib/hosts/registry.ts,src/commands/exec.ts,src/lib/hosts/option.ts.
[secrets] recover credentials orphaned under a stale keychain access group (RUSH-1413)
- Secrets written before the access-group pin (#279, first shipped v1.20.27) were filed by macOS under the implicit default group — the literal wildcard
2HTP252L87.*, not the concrete2HTP252L87.com.phnx-labs.agents-keychainthat every query now pins (keychain-helper.swiftdpBase). Those items are intact and the wildcard entitlement authorizes reading them, but the pinned queries never ask for that group, sohas/get/listreported them missing and whole bundles vanished fromsecrets list(their metadata was orphaned too). On one machine this stranded 43 items including the ssh private key, the release signing key, and identity secrets. The helper now recovers them on three levels: (1)readItem/hasadd an un-pinned data-protection fallback pass after the pinned miss, so an orphan reads and reports present instead of missing; (2)get/get-batchre-home an orphan inline the first time it's read — reusing the read's Touch ID, add-before-delete, deleting the exact orphan bykSecValuePersistentRef— mirroring the existing file-basedmigrateInline; (3) a newmigrate-orphanshelper verb bulk re-homes every orphan behind a single Touch ID.listis now un-pinned so orphaned bundle metadata reappears, andset/deleteclear across all groups so a rotate/delete can't leave a shadow copy. Newlist-orphansverb enumerates orphans prompt-free. Source:src/lib/secrets/keychain-helper.swift,src/lib/secrets/index.ts. agents secrets migrate-aclnow sweeps orphaned-access-group items in addition to legacy-ACL stragglers: the dry-run lists both classes,--commitre-homes the orphans in one batched Touch ID (add-before-delete needs no pre-write backup), and any listed orphan the helper can't reach (e.g. under a different signing team) is surfaced, never dropped silently. Because every published helper shares team2HTP252L87and the same wildcard entitlement, one run recovers every affected user losslessly. Source:src/commands/secrets-migrate.ts,src/lib/secrets/index.ts(listOrphanedKeychainItems,migrateOrphanedKeychainItems,parseOrphanMigrationOutput). The signed helper must be rebuilt + re-signed + notarized and its sha re-pinned (scripts/build-keychain-helper.sh,scripts/Agents CLI.app.sha256) at release, per the standard keychain-helper release step.
CI: audit-event tests are green on Windows; the release re-gates on the windows-latest matrix legs (RUSH-1412)
- The cross-platform matrix (
ci.yml, runs only onrelease/**+v*) had bothbuild (windows-latest, …)legs red:tests/events-audit.test.tsandtests/teams-events.test.tsspawn the CLI with a redirectedHOMEand then read the audit trail under it, but the events writer rooted its log dir at a bareos.homedir()(src/lib/events.ts:24). On Windowsos.homedir()resolves fromUSERPROFILEand ignores aHOMEoverride, so everycommand.start/command.endrecord was silently written to the real profile instead of the test's temp home — the events array came back empty and the log fileENOENT'd (macOS/Ubuntu were green becauseos.homedir()honors$HOMEon POSIX). The writer now roots its log dir throughstate.getLogsDir(), the single canonical home anchor (process.env.HOME ?? os.homedir()), which honors an explicitHOMEon every platform and still resolves toUSERPROFILEin production on Windows (whereHOMEis unset), so real users are unaffected. Oneevents-auditcase also reconstructed its log filename from a UTCtoISOString()while the writer names files from the local date, so itENOENT'd whenever a runner's local and UTC dates straddled midnight; it now globs the log dir like the other assertions.scripts/release.shrestores bothbuild (windows-latest, 22|24)entries toEXPECTED_CHECKS, so Windows is a release gate again. Source:src/lib/events.ts,tests/events-audit.test.ts,scripts/release.sh. - Three more
build (windows-latest, …)failures fixed. (1) Antigravity sessions were invisible to Windows users, not just tests.parseAntigravityread its conversation SQLite DBs by shelling out to thesqlite3CLI (src/lib/session/parse.ts:893), which is absent on Windows — soexecFileSync('sqlite3', …)threwspawnSync sqlite3 ENOENTand the parser silently returned[]for every real Antigravity session on Windows. It now reads thestep_payloadBLOBs through the runtime-agnosticsrc/lib/sqlite.tswrapper (node:sqlite / bun:sqlite, the same path production already uses for the session index), so it works on every OS with no CLI dependency;parse-antigravity.test.tsbuilds its fixture DB through the same wrapper instead of the CLI. (2)parse-droid.test.tsderived itstestdatadir fromnew URL(import.meta.url).pathname, which on Windows yields/C:/…— sopath.joinproduced a doubled-driveC:\C:\…thatENOENT'd; it now usesfileURLToPath(import.meta.url). (3)git.test.ts'ssyncRepoGit"pull-only" case failed because the Windows runner'score.autocrlf=trueconverted the freshly-clonedREADME.mdto CRLF duringgit clone— beforeconfigIdentity()could setautocrlf=falseon the clone — sostatus.isClean()saw a phantom modification andsyncRepoGitrefused with "Working tree has uncommitted changes." The test seed now commits a.gitattributes(* -text), which wins overautocrlfat checkout time so every clone lands byte-identical LF content. (parseOpenCodeshells out tosqlite3the same way and has the same latent Windows gap, but its test mocksexecFileSyncso CI never caught it and its argv-injection regression test pins the CLI call — left untouched to avoid scope creep.) Source:src/lib/session/parse.ts,src/lib/session/__tests__/parse-antigravity.test.ts,src/lib/session/__tests__/parse-droid.test.ts,src/lib/git.test.ts.
agents message <target> <text>: deliver a message to an already-running agent mid-flight [RUSH-1415]
- One verb now reaches a live agent while it works, not just a cloud task.
agents message <id> <text>resolves the target to exactly one destination and routes it: a cloud task id takes the existing provider follow-up path (wasagents cloud message); a live local/teams/loop agent gets the text enqueued into a per-agent file-spool mailbox that aPreToolUsehook drains and injects at the agent's next tool call.resolveMessageTarget()is the anti-misroute gate — exact id wins over prefix, results de-dupe by canonical mailbox id, and a target matching zero or more-than-one live agent (or an empty string) is never guessed: the command errors with the candidate list.--from <who>records a sender label;--host <h>routes the whole command over SSH (viaREMOTE_PASSTHROUGH) to the box that owns the agent, andmessageregisters as a lazy SQLite-backed command likecloud/sessions/teams. Source:src/commands/message.ts,src/lib/mailbox-target.ts,src/lib/hosts/passthrough.ts,src/lib/startup/command-registry.ts. - The mailbox itself is a crash-safe file-spool under
~/.agents/.history/mailbox/<id>/{inbox,processing,consumed}/. Enqueue is atomic (temp-write +rename); drain is claim-first (inbox → processing → consumed) so an interrupted drain is recovered on the next call (at-least-once delivery; consumers dedup bymsgId). Every message stamps atofield and a monotonic FIFOmsgId; a message that lands in the wrong box or fails to parse is archived and dropped, never delivered or looped. A mailboxId must be a single separator-free path segment ([A-Za-z0-9._-], not./..) — validated at the id→path boundary and the write-timetostamp so a traversal-bearing id fails loud instead of silently misrouting. At spawn,buildExecEnvpoints each agent at its own box viaAGENTS_MAILBOX_DIR(keyed by session id); a loop overrides it to the run-level box so every iteration shares one inbox, and printsagents message <runId>at start since the runId is otherwise undiscoverable. Source:src/lib/mailbox.ts,src/lib/state.ts,src/lib/exec.ts,src/lib/loop.ts.
Watchdog core: stall detection + nudge decision for a stalled agent (#612) [RUSH-1415]
- Ports the pure, fs/vscode-free watchdog core so agents-cli can decide when a running agent has stalled and what to say to un-stall it:
classifyTerminal+isLikelyTrulyBlocked(blocked / waiting / completion-hint signals plus a promise-without-toolcall detector),renderWatchdogPrompt/composePromptWithPlaybook/WATCHDOG_SYSTEM_PROMPT, and a tolerantparseWatchdogResponse.summarizeWatchdogTailextracts the last user/assistant turn across Claude/Codex/Gemini transcript shapes and filters synthetic<system-reminder>-style tags. The session-tail reader seeks backward from EOF for the last N JSONL lines and resolves a transcript fromsessionId + agentby reusinggetAgentSessionDirs()rather than hardcoding paths — including the recursivewalkForFileswalk that reaches Codex's deepsessions/YYYY/MM/DD/rollout-…jsonllayout and Gemini's tmp layout, driven per-agent byWATCHDOG_SESSION_LAYOUT. Source:src/lib/watchdog/watchdog.ts,src/lib/watchdog/watchdogTail.ts,src/lib/watchdog/read.ts,src/lib/watchdog/index.ts.
Terminal injection: type into an already-running agent's exact terminal (#611, #616) [RUSH-1415]
injectIntoTerminalextends the Terminal Engine to type into a running surface, not just open new ones — the primitive a native watchdog needs to nudge a stalled agent with "continue" delivered into the precise terminal it lives in. It mirrors the engine's shape: pure per-backend spec builders produce aLaunchSpecrun through the samerunSpectransport, so injection inherits local/remote (--hostover SSH) execution for free. Backends: tmuxsend-keys -t <pane>(socket-addressed), itermtell session id "<uuid>" to write text(noactivate, so it addresses the exact split without stealing focus), vscodium (VSCodium/Cursor/VS Code) over the editor CLI's--open-urlinto the extension's/injectverb, and pty via the agents-pty sidecar (local-only). Ink-TUI Enter semantics: text and Enter are two separate writes by default (a fusedtext\ris swallowed by Claude's Ink TUI), andcombinedopts into the single fused write for plain shells. Source:src/lib/terminal/inject.ts,src/lib/terminal/index.ts.resolveInjectTargetis the single resolver the watchdog calls:sessionId →a preciseInjectTargetor an honest{ addressable: false, reason }, with precedence tmux > iterm > vscodium > pty and a deliberate safe skip for Ghostty (no addressable split API).deriveProvenancenow captures$ITERM_SESSION_IDand, absent tmux, exposes anitermreply rail carrying the iTerm2 session UUID — tmux still wins whenever present because a pane is reachable inside any host app.agents sessions inject <id> <text>is the CLI face: it resolves an active session to its provenance reply rail and routes to the matching backend, with--pane/--ptyto target a backend directly,--combinedto toggle the Ink-safe two-write default,--no-enterto send without submitting, and--hostto inject over SSH. Source:src/lib/terminal/resolve.ts,src/lib/session/provenance.ts,src/lib/session/inject.ts,src/commands/sessions-inject.ts.
Watchdog consumer + agents watchdog: run one stall-detection tick end to end (#619, #622) [RUSH-1415]
runWatchdogTickties the pure pieces together into one pass overgetActiveSessions():classifyTerminal()finds stalls,readWatchdogTail()reads the transcript,isLikelyTrulyBlocked()gates on the promise-without-toolcall heuristic (deterministic v1) or an optional--smartLLM decider,resolveInjectTargetForSession()is the absolute safety gate, andinjectIntoTerminal()deliversContinue.into the EXACT split. A nudge fires ONLY onaddressable:true; anaddressable:falsestall is flagged to a tray-readable state file and skipped — never a guessed target. Per-session policy isoff|keep|handsoff(handsoff detects and flags but never injects); cooldown and un-addressable flags persist under~/.agents/.cache/state/watchdog/. Theagents watchdogcommand runs it without the menu-bar: bare = one dry tick (reports would-nudge/skip + why),--nudgeinjects for real,--watchis a daemon loop (--interval, default 30s),--jsonis machine-readable, and--stall/--cooldown/--dormantoverride thresholds.runner.test.tsdrives real synthetic sessions through the pure logic (nothing mocked) with dry-run injection. Source:src/lib/watchdog/runner.ts,src/commands/watchdog.ts,src/lib/startup/command-registry.ts.- The macOS menu-bar helper now auto-nudges from its native tick:
StatusItemController.tick()reads the enable sentinel and runs one watchdog tick (nudge=enabled, detect-only when off), a checkable Auto-nudge menu row toggles it viaagents watchdog enable|disableand showsN stalled · M nudged, andAgentsCLIgainswatchdogStatus()/watchdogTick(nudge:)/watchdogSetEnabled()mirroring thedoctorOverview()shell-and-decode pattern.refreshWatchdog()is throttled to a 30s floor (siblings: doctor 60s, routines 20s) so it doesn't spawn two node subprocesses on every 10s tick — still well under the 5-minute stall threshold. Source:packages/menubar-helper/Sources/MenubarHelper/StatusItemController.swift,packages/menubar-helper/Sources/MenubarHelper/AgentsCLI.swift,src/commands/watchdog.ts.
VSCodium / Cursor / VS Code terminal backend (#608, #620)
- A new
vscodium-agentterminal backend opens each resumed session as an agent-terminal tab in a running VSCodium / Cursor / VS Code window — via theswarm-extextension's/spawnURI verb — instead of scripting a GUI terminal app. It builds<cli> --open-url '<scheme>://swarmify.swarm-ext/spawn?…'(default VSCodium:codium/vscodium://); the editor CLI forwards the URL over its IPC socket, so it needs no OS scheme handler, works on Linux, and flows over--host(SSH) like the other backends — with nozsh -ilcwrap since the target is already an interactive login shell. The{command, cwd, split}payload is base64url-encoded into a single query param because VS Code percent-decodesuri.queryonce before the handler parses it (a bareecho a && touch bwas otherwise truncated at the&). Wired intosessions resumeas--vscodium; auto-detect is intentionally omitted (TERM_PROGRAM=vscodecan't disambiguate the three products). Because VSCodium agent terminals open as individual full-width editor tabs, this backend defaults packing to one tab per session (--tabsstill forces tabs elsewhere). Source:src/lib/terminal/backends/vscodium-agent.ts,src/lib/terminal/index.ts,src/commands/sessions-resume.ts.
agents sync <repo>: git-sync a single DotAgent repo (#535)
- Giving a DotAgent repo name alone —
agents sync system/agents sync user/agents sync <alias>— now git-syncs just that one repo instead of running the umbrella reconcile. The newsyncRepoGitrefuses on a dirty working tree (commit or discard first), otherwisegit fetch origin+git pull --rebase origin <branch>against the repo's own HEAD branch (falling back tomain), reinstalls the git hooks, and reports the resulting short commit. Theuserrepo and enabled extra-repo aliases alsogit pushlocal commits up;systemis a pull-only mirror of the npm-shipped upstream (push: false).projectand unknown names are rejected — the project.agents/lives inside the user's own repo and isn't independently synced. This repo-name form is matched before agent-spec parsing, since names likesystem/userwould otherwise failparseAgentSpec. Source:src/lib/git.ts,src/commands/sync.ts. - Bare
agents syncno longer eager-fetches secrets and sessions: the umbrella planner now defaults to config repos + reconcile only, with secret bundles and session transcripts made opt-in via--secrets/--sessions(pulling every secret bundle onto a machine was more blast radius than a bare sync should carry; transcripts stay queryable on demand viaagents sessions --host <machine>). Interactive bareagents sync(TTY, no flags) now drops into a two-checklist picker — which repos to sync FROM, which installed agents to sync INTO — then pull-only freshens the selected repos and reconciles a single merged selection into each agent, unioned across repos viamergeRepoScopedSelections/unionResourceSelections. Source:src/lib/sync-umbrella.ts,src/lib/versions.ts,src/commands/sync.ts.
Split agents.yaml into portable, per-device, and machine-local files (#538)
- The committed central
~/.agents/agents.yamlused to carry machine-specific fields and was held back with agit skip-worktreeband-aid so it wouldn't sync. It's now partitioned by sync-domain:agents:(version pins) moves to per-device~/.agents/devices/<machineId>/agents.yaml(committed and synced, but each machine only writes its own folder so pulls never conflict),versions:(per-version resource tracking) moves to gitignored, machine-local~/.agents/.history/version-resources.json, and centralagents.yamlis left portable.writeMetaUnlockedwrites the device and history files BEFORE stripping and rewriting central, so a crash mid-write never drops pins/versions before they persist;readMetaoverlays the machine-local files back on viaoverlayMachineLocal(device pins win and self-heal a pre-migration central). Source:src/lib/state.ts,src/lib/machine-id.ts. - Migration
migrateSplitDeviceLocalMeta(sentinel bumped tov11) performs the one-time split on raw YAML, merging into any existing device/history files (existing entries win) viaatomicWriteFileSync, and only rewrites central when it actually carries machine-local fields — a portable-onlyagents.yamlis left byte-untouched — while always clearing theskip-worktreebit so every machine's file syncs cleanly. The meta cache stamp is now a|-delimited string of all four source files' mtimes rather than a numeric sum that could round sub-unit device/history changes away and serve stale reads in long-lived processes.machineId()/normalizeHost()were extracted to a dependency-free leaf module so low-levelstate.tscan key per-device paths without an import cycle. Source:src/lib/migrate.ts,src/lib/machine-id.ts,src/lib/session/sync/config.ts,src/index.ts.
agents sessions: the interactive picker now shows origin machine, PR/ticket, and worktree columns
- Every discovered session carries the machine it originated on — the local box for live-home transcripts, or the origin host parsed from the cross-machine mirror layout (
backups/<agent>/<machine>/…) — recorded onSessionMeta.machinebydiscoverSessions. The picker row, previously stuck onshortId · agent · version · project · topic · when, now folds in a gray machine column (only when the pool spans >1 box, with the longest shared dash-delimited prefix stripped soyosemite-s0/yosemite-s1read ass0/s1), a bluePR#/ticket column (only when some row carries a ref), and a magentawt:<slug>worktree badge. Column flags are computed once over the whole pool viapickerColumnsForand shared by both the browse picker and the multi-select resume picker, and the topic width is now terminal-aware so the extra columns never wrap. - A dim
subtitlehint line renders between the header and the rows (newsubtitlefield onPickerConfig/SessionPickerConfig), rotating aTip:that surfaces the filter flags (-a/--agent,--project,--all,-H/--host,--since/--until), keyed off pool size so it stays fixed across re-renders. Fixed a wrap bug where the resume picker prepends a 6-cell> [x]gutter butformatPickerLabelreserved only the 2-cell single-select cursor, overflowing every row by 4 cells and halving the viewport; the gutter width (2 browse, 6 resume) now threads throughPickerColumnsand is reserved from the topic width. Source:src/commands/sessions.ts,src/commands/sessions-resume.ts,src/commands/sessions-picker.ts,src/lib/picker.ts,src/lib/session/discover.ts,src/lib/session/types.ts.
Reach Windows peers over --host (RUSH-1429)
- The SSH command layer gained a PowerShell dialect so
--hostoperations can target Windows remotes, where ssh lands incmd.exe/PowerShell andbash -lcdoes not exist.remoteShellFor(os)routeswindows → powershelland everything else (including unknown/absent) → posix, so linux/macOS never regress;buildWindowsAgentsCommandemitspowershell -NoProfile -EncodedCommand <base64-utf16le>, which survivescmd.exere-parsing with zero quoting hazards. The peer OS is resolved from the tailscale-synced device registry (fleet fan-out) or the enrolledHostEntry.os(explicit--host). This fixesagents sessions --host/--activeand remote secrets reads (browse + use-a-remote-bundle), which previously wrapped the remote invocation inbash -lcand got'bash' is not recognizedfrom a Windows peer. Thesecrets export/import --hostwrite path stays POSIX-only for now (documented follow-up). Source:src/lib/hosts/remote-cmd.ts,src/lib/hosts/remote-os.ts,src/lib/devices/registry.ts.
Windows portability + CI hardening
agents sessions … resumeno longer crashes on Windows withspawn EFTYPE:resumeSessionInPlacespawned the version-pinned launcher (claude@2.1.196) withshell:false, but on Windows that shim is a.cmd/extensionless file, so spawn threw synchronously and the error was mis-reported as a discovery failure. It now spawns through the shell on Windows vianeedsWindowsShelland reports a synchronous launch failure truthfully. The generated hook-cache shim also hardcodedpython3for its hash/timer/mtime, but on Windowspython3is often the Microsoft Store execution-alias stub (prints to stderr, exits non-zero, 0 bytes) — silently emptyingmtimeso every call missed the cache and re-ran the hook; it now probes for a runnable interpreter (python3, thenpython) by executing-c 'import sys'. Source:src/commands/sessions.ts,src/lib/hooks/cache.ts.- Two new CI guards keep these Windows-only, separator-prone bugs from reaching a release: a path-filtered
test-windowsjob runs the suite onwindows-latestfor changes under hooks/platform/shims (the requiredtestgate runs onubuntu-latest, wherepath.sepis/, so a backslash-path bug is invisible), andtoPortableCommandis now pure/exported with injectable home + separator so a unit test can assert WindowsC:\…→~/…folding on any host. Separately, aprepare: npm run buildhook rebuilds the gitignoreddist/on every install/link (and beforenpm publish), so a dev-linked checkout can't silently run a staledist/behind a source fix. Source:.github/workflows/tests-windows.yml,package.json.
License: MIT → Apache-2.0 (#504)
- The project relicenses from MIT to Apache-2.0.
LICENSE,README, andpackage.jsoncarry the new license, and the human-facing docs (theAGENTS.mdbrand lines, theCONTRIBUTING.mdCLA clause,DESIGN.md) were aligned so the stated license is consistent everywhere.
Security hardening batch (#474–#478)
- Shell / option injection.
agents inspectno longer builds itsgitcall as a shell string: a crafted repo path could inject via$(…)or other shell syntax throughexecSync(\git -C ${…} ${args}`). It now uses argv-formexecFileSync('git', ['-C', root, …args]), so the path can never reach a shell (#474). Separately, MCP server management rejects a server name that starts with-or contains whitespace/control characters and places every user-controlled positional after--, closing an option-injection vector (#478). Source:src/commands/inspect.ts,src/lib/mcp.ts`. - Path-traversal containment. Plugin resolution rejects a plugin name that resolves to the plugins root itself, so a crafted name can't escape or target the directory root (#475). Hook-shim generation validates the shim name before constructing any path and asserts the resolved shim path stays inside the shims directory — rejecting separators, traversal components (
..), NUL bytes, and leading dashes (#477). Source:src/lib/plugins.ts,src/lib/hooks.ts,src/lib/hooks/cache.ts. - Supply-chain. Per-version agent installs now run
npm install --ignore-scripts, so a dependency's install/postinstall lifecycle script can't execute arbitrary code during anagentsversion install (#476). Source:src/lib/versions.ts.
1.20.35
CI: build node-pty's native binary on macOS/Windows so the release matrix is green cross-platform
- The cross-platform matrix (
ci.yml, runs only onrelease/**+v*) installed deps withbun install --ignore-scripts, sopty.nodefrom@homebridge/node-pty-prebuilt-multiarchwas never fetched/built. That package ships prebuilt binaries only for Linux; macOS/Windows obtainpty.nodevia its own install script (prebuild-install download, else a node-gyp compile). With that script skipped the native module was absent, so the daemon-liveness integration test added in #568 — which spawns the real daemon (it loads node-pty) and asserts the browser IPC socket stays up — crashed on macOS/Windows while passing on Linux, and had been red on every release since. The matrix runs only on release branches, so it never surfaced on normal PRs (bun does not run that install script even without--ignore-scriptsin bun 1.3.x). CI now runs a dedicated step that invokes the package's own install script (npm run install), which prefers a prebuilt download and falls back to a node-gyp compile, so it self-heals across platforms and node ABIs. Production (npm install) already built the native module, so end users were unaffected. A second macOS/Windows-only failure in the same #568 daemon-liveness test was also fixed: the test rooted its fakeHOMEunderos.tmpdir(), which on macOS is the long/var/folders/…/T/…, pushing the daemon's AF_UNIX socket path to ~116 bytes — past macOS's 104-bytesun_pathlimit — sobind()failed withEADDRINUSE. The test now rootsHOMEat a short base on POSIX (Windows uses length-unlimited named pipes); real users with a normalHOMEwere never affected. Source:.github/workflows/ci.yml,src/lib/daemon.test.ts.
agents logs: a top-level, unified run-log viewer (#575)
- Viewing a dispatched run's output used to be nested and undiscoverable — only
agents hosts logs <id>andagents daemon logsexisted, andagents hostswasn't even in--help.agents logs [id]is now a discoverable top-level command that resolves a run across two substrates — host-dispatch task stdout (agents run --host) and the local session index — and shows or (-f) follows it.[id]/--sessionload directly (host task tried first, then session); with no id,--host/--agent/--versionfilter a merged candidate list (one match shows, several open a fuzzy picker, non-TTY prints the list). Additive:agents hosts logsandagents sessions tailare unchanged and share the same helpers. Source:src/commands/logs.ts,src/lib/hosts/logs.ts.
Host-follow log tailer: no self-corruption on localhost, byte-accurate offsets (#586, #589)
- Following a run dispatched to localhost tripled the on-disk log and triple-printed the output, because the local mirror file and the remote log were the same file and the tailer appended its own reads back into it; it now detects that aliasing by file identity (
dev:ino) and echoes only. Separately, the offset tracker advanced by a re-encoded string length, so a multibyte UTF-8 char split at a poll boundary drifted the offset and corrupted the stream on non-ASCII output; the tail is now byte-exact (rawBufferviasshExecRaw). Source:src/lib/hosts/progress.ts,src/lib/ssh-exec.ts.
agents upgrade: the "What's new" changelog is now a compact heading list (#562)
- The post-upgrade changelog dumped every heading and every verbose sub-bullet for each version in the range — a screenful across a multi-version jump. It now prints one bullet per feature/fix heading and links to the full CHANGELOG for the details. The parser was extracted to a pure, unit-tested
renderWhatsNewso it can be exercised without the CLI's import-time side effects. Source:src/lib/whats-new.ts,src/index.ts.
agents sessions --active: a per-pid registry de-collapses co-located agents (#546)
- On a host with no terminal extension (bare SSH/tmux — e.g. any Linux box),
--activecould only map a discovered agent process to a session by guessing the newest.jsonlin its cwd, so several agents in the same repo collapsed onto one session row (observed live: a single id listed 28 times), and/restorecouldn't tell them apart.agents runnow records each launch to~/.agents/.cache/terminals/by-pid/<pid>.json({agent, cwd, tmuxPane, sessionId, startedAtMs}) — the headless equivalent of the terminal extension'slive-terminals.json— so--activeand/restoreattribute each co-located agent correctly. Source:src/lib/session/pid-registry.ts,src/lib/session/active.ts,src/lib/exec.ts.
1.20.34
Test suite runs remotely on a crabbox VM (#525, #540)
scripts/release.sh's test gate now runsbun install && bun run build && bun run teston a leased crabbox VM viascripts/sandbox.shinstead of freezing the local machine, matching CI's Build→Test order (crabbox's sync honors.gitignore, so the gitignoreddist/is built on the box). A newbun run test:remoteoffloads the suite the same way for local dev. Publishing still happens locally — only the signed macOS keychain helper can be produced and notarized here, and crabbox boxes are Linux. Source:scripts/sandbox.sh,scripts/release.sh,package.json.scripts/sandbox.shbox acquisition is now robust: secrets load viaagents secrets export --plaintext(the bare form now hard-errors), a missing.crabbox.yamlno longer aborts the script underset -e, and the agents-cli/claude install is gated to PR mode so test-mode runs match GitHub CI. Box selection gates oncrabbox status … ready=true— skipping failed-bootstrap duds (which still reportstatus=running) and warming a fresh box if none are ready — keyed on the stableprofilelabel rather than an ephemeral slug. A dedicatedagents-clicrabbox profile (.crabbox.yaml) isolates this repo's warm pool. Source:scripts/sandbox.sh,.crabbox.yaml.
1.20.31
agents sessions <id>: a catch-up digest for switching between many agents (#502)
- Opening a single session now leads with its auto-inferred title (user
/rename> Claudeai-title> first-prompt topic) and PR / worktree / ticket badges, then a Changes section that groups touched files by directory and tags each as created / modified / deleted (with a+N ~N -Nsummary) instead of the old flat "Modified" list, a Tools histogram (per-tool call counts), and a Tests verdict parsed from the lastvitest/jest/pytest/go test/cargo test/tscrun. The same signals are folded into the interactive picker preview. agents sessions --activenow collapses the many subagent/fork PIDs of one session into a single row with a×Ncount instead of printing dozens of identical lines. Source:src/lib/session/digest.ts,src/lib/session/render.ts,src/lib/session/active.ts,src/commands/sessions.ts,src/commands/sessions-picker.ts.
1.20.30
agents sessions live state engine: waiting / PR / worktree / ticket detection + reliable preview (#494)
agents sessions --activeinfers real activity from each transcript's tail — working / waiting / idle — rather than the old mtime-only running/idle guess, using structural signals (ClaudeExitPlanMode/AskUserQuestion) plus a question + mtime heuristic for Codex. It detects and badges a PR opened during the session (gh pr create+ the resulting pull URL), a git worktree (.agents/worktrees/<slug>/), and a Linear/Jira ticket (from the prompt or branch), and shows the latest turn as the preview instead of the first prompt.--waitingfilters--activeto only sessions blocked on your input and exits non-zero (a scriptable gate);--treegroups the listing by directory, dropping the id/version columns while keeping the short-id handle.- The preview line is now width-correct: measurement is ANSI- and wide-char-aware and reads
$COLUMNSfirst, so it no longer wraps or drifts under tmux or over--hostSSH (the remote is handed the caller's width). Session index schema v7 persists the PR / worktree / ticket signals so historical listings carry them too. Source:src/lib/session/state.ts,src/lib/session/tail.ts,src/lib/session/width.ts,src/lib/session/{discover,db,active}.ts,src/commands/sessions.ts.
agents sessions --host <machine>: query a remote machine's sessions live over SSH
agents sessions "<query>" --host <alias|user@host>runs the same session query on a remote machine's own index over SSH and streams the result back — repeat--host(or pass several) to fan out across machines. SSH access is the only auth; there's no daemon or shared store. Targets are validated against a strict allowlist (SSH_TARGET_RE) to block flag-smuggling, and the forwarded invocation is double-quoted (shellQuote) so a query like$(whoami)survives as a literal string on both shell layers. Source:src/lib/session/remote.ts,src/commands/sessions.ts,docs/05-sessions.md.
Fix: migrations + menu-bar self-heal were silently disabled on Homebrew-node installs
- The "is this a dev build?" check walked
dirname(dirname(argv[1]))looking for a.git, without resolving the bin symlink. On a Homebrew-node setupagentsis/opt/homebrew/bin/agents, so it walked up to/opt/homebrew— which is itself a git repo — and false-positived as a dev build. Dev builds auto-setAGENTS_SKIP_MIGRATION=1, which gates both one-shot migrations and the menu-bar upgrade self-heal. Net effect: every Homebrew-node user ran with migrations and the menu-bar refresh permanently off. - Detection now
realpaths the entrypoint (so a symlinked bin resolves into the real package dir) and requires the.git's repo root to actually be the@phnx-labs/agents-clipackage — an unrelated ancestor repo no longer counts. Extracted tosrc/lib/startup/dev-build.tswith tests covering the Homebrew symlink layout, a real checkout, and unrelated-ancestor cases.
Secrets default policy is now daily (one Touch ID per ~24h), not always
- The default prompt policy for bundles without an explicit one flipped from
always(Touch ID on every read) todaily(one prompt, then held ~24h until screen-lock / sleep / logout). This is the fix for the prompt storm: a background reader like sessions-sync hammering a bundle now costs one Touch ID per ~24h instead of one per read. - Auto-cache is on by default. The secrets-agent is the mechanism that delivers the daily policy, so it self-caches a
dailybundle on first read with nosecrets.agent.auto: trueneeded. Opt out withsecrets.agent.auto: false. - Configurable, still flexible. Set the global default in
agents.yaml(secrets.policy: alwaysto restore prompt-every-time), or override per bundle withagents secrets policy <bundle> alwaysfor high-value keys (signing, SSH) you want to confirm on every read. - Explicit
alwaysnow persists under the legacytier: biometrytoken (older CLIs read it as their own always default). Bundles with no stored policy inherit the configured default — so an existing always-by-default bundle quietly becomesdailyon first read by the new CLI, which is the intended migration.
Menu bar: a macOS status item for agent activity (agents menubar)
- New no-Dock menu bar app showing live agent activity on the machine: a NEEDS YOU section (sessions awaiting input + failed/overdue routines), a per-agent roster (running / idle counts across installed agents), a + New session launcher, and a one-line routines summary. The icon badges red
!when something needs you, green with a count when sessions are running. - Reads state directly from disk —
live-terminals.json, teamsmeta.json, and the cloudtasks.db— so opening the menu never triggers the costly sessions transcript re-index. The CLI is shelled only for actions (start a session, run a routine). - Auto-enabled on macOS for every user as a launchd login service (
com.phnx-labs.agents-menubar); a fresh install brings the icon up with no manual step. Manage withagents menubar enable | disable | status. Opt out withagents menubar disable— sticky across upgrades. - Upgrade self-heal: the installed bundle is version-stamped, and the startup self-heal now re-installs the helper when a newer release ships a newer build (or the installed copy goes missing), instead of skipping whenever a service already existed. So
npm updateactually moves users onto the new helper binary + plist rather than leaving the old one running (#442).agents menubar statusshows installed vs current version and staleness. - Docs: Menu bar. macOS only.
agents repos view [name]: inspect one repo's contents without opening it
- New
agents repo view <name>(also reachable asagents repos view, now a first-class alias of therepocommand) prints a single repo's git state and per-kind resource counts —system,user,project, or an extra-repo alias. Omit the name for an interactive picker over the registered repos. It reuses theinspectrepo renderer, so output matchesagents inspect <repo>; supports--briefand--json. Source:src/commands/repo.ts,src/commands/inspect.ts.
agents doctor --fix + a daemon safety check: heal the gap between defined and installed
- Root cause behind "a plugin/command silently vanished": a DotAgents repo can DEFINE a resource that never makes it into an agent home, and nothing closes the gap. Two concrete failure modes — (1)
agents plugins update/synconly reconcile each agent's default version, so a non-default installed version keeps serving stale/invalid resources; (2) a plugin.json with a bare-nameskills/commandsfield makes Claude Code silently reject the entire plugin, and the sync path only warned. The detection (agents doctor's live-home diff) and the healing (syncResourcesToVersion) existed but were never wired together — and the sync fast-guard keyed off the staleness manifest, which is blind to home-side rot. agents doctor --fixturns the read-only diagnosis into a heal: installs missing resources, repairs Claude-invalid plugin manifests (strips the bareskills/commandsfield — Claude auto-discovers from the dirs), fast-forwards stale plugins from their.source, and reconciles drift — across every installed version, not just defaults. With no target it heals the whole install;agents doctor <agent> --fixscopes to one.- Daemon safety check: the routines daemon now runs the same heal in conservative
safemode (~every 6h + ~30s after start) — it fixes only unambiguous gaps (missing resources, invalid manifests, provably-unmodified stale plugins) and notifies rather than clobbers on hand-edited content or a plugin it can't prove is pristine. - Built on the live-home diff, not the staleness manifest, so it catches drift the sync fast-guard can't. Heal fills and fixes, never deletes (orphans stay
agents prune cleanup's job), excludes the project layer (the global home isn't reconciled against per-cwd project resources), and verifies after writing — it only claims resources that actually reconciled, so repeated runs converge instead of "fixing" the same item forever. .sourcenow records the plugin version at pull time, a baseline that lets the safe path tell an untouched mirror (fast-forward) from a user edit (leave alone).agents doctoroverview now covers every installed version, not just defaults. Sync status and orphans previously reported only each agent's default version — so a stale NON-default version (the exact rot--fixheals) was invisible in the readout. Each version is now listed with its default marked. The Agent CLIs list also stops nagging: it shows the agents you actually run (ready, or managed-but-broken) and collapses the rest of the supported catalog to a single+N more supported …hint instead of a column of red "not installed" lines for tools you never adopted.- Says exactly WHAT is out of sync — plugins first. A stale version in the overview now lists the specifics under it, prioritizing plugins and their bundled content:
plugin code — 0.6.1→0.7.0, missing skills: ship, learn. The plugin diff went from presence-only ("installed: yes") to content-aware — it compares the version's marketplace mirror against the central source and surfaces a stale mirror version, a Claude-invalid manifest, and the plugin's own skills/commands that never reached the mirror (the system-repo content that matters most).agents doctor <agent>@<version>shows the same detail per plugin row. - Fixed a false "drift" that could never be reconciled: a hook's
.md/.rstdoc sibling (e.g.git-guard.mdnext togit-guard.sh) was wrongly treated as the hook's runtime data file, so the installer's correct omission of docs showed as perpetual drift indoctor(and as an un-healable item under--fix). Docs are no longer counted as hook data; structured siblings (.yaml/.json/...) still are. - Corrected a false promise in the sync-status readout. Stale/cold versions used to say "will sync on next launch" / "first launch will populate" — but version homes are NOT reconciled on launch (the shim hot path only resolves a version and compiles project-scoped resources; v15/v16 moved version-home reconciliation to management commands). The readout now states the fact ("sources changed since last sync" / "never synced") and points at the real fix:
agents doctor <agent>@<version> --fixoragents sync <agent>@<version>.
Secrets prompt policy: human-readable always / daily, and secrets list now shows it
- Renamed the secrets-agent
tierto a prompt policy with plain-language names:biometry→always(ask every time),session→daily(ask once, then held ~24h until screen-lock / sleep / logout). The old namesessionwas misleading — it never meant "once per login session" — and collided with the half-dozen other "session" concepts in the CLI (agents sessions, sessions-sync, pty/browser sessions). Set it withagents secrets policy <bundle> [always|daily]. - Disclosure fixed.
agents secrets listnow has aPOLICYcolumn — previously there was no way to tell which bundles would Touch-ID-prompt you.dailybundles currently held by the agent showdaily · Nh left.agents secrets viewandcreatenow always state the policy (before, only the quiet tier was shown; the noisy default printed nothing). - Back-compat: the policy still persists under the legacy
tier/sessiontoken, so bundles stay readable across mixed CLI versions on synced machines.agents secrets tier,--tier, and thebiometry/sessionvalues keep working as aliases. - A third
neverpolicy (silent, no biometry ACL) is tracked for later in #421.
Self-healing: long-running processes reload onto new code after an upgrade
- Root cause behind a class of "stale behavior" bugs: a routines daemon or secrets-agent broker keeps running pre-upgrade code for days. An in-place
npm i -gswaps the files but not the running processes, so fixes (keychain read-memoization, the broker fast-path, etc.) silently never take effect — the daemon kept popping Touch ID from the keychain because it predated the fix. - Heal-on-upgrade:
postinstallnow bounces the routines daemon and kickstarts the persistent secrets-agent broker onto the just-installed code — the one moment we know the code changed. Best-effort, non-fatal, skipped in CI / withAGENTS_NO_HEAL=1. - Broker version-skew self-heal: the broker's
pingreports the version of the code it's running;ensureAgentRunning(the unlock / auto-cache path, never per-read) restarts a broker found running stale code, and a persistent broker self-exits on detecting an in-place upgrade so launchd relaunches it fresh. NewgetCliVersionFresh()re-readspackage.jsonto detect the swap. - No hot-path cost: all checks live on existing control-plane paths (postinstall, the broker sweep,
ensureAgentRunning), never on a per-secret-read. macOS only. Complements #412 (daemon session-sync memoization) by ensuring the daemon actually runs that code.
agents secrets start: persistent secrets-agent service (fixes the broker under heavy load)
- On a heavily-loaded machine (many concurrent agents, high load average) the on-demand broker — a full CLI cold-start — couldn't get scheduled enough CPU to finish booting and bind its socket, so
unlock/auto-cache silently failed and reads kept prompting. Newagents secrets startinstalls the broker as a launchd user service (RunAtLoad+KeepAlive,ProcessType: Interactivefor foreground scheduling priority): it starts once and stays up for the whole login session, so every read just connects — the cold start happens once (and launchd retries until it wins), never per read.agents secrets stopremoves it;agents secrets statusshows whether it's installed. unlockand the auto-cache worker now install/kickstart this service automatically viaensureAgentRunning, falling back to the old one-off detached spawn only if the service path is unavailable. So the persistent broker is set up on first use with no extra step.- macOS only. Security model unchanged: in-memory only, per-bundle TTL, wiped on screen-lock/sleep.
Fix: secrets-agent auto-cache now survives a slow broker cold-start under load
secrets.agent.auto(auto-cache on first read of asession-tier bundle) used a fire-and-forget inline loader that gave up connecting to the broker after 3s. But the broker it spawns is itself a full CLI cold-starting; under heavy load (many concurrent agents) that can exceed 3s, so the loader quit before the broker bound and the cache silently never populated — every read kept prompting. The auto-load now runs through a detachedsecrets _agent-loadworker that reuses the robustensureAgentRunningpath (spawn-then-ping, 20s budget) and loads synchronously, so it reliably populates even when the broker is slow to start. Manualagents secrets unlockwas always reliable and is unchanged. (secret values still travel over stdin, never argv.)
agents secrets unlock: a secrets-agent that ends Touch ID prompt spam (macOS)
- macOS pops a Touch ID prompt per bundle, per process — the biometry assertion is process-local and macOS refuses to cache
kSecAccessControl+biometry items, so running several agents at once (agents teams, parallelagents run --secrets) re-prompts once per process. Newagents secrets unlock <bundle>reads the bundle once (one prompt) and holds the resolved env in a local broker; every later resolution —agents run, teammates, browser profiles, the routines daemon — is served from memory over a user-only Unix socket (~/.agents/.cache/helpers/secrets-agent/,0700) with no prompt.agents secrets lockwipes it;agents secrets statusshows what's held and when it locks. The hold also ends on TTL expiry (default 24h,--ttl) and on screen-lock / sleep. - Opt-in by construction: if you never
unlock, resolution is byte-for-byte the existing keychain path — guarded behind a singleagentSocketExists()stat. The single integration point isreadAndResolveBundleEnv, so every consumer benefits without per-call-site changes. Broker-served reads are tagged"source":"agent"in the audit log. - Security trade-off (documented in
docs/secrets.md): while unlocked, a same-user process that can reach the socket reads the bundle silently — the same trust boundary the keychain already concedes (the ACL is user-presence, not code-identity), minus the visible prompt. Bounded by explicit per-bundle opt-in, TTL, screen-lock/sleep auto-lock, andlock. - Snapshot semantics:
unlockfreezes a bundle's dynamicexec:/env:/file:refs at unlock time; keychain and literal values are unaffected. - Release note: auto-lock on screen-lock/sleep adds a
watch-locksubcommand tokeychain-helper.swift. The signed helper must be rebuilt + re-notarized and its sha re-pinned (scripts/build-keychain-helper.sh,scripts/Agents CLI.app.sha256) for that path to ship; until then the agent degrades gracefully to TTL-only locking. Source:src/lib/secrets/agent.ts.
Per-bundle tiers + opt-in auto-cache for the secrets-agent
- Bundles now carry a tier (
agents secrets tier <bundle> [biometry|session], or--tieroncreate).biometry(default) is today's behavior — only an explicitunlockputs it in the agent.sessionmakes a bundle agent-eligible. - New
secrets.agent.auto: trueinagents.yaml(default off): the first real keychain read of asession-tier bundle auto-loads it into the broker in the background (no added latency, secret passed over stdin not argv), so the next concurrent run reads it silently — no manualunlock. Abiometry-tier bundle is never auto-held. - A
nonetier (items without the biometry ACL, fully silent, no agent) is intentionally not offered yet — it needs a separate signed-helper change and is the global downgrade the agent exists to avoid. - Default secrets-agent TTL is 24h.
Headless Linux: agents secrets works out of the box when the keyring is locked
- On a headless server the libsecret/GNOME-keyring collection is locked, so the encrypted-file fallback is the only option — but it previously hard-failed unless
AGENTS_SECRETS_PASSPHRASEwas set, leavingagents secretssilently unusable. Now, on a headless run with no passphrase set, a random machine-local passphrase is auto-provisioned once at~/.agents/.cache/secrets/.passphrase(mode 0600) so the encrypted-file store just works.AGENTS_SECRETS_PASSPHRASEstill takes precedence (off-disk key), an existing.passphraseis reused for stable interactive/headless behavior, and interactive TTY sessions are still prompted. Security model + resolution order documented indocs/secrets.md. (#371)
agents secrets get/set <item>: raw, cross-platform keychain access for hooks
- New
agents secrets get <item>/agents secrets set <item>read and write a single keychain item by bare name (outside the bundle namespace), so shell hooks and automation have one platform-agnostic credential primitive to call instead of hardcoding/usr/bin/security(macOS-only) orsecret-tool(Linux-only).getprints the value to stdout (newline-terminated for clean$(…)capture), sends diagnostics to stderr, and exits 1 with empty stdout when the item is missing — exactly what aSessionStarthook needs to probe-and-fallback quietly. Routing goes through the existing cross-platform keychain layer: macOS via/usr/bin/security, Linux viasecret-toolwith the encrypted-file fallback. setKeychainTokennow writes bare (non-agents-cli.) items on macOS without the biometry ACL, mirroring the existing no-prompt read path for such items. This is what lets a hook read e.g.linear-api-keysilently on every launch — routing it through the Touch ID helper would attach an ACL the/usr/bin/securityread can't satisfy without popping the legacy password sheet. The change is purely additive: every existing caller passes anagents-cli.-namespaced item and is unaffected (still biometry-gated via the signed helper).
agents inspect summary: expanded detail for hooks, plugins, and MCP
- The bare
agents inspect <agent>/agents inspect <repo>summary no longer collapses everything to a count table. Simple kinds (commands, skills, rules, subagents, workflows) keep a count line but now preview a few names; the rich kinds get their own expanded sections: hooks show their events +matches:predicates + cache (PreToolUse(Bash) · git_dirty · prompt~"deploy" (5m cache)), plugins show version + bundle contents (v2.1.0 skills:6 commands:5 hooks:2 mcp:1), and MCP show transport + url/command. Drill-down flags (--hooks,--plugins,--mcp) and--briefare unchanged;--jsongains the structured detail additively (existing keys retained). - Hook detail joins installed hooks to the manifest by script basename (installed hooks are named after their script file while the manifest keys on the logical name), and the repo Hooks section uses the grouped hook reader so a script + its data file collapse to one clean entry.
Plugin hooks were misreported — fixed
discoverPluginHooksread the top-level keys of a plugin'shooks/hooks.json, so the official{ description, hooks: { SessionStart: [...] } }format surfaced asdescription, hooksinstead of the real events. It now reads thehookswrapper when present (falling back to top-level keys for the flat format), soagents inspect --plugin <name>and the plugin row show the actual lifecycle events (e.g.SessionStart, PreToolUse, …).
agents doctor / agents prune: precise orphan-hooks detection
- Orphan-hook detection now flags hook scripts present in a version home that no
agents.yaml/hooks.yamlentry registers — i.e. scripts that sync to disk but are never wired to a lifecycle event, so they never fire. This replaces the source-diff heuristic, which compared only against the user hooks dir and so false-flagged valid system-sourced, registered hooks (e.g.03-linear-inject,04-capture) as orphans — meaningagents prune cleanupcould have deleted live hooks. Doctor's Orphans section andprune cleanup hooksnow share this single manifest-based definition.parseHookManifestgained a silent ({ warn: false }) option so the diagnostic doesn't emit shadow/override warnings.
Regression coverage: resource sync from extras repos
- Added end-to-end regression tests (
src/lib/__tests__/extras-sync.test.ts) locking in two behaviors for repos registered viaagents repo add(~/.agents-<alias>/): a top-levelcommands/<name>.mdis written into the agent's version home onagents sync, and plugins underplugins/<name>/are synthesized into a registeredagents-<alias>marketplace on launch. Both already work inmain; the tests exercise the real sync path (no mocking, isolated$HOME) so the extras-repo behavior can't silently regress (#313, #314).
Windows: agents is discoverable right after npm i -g
- On a global Windows install, postinstall now prepends npm's global-bin dir (where
agents.cmd/agents.ps1live) to the User PATH via the .NET environment API. Node's installer normally adds it, but winget / portable / nvm-windows setups often don't — and thennpm i -g @phnx-labs/agents-clisucceeds yetagentsis "not recognized". The shims dir (claude/codex/…) is still left toagents setup, which the user can now run becauseagentsresolves. - Postinstall also detects a
Restricted/AllSignedPowerShell execution policy (which blocks the generated.ps1launchers, so even an on-PATHagentsfails in PowerShell) and prints the one-line fix (Set-ExecutionPolicy -Scope CurrentUser RemoteSigned). The policy is a security setting, so it is never changed silently — only surfaced. - Refactor: the Windows User-PATH prepend logic moved from
shims.tsinto a newsrc/lib/platform/winpath.tsleaf module (prependToWindowsUserPath,getEffectiveExecutionPolicy,blocksLocalScripts,npmGlobalBinFromEntry);addShimsToWindowsUserPathnow delegates to it. Pure helpers are unit-tested.
Factory AI Droid (first-class support)
- Add
droidas a first-class supported agent (AgentId + full registry entry for Factory AI'sdroidCLI, config in~/.factory/). Installs via the official script (curl -fsSL https://app.factory.ai/cli | sh); the binary is resolved through the standard install-script path and isolated per version via the~/.factoryconfig symlink (Droid has no*_HOMEoverride). - Resource sync wired for the four resource types Droid supports natively: MCP (
~/.factory/mcp.json), rules (nativeAGENTS.md), subagents (custom droids flattened to~/.factory/droids/*.md, with the unsupportedcolorfrontmatter key stripped), and commands (~/.factory/commands/). Skills/plugins/workflows have no Droid equivalent and are disabled; hooks/permissions are deferred. agents run droidandagents teams add … droidwork end-to-end: headlessdroid execwith mode mapping (plan → read-only, edit →--auto low, auto →--auto high, skip →--skip-permissions-unsafe),-o stream-jsonoutput,-mmodel selection, and-rreasoning effort. Routine/daemon jobs (buildJobCommand) support Droid too.- Known limitation:
agents teamsrenders Droid events through the generic normalizer pending a verifieddroid exec -o stream-jsonevent schema; structured tool/file categorization will follow. Session reading and Factory cloud dispatch remain follow-ups.
agents upgrade now refreshes the macOS Keychain helper
- Upgrading runs
npm install -g … --ignore-scripts, so the postinstall that installs the signed Keychain helper never fired — a user upgrading away from a broken build (e.g. the entitlement-less 1.20.4 helper that failedSecItemAddwitherrSecMissingEntitlement -34018) kept the broken helper until the lazy staleness check ingetKeychainHelperPath()happened to repair it on their next secret operation.installResolvedPackagenow force-refreshes the helper (ensureKeychainHelperInstalled({ forceReinstall: true })) on darwin after the install, so both the explicitagents upgradeand the auto-update prompt land the fixed helper immediately. Best-effort and non-fatal: an upgrade never fails because the helper could not be reinstalled, andagents helper install --forceremains the manual path.
agents inspect <repo> summary now shows what's actually inside, not just counts
- The bare repo summary gained four enrichments so it reads as an inventory instead of a tally: (1) resource name previews — each kind lists its first few names with a
…(+N)tail; (2) manifest summary —agents.yamlis parsed for itsrun.<agent>.strategyand anyagents.<agent>version pins, shown undermanifestsinstead of just the filename; (3) git detail — last commit (sha, subject, relative time), ahead/behind upstream when non-zero, and the names of dirty files; (4) size + file counts — total repo size and a per-kind byte size.--jsoncarries all of it (git.lastCommit,git.ahead/behind,manifest,size, and per-kind{count, bytes, files, names});--briefstill skips resources and size. - Fixed a path-parse bug surfaced by the dirty-files list: the shared git helper trimmed leading whitespace, which clipped the first character off the first
git status --porcelainpath; status is now read untrimmed.
agents inspect . reads the project .agents/, and plugin drill-down shows bundled skills
agents inspect .(and any path to a repo root) now resolves to the project's nested.agents/tree when that tree is a populated DotAgents root, instead of the project root itself. Previously a top-levelagents.yamlversion-pin or an unrelated sourceskills/dir at the repo root was mistaken for a DotAgents root, soinspect .reported the wrong directory's resources (e.g.plugins 0while the real.agents/plugins/held a plugin). A bare.agents-named dir still resolves to itself, and standalone clones / extra repos that keep resources at the top level (using.agents/only for worktrees) are unaffected — their nested.agents/is not a DotAgents root, so the top level still wins.agents inspect <repo> --pluginsnow reads plugin bundles through the plugin discoverer: the list shows each plugin's manifest description, and drilling into one (--plugins <name>) reports its bundled skills, commands, subagents, hooks, MCP servers, and version. Previously plugins were treated as opaque directories with no description and no view into what they ship.
Single-typo agent names auto-correct everywhere, not just agents run
agents view cladueused to printUnknown agent 'cladue'even thoughagents run cladueauto-corrected.resolveAgentName— the canonical resolver behindview,usage,inspect,doctor,sync,models,skills,hooks,import,sessions --agent, and everyagent@versionspec (agents add claud@latest,agents use codx@2.1.170) — now falls back to Damerau-Levenshtein distance-1 matching against canonical ids and multi-letter aliases:cladue->claude(transposition),kim->kimi,codx->codex,gemni->gemini.- Corrections apply only when unambiguous: every distance-1 candidate must agree on one agent.
kiri(one edit from bothkiroandkimi) and inputs under 3 characters still error.agents runkeeps its existing exact -> profile -> workflow -> fuzzy precedence, so a profile namedclaudstill beats the typo correction. - Fixes
kimibeing listed as a valid agent but missing from the alias map —agents view kimipreviously errored. Addedkimi/kimi-codeentries.
1.20.7
agents inspect — DotAgents repo targets (#256)
agents inspectnow accepts a DotAgents repo as the target, not just an installed agent:user(/.agents/),/.agents/.system/),system(project(nearest.agents/from cwd), any extra-repo alias registered viaagents repo add, or a filesystem path. Paths accept either a repo containing a.agents/dir or a DotAgents root directly.- Repo summary shows the root (OSC-8 linked), git branch / dirty count / origin URL, manifest files (
agents.yaml,hooks.yaml), and per-kind resource counts. All existing drill-down flags (--commands,--skills,--plugins, ... with fuzzy queries and--json) work against the single repo root — what is physically in that repo, with no layered resolution or same-name overrides. - Resolution precedence: a directory that is itself a DotAgents root wins over its nested
.agents/, so extra repos that keep resources at the top level and use.agents/only for worktrees resolve to their real resources. - Unknown targets now error with both halves of the namespace: the known agent ids and the available repo targets (built-in layers plus registered aliases).
scripts/install.sh — bash 3.2 fix (#256)
set -uplus"${BUILD_ARGS[@]}"on an empty array aborted the dev install withBUILD_ARGS[@]: unbound variableunder macOS system bash; the expansion is now guarded with${BUILD_ARGS[@]+...}.
1.20.5
agents inspect — per-agent+version detail view with drill-down (#217)
- New top-level command
agents inspect <agent>[@version]. Summary mode shows install path, config symlink target, shim path, versioned alias, run strategy, capability table (hooks/mcp/skills/commands/subagents/plugins/workflows/rules/allowlist), resource counts with project/user/system scope breakdown, and session total. Replaces the awkwardagents view <agent>@<version>deep-detail mode as a dedicated verb;viewitself is unchanged. - Drill-down flags for every resource kind —
--commands,--skills,--hooks,--mcp,--rules,--plugins,--workflows,--subagents. Bare flag lists every entry; passing a positional query fuzzy-searches that kind, ranking exact > substring > Damerau-Levenshtein. Zero matches exit 1 with the three closest names as suggestions. One drill-down at a time (validation error otherwise).--jsonworks with summary and every drill-down for scriptable consumption. - Resource names render as OSC-8 terminal hyperlinks to the marker file (
SKILL.md/WORKFLOW.md/AGENT.md) for clickable navigation in modern terminals (Ghostty, iTerm2, WezTerm) — no inline path noise. Plain text on terminals without OSC-8 support. - MCP detail intentionally suppresses path and env values to avoid leaking secrets — only the server name, scope, and version reach the output.
- Removes the deprecated
agents statusalias forview @default. Top-level help text updated; no consumers referenced it.
Headless Linux: encrypted-file fallback when libsecret collection is locked (#183)
- On server-class Linux (Ubuntu 24.04 over SSH on the reporter's box),
agents secrets create xfailed withsecret-tool: Cannot create an item in a locked collection. Diagnosis in the issue:gnome-keyring-daemonis running and D-Bus is reachable, but the defaultlogincollection is locked because no graphical login has fed the daemon the passphrase, andsecret-toolfromlibsecret-toolshas no--collectionflag so it can't target the unlockedsessioncollection. This madeagents secretseffectively macOS-only on any headless box. src/lib/secrets/linux.tsnow transparently falls back to a file-based AES-256-GCM encrypted store at~/.agents/.cache/secrets/<item>.enc(mode 0600, per-file random scrypt salt + 96-bit IV, GCM auth tag). The encryption key is scrypt-derived from a passphrase read fromAGENTS_SECRETS_PASSPHRASE(preferred) or a TTY prompt via/dev/ttywithstty -echofor non-echoing input. The fallback also activates whenlibsecret-toolsis not installed at all butAGENTS_SECRETS_PASSPHRASEis set, so a fresh install can store secrets without any apt-get step.- The decision is cached per process; on first activation we emit one stderr line:
[agents] secret-service collection locked, using file-based store at <dir>. TheKeychainBackendinterface insrc/lib/secrets/index.tsis unchanged —has/get/set/delete/listwork identically against either backend, sobundles.ts,sync.ts, and every consumer above it sees no API change. - Items written into the file store before the fallback was added remain accessible only via libsecret if/when the collection is later unlocked; this PR does not migrate stranded items in either direction — the user simply re-creates them on a freshly headless box.
1.20.4
Plugin marketplace sync (skip outside-pointing symlinks)
copyPluginToMarketplaceusedfs.cpSync(plugin.root, dest, { recursive: true, dereference: false }), which faithfully preserved every symlink — including the ones plugin authors put at the top of their plugin source for prompt-side references (the rush plugin'sapp -> ../../../rush/app,web -> rush/web,widgets -> rush/widgets). Those targets resolve to the rush monorepo (~8.7 GB ofapp/including node_modules + .next builds, 782 MB ofweb/, plus 463 MB brand-assets). Every claude version got a full set of those symlinks in~/.claude/plugins/marketplaces/agents-cli/plugins/rush/. When the consumer (Claude Code, OpenClaw) discovers plugins, it walks the marketplace tree and follows those symlinks — producing multi-minute startup hangs.- The copy now walks the source tree and drops symlinks whose
realpathescapes the plugin root, leaving internal symlinks intact (cpSync rewrites internal targets to absolute paths into the source tree, which the consumer still resolves correctly). One informational line per plugin lists the skipped names so plugin authors notice. - Existing per-version marketplace directories still hold the bloat from prior syncs; clean up with
rmagainst~/.claude/plugins/marketplaces/agents-cli/plugins/*/{app,web,widgets,*-symlinks-that-escaped}then re-runagents pullor any plugin sync to re-copy with the filter.
1.20.3
agents run startup latency (stale-while-revalidate the usage probe + memoize agents.yaml)
- The default
agents runstrategy isavailable, which callsgetUsageInfoForIdentityto skip rate-limited accounts. With a 2-minute cache, every cold invocation past that window made a blockingfetchtoapi.anthropic.com/api/oauth/usage(5 s timeout, plus an optional 15 s OAuth token refresh) beforespawn(claude)— soagents run clauderegularly stalled 5–8 s with nothing on screen after the rotation banner. - The cache is now stale-while-revalidate: fresh (<2 min) returns instantly with no network, stale-but-recent (<24 h) returns the cached snapshot instantly and refreshes in the background, and only a fully cold / >24 h cache blocks on the live fetch. The background refresh defers its first await past
setImmediateso the synchronous Keychain CLI call (security find-generic-password, invoked byloadClaudeOauth) cannot block the foreground caller — that's how an SWR returns "instantly" even while the refresh is technically still on its first sync step. readMeta()had ametaCachemodule global pluswriteMetaUnlockedcache-invalidation logic wired in years ago — but no read path ever consulted the cache. So every call did 2xfs.readFileSync+ 2xyaml.parseon system + useragents.yaml, and hot callers (getConfiguredRunStrategy,getGlobalDefault,getVersionResources,ensureVersionResourcePatterns) fire it multiple times peragents run. The read path now consults the cache, keyed on the combined mtime of both source files — out-of-band edits still invalidate on the next stat, and in-process writers already clear it.
1.20.2
Grok and Antigravity Support & Documentation
- Grok CLI Integration: Added support for installing Grok via
agents add grok@<version>, which invokes the official xAI installer with the specified version. Grok MCP server configuration paths (viaconfig.toml) and memory file mapping are now correctly documented. - Antigravity (AGY) CLI Integration: Added support for the Google Antigravity CLI. Since the AGY installer doesn't support version-pinned installs currently,
agents add agyuses thelatestversion. Documented the canonical config path~/.gemini/antigravity-cli/and itsmcp_config.json. - Documentation: Updated
02-resource-sync.mdto reflect accurate MCP mappings and memory file symlinks for both Grok and Antigravity. - Profiles: Hardened presets with verified 2026 model IDs and added generic proxy configuration. Show custom profiles in agents view.
1.20.1
Agents selector (auto-install missing versions + unified @all everywhere)
--agents claude@2.1.999used to hard-error when 2.1.999 wasn't installed. Now the CLI prompts to install it inline and continues (auto-install with--yes). No more breaking flow to runagents addfirst.--agents claude@alland the bareallliteral now work across every callsite that takes--agents— previouslyagents install gh:...,mcp register,mcp remove, and inlinemcp addhad diverged from the canonical syntax and threw "Version all is not installed" despite the help text advertising it. Selector is unified end-to-end.
Prompt (fail loud on non-TTY + @all syntax in picker)
- Scripts that called
agents <resource> addwith no--agentsand no--yesused to silently auto-pick a default version. That hid scripted misuse behind unpredictable picks. The non-TTY path now throws with a clear pointer at the new syntax:--agents claude@all(every installed version of Claude),--agents all(every capable agent at all versions), or--agents claude@2.1.141(one specific version). --agentsparsing in<resource> addunderstands@alland the bareallliteral;promptAgentVersionSelection's picker surfaces version counts when there's more than one installed, mirroring what@allwould target.
Resources / install (gh: form sniffs every type, mcp add gh:, --names + @all unified across resource add)
agents install gh:<owner>/<repo>now sniffs every resource type in the source repo (commands, skills, hooks, MCP, permissions, profiles, subagents, workflows) instead of requiring one--typesper kind. Pass--types skills,workflowsto narrow.- New
agents mcp add gh:<owner>/<repo>form — install MCP servers directly from a git source, parallel to the other<resource> add gh:paths. <resource> addaccepts--namesand@alluniformly across commands, skills, hooks, MCP, permissions, profiles, rules, subagents, workflows — same flags, same semantics, regardless of resource kind.
Profiles (interactive create wizard, gateway + self-hosted presets)
- New
agents profiles createcommand — interactive wizard to assemble a profile from gateway or self-hosted presets (OpenRouter, OpenAI-compatible) without hand-writing YAML. --smoke-testexercises the resolved env block against the configured endpoint before writing the profile.
Feedback (in-CLI bug / idea / question routing)
- New
agents feedbackcommand — collects a short description + optional category (bug, idea, question) and routes to the project's tracker without leaving the terminal.
Routines (real exit codes for detached scheduled runs)
monitorRunningJobsused to hardcodestatus: 'failed'whenever it detected that a detached child had exited —executeJobDetachedfires-and-forgets, so the real exit code was unreachable. Every scheduler-driven routine ended up labeledfailed/exitCode: null, even when the agent completed cleanly.- Fix: when finalizing a vanished child, scan the tail of its stream-json
stdout.logfor Claude'stype: resultterminator (which carriesis_error). If found, setstatusandexitCodefrom it. Only fall back tofailedwhen no result marker exists (process was killed mid-run). - Routines list cell rendering hardened around 7-day retention boundaries.
- Codex/Gemini run finalization continues to fall back to
faileduntil their stream tail parsers are added.
Security
security(cli): eliminatedshell: truefrom manifest-driven installs — closes a command-injection vector ininstall/addpaths that took git URLs or shell-interpolated metadata.security(logs): prompts and tokens are redacted beforeevents.jsonlis written, and event retention is shortened from 30d to 7d. Reduces blast radius on accidental disclosure.security(exec): strip loader env vars (DYLD_*,LD_*,NODE_OPTIONS) from environments propagated to child agents — avoids passing host-process loader state into spawned binaries.security(browser): CDP origin allowlist replaces the previous wildcard — onlylocalhostand explicitly configured browser hosts can speak CDP into a session.security(ci): keychain helper SHA is verified at publish time, so a tampered helper binary cannot ride a release.
Copilot (fix user-scoped MCP path)
- Copilot's user-scoped MCP path now correctly resolves to
mcp-config.json(the path the IDE actually reads) instead of the legacy filename. Fixes user-level MCP registrations not appearing in Copilot sessions.
Docs
- Full docs site IA shipped: browser, cloud, computer, hooks, plugins, profiles, pty, secrets, subagents, teams, workflows.
- Brand identity block:
agents-cliis Phoenix Labs OSS, not part of the Rush brand — guards downstream agents against pulling Rush styling into this project.
Build / install
- Staged dev install tarball strips
prepackandpreparehooks so side-by-side dev installs don't accidentally re-run the full publish pipeline locally. test(jobs): un-break 3 stale assertions on main.
1.20.0
Routines (overdue detection + catchup)
- Detect routines whose most recent scheduled fire was missed (laptop off, daemon crashed, reboot). The daemon logs them on startup and pops a native desktop notification (
osascripton macOS,notify-sendon Linux). agents routines listannotates overdue rows with(overdue)and prints a footer pointing at the catchup command.- New
agents routines catchupcommand: lists overdue routines and fires them in the background under the scheduler.--dry-runlists without triggering. JobScheduler.schedulenow sets croner'scatch: trueand forwardstimezonedefensively, so a synchronous throw in one job's callback can't kill the whole cron loop.
Landing page (agents-cli.sh)
- Expanded the homepage with seven new sections: rotate accounts (
--rotate), parallel teams (agents teams), browser automation, cross-agent session search, routines/cron, keychain secrets, and machine-to-machine sync (agents drive). - Rewrote meta description + lede to spell out the actual feature set (pin versions, swap models, rotate accounts, drive a browser, spawn parallel teams, schedule on cron) instead of just "same interface, on your machine."
Codex (commands-as-skills sync fix)
- Fix recurring "N commands new" prompt on
agents view codexfor Codex >= 0.117.0.getActuallySyncedResourcesnow detects converted command-skills via theagents_commandmarker in~/.codex/skills/<name>/SKILL.mdinstead of only scanning the empty legacyprompts/directory. - Summary and selection prompts are version-aware: the static
COMMANDS_CAPABLE_AGENTSgate is replaced bysupports(agent, 'commands', version)so the "X commands" line only appears for versions that can actually take them. - Generalize
shouldInstallCommandAsSkillbeyond Codex — any agent where commands are gated off and skills are on (e.g. Grok) now gets the same automatic slash-command → skill conversion at install/sync time.
Grok Build (first-class support)
- Add
grokas a first-class supported agent (AgentId + full registry entry using official~/.grok/README.mdpaths). - Implement proper binary resolution from
~/.grok/downloads/. - Add
GROK_HOMEisolation to generated shims for true versioned config (skills, hooks, plugins, agents/, MCP, memory, etc.). - Extend
installVersionto support Grok via its official installer script (curl ... -s <version>). - Update shims, exec templates, MCP path helpers, session helpers, unmanaged detection, and docs.
agents add grok@<ver>,agents use grok@<ver>, resource sync, and shims now work end-to-end for Grok Build.
Browser
agents browser start --recordconvenience flag for one-shot recording sessions.- Auto-discover per-site
SKILL.mdonbrowser startso skills appear under the active task without manual wiring. - Auto-pick a Chromium-family browser when
--profileis omitted; the limitation is surfaced in--helpand the auto-pick error. - No more stacktraces when the daemon is down or CDP is unreachable — error paths print a single human-readable line.
- Drop the Playwright
bundled-chromiumdevdependency.
Secrets / Keychain
agents secrets listandagents run --secrets <bundle>collapse to one Touch ID prompt per bundle instead of one per key. Previously every secret in a bundle would re-prompt for keychain unlock.
Sessions
- Extract
groupActiveSessionsinto a tested helper for--activewindow grouping. - Propagate
windowidfrom live-terminals into the active session record.
Copilot
- Emit
COPILOT_HOMEin the shim and exec env builder for versioned isolation. - Wire the Copilot session dir and
.jsonlextension into the sessions reader.
OpenClaw
- Carry OpenClaw user data forward on version switch.
Teams
- Warn loudly when
--afterteammates reference a name whose watch process never launched, instead of silently sitting in pending state.
Plugins
- Use
'directory'source discriminator (not'local') for marketplace registration so plugins reload correctly.
Dependencies
- Bump
@inquirer/prompts7.10.1 → 8.5.1,diff8.0.4 → 9.0.0,tsx4.22.2 → 4.22.3,actions/setup-node4.4.0 → 6.4.0.
1.18.6
Claude
- Add auto permission mode support for Claude runs.
- Remove a dead automatic mode flag from the Claude command template.
Teams
- Fix the cycle-detection test to accept running or failed teammate status.
1.18.5
Browser
- Breaking: action commands no longer accept a leading
<task>positional. Bind the task once per shell viaAGENTS_BROWSER_TASK, or pass--task <name>for a per-call override:
Env vars are per-process, so parallel agents in different shells never collide.export AGENTS_BROWSER_TASK=$(agents browser start --profile work) agents browser navigate --url https://example.com agents browser click 42 agents browser screenshot - Breaking: URL/text/expression/scroll arguments are now flag-only — positional forms removed:
navigate --url <url>(wasnavigate <url>)tab add --url <url>(wastab add <url>)type <ref> --text "..."(wastype <ref> "...")evaluate --expression "..."or--file <path>(wasevaluate "...")scroll --dx <n> --dy <n>(wasscroll <dx> <dy>— fixes negative-value parser collision)
screenshotprints a one-line auto-save tip on stderr when--outputis not passed, so agents see the directory without having to dirname() the path.
1.18.4
Browser
agents browser startwrites the resolved task name to stdout as a single line (e.g.swift-crab-falcon-a3f92b1c), and routes the human commentary ("Started task ... with tab ...", "Tip: export AGENTS_BROWSER_TASK=...") to stderr. This makesT=$(agents browser start --profile X)Just Work — no--quietflag needed.- Auto-generated task names are now three English words plus an 8-char hex
suffix, e.g.
swift-crab-falcon-a3f92b1c. Memorable, distinct, 32 bits of entropy so parallel agents never collide. Daemon retries on the (vanishingly rare) name clash and rejects explicit--task <name>values that already exist. agents browser start --profile <name>now pre-validates the profile locally before touching the daemon. Missing profile prints the list of available profiles plus the create-command hint instead of a generic error.agents browser tab listis nowagents browser tabs(top-level), pairing cleanly withagents browser tab focus <id>. The oldtab listform is removed.agents browser --helpis reorganized by mental model — Session lifecycle, Drive the page, Capture evidence — instead of an alphabetical dump. Rare commands stay under a trailing Commands section.- BREAKING:
agents browser profiles primeandagents browser profiles launchare removed. Both were thin duplicates ofstart. For first-run onboarding, justagents browser start --profile <name>and complete the interactive screens in the browser; the user-data-dir persists across runs. The daemon'slaunch-profileIPC action is also gone. - Named endpoint presets per profile. One profile can now cover the local
and remote variants of the same app instead of forcing two parallel
profiles. YAML supports both the legacy
endpoints: [url]shape and the new map form:name: rush browser: custom electron: true endpoints: local: target: cdp://127.0.0.1:9223 binary: /Applications/Rush.app/Contents/MacOS/Rush mac-mini: target: ssh://mac-mini?port=9223 # no binary — daemon attaches only defaultEndpoint: localagents browser start --profile rush --endpoint mac-minipicks a specific preset;--endpointfalls back todefaultEndpointor the first preset. Pre-validated client-side so a typo doesn't waste an IPC round-trip. Per-endpointbinaryandtargetFilteroverride the profile-level fields.agents browser profiles showlists every preset, marks the default, and shows per-endpoint overrides. - The daemon's runtime identity is now
<profile>@<endpoint>so the same profile can run at multiple endpoints concurrently without colliding on pid/port files.agents browser statusandtasksshow the composite name, so you can tell at a glance which variant a task is using. agents browser screenshot --quality rawcaptures pixel-faithful PNG (no downscale) for archived QA evidence. Default stayscompressed(JPEG, capped near 100 KB) for chat-injected screenshots.- New
agents browser record start/agents browser record stoprecording verbs. Captures via CDPPage.startScreencast, pipes frames into ffmpeg (image2pipe → libvpx-vp9) and writes a webm undersessions/<task>/recordings/. Bounded three ways —--fps(default 5),--duration(hard cap, default 60s),--max-mb(default 25); whichever fires first auto-finalizes the file. Requires ffmpeg on PATH (brew install ffmpeg).
1.18.3
Plugins (#22)
agents plugins syncnow installs plugins via Claude Code's native marketplace path —<versionHome>/.{claude,openclaw}/plugins/marketplaces/agents-cli/plugins/<name>/— instead of flattening contents into~/.claude/skills/<plugin>--<skill>/. Skills resolve as/plugin:skill(the documented form) instead of/plugin--skill. Plugins appear in Claude's/pluginsUI under Installed and respond to/plugin enable,/plugin disable.- A synthetic
agents-climarketplace is materialized per version:.claude-plugin/marketplace.jsonis synthesized from discovered plugins, an entry is added to<versionHome>/.claude/plugins/known_marketplaces.json, andsettings.json#enabledPlugins["<plugin>@agents-cli"]is flipped totrue. Removal is symmetric — last plugin out drops the marketplace dir and the known_marketplaces entry. - The sync now copies the whole plugin tree verbatim (single
fs.cpSync) instead of re-implementing per-feature merges intosettings.json. Every Claude plugin feature — skills, commands, subagents, hooks,.mcp.json,.lsp.json,monitors/monitors.json,bin/,settings.json— is preserved end-to-end.${CLAUDE_PLUGIN_ROOT}and${CLAUDE_PLUGIN_DATA}are left intact so Claude can expand them at runtime; only${user_config.*}(agents-cli-specific) is pre-expanded in copied text files. - Legacy dual-dash layout from prior versions is auto-migrated at sync time —
~/.claude/skills/<plugin>--*,~/.claude/commands/<plugin>--*.md,~/.claude/agents/<plugin>--*.md,plugin-bin/<plugin>/, and namespacedmcpServers["<plugin>--*"]entries are removed after the marketplace install succeeds. agents plugins view <name>surfaces every feature the plugin ships: Skills, Commands, Subagents, Hooks, MCP Servers, LSP Servers, Monitors, Bin, Scripts, Settings. Theagents view <agent>@<version>Plugins section gains MCP/LSP/Monitor/Bin/Settings counts. NewdiscoverPluginMcpServers,discoverPluginLspServers,discoverPluginMonitorshelpers parse.mcp.json,.lsp.json, andmonitors/monitors.json.
1.18.2
Teams
- Dropped
~/.agents/teams/config.jsonentirely. It duplicated information agents-cli already has — agent commands, enabled flags, model defaults, provider endpoints — none of which the team runner was actually reading. Teams now discover agents vialistInstalledVersions()(the same sourceagents viewuses) and invoke them via the canonicalagents runsubcommand. One spawn path, one canonical exec module (src/lib/exec.ts). The deprecatedAGENT_COMMANDS,applyEditMode,applyFullMode,readConfig,writeConfig,setAgentEnabled,AgentConfig,SwarmConfig,ProviderConfig,ModelOverrides,ReadConfigResult, andEffortLevel(the persistence-module copy) exports are removed from@phnx-labs/agents-cli/teams. Migration deletes both~/.agents/teams/config.jsonand the legacy~/.agents/config.json. ~/.agents/teams/registry.jsonmoves to~/.agents/.history/teams/registry.json— it's per-machine runtime state (timestamps + absolute worktree paths) and shouldn't be synced across machines viaagents repo push.- New
agents run --quietflag suppresses the rotation banner andRunning: …preamble lines. Used by the team runner so stream-json events reach the parser without non-JSON preamble.
Dev builds
- The CLI auto-detects dev builds (version stamped
0.0.0-dev.<sha>byscripts/install.sh, or invoked from a working tree where<cli-dir>/../.git/exists) and defaultsAGENTS_NO_AUTOPULL=1,AGENTS_SKIP_MIGRATION=1, andAGENTS_CLI_DISABLE_AUTO_UPDATE=1. No more typing those three env vars on every iteration. Production installs (registry global, no.git/at package root) are unaffected.
1.18.1
Fixes
scripts/build.shnow sets mode0o755on every file declared inpackage.json#binaftertscemits dist/. Newer npm versions preserve file mode from the published tarball and do NOT auto-chmod the bin target duringnpm install -g, so 1.18.0 shipped with mode-644 entrypoints. Users hitzsh: permission denied: agentsafter auto-update. Re-install to recover:npm install -g @phnx-labs/agents-cli@latest.- New
scripts/install.shbuilds the working tree as a side-by-side dev install at$HOME/.local/agents-cli-dev/, symlinked into$HOME/.local/bin/agents. The registry install is never touched —agents --versionshows0.0.0-dev.<sha>[-dirty]when the dev build is on PATH.
1.18.0
Plugins
~/.agents/plugins/is now a first-class user-resource location, alongsideskills/,commands/,hooks/, etc. — git-tracked as source of truth. Previously,migrateRuntimeToCachemoved~/.agents/plugins/into~/.agents/.cache/plugins/on every CLI version bump, silently destroying user-authored plugins in the working tree. Fixed by (1) removing the destructive move, (2) restoring discovery to the user-root, (3) a one-shot reverse migration that moves any cached plugins back to the user-root without overwriting an existing user-root copy, and (4) decoupling the migration sentinel from the binary version so migrations only re-run on real schema bumps. (#20)agents view <agent>@<version>gains aPluginssection listing each plugin that supports the agent, with a(N skills, N commands, …)content summary and an OSC 8 hyperlink to the plugin source.
Hooks
getAvailableResourcesand the version-home sync now treat only executable files inhooks/as hooks. Docs (README.md) and data files (promptcuts.yaml) that live alongside hooks no longer get synced into version homes as hooks, and the orphan-pruner trusts the manifest's declared hook list rather than re-scanning every source dir.
1.17.6
Workflows
- New
workflowsskill — author-and-run guide for workflow bundles (WORKFLOW.mdfrontmatter,subagents/directory for multi-agent pipelines, scopedskills/andplugins/, sharing viaagents repo pushor GitHub install). Calls out the--mode plandeadlock that bites workflows which need to post comments or edit files. agents workflows --helprewritten with a structure diagram, project > user > system resolution order, and an explicit note that workflows mutating state need--mode editor--mode fullto avoid a headless deadlock atExitPlanMode.- README gains a
Workflowssection between Teams and Browser covering the bundle layout, frontmatter, subagents/skills/plugins, and the--moderequirement.
1.17.4
Browser
agents browser typenow detects rich-text editor frameworks (Lexical, ProseMirror, Slate, Draft.js, Quill, CKEditor5, Trix) by walking up to 5 ancestor levels from each textbox and tagging refs with[editor=<framework>]. Editor-tagged refs route through the WHATWGbeforeinputdispatch (InputEvent('beforeinput', { inputType: 'insertText', ... })) for Lexical/ProseMirror/Slate/Quill/CKEditor5/Draft andel.editor.insertString()for Trix.agents browser refs --jsonsurfaces the neweditorfield, andtype --clearprepends a select-all +deleteContentBackwarddispatch before inserting.- Plain-input reliability also improved:
typeTextnow issues a single CDPInput.insertTextinstead of per-characterdispatchKeyEvent, so framework-controlled inputs (React, Vue, Solid, MUI/Chakra/MantineTextField, masked-number fields, Canva-style pickers) actually receivebeforeinput/input/textInputevents.focusNodefalls back to the first focusable descendant whenDOM.focusthrows "Element is not focusable" — fixes wrapper-ref UIs like Slack composer, Linear comments, Notion blocks, and every MUI/Chakra/MantineTextField. (#12)
1.17.3
Browser
agents browser profiles creategains--electron,--binary, and--target-filterfor driving Electron desktop apps (Canva, Slack, etc.) that expose multiple CDP page targets. The picker matches byurl:<substring>ortitle:<substring>(case-insensitive) and falls back to a skip-invisible heuristic when no filter is set; misses against an explicit filter throw with the full candidate list.BrowserService.evaluatenow usesawaitPromise: trueand surfacesexceptionDetailsso async script errors propagate as thrown errors. (#14)
Secrets
agents secrets listrework — drop the misleadingSENSITIVEcolumn and addSYNC(iCloud yes/no) plusCREATED/UPDATED/USEDrelative-age columns. Timestamps live inside the keychain bundle JSON, are stamped on write (created sticky, updated always advances), and on resolve via a 60s throttle. SetAGENTS_NO_USAGE_TRACK=1to disable the usage stamp.agents secrets viewshows the matching absolute ISO + relative age fields. (#18)
1.17.2
Fixes
- Auto-update prompt no longer hangs in non-interactive environments (CI, k8s pods, cloud sandbox factories). The TTY check now requires both stdin and stdout to be terminals before prompting, and
AGENTS_CLI_DISABLE_AUTO_UPDATE=1forces the check off entirely for headless deploys. (#15)
1.17.1
Agent management
agents import <agent>— adopt an existing global npm/homebrew install into agents-cli management without reinstalling. Supports--version,--from-path,--yes. The imported version is wired in as the global default with shim + versioned alias so it behaves the same as a freshlyagents add'd install.
1.17.0
Workflows: a new first-class resource
agents workflows list / add / remove / view— WORKFLOW.md bundles (with optionalsubagents/,skills/,plugins/) install from GitHub or a local path and resolve through the same system → user → project layer model as every other resource.agents run <name>resolves a workflow or named subagent as an orchestrator: prepends WORKFLOW.md / AGENT.md body to the prompt, copiessubagents/*into~/.claude/agents/for Agent-tool discovery, and syncs workflow-scopedskills/andplugins/at run time.agents viewnow has a workflows section.
Browser
- Port-per-profile with auto-allocation and viewport enforcement — concurrent browser profiles no longer collide on CDP ports.
agents browser scrollplus newprofiles launch,profiles doctor,profiles prime, viewport position, and port diagnostics commands.agents browser profiles listnow shows a description column when any profile has one.isProcessRunningtreats EPERM as process-alive (fixes false-negative on sandboxed processes).
Cloud dispatch
--balancedstrategy and--upload-account-tokensflag on cloud dispatch.- Remote account API client;
--balancedskips the client manifest path.
Plugin system extension
- Plugins now ship with
commands/,agents/,bin/, MCP configs, settings, andinstall/updatehooks. Discovery and sync extended end-to-end.
Secrets
agents secrets import <bundle> --from-1password/export <bundle> --to-1passwordwith vault picker, skip-empty-fields on import, overwrite-only-with---forceon export. Wires the existing 1Password library into the CLI.
Sandbox
scripts/sandbox.sh --pr— author real PRs from a Crabbox-isolated box via a bare-mirror clone off main.sandbox.sh --linearand--post-filepost run output to Linear tickets.- Dynamic GitHub App token,
ghCLI installed, stale git credentials cleaned.
Sessions / SQLite concurrency
- Scan coordinator prevents concurrent session indexing.
- SQLite concurrency hardened with
BEGIN IMMEDIATEand ledger recheck on contention. - Session discovery uses
getHistoryDirfor version roots and backup paths.
Run / shims / hooks
- Versioned alias shims regenerate on startup if missing.
- Hooks prefer version-home scripts to prevent path breakage when the source dir moves.
- Linux: claude shim sources
CLAUDE_CODE_OAUTH_TOKENfrom the per-version.oauth_tokenfile when unset.
Resource UI
agents viewreplaces path columns with OSC 8 hyperlinks for commands, skills, and rules.- Flat version resource lists replaced with source-pattern selection.
CI / security
- Gitleaks secret-scanning workflow on every push (switched to the free CLI, no org license needed).
Postinstall
- Correct shims dir, expanded aliases, prints changelog on install.
Dev
- Test isolation via vitest
pool: 'forks'; mock state paths instead of hitting real~/.agents/. - Concurrent-writes benchmark for the session indexer.
- Dead code + phantom deps removed:
src/commands/fork.ts,@aws-sdk/client-s3,@modelcontextprotocol/sdk,semver.
1.16.0
System-repo sweep: ~/.agents-system reduced to npm-shipped defaults only
- New migrators move every form of operational state out of ~/.agents-system into user-side buckets: sessions, teams (live + per-run), trash, repos (→ ~/.agents-
/ peer dirs), legacy swarm/, cache/, cloud/. - SQLite DBs merge row-level (INSERT OR IGNORE) into the user-side DB; filesystem dirs merge dir-by-dir with user-side winning on collision.
- Dead artifacts dropped automatically: bin/agents-keychain-*, empty shims/, .DS_Store-only versions/ skeletons.
- Unrecognized leftover dirs print a one-line stderr warning so future drift surfaces immediately.
- Migration diagnostics moved to stderr —
eval "$(agents secrets export …)"stops being polluted by log lines. - DB merge now skips FTS5 virtual + shadow tables (previously corrupted the session_text index). Indexer re-populates FTS on the next scan.
- Stale ~/.agents-system/agents.yaml is now dropped when a user copy exists.
~/.agents split into .history/ and .cache/ buckets
- Durable runtime state (sessions, versions, runs, teams/agents, trash, backups) moves to ~/.agents/.history/.
- Regenerable runtime state (shims, packages, cloud, logs, companion, helpers, browser runtime, fetch cache, dot-files) moves to ~/.agents/.cache/.
- Single-line gitignore for backing up ~/.agents/ — no more per-subdir cherry-picking.
Browser: profiles fold into agents.yaml + many new automation commands
- Profile YAMLs at ~/.agents/browser/profiles/*.yaml now live as a
browser:section in agents.yaml. Single user-facing file, single sync. - Single window per profile;
startrenamed toopen; new tab subcommands; session history with profile picker; viewport piped through to the launched browser. - New commands:
agents browser set viewport,set device,devices,console,errors,requests,responsebody,wait,download,waitdownload.
Hooks: hooks.yaml folded into agents.yaml hooks: section
- ~/.agents/hooks.yaml is migrated into agents.yaml on first run; the standalone file is removed.
- System repo ships the same shape — one config file, layered project > user > system.
Sessions & secrets
agents secrets exec <bundle> -- <command>injects a bundle's env vars into a one-shot subprocess (no shell-state leakage).agents sessionsnow groups active sessions by workspace and surfaces session topics in the picker.- Session discovery scans both version repos; migrator merges overlapping versions instead of leaving duplicates.
Renames
agents init→agents setup.permissions/sets/→permissions/presets/(resource directory + on-disk migration to match rules/presets convention).
Dev
- Crabbox remote-test profile (~$0.14/hr) +
scripts/sandbox.shdocumented in README and CLAUDE.md. Tests run remotely to avoid freezing the local machine.
1.15.0
Secrets: Linux support via libsecret/GNOME Keyring
agents secretsnow works on Linux backed by libsecret/GNOME Keyring with the same UX as macOS Keychain. Headless workarounds documented.- New
agents password generatesubcommand. - Lifecycle events emitted for secrets and other subsystems; richer metadata (timing helpers) on the events system.
Browser
- HTTP and WebSocket endpoint support for remote browsers.
- Concurrent Electron profile forks no longer step on each other; cleanup hardened.
- Remote browser restart works; SSH port handling improved; page target created when none exists for Electron apps.
- Events emitted for navigation and screenshots.
First-run UX
- Improved new-user experience: clearer CLI help, better defaults, audit-log opt-out, better run-timing display.
Prune
agents prunelearnedtrash,sessions, andrunscleanup targets.
Fixes
- Command-injection hole in daemon + secrets closed.
- Layered permission resolution corrected; daemon tests isolated from real user state.
.tmp-bungitignore pattern fixed.codexinteractive mode no longer routes throughexecsubcommand.
Docs
- Security/privacy section in README, browser skill + automation guide, FAQ updated with audit-log transparency.
1.14.6
Fix: OAuth token refresh now persists to Keychain
- Fixed bug where refreshed Claude OAuth tokens were used but never saved back to macOS Keychain
- Previously, agents-cli would refresh expired tokens on each run but discard them, eventually exhausting the refresh token
- Now refreshed
accessToken,refreshToken, andexpiresAtare written back to Keychain after successful refresh - Accounts will stay healthy across runs without requiring re-login
1.14.5
Browser: custom binary and Electron app support
- Added
binaryfield to browser profiles for specifying custom executable paths (e.g., Electron apps like Rush) - Added
electronfield to browser profiles — when true, uses existing windows instead of creating new ones (Electron doesn't supportTarget.createTarget) - New
custombrowser type that requires a binary path - Works with both local and SSH-based browser connections
- Example profile for Rush:
agents browser profiles edit rush --browser custom --binary "/Applications/Rush.app/Contents/MacOS/Rush" --electron
1.12.0
JSON output for sessions list
- Added
--jsonflag toagents sessions listandagents sessionsfor programmatic use - Output is a JSON array of session metadata (id, shortId, agent, version, account, project, cwd, filePath, topic, messageCount, tokenCount, timestamp)
- Enables the Companion VS Code extension's "Agents: Session Resume" and "Agents: Session Trace" pickers
OpenClaw workspace-aware sessions
- Fixed
agents sessions --agent openclawso synthetic OpenClaw rows now use the configured agent workspace from~/.openclaw/openclaw.json - When no per-agent workspace is available, OpenClaw session discovery now falls back to
~/.openclawinstead of leavingcwdempty or filling it with status text - Added a regression test covering managed OpenClaw homes symlinked through
~/.agents/versions/openclaw/...
1.11.1
Session search and version labeling
agents sessions viewnow opens a live-search picker by default in interactive terminalsagents sessions --agent ...andagents sessions --project ...now open the same live-search picker before falling back to the table viewagents sessions view <query>now resolves prompt text, not just exact session IDs- Fixed
--projectsearch so it scans across directories instead of intersecting with the current working directory - Session topics now skip injected scaffolding and use the first human prompt
- Codex session rows now show the real CLI build from
cli_version(for examplecodex@0.113.0) - Gemini, OpenCode, and OpenClaw session rows now resolve and display agent versions consistently in the shared
Agentcolumn - Claude usage lookup now falls back across scoped and legacy Keychain services when loading OAuth credentials
1.11.0
PTY -- interactive terminal sessions for AI agents
- New
agents ptycommand suite for persistent, interactive PTY sessions - Sidecar server architecture -- lightweight daemon on
~/.agents/pty.sock, auto-starts on first use agents pty start-- spawn a session with configurable rows, cols, shell, and working directoryagents pty exec <id> <command>-- submit commands (non-blocking, sentinel-based completion detection)agents pty screen <id>-- render the terminal as clean text (no ANSI codes), powered by xterm-headlessagents pty write <id> <input>-- send keystrokes with escape sequence support (\n,\t,\e,\xHH)agents pty read <id>-- read raw PTY output with configurable timeoutagents pty signal <id> [INT|TERM|KILL]-- send signals to the PTY processagents pty list-- show active sessions with status, PID, age, and active commandagents pty server start|stop|status-- manage the sidecar server directly- Session idle cleanup (30 min) and server auto-exit (1 hour with no sessions)
--jsonoutput on all commands for scripting- Auto-fixes node-pty spawn-helper permissions on startup (bun install workaround)
1.10.0
Drive -- sync agent sessions across machines
- New
agents drivecommand for syncing agent state between machines via rsync over SSH agents drive remote <user@host>-- set sync target (syncs to~/.agents/drive/on remote)agents drive pull/push-- additive rsync (no data loss, both sides accumulate)agents drive attach-- swap~/.claudesymlinks to the drive, so Claude reads/writes thereagents drive detach-- restore symlinks to the version homeagents drive status-- show remote, attached state, symlink targets, last sync times
1.9.1
Better sessions
- Sessions list and picker show
Agent@Versioncombined column (e.g.,claude@2.1.85) - Added
Topiccolumn showing first user message of each session - Account shows email instead of display name
1.9.0
New agents, routines, and better sessions
Agents:
- Added support for 5 new agents: Copilot, Amp, Kiro, Goose, and Roo Code
- Agent type expanded to 11 agents total
Routines (renamed from cron):
agents cronis nowagents routines-- aligns with Claude Code Routines namingagents cronandagents jobsstill work as deprecated aliases~/.agents/cron/directory renamed to~/.agents/routines/
Sessions:
- Sessions list now shows
Agent@Versionin a combined column (e.g.,claude@2.1.85) - Added
Topiccolumn showing the first message of each session - Account column now shows email instead of display name
- Session picker uses the same columns as the list view
Other:
- Account email preferred over display name across the CLI
- Rewritten help text for all top-level commands
1.6.12
"memory" is now "rules"
The agents memory command has been renamed to agents rules. This better reflects what these files actually are -- instruction files like AGENTS.md, CLAUDE.md, and .cursorrules that tell your agents how to behave.
agents rules list-- see your instruction files across all agentsagents rules add-- install and sync rule files from a repo or local pathagents rules view-- view rule file content for any agentagents rules remove-- remove a rule file
If you run agents memory, you'll see a message pointing you to the new command.
The files themselves haven't changed -- AGENTS.md is still AGENTS.md. Only the CLI command name changed.
1.6.8
Bug fix
- Skip commands and memory sync for agents that don't support file-based commands (openclaw)
- Added
commandscapability flag to agent configs agents use openclawandagents view openclawno longer show or sync slash commands or memory files- Fixed
hasNewResourcesto filter by agent capabilities (was triggering prompt even when no applicable resources existed)
1.6.5
Bug fix
- Fixed memory file detection counting symlinks as separate files (CLAUDE.md/GEMINI.md -> AGENTS.md)
1.6.4
Bug fixes
- Fixed Claude email not showing in
agents view(was reading from version home instead of real ~/.claude.json) - Fixed memory file updates not being detected in
agents use(now compares content, not just existence)
1.6.3
Bug fix
- Fixed infinite "new resources available" loop in
agents view - Partial resource syncs no longer wipe out previously synced resources
1.5.82
MCP & Permission improvements
- MCP configs now stored as YAML in
~/.agents/mcp/(was JSON) - Permissions now use groups from
~/.agents/permissions/groups/ - Resource selection shows proper counts: "Permissions (19 groups, 3132 rules)"
- When selecting "specific" permissions, shows individual groups with rule counts
- Added MCP support for cursor and opencode agents
- Removed
agentsfilter from MCP configs - selection tracked in agents.yaml - Added capability checks for MCPs (consistent with hooks/permissions)
1.5.81
Cron jobs & unified execution
- Renamed
jobscommand tocron(jobsstill works with deprecation warning) - New
agents exec <agent> <prompt>for unified agent execution across all CLIs - Inline job creation:
agents cron add my-job --schedule "..." --agent claude --prompt "..." - One-shot jobs with
--at:agents cron add reminder --at "14:30" -a claude -p "..." - New
agents cron edit [name]opens job in$EDITOR - Timezone support:
--timezone America/Los_Angeles - Custom variables in prompts: define
variables:block, use{var_name}in prompt - Interactive pickers for all cron subcommands when name is omitted
- Smart filtering:
resumeshows only paused jobs,pauseshows only enabled jobs - Effort-based model mapping:
--effort fast|default|detailedmaps to agent-specific models
Resource command cleanup
- Added
viewcommand to commands, mcp, hooks, and permissions - Removed
pushcommands from all resources (commands, skills, mcp, memory, hooks) - Deprecated
permsalias forpermissions(shows warning but still works) - Deprecated
infoalias forskills view,showalias formemory view
1.5.68
- Upgrade prompt now shows on ALL command flows (--version, --help, bare
agents)
1.5.67
Unified view command
- New
agents viewcommand replaceslistandstatus agents view/agents view claudeshows installed versionsagents view claude@2.0.65shows full resources (commands, skills, mcp, hooks, memory)- Old commands show deprecation warning but continue to work
1.5.48
Simplified repo structure
- Flattened repo structure: removed
shared/prefix - Resources now live at top level:
commands/,skills/,hooks/,memory/,permissions/ - Removed agent-specific override directories (no more
claude/commands/, etc.) - Simplified discovery functions
1.5.29
Version-aware resource installation
agents pullnow prompts for version selection per agent when multiple versions are installed- Resources (commands, skills, hooks, memory) are linked into version homes at pull time via
syncResourcesToVersion() - Simplified shims: HOME overlay + exec only (~80 lines, down from ~160). No more runtime sync logic.
- MCP registration uses direct binary path for version-managed agents (bypasses shim)
1.5.7
- Remove trailing newlines from command output
1.5.5
- Update prompt: Interactive menu before command runs (Upgrade now / Later)
1.5.4
cli list: Shows spinner while checking installed CLIs
1.5.3
skills view: Opens in pager (less) for scrolling, pressqto quit
1.5.2
skills view: Truncate descriptions to fit on one line
1.5.1
- Update check: Shows prompt when new version available
- What's new: Displays changelog after upgrade
skills view: Interactive skill selector (renamed frominfo)- Fixed
--versionshowing hardcoded 1.0.0 (now reads from package.json) - Silent npm/bun output during upgrade
1.5.0
Pull command redesign
- Agent-specific sync:
agents pull claudesyncs only Claude resources - Agent aliases:
cc,cx,gx,cr,ocfor quick filtering - Overview display: Shows NEW vs EXISTING resources before installation
- Per-resource prompts: Choose overwrite/skip/cancel for each conflict
-yflag: Auto-confirm and skip conflicts-fflag: Auto-confirm and overwrite conflicts- Graceful cancellation: Ctrl+C shows "Cancelled" cleanly
1.4.0
- Conflict detection for pull command
- Bulk conflict handling (overwrite all / skip all / cancel)
1.3.13
- Enabled skills support for Cursor and OpenCode
- Fixed Cursor MCP config path (now uses mcp.json)
1.3.12
- Fixed MCP detection for Codex (TOML config format)
- Fixed MCP detection for OpenCode (JSONC config format)
- Added smol-toml dependency for TOML parsing
1.3.11
- Status command shows resource names instead of counts
- Better formatting for installed commands, skills, and MCPs
1.3.0
- Added Agent Skills support (SKILL.md + rules/)
- Skills validation with metadata requirements
- Central skills directory at ~/.agents/skills/
1.2.0
- Added hooks support for Claude and Gemini
- Hook discovery from hooks/ directory
- Project-scope hooks support
1.1.0
- Added MCP server registration
- Support for stdio and http transports
- Per-agent MCP configuration
1.0.0
- Initial release
- Pull/push commands for syncing agent configurations
- Slash command management
- Multi-agent support (Claude, Codex, Gemini, Cursor, OpenCode)