@phnx-labs/agents-cli 1.20.51 → 1.20.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +71 -0
- package/README.md +12 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/browser.js +215 -7
- package/dist/commands/cloud.d.ts +3 -0
- package/dist/commands/cloud.js +8 -1
- package/dist/commands/events.d.ts +1 -1
- package/dist/commands/events.js +2 -3
- package/dist/commands/exec.js +82 -3
- package/dist/commands/factory.js +8 -0
- package/dist/commands/feed.d.ts +38 -0
- package/dist/commands/feed.js +274 -0
- package/dist/commands/logs.d.ts +5 -1
- package/dist/commands/logs.js +248 -3
- package/dist/commands/mcp.js +7 -0
- package/dist/commands/memory.d.ts +9 -0
- package/dist/commands/memory.js +164 -0
- package/dist/commands/message.d.ts +11 -6
- package/dist/commands/message.js +140 -5
- package/dist/commands/routines.js +12 -0
- package/dist/commands/secrets-migrate.d.ts +2 -1
- package/dist/commands/secrets-migrate.js +88 -13
- package/dist/commands/secrets.d.ts +22 -0
- package/dist/commands/secrets.js +176 -42
- package/dist/commands/sessions.js +10 -1
- package/dist/commands/teams.js +4 -0
- package/dist/commands/worktree.js +4 -2
- package/dist/index.js +21 -22
- package/dist/lib/agents.js +249 -17
- package/dist/lib/answer-router.d.ts +75 -0
- package/dist/lib/answer-router.js +149 -0
- package/dist/lib/ask-classifier.d.ts +71 -0
- package/dist/lib/ask-classifier.js +197 -0
- package/dist/lib/browser/login-detection.d.ts +94 -0
- package/dist/lib/browser/login-detection.js +274 -0
- package/dist/lib/browser/profiles.d.ts +17 -8
- package/dist/lib/browser/profiles.js +27 -8
- package/dist/lib/browser/secret-ref.d.ts +10 -0
- package/dist/lib/browser/secret-ref.js +14 -0
- package/dist/lib/browser/service.js +14 -12
- package/dist/lib/cloud/antigravity.d.ts +0 -2
- package/dist/lib/cloud/antigravity.js +2 -17
- package/dist/lib/cloud/codex.js +3 -18
- package/dist/lib/cloud/rush.d.ts +15 -0
- package/dist/lib/cloud/rush.js +10 -16
- package/dist/lib/cloud/stream.js +2 -0
- package/dist/lib/cloud/types.d.ts +21 -0
- package/dist/lib/cloud/types.js +81 -0
- package/dist/lib/crabbox/cli.d.ts +1 -1
- package/dist/lib/crabbox/cli.js +12 -2
- package/dist/lib/crabbox/lease.d.ts +19 -0
- package/dist/lib/crabbox/lease.js +22 -11
- package/dist/lib/crabbox/progress.d.ts +62 -0
- package/dist/lib/crabbox/progress.js +129 -0
- package/dist/lib/crabbox/runtimes.d.ts +38 -1
- package/dist/lib/crabbox/runtimes.js +98 -5
- package/dist/lib/daemon.d.ts +12 -9
- package/dist/lib/daemon.js +32 -17
- package/dist/lib/events.d.ts +31 -5
- package/dist/lib/events.js +291 -101
- package/dist/lib/exec.js +20 -1
- package/dist/lib/feed-outcome.d.ts +101 -0
- package/dist/lib/feed-outcome.js +244 -0
- package/dist/lib/feed-policy.d.ts +30 -0
- package/dist/lib/feed-policy.js +133 -0
- package/dist/lib/feed.d.ts +180 -0
- package/dist/lib/feed.js +627 -0
- package/dist/lib/git.d.ts +17 -1
- package/dist/lib/git.js +20 -1
- package/dist/lib/hooks.js +529 -14
- package/dist/lib/hosts/passthrough.d.ts +3 -3
- package/dist/lib/hosts/passthrough.js +3 -3
- package/dist/lib/mailbox-gc.d.ts +22 -0
- package/dist/lib/mailbox-gc.js +161 -0
- package/dist/lib/mailbox.d.ts +26 -2
- package/dist/lib/mailbox.js +80 -5
- package/dist/lib/mcp.js +82 -0
- package/dist/lib/memory.d.ts +55 -0
- package/dist/lib/memory.js +274 -0
- package/dist/lib/notify.d.ts +16 -0
- package/dist/lib/notify.js +61 -0
- package/dist/lib/operator.d.ts +26 -0
- package/dist/lib/operator.js +107 -0
- package/dist/lib/plugins.d.ts +35 -0
- package/dist/lib/plugins.js +217 -0
- package/dist/lib/remote-agents-json.d.ts +14 -0
- package/dist/lib/remote-agents-json.js +94 -0
- package/dist/lib/resources/mcp.js +44 -0
- package/dist/lib/resources/memory.d.ts +15 -0
- package/dist/lib/resources/memory.js +46 -0
- package/dist/lib/resources/types.d.ts +2 -2
- package/dist/lib/rotate.js +2 -0
- package/dist/lib/runner.d.ts +43 -0
- package/dist/lib/runner.js +323 -74
- package/dist/lib/sandbox.js +6 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/agent.d.ts +21 -0
- package/dist/lib/secrets/agent.js +63 -1
- package/dist/lib/secrets/bundles.d.ts +33 -1
- package/dist/lib/secrets/bundles.js +76 -21
- package/dist/lib/secrets/icloud-import.d.ts +79 -0
- package/dist/lib/secrets/icloud-import.js +203 -0
- package/dist/lib/secrets/index.d.ts +142 -2
- package/dist/lib/secrets/index.js +695 -30
- package/dist/lib/secrets/remote.js +1 -1
- package/dist/lib/secrets/sync.js +1 -1
- package/dist/lib/session/active.d.ts +18 -0
- package/dist/lib/session/active.js +47 -17
- package/dist/lib/session/db.d.ts +9 -1
- package/dist/lib/session/db.js +18 -3
- package/dist/lib/session/discover.d.ts +13 -0
- package/dist/lib/session/discover.js +32 -2
- package/dist/lib/session/parse.d.ts +8 -0
- package/dist/lib/session/parse.js +42 -21
- package/dist/lib/session/remote-active.js +8 -89
- package/dist/lib/session/state.d.ts +11 -0
- package/dist/lib/session/state.js +50 -1
- package/dist/lib/session/tail.d.ts +23 -4
- package/dist/lib/session/tail.js +34 -16
- package/dist/lib/session/throughput.d.ts +30 -0
- package/dist/lib/session/throughput.js +86 -0
- package/dist/lib/shim-heal.d.ts +12 -3
- package/dist/lib/shim-heal.js +12 -6
- package/dist/lib/staleness/detectors/subagents.js +57 -3
- package/dist/lib/staleness/writers/hooks.js +7 -3
- package/dist/lib/staleness/writers/subagents.js +37 -6
- package/dist/lib/startup/command-registry.d.ts +2 -0
- package/dist/lib/startup/command-registry.js +4 -0
- package/dist/lib/state.d.ts +2 -0
- package/dist/lib/state.js +25 -8
- package/dist/lib/subagents.d.ts +52 -0
- package/dist/lib/subagents.js +315 -12
- package/dist/lib/teams/agents.js +6 -3
- package/dist/lib/teams/worktree.d.ts +8 -0
- package/dist/lib/teams/worktree.js +8 -0
- package/dist/lib/types.d.ts +20 -2
- package/dist/lib/versions.js +38 -48
- package/dist/lib/whats-new.d.ts +5 -3
- package/dist/lib/whats-new.js +25 -5
- package/package.json +4 -3
- package/scripts/postinstall.js +61 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,77 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 1.20.53
|
|
6
|
+
|
|
7
|
+
- **`agents add <agent>@latest` resolves to a concrete version before installing (no install race).** `latest` (like `oldest`) is now resolved via `npm view` up front and installed as a pinned spec directly into `versions/<agent>/<version>/`. Previously `latest` installed into a shared, well-known `versions/<agent>/latest/` scratch dir and was renamed to the real version only after npm finished — so a concurrent `agents view` reconcile (`reconcileStaleLatestForAgent`) or a second `latest` install could rename that dir out from under npm mid-extraction, corrupting the install with `ENOENT` on the seeded `package.json`. A concrete dir per version has no shared name to race on. Source: `apps/cli/src/lib/versions.ts`.
|
|
8
|
+
- **Native memory sync preserves unmanaged Markdown files (RUSH-1621).** Sync tracks managed fact names in `.agents-cli-memory.json` and only deletes those; user-authored `*.md` under the agent memory dir survive. Source: `apps/cli/src/lib/memory.ts`.
|
|
9
|
+
- **Feed high-consequence authz uses the canonical operator registry (RUSH-1618).** `recordAnswer` no longer looks for `operators.yaml` under the feed root; it resolves operators from `~/.agents/` via `loadOperators()`. Source: `apps/cli/src/lib/feed.ts`.
|
|
10
|
+
- **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`.
|
|
11
|
+
|
|
12
|
+
- **PR outcome keys include repository identity (RUSH-1630).** Full GitHub pull URLs normalize to `owner/repo#N` so two repos' PR #10 no longer collide under `pr:#10`. Source: `apps/cli/src/lib/feed-outcome.ts`.
|
|
13
|
+
|
|
14
|
+
- **Urgent OpenClaw notifications use `--target` and `--message` (RUSH-1620).** `openclaw message send` requires a destination and the `--message` flag (not `--text`); without `--target` the send was invalid. Source: `apps/cli/src/lib/notify.ts`.
|
|
15
|
+
- **High-consequence answers require env-proven operator identity (RUSH-1619).** `agents message --as <id>` alone is not verification; `AGENTS_OPERATOR_ID` must match the claimed id (and the id must be in `operators.yaml`). Source: `apps/cli/src/lib/operator.ts`, `apps/cli/src/commands/message.ts`.
|
|
16
|
+
- **Hermes and ForgeCode are first-class install targets (RUSH-559).** `AgentId` now includes `hermes` and `forge`, with resource capability metadata for skills, rules, and MCP. MCP sync writes Hermes `mcp_servers` YAML in `~/.hermes/config.yaml` and ForgeCode `mcpServers` JSON 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`.
|
|
17
|
+
- **Cloud tasks can now report a resumable `idle` status (RUSH-601).** Provider-side stopped states such as Rush `idle`/`paused`/`needs_review`, Codex `paused`/`needs_review`, and Antigravity `idle`/`paused` normalize to canonical `idle`; stream output, `agents sessions`, and `agents cloud list/status` render 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`.
|
|
18
|
+
- **OpenCode plugin install only writes loader-visible direct `.ts`/`.js` files (RUSH-1617).** Drop nested and `.mjs`/`.cjs` installs that OpenCode never scans; multi-module plugins flatten into `~/.config/opencode/plugins/`. Source: `apps/cli/src/lib/plugins.ts`.
|
|
19
|
+
- **Routine credit-failover scans only the current attempt's log (RUSH-1616).** Each failover spawn writes `stdout.attempt-N.log` and rate-limit detection uses that file alone; prior attempts still append into `stdout.log` for the continuous trail. Source: `apps/cli/src/lib/runner.ts`.
|
|
20
|
+
- **Cursor hook sync drops stale managed entries when matcher/event change (RUSH-1615).** GC keys managed hooks by `event|command|matcher` instead of command path alone, so a matcher or event edit no longer leaves dead entries. Source: `apps/cli/src/lib/hooks.ts`.
|
|
21
|
+
- **Stop advertising Goose SubagentStart/SubagentStop hooks (RUSH-1613).** Goose does not emit those events; drop them from `GOOSE_EVENT_MAP` so sync no longer installs dead entries. Source: `apps/cli/src/lib/hooks.ts`.
|
|
22
|
+
- **Mailbox delivery receipts are monotonic (RUSH-1614).** `recordMessageReceipt` no longer lets a late `queued` write overwrite an already-recorded `consumed`/`continued` when enqueue races the drain. Source: `apps/cli/src/lib/feed.ts`.
|
|
23
|
+
- **Per-session rate-limit detection + feed badge (RUSH-1523).** The session state engine flags rate/usage-limit text in the transcript (`detectRateLimited`); `ActiveSession.rateLimited` flows through remote fan-out into Factory's `FloorAgent.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`.
|
|
24
|
+
- **Kiro launches with `--v3` so 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.base` now includes `--v3` so `agents run kiro` opts into the engine that reads them. Source: `apps/cli/src/lib/exec.ts`.
|
|
25
|
+
|
|
26
|
+
- **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 feed` reports a digest (`N stalls auto-resolved by policy`); `--all` shows suppressed items; `--json` stamps each block with its `ask` classification. Agent-tagged `blockClass: decision` is never auto-suppressed. Source: `apps/cli/src/lib/ask-classifier.ts`, `apps/cli/src/commands/feed.ts`.
|
|
27
|
+
- **Parked-agent answer router: PTY-select / resume / mailbox by runtime (RUSH-1474).** `agents message` no 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 via `agents 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`.
|
|
28
|
+
- **Wire subagents support for OpenCode.** OpenCode loads agent markdown from `~/.config/opencode/agents/` with frontmatter `mode: subagent`. Flip `subagents: true`, add `transformSubagentForOpenCode`, wire writer/detector/list/diff/remove. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/subagents.ts`. (RUSH-1386)
|
|
29
|
+
- **`agents feed` groups 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; `--flat` restores the per-agent list; `--json` stamps each block with its `outcome` ref. 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`.
|
|
30
|
+
- **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 (`menubarDensity` in UserDefaults, `MENUBAR_DENSITY` env 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". `Session` gained `title`/`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`.
|
|
31
|
+
- **Wire allowlist support for OpenCode.** OpenCode stores per-tool allow/ask/deny rules in `opencode.json`/`opencode.jsonc` under `permission` (bash patterns etc.; present since ~1.1.1). Flip `allowlist: { since: '1.1.1' }` so the existing `convertToOpenCodeFormat` / `applyPermissionsToVersion` / detector path actually runs. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/permissions.ts`. (RUSH-1385)
|
|
32
|
+
- **Wire subagents support for Grok CLI.** Grok discovers agent definitions as Claude-compatible `.md` files under `~/.grok/agents/` (docs: user-guide/16-subagents.md). Flip `subagents: 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)
|
|
33
|
+
- **Wire subagents support for Kimi CLI.** Kimi Code loads custom agents as YAML under `~/.kimi-code/agents/*.yaml` with a sibling `*.system.md` referenced via `system_prompt_path` (Kimi has no inline `system_prompt` field) and a managed parent `_agents-cli.yaml` that declares `agent.subagents` for `--agent-file`. Flip `subagents: true`, add `transformSubagentForKimi` / `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)
|
|
34
|
+
- **Grok + Antigravity now register dir-form subrule hooks.** The hooks writer excluded `grok` from `registerHooksToSettings`, 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 carry `matcher` so guards are tool-scoped. Source: `apps/cli/src/lib/staleness/writers/hooks.ts`, `apps/cli/src/lib/hooks.ts`. (RUSH-1353)
|
|
35
|
+
- **Menu-bar Quick Dispatch can now pick agents and fan out autonomous fixes from the screenshot panel.** `Cmd-Shift-O` still 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 with `agents 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,codex` filters visible agents and `AGENTS_QUICK_DISPATCH_AGENTS=claude,codex` preselects them. Source: `apps/cli/menubar/Sources/MenubarHelper/{LocalState,PromptPanel,AgentsCLI,IssueSelfTest}.swift`, `apps/cli/docs/menubar.md`. (RUSH-1416)
|
|
36
|
+
- **Menu-bar Quick Dispatch keeps immediate typing in the capture field.** The `Cmd-Shift-O` quick-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)
|
|
37
|
+
- **Internal: consolidated the drifted provider status-normalizers and git-root helpers (#753).** The three copies of `mapStatus` in `cloud/{rush,codex,antigravity}.ts` — which had drifted (different vocabularies and defaults) — collapse into one exported `normalizeProviderStatus(provider, wireStatus)` in `cloud/types.ts`, with provider-specific defaults still explicit (rush default `running`, codex default `running`, antigravity `undefined`-safe default `completed`); factory's structurally-different `mapResultStatus` is left in place. `getGitRoot` moves to `lib/git.ts` and `commands/worktree.ts` now calls it instead of a private `gitRoot` copy; the two divergent `isGitRepo` variants (synchronous root-only in `git.ts` vs async worktree-correct in `teams/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)
|
|
38
|
+
- **macOS installs now run a Developer-ID-signed + notarized `agents` binary — the primary fix for EDR (CrowdStrike Falcon) blocking the CLI.** The resolved `agents` on 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 with `bun 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 with `notarytool`, and ship it in the npm tarball at `dist/bin/agents`. `postinstall` points the alias shims and the `~/.local/bin/agents`/`ag` links 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. A `prepack` gate (`scripts/verify-cli-binary.sh`) refuses to pack unless the binary matches its sign-run sha pin, embeds the release version, and (on macOS) passes `codesign --verify` with a Developer ID authority. Linux-driven releases build + sign the binary on the mac sign host via `scripts/remote-sign-mac.sh`. Intel Macs and non-mac platforms keep the JS entrypoint. (#315)
|
|
39
|
+
- **Codex multi-file apply_patch now surfaces every path.** `parseCodex` used `.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. Now `applyPatchTargetPaths` uses `matchAll` and emits one Edit event per file. Source: `apps/cli/src/lib/session/parse.ts`. (RUSH-1410)
|
|
40
|
+
- **`memory` is a first-class top-level resource (distinct from `rules`).** `agents memory list|add|remove|view|sync` manages portable knowledge facts under `~/.agents/memory/` (project > user > system; `MEMORY.md` index + one `<slug>.md` per fact). The legacy `agents memory` → `rules` tombstone is gone. Capable agents (claude, codex, openclaw, grok) get facts fanned into version homes on `syncResourcesToVersion` / `agents memory sync`. Plugins can ship a `memory/` dir (surfaced in `pluginResourceGroups`). Note: internal `ResourceSelection.memory` still means the composed *rules* file — rename to `rules` is 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)
|
|
41
|
+
- **`agents sessions --active --json` now carries `tokPerSec`, and `agents sessions --roots --json` emits the session-scan directories — one CLI contract the Factory extension consumes instead of re-implementing (issue #741).** Every active row gained `tokPerSec`: live output-token throughput over a rolling 60s window from the transcript tail (Claude assistant `output_tokens`; Codex `token_count` output + reasoning; Gemini output + thoughts), absent when the session is idle or its format reports no usage. The new `agents sessions --roots --json` prints, per on-disk agent, the exact directories the CLI scans for transcripts (every version home + backup mirror), so an external watcher (the Factory Floor's `fs.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`).
|
|
42
|
+
- **Fix: `agents sessions --active` no longer attaches stale Codex transcripts to live processes.** Codex session lookup now sorts by indexed `last_activity` and 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 as `running`. Source: `apps/cli/src/lib/session/active.ts`, `apps/cli/src/lib/session/db.ts`. (RUSH-1489)
|
|
43
|
+
- **Startup shim self-heal stays silent by default, with `--verbose` diagnostics on stderr.** The unified shim/shadow/PATH repair path no longer pollutes command stdout, including `agents 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)
|
|
44
|
+
- **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). Flip `subagents: { since: '0.117.0' }`, add `transformSubagentForCodex`, 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)
|
|
45
|
+
- **Routines use the same version/account selection and credit failover as `agents run`.** Scheduled jobs used to spawn a bare `claude`/`codex` name under a sandbox HOME, which could surface as `agents: no version of claude configured` even when installs existed, and never walked past a credit-exhausted default. The runner now resolves a healthy install via the configured run strategy (default `balanced`), pins the absolute binary, injects per-version config dirs + the daemon's `CLAUDE_CODE_OAUTH_TOKEN` into sandboxed spawns, and on foreground `agents routines run` re-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)
|
|
46
|
+
- **Wire plugin support for Goose.** Goose loads Open Plugins from `$HOME/.agents/plugins/<name>/` (same layout as agents-cli). Flip `plugins: true` and 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)
|
|
47
|
+
- **Wire plugin support for Cursor CLI.** Cursor agent plugins use `.cursor-plugin/plugin.json` (re-enabled 2026-05). Flip `plugins: true`, set `pluginManifestDir: '.cursor-plugin'`, and reuse the centralized marketplace mirror path under `~/.cursor/plugins/`. Source: `apps/cli/src/lib/agents.ts`. (RUSH-1338)
|
|
48
|
+
- **Wire plugin support for OpenCode.** OpenCode loads JS/TS plugin modules from `$HOME/.config/opencode/plugins/` (not Claude marketplace layout). Flip `plugins: true`, install modules from a plugin's `opencode/` or `plugins/` dir (or root) into the version home, and track install/remove via `isPluginSynced` / `removePluginFromVersion`. Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/plugins.ts` (`installOpenCodePlugin`, `openCodePluginsDir`). (RUSH-1336)
|
|
49
|
+
- **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 into `hooks.json` while 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)
|
|
50
|
+
- **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`. Flip `supportsHooks` and gate `hooks: { 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)
|
|
51
|
+
- **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. Flip `supportsHooks` and gate `hooks: { since: '0.10.0' }`, map canonical events to Kiro PascalCase triggers, and write a single managed `agents-cli-hooks.json` on 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)
|
|
52
|
+
- **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 declared `hooks: false` so 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)
|
|
53
|
+
- **`agents run --lease` now 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 (`crabboxWarmup` was a blocking `spawnSync`, 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; `createLeaseOutputRouter` splits 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. `crabboxWarmup` is now async so the event loop stays free to animate. The spinner is a purpose-built `createSpinner` (NOT `ora`): ora hooks `stream.write` and re-renders on every external write while spinning, which ballooned to multi-GB output when a lease streamed past a live spinner — `createSpinner` writes exactly one short line per fixed tick and nowhere else (a unit test asserts 100k `update()` 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, clean `LOGIN_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`.
|
|
54
|
+
- **Fix: Grok hooks now respect their `matcher` and no longer run twice per event.** The Grok hook registrar (`registerHooksForGrok`) dropped the manifest `matcher` when writing `~/.grok/hooks/`, so a matcher-scoped hook fired on every tool call — the plan-presentation hook (meant for `ExitPlanMode` only) hard-blocked whole Grok sessions (exit 2 = explicit deny). It also double-registered every hook into BOTH `hooks.json` AND a per-event file (`pretooluse.json`, …); Grok merges all of `~/.grok/hooks/*.json`, so each hook ran twice. Now the registrar emits `matcher` only 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 is `exit_plan_mode`), and writes a single `hooks.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`).
|
|
55
|
+
- **Agent feed replies now have delivery confirmation and atomic first-answer-wins closure (RUSH-1476).** `agents message` ties 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: `queued` when `agents message` enqueues, `consumed` when the mailbox drain archives the message, and `continued` once the agent continues past the block. A human answer typed directly in the terminal is reconciled via the `UserPromptSubmit` hook, 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` (`blockId` on `MailboxMessage`, consumed-receipt surfacing), `apps/cli/src/commands/message.ts` (open-block lookup + `--surface`), `apps/cli/src/commands/feed.ts` (receipt rendering).
|
|
56
|
+
- **24/7 multi-operator controls for agent feed (RUSH-1480).** Blocks can now carry `blockClass` (`approval`/`decision`), `consequence` (`normal`/`high`), `allowedOperators`, `timeoutMinutes`, `safeDefault`, and `costOfDelay`; the feed-publish hook captures these from the agent's `AskUserQuestion` tool_input. High-consequence answers require a verified operator id known to `~/.agents/operators.yaml` (`agents message --as <operator>`). `agents feed --dispatch` evaluates 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 (`costOfDelay` at 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).
|
|
57
|
+
- **Mailbox TTL + liveness/GC so messages never rot in dead-agent inboxes (RUSH-1475).** `enqueue` accepts an optional `ttlSeconds`; expired messages are archived to `consumed/` with `dropped: expired` instead of being returned by `drain`/`peek`. `agents feed --dispatch` runs a liveness sweep against `getActiveSessions()`: boxes whose owning agent is no longer alive are treated as dead, their pending messages are archived with `dropped: dead`, and any feed block tied to that mailbox is removed so the operator never answers a ghost. Old `consumed/` 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` (`--dispatch` liveness sweep).
|
|
58
|
+
- **Keychain service names are no longer silently enumerable — items are stored under opaque HMAC-hashed names (macOS).** The helper's `list` never decrypts and never prompts (that's what keeps `agents secrets list` snappy), 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. Every `agents-cli.*` item now lives under an opaque name (`agents-cli.h.<ns>.m` for 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, so `secrets exec`/`run --secrets` still 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 new `agents secrets rekey` (`--status` to 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 a `never`-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)
|
|
59
|
+
|
|
60
|
+
## 1.20.52
|
|
61
|
+
|
|
62
|
+
- **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 reachable `bin/Agents CLI.app` matched the pin. Rebuilt the helper from current source on the release machine (universal, signed, notarized: Gatekeeper `Notarized Developer ID`, submission accepted) and pinned that binary. Same class of fix as #835.
|
|
63
|
+
- **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.** `ensureAgentRunning` tore down a reachable broker whenever the broker's running version differed from the caller's on-disk version — unconditionally, via `launchctl kickstart -k`, wiping every unlocked bundle. On a machine where installed versions churn (dev builds stamp a fresh `0.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`).
|
|
64
|
+
- **Fix: mutating a bundle now evicts the broker-held copy — `rotate` no longer serves the old secret for up to 7 days.** Only `secrets policy` invalidated the secrets-agent after a write; `add`, `rotate`, `remove`, `rename`, `delete`, and `import` updated 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. `writeBundle` and `deleteBundle` now 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 honors `AGENTS_SECRETS_NO_AGENT` plus the test-backend override so suites never evict a user's real unlocks (`shouldEvictAfterBundleWrite`). The `policy` command'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`.
|
|
65
|
+
- **`agents secrets import` gains 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 new `icloud`. Bundles created in the pre-biometry era were synced via iCloud Keychain; the device-local cutover pinned every query to `kSecAttrSynchronizable: false`, which orphaned those items — visible in Keychain Access under iCloud, invisible to `secrets list` and `migrate-acl`. `agents secrets import --from icloud` discovers 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 `--purge` deletes 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 as `reserved, not importable` instead 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`).
|
|
66
|
+
- **`agents feed` surfaces every top-level agent block that is waiting on the user across the fleet.** `AskUserQuestion` and waiting `Notification` hooks 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 bare `agents feed` merges local blocks with every registered online device in parallel, `--host`/`--device` scopes the fleet, `--local` skips SSH, and `--json` returns 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 in `apps/cli/src/lib/exec.ts` and `apps/cli/src/lib/teams/agents.ts`. (RUSH-1473)
|
|
67
|
+
- **Browser-profile credentials: account identity, `secrets get <bundle> <KEY>`, and `browser type --secret` for leak-free login.** `agents browser profiles logins` now shows, per profile, the account signed into each live service (plaintext username from Chromium `Login Data` — never decrypts the encrypted password) and whether login creds are declared in the profile's secrets bundle (columns `SERVICE | ACCOUNT | CREDS`); `profiles show` gains a `Logins:` block. `agents secrets get <bundle> <KEY>` prints one resolved value from a bundle (arg-count overload of the existing raw `get <item>`; ungated like it, and the `secrets.get` audit 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 composing `profiles 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 `--secrets` bundle is the credential store, keyed by the `<PREFIX>_USERNAME`/`<PREFIX>_PASSWORD` convention (per-service prefixes in `AUTH_SIGNATURES`); `profiles create --secrets` now warns if the bundle doesn't exist yet. Cookie-persistence-first remains the headline (the `browser` skill'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`.
|
|
68
|
+
- **`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 audit` queries events with filters (`--module`, `--command`, `--event`, `--agent`, `--caller`, `--level`, `--since`, `--limit`, `--json`) and `--follow` for live tailing; `agents logs stats` shows aggregate breakdowns by level, event type, module, and user; `agents logs rotate` prunes old numbered archives (`--days`, default 7). Events carry `level` (audit/warn/info/debug) and an environment-derived `caller` (`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 as `audit`. At 10 MB the active file rotates losslessly through `events.1.jsonl.gz`, `events.2.jsonl.gz`, and so on; `query()` reads every archive transparently. New instrumentation in `cloud.ts`, `factory.ts`, `teams.ts`, `secrets.ts`, `mcp.ts`, and `rotate.ts`. Source: `apps/cli/src/lib/events.ts`, `apps/cli/src/commands/logs.ts`, instrumentation call sites. (RUSH-460)
|
|
69
|
+
- **`agents run claude --lease` now 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.json` file branch. `buildCredentialScript` writes that blob to `~/.claude/.credentials.json` (0600) via the same quoted-heredoc that carries every other cred (the box's `~/.claude` is a symlink into the versioned home, so it lands exactly where the shim's `CLAUDE_CONFIG_DIR` reads 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 ran `agents run claude` and 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`.
|
|
70
|
+
- **`agents browser profiles set-default <name>` picks the profile a bare `agents browser start` uses — so agents stop opening a logged-out Chrome.** With no `--profile`, `start` used to auto-detect the first installed Chromium-family browser (Chrome first on macOS) and save it as `default`, ignoring a profile you'd actually logged into. Now `start` resolves in order: (1) your configured default, (2) an existing `default` profile, (3) auto-detect. The configured default ALSO re-points an explicit `--profile default`, so an agent that hardcodes `default` still 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`/`show` mark it; `set-default --unset` reverts 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`.
|
|
71
|
+
- **`agents browser` now warns when a task opens a login-gated site on a logged-out profile — grounded in real session state.** New `apps/cli/src/lib/browser/login-detection.ts` reads 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 trip `node: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 like `profile "default" has no linkedin.com session. logged in elsewhere: comet-local. try: --profile comet-local` when the chosen profile is logged out; it never blocks or slows start. `agents browser profiles logins` shows a profile-by-service table. Source: `apps/cli/src/lib/browser/login-detection.ts`, `apps/cli/src/commands/browser.ts`.
|
|
72
|
+
- **Fix: a finished session that signed off with a trailing "?" no longer reads as `input_required` forever (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 as `idle`, not `waiting_input` — so `agents sessions --active` and 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 pending `ExitPlanMode` (plan review) or `AskUserQuestion` still classifies as `waiting_input` at any age. Source: `apps/cli/src/lib/session/state.ts` (`inferActivity`, `PROSE_QUESTION_FRESH_MS`).
|
|
73
|
+
- **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`.
|
|
74
|
+
- **Fix: daemon no longer crash-loops when started from the bare `browser` or `computer` shim.** Daemon launch resolution now maps installed sibling shims to the `agents` launcher and compiled shims to `index.js` before 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)
|
|
75
|
+
|
|
5
76
|
## 1.20.51
|
|
6
77
|
|
|
7
78
|
- **Fix: `agents run --lease` bootstraps 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's `npm install -g @phnx-labs/agents-cli` ran with no node/npm on the box and swallowed the failure with `|| true`, so every run died deep in the script with `agents: command not found` (exit 127) and no hint why; (2) even with the CLI installed, a fresh install refuses `agents run` with "agents-cli is not set up" until `agents setup` has run; (3) teardown called `crabbox stop --id <slug>`, but crabbox's `stop` takes a positional target (unlike `status`/`run`/`ssh`) and died with `flag provided but not defined: -id` — silently, because `crabboxStop` is best-effort — so every one-shot lease box was **kept, billed, and left carrying the run's working data** until someone noticed (`Box … kept` instead of destroyed). The bootstrap now: exports `~/.local/bin` onto PATH, installs node user-level from the official `latest-v22.x` tarball when missing (arch-aware, satisfies `engines.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 runs `agents setup` behind the same `[ ! -d ~/.agents/.system ]` first-run guard the hosts bootstrap uses; `crabboxStop` passes 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`) with `Box … kept`; after the node/npm fix it reached `agents-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 with `Box <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.
|
package/README.md
CHANGED
|
@@ -238,6 +238,18 @@ agents message tester "also cover the null case"
|
|
|
238
238
|
|
|
239
239
|
`agents message <target> <text>` reaches any running agent by name or id -- a live local run, a teammate, a loop agent, or a cloud task -- and the text lands at its next tool call. Tag the sender with `--from <who>`.
|
|
240
240
|
|
|
241
|
+
### See every open block
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
agents feed # grouped by outcome (ticket/PR/worktree) across the fleet
|
|
245
|
+
agents feed --flat # one row per agent (legacy)
|
|
246
|
+
agents feed --host mac-mini # scope the view to one or more hosts
|
|
247
|
+
agents feed --local # skip the SSH fan-out
|
|
248
|
+
agents feed --json # blocks stamped with their outcome key
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Top-level questions and waiting notifications publish one atomic open-block record per session, including the mailbox id, host, runtime, and every answer option. The default view collapses agents under the **outcome** they serve (Linear ticket, PR, worktree slug, or Unassigned) so a 1,100-agent fleet reads as dozens of deliverables. Answered, resumed, and stopped blocks clear automatically; Task subagents are excluded. The rendered reply command uses the same mailbox id with `agents message`, so the decision routes back to the agent that asked it.
|
|
252
|
+
|
|
241
253
|
### Auto-nudge stalls
|
|
242
254
|
|
|
243
255
|
```bash
|
package/dist/bin/agents
ADDED
|
Binary file
|
package/dist/commands/browser.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
-
import { listProfiles, getProfile, createProfile, deleteProfile, ensureDefaultBrowserProfile, getProfileRuntimeDir, extractConfiguredPort, findFreeProfilePort, getEndpointPresets, } from '../lib/browser/profiles.js';
|
|
3
|
+
import { listProfiles, getProfile, createProfile, deleteProfile, ensureDefaultBrowserProfile, getConfiguredDefaultProfileName, DEFAULT_BROWSER_PROFILE_NAME, getProfileRuntimeDir, extractConfiguredPort, findFreeProfilePort, getEndpointPresets, } from '../lib/browser/profiles.js';
|
|
4
|
+
import { updateMeta } from '../lib/state.js';
|
|
5
|
+
import { loginsForProfile, profilesLoggedInto, serviceForUrl, loginsWithAccountsForProfile, accountsForProfile, credKeysForService, AUTH_SIGNATURES, } from '../lib/browser/login-detection.js';
|
|
6
|
+
import { parseSecretRef } from '../lib/browser/secret-ref.js';
|
|
7
|
+
import { readAndResolveBundleEnv, bundleExists, readBundle, describeBundle } from '../lib/secrets/bundles.js';
|
|
4
8
|
import { findBrowserPath, getPortOccupant, isLauncherScript } from '../lib/browser/chrome.js';
|
|
5
9
|
import { listProfileCacheDirs, removeProfileCache, listAllProfileSnapshots, } from '../lib/browser/runtime-state.js';
|
|
6
10
|
import { DEFAULT_VIEWPORT } from '../lib/browser/devices.js';
|
|
@@ -94,6 +98,29 @@ export function registerBrowserSubcommands(program) {
|
|
|
94
98
|
registerTaskCommands(program);
|
|
95
99
|
registerCommandGroups(program, BROWSER_HELP_GROUPS);
|
|
96
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Whether a profile's secrets bundle holds login creds for `service`
|
|
103
|
+
* (`<PREFIX>_USERNAME` + `<PREFIX>_PASSWORD`). Reads bundle METADATA only (key
|
|
104
|
+
* names, never values) so it doesn't decrypt. Returns the bundle name when both
|
|
105
|
+
* keys exist, '-' when not, or '(unknown)' if the metadata read would prompt or
|
|
106
|
+
* fails — the view must never block on Touch ID.
|
|
107
|
+
*/
|
|
108
|
+
function credAvailability(bundleName, service) {
|
|
109
|
+
if (!bundleName)
|
|
110
|
+
return '-';
|
|
111
|
+
const keys = credKeysForService(service);
|
|
112
|
+
if (!keys)
|
|
113
|
+
return '-';
|
|
114
|
+
try {
|
|
115
|
+
if (!bundleExists(bundleName))
|
|
116
|
+
return '-';
|
|
117
|
+
const present = new Set(describeBundle(readBundle(bundleName)).map((e) => e.key));
|
|
118
|
+
return present.has(keys.user) && present.has(keys.pass) ? bundleName : '-';
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return '(unknown)';
|
|
122
|
+
}
|
|
123
|
+
}
|
|
97
124
|
function registerProfilesCommands(browser) {
|
|
98
125
|
const profiles = browser
|
|
99
126
|
.command('profiles')
|
|
@@ -109,6 +136,10 @@ function registerProfilesCommands(browser) {
|
|
|
109
136
|
console.log('Create one with: agents browser profiles create <name> --endpoint <url>');
|
|
110
137
|
return;
|
|
111
138
|
}
|
|
139
|
+
// The configured default is what a bare `agents browser start` (and an
|
|
140
|
+
// explicit `--profile default`) resolves to on THIS machine — mark it.
|
|
141
|
+
const configuredDefault = getConfiguredDefaultProfileName();
|
|
142
|
+
const marker = (name) => (name === configuredDefault ? ' (default)' : '');
|
|
112
143
|
const hasDescriptions = allProfiles.some(p => p.description);
|
|
113
144
|
if (hasDescriptions) {
|
|
114
145
|
console.log('NAME'.padEnd(20) + 'BROWSER'.padEnd(12) + 'DESCRIPTION'.padEnd(38) + 'ENDPOINTS');
|
|
@@ -119,7 +150,7 @@ function registerProfilesCommands(browser) {
|
|
|
119
150
|
.map(([name, ep]) => (name.startsWith('endpoint-') ? ep.target : `${name}=${ep.target}`))
|
|
120
151
|
.join(', ');
|
|
121
152
|
const desc = (p.description ?? '').slice(0, 36).padEnd(38);
|
|
122
|
-
console.log(p.name.padEnd(20) + (p.browser || '-').padEnd(12) + desc + endpoints);
|
|
153
|
+
console.log(p.name.padEnd(20) + (p.browser || '-').padEnd(12) + desc + endpoints + marker(p.name));
|
|
123
154
|
}
|
|
124
155
|
}
|
|
125
156
|
else {
|
|
@@ -130,7 +161,78 @@ function registerProfilesCommands(browser) {
|
|
|
130
161
|
const endpoints = Object.entries(presets)
|
|
131
162
|
.map(([name, ep]) => (name.startsWith('endpoint-') ? ep.target : `${name}=${ep.target}`))
|
|
132
163
|
.join(', ');
|
|
133
|
-
console.log(p.name.padEnd(20) + (p.browser || '-').padEnd(12) + endpoints);
|
|
164
|
+
console.log(p.name.padEnd(20) + (p.browser || '-').padEnd(12) + endpoints + marker(p.name));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (configuredDefault) {
|
|
168
|
+
console.log('');
|
|
169
|
+
console.log(`Default profile (this machine): ${configuredDefault}`);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
profiles
|
|
173
|
+
.command('set-default [name]')
|
|
174
|
+
.description('Set the profile `agents browser start` uses when no --profile is passed (also re-points an explicit `--profile default`). Device-local — each machine has its own. No name prints the current value.')
|
|
175
|
+
.option('--unset', 'Clear the configured default (revert to auto-detecting an installed browser)')
|
|
176
|
+
.action(async (name, opts) => {
|
|
177
|
+
if (opts.unset) {
|
|
178
|
+
updateMeta((m) => {
|
|
179
|
+
const { defaultBrowserProfile, ...rest } = m;
|
|
180
|
+
void defaultBrowserProfile;
|
|
181
|
+
return rest;
|
|
182
|
+
});
|
|
183
|
+
console.log('Default browser profile cleared. `agents browser start` will auto-detect an installed Chromium-family browser.');
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (!name) {
|
|
187
|
+
const current = getConfiguredDefaultProfileName();
|
|
188
|
+
if (current) {
|
|
189
|
+
console.log(`Default browser profile (this machine): ${current}`);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
console.log('No default browser profile set. `agents browser start` auto-detects an installed Chromium-family browser.');
|
|
193
|
+
console.log('Set one with: agents browser profiles set-default <name>');
|
|
194
|
+
}
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const target = await getProfile(name);
|
|
198
|
+
if (!target) {
|
|
199
|
+
console.error(`Profile "${name}" not found.`);
|
|
200
|
+
const all = await listProfiles();
|
|
201
|
+
if (all.length > 0)
|
|
202
|
+
console.error(`Available profiles: ${all.map((p) => p.name).join(', ')}`);
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
updateMeta((m) => ({ ...m, defaultBrowserProfile: name }));
|
|
206
|
+
const eps = getEndpointPresets(target);
|
|
207
|
+
const epStr = Object.values(eps)[0]?.target ?? '';
|
|
208
|
+
console.log(`Default browser profile (this machine) is now "${name}" (${target.browser}${epStr ? `, ${epStr}` : ''}).`);
|
|
209
|
+
console.log('Bare `agents browser start` and `--profile default` will use it.');
|
|
210
|
+
});
|
|
211
|
+
profiles
|
|
212
|
+
.command('logins')
|
|
213
|
+
.description('Show which login-gated services each profile has a live session for, the account signed in, and whether login creds are available in the profile\'s secrets bundle (reads cookie/username presence only, never decrypts).')
|
|
214
|
+
.action(async () => {
|
|
215
|
+
const allProfiles = await listProfiles();
|
|
216
|
+
if (allProfiles.length === 0) {
|
|
217
|
+
console.log('No browser profiles configured.');
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
console.log('PROFILE'.padEnd(20) + 'SERVICE'.padEnd(12) + 'ACCOUNT'.padEnd(32) + 'CREDS');
|
|
221
|
+
console.log('-'.repeat(80));
|
|
222
|
+
for (const p of allProfiles) {
|
|
223
|
+
const rows = await loginsWithAccountsForProfile(p.name);
|
|
224
|
+
if (rows.length === 0) {
|
|
225
|
+
console.log(p.name.padEnd(20) + '(none detected)');
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
let first = true;
|
|
229
|
+
for (const r of rows) {
|
|
230
|
+
const name = first ? p.name : '';
|
|
231
|
+
console.log(name.padEnd(20) +
|
|
232
|
+
r.service.padEnd(12) +
|
|
233
|
+
(r.username ?? '(unknown)').padEnd(32) +
|
|
234
|
+
credAvailability(p.secrets, r.service));
|
|
235
|
+
first = false;
|
|
134
236
|
}
|
|
135
237
|
}
|
|
136
238
|
});
|
|
@@ -220,6 +322,11 @@ function registerProfilesCommands(browser) {
|
|
|
220
322
|
};
|
|
221
323
|
await createProfile(profile);
|
|
222
324
|
console.log(`Created profile: ${name}`);
|
|
325
|
+
// Warn (don't fail) if the declared secrets bundle doesn't exist yet — it
|
|
326
|
+
// may be created later, but a typo should surface now.
|
|
327
|
+
if (opts.secrets && !bundleExists(opts.secrets)) {
|
|
328
|
+
console.error(`warning: secrets bundle "${opts.secrets}" does not exist yet. Create it with: agents secrets create ${opts.secrets}`);
|
|
329
|
+
}
|
|
223
330
|
});
|
|
224
331
|
profiles
|
|
225
332
|
.command('show <name>')
|
|
@@ -242,6 +349,9 @@ function registerProfilesCommands(browser) {
|
|
|
242
349
|
}
|
|
243
350
|
console.log(`Name: ${profile.name}`);
|
|
244
351
|
console.log(`Browser: ${profile.browser}`);
|
|
352
|
+
if (getConfiguredDefaultProfileName() === profile.name) {
|
|
353
|
+
console.log('Default: yes (this machine — used by `agents browser start`)');
|
|
354
|
+
}
|
|
245
355
|
if (profile.binary)
|
|
246
356
|
console.log(`Binary: ${profile.binary}`);
|
|
247
357
|
if (profile.electron)
|
|
@@ -273,6 +383,27 @@ function registerProfilesCommands(browser) {
|
|
|
273
383
|
console.log(`Secrets: ${profile.secrets}`);
|
|
274
384
|
if (profile.chrome?.headless)
|
|
275
385
|
console.log(`Headless: true`);
|
|
386
|
+
// Login state per known service: live session + account identity + whether
|
|
387
|
+
// login creds are declared in the profile's secrets bundle.
|
|
388
|
+
const active = await loginsForProfile(profile.name);
|
|
389
|
+
const accounts = await accountsForProfile(profile.name);
|
|
390
|
+
const lines = [];
|
|
391
|
+
for (const service of Object.keys(AUTH_SIGNATURES)) {
|
|
392
|
+
const isActive = active.includes(service);
|
|
393
|
+
const creds = credAvailability(profile.secrets, service);
|
|
394
|
+
if (!isActive && creds === '-')
|
|
395
|
+
continue; // nothing interesting to show
|
|
396
|
+
const status = isActive
|
|
397
|
+
? `logged in${accounts[service] ? ` as ${accounts[service]}` : ''}`
|
|
398
|
+
: 'logged out';
|
|
399
|
+
const credNote = creds !== '-' ? ` creds: ${creds}` : '';
|
|
400
|
+
lines.push(` ${service.padEnd(10)} ${status}${credNote}`);
|
|
401
|
+
}
|
|
402
|
+
if (lines.length > 0) {
|
|
403
|
+
console.log('Logins:');
|
|
404
|
+
for (const l of lines)
|
|
405
|
+
console.log(l);
|
|
406
|
+
}
|
|
276
407
|
});
|
|
277
408
|
profiles
|
|
278
409
|
.command('delete <name>')
|
|
@@ -302,6 +433,10 @@ function registerProfilesCommands(browser) {
|
|
|
302
433
|
}
|
|
303
434
|
console.log(`Deleted profile: ${name}` +
|
|
304
435
|
(removed > 0 ? ` (and ${removed} cache dir${removed === 1 ? '' : 's'})` : ''));
|
|
436
|
+
if (getConfiguredDefaultProfileName() === name) {
|
|
437
|
+
console.error(`warning: "${name}" was this machine's default browser profile; ` +
|
|
438
|
+
`\`agents browser start\` will auto-detect until you run: agents browser profiles set-default <name>`);
|
|
439
|
+
}
|
|
305
440
|
});
|
|
306
441
|
profiles
|
|
307
442
|
.command('doctor <name>')
|
|
@@ -475,8 +610,8 @@ function registerProfilesCommands(browser) {
|
|
|
475
610
|
function registerTaskCommands(browser) {
|
|
476
611
|
browser
|
|
477
612
|
.command('start')
|
|
478
|
-
.description('Start a browser task. Pass --profile <name
|
|
479
|
-
.option('-p, --profile <name>', 'Browser profile to use (auto-
|
|
613
|
+
.description('Start a browser task. Pass --profile <name>; omit to use your configured default (`agents browser profiles set-default`), else auto-pick an installed Chromium-family browser.')
|
|
614
|
+
.option('-p, --profile <name>', 'Browser profile to use (omit to use the configured default, else auto-pick an installed Chromium-family browser)')
|
|
480
615
|
.option(TASK_OPTION_FLAG, 'Task name (auto-generated if omitted)')
|
|
481
616
|
.option('-e, --endpoint <name>', 'Endpoint preset (defaults to the profile\'s default)')
|
|
482
617
|
.option('-u, --url <url>', 'Open URL in first tab')
|
|
@@ -497,6 +632,23 @@ function registerTaskCommands(browser) {
|
|
|
497
632
|
process.exit(1);
|
|
498
633
|
}
|
|
499
634
|
}
|
|
635
|
+
else if (profileName === DEFAULT_BROWSER_PROFILE_NAME) {
|
|
636
|
+
// Explicit `--profile default` honors the configured default too, so an
|
|
637
|
+
// agent that hardcodes the reserved `default` name still lands on the
|
|
638
|
+
// user's chosen profile (e.g. their logged-in Comet) rather than a
|
|
639
|
+
// literal `default` (which auto-detects Chrome). Narrowly scoped: only
|
|
640
|
+
// the reserved name, only here in `start`.
|
|
641
|
+
const configured = getConfiguredDefaultProfileName();
|
|
642
|
+
if (configured && configured !== DEFAULT_BROWSER_PROFILE_NAME) {
|
|
643
|
+
const target = await getProfile(configured);
|
|
644
|
+
if (target) {
|
|
645
|
+
profileName = target.name;
|
|
646
|
+
}
|
|
647
|
+
else {
|
|
648
|
+
console.error(`warning: configured default profile "${configured}" not found; using "${DEFAULT_BROWSER_PROFILE_NAME}".`);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
500
652
|
// Pre-check the profile locally so we fail fast with a helpful error
|
|
501
653
|
// instead of round-tripping a generic "Profile not found" through the daemon.
|
|
502
654
|
const profile = await getProfile(profileName);
|
|
@@ -518,6 +670,26 @@ function registerTaskCommands(browser) {
|
|
|
518
670
|
process.exit(1);
|
|
519
671
|
}
|
|
520
672
|
}
|
|
673
|
+
// Grounded login guardrail: if the opening URL is a known login-gated
|
|
674
|
+
// service and the resolved profile has no session for it, say so on stderr
|
|
675
|
+
// and name a profile that IS logged in. Reads the profile's real cookie DB
|
|
676
|
+
// (presence only) — never blocks or slows start; any failure is silent.
|
|
677
|
+
if (opts.url) {
|
|
678
|
+
try {
|
|
679
|
+
const service = serviceForUrl(opts.url);
|
|
680
|
+
if (service) {
|
|
681
|
+
const here = await loginsForProfile(profileName);
|
|
682
|
+
if (!here.includes(service)) {
|
|
683
|
+
const elsewhere = (await profilesLoggedInto(service)).filter((p) => p !== profileName);
|
|
684
|
+
const hint = elsewhere.length
|
|
685
|
+
? `logged in elsewhere: ${elsewhere.join(', ')}. try: --profile ${elsewhere[0]}`
|
|
686
|
+
: `no profile on this machine has a ${service} session.`;
|
|
687
|
+
console.error(`warning: profile "${profileName}" (${profile.browser}) has no ${service} session. ${hint}`);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
catch { /* login detection is best-effort; never block start */ }
|
|
692
|
+
}
|
|
521
693
|
const response = await sendIPCRequest({
|
|
522
694
|
action: 'start',
|
|
523
695
|
profile: profileName,
|
|
@@ -1219,7 +1391,8 @@ function registerTaskCommands(browser) {
|
|
|
1219
1391
|
.description('Type text into an element by ref')
|
|
1220
1392
|
.option(TASK_OPTION_FLAG, TASK_OPTION_DESC)
|
|
1221
1393
|
.option('-t, --tab <tabId>', 'Tab ID (defaults to current)')
|
|
1222
|
-
.
|
|
1394
|
+
.option('--text <text>', 'Text to type (use quotes for spaces/special chars)')
|
|
1395
|
+
.option('--secret <ref>', 'Resolve <bundle>/<KEY> from `agents secrets` and type the value. The secret is resolved in-process and never printed to stdout or the transcript. Alternative to --text.')
|
|
1223
1396
|
.option('--clear', 'Clear editor content before typing')
|
|
1224
1397
|
.action(async (ref, opts) => {
|
|
1225
1398
|
const task = resolveTaskName(opts);
|
|
@@ -1228,18 +1401,53 @@ function registerTaskCommands(browser) {
|
|
|
1228
1401
|
console.error(`<ref> must be an integer, got: ${ref}`);
|
|
1229
1402
|
process.exit(1);
|
|
1230
1403
|
}
|
|
1404
|
+
// Resolve the text to type: literal --text, or a --secret <bundle>/<KEY>
|
|
1405
|
+
// reference resolved in-process so the value never crosses stdout/transcript.
|
|
1406
|
+
let text = opts.text;
|
|
1407
|
+
if (opts.secret) {
|
|
1408
|
+
if (opts.text !== undefined) {
|
|
1409
|
+
console.error('Pass either --text or --secret, not both.');
|
|
1410
|
+
process.exit(1);
|
|
1411
|
+
}
|
|
1412
|
+
const parsed = parseSecretRef(opts.secret);
|
|
1413
|
+
if (!parsed) {
|
|
1414
|
+
console.error(`--secret must be <bundle>/<KEY>, got: ${opts.secret}`);
|
|
1415
|
+
process.exit(1);
|
|
1416
|
+
}
|
|
1417
|
+
if (!bundleExists(parsed.bundle)) {
|
|
1418
|
+
console.error(`Secrets bundle "${parsed.bundle}" not found.`);
|
|
1419
|
+
process.exit(1);
|
|
1420
|
+
}
|
|
1421
|
+
try {
|
|
1422
|
+
const { env } = readAndResolveBundleEnv(parsed.bundle, { caller: 'browser type', keys: [parsed.key] });
|
|
1423
|
+
if (!(parsed.key in env)) {
|
|
1424
|
+
console.error(`Key "${parsed.key}" not in bundle "${parsed.bundle}".`);
|
|
1425
|
+
process.exit(1);
|
|
1426
|
+
}
|
|
1427
|
+
text = env[parsed.key];
|
|
1428
|
+
}
|
|
1429
|
+
catch (err) {
|
|
1430
|
+
console.error(err.message);
|
|
1431
|
+
process.exit(1);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
if (text === undefined) {
|
|
1435
|
+
console.error('Provide --text <text> or --secret <bundle>/<KEY>.');
|
|
1436
|
+
process.exit(1);
|
|
1437
|
+
}
|
|
1231
1438
|
const response = await sendIPCRequest({
|
|
1232
1439
|
action: 'type',
|
|
1233
1440
|
task,
|
|
1234
1441
|
tabId: opts.tab,
|
|
1235
1442
|
ref: refNum,
|
|
1236
|
-
text
|
|
1443
|
+
text,
|
|
1237
1444
|
clear: opts.clear,
|
|
1238
1445
|
});
|
|
1239
1446
|
if (!response.ok) {
|
|
1240
1447
|
console.error(response.error);
|
|
1241
1448
|
process.exit(1);
|
|
1242
1449
|
}
|
|
1450
|
+
// Never echo the typed text — with --secret it would leak the credential.
|
|
1243
1451
|
console.log('Typed');
|
|
1244
1452
|
});
|
|
1245
1453
|
browser
|
package/dist/commands/cloud.d.ts
CHANGED
|
@@ -6,5 +6,8 @@
|
|
|
6
6
|
* All tasks are tracked locally in a SQLite database for cross-provider listing.
|
|
7
7
|
*/
|
|
8
8
|
import type { Command } from 'commander';
|
|
9
|
+
/** Print an error message to stderr and exit. */
|
|
10
|
+
/** Return a chalk color function appropriate for the given task status. */
|
|
11
|
+
export declare function statusColor(status: string): (s: string) => string;
|
|
9
12
|
/** Register the `agents cloud` command tree (run, list, status, logs, cancel, message, providers). */
|
|
10
13
|
export declare function registerCloudCommands(program: Command): void;
|
package/dist/commands/cloud.js
CHANGED
|
@@ -8,6 +8,7 @@ import { insertTask, updateTaskStatus, getTaskById, listTasks as listStoredTasks
|
|
|
8
8
|
import { renderStream } from '../lib/cloud/stream.js';
|
|
9
9
|
import { MissingTargetError, MAX_IMAGES_PER_DISPATCH } from '../lib/cloud/types.js';
|
|
10
10
|
import { normalizeTriggerEvent, validateTrigger, writeJob, jobExists, GITHUB_TRIGGER_EVENTS } from '../lib/routines.js';
|
|
11
|
+
import { emit } from '../lib/events.js';
|
|
11
12
|
/** Map a supported image file extension to its wire mimeType. Rejects anything else. */
|
|
12
13
|
function imageMimeFromPath(file) {
|
|
13
14
|
const ext = path.extname(file).toLowerCase();
|
|
@@ -37,11 +38,12 @@ function parseSkillRef(raw) {
|
|
|
37
38
|
}
|
|
38
39
|
/** Print an error message to stderr and exit. */
|
|
39
40
|
/** Return a chalk color function appropriate for the given task status. */
|
|
40
|
-
function statusColor(status) {
|
|
41
|
+
export function statusColor(status) {
|
|
41
42
|
switch (status) {
|
|
42
43
|
case 'queued':
|
|
43
44
|
case 'allocating': return chalk.blue;
|
|
44
45
|
case 'running': return chalk.yellow;
|
|
46
|
+
case 'idle': return chalk.gray;
|
|
45
47
|
case 'completed': return chalk.green;
|
|
46
48
|
case 'input_required': return chalk.magenta;
|
|
47
49
|
case 'failed': return chalk.red;
|
|
@@ -346,6 +348,7 @@ Examples:
|
|
|
346
348
|
}
|
|
347
349
|
// Persist locally
|
|
348
350
|
insertTask(task);
|
|
351
|
+
emit('cloud.dispatch', { module: 'cloud', taskId: task.id, agent: task.agent, provider: task.provider, status: task.status });
|
|
349
352
|
if (json) {
|
|
350
353
|
process.stdout.write(JSON.stringify(task) + '\n');
|
|
351
354
|
}
|
|
@@ -371,6 +374,7 @@ Examples:
|
|
|
371
374
|
summary: result.summary,
|
|
372
375
|
prUrl: result.prUrl,
|
|
373
376
|
});
|
|
377
|
+
emit('cloud.complete', { module: 'cloud', taskId: task.id, status: result.status, prUrl: result.prUrl });
|
|
374
378
|
if (gated?.gate.breached()) {
|
|
375
379
|
const b = gated.gate.breach();
|
|
376
380
|
process.stderr.write(`[budget] cap ${b?.cap} exceeded — cancelled cloud task ${task.id}\n`);
|
|
@@ -533,6 +537,7 @@ Examples:
|
|
|
533
537
|
summary: result.summary,
|
|
534
538
|
prUrl: result.prUrl,
|
|
535
539
|
});
|
|
540
|
+
emit('cloud.complete', { module: 'cloud', taskId: id, status: result.status, prUrl: result.prUrl });
|
|
536
541
|
if (gated?.gate.breached()) {
|
|
537
542
|
process.exitCode = 7;
|
|
538
543
|
}
|
|
@@ -553,6 +558,7 @@ Examples:
|
|
|
553
558
|
try {
|
|
554
559
|
await provider.cancel(id);
|
|
555
560
|
updateTaskStatus(id, 'cancelled');
|
|
561
|
+
emit('cloud.cancel', { module: 'cloud', taskId: id, provider: task.provider });
|
|
556
562
|
console.log(chalk.green(`Task ${id} cancelled.`));
|
|
557
563
|
}
|
|
558
564
|
catch (err) {
|
|
@@ -571,6 +577,7 @@ Examples:
|
|
|
571
577
|
try {
|
|
572
578
|
await provider.message(id, text);
|
|
573
579
|
updateTaskStatus(id, 'running');
|
|
580
|
+
emit('cloud.message', { module: 'cloud', taskId: id });
|
|
574
581
|
console.log(chalk.green(`Message sent to task ${id}. Agent is continuing.`));
|
|
575
582
|
}
|
|
576
583
|
catch (err) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `agents events` — read the structured audit/event log.
|
|
3
3
|
*
|
|
4
|
-
* The event log (`~/.agents
|
|
4
|
+
* The event log (`~/.agents/events.jsonl`) records every
|
|
5
5
|
* `agents <module> <cmd>` invocation plus richer typed events (secrets access,
|
|
6
6
|
* version installs, ...), each stamped with who ran it and from where (OS user,
|
|
7
7
|
* local vs SSH, remote client IP). This command reads it back — the audit trail
|
package/dist/commands/events.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `agents events` — read the structured audit/event log.
|
|
3
3
|
*
|
|
4
|
-
* The event log (`~/.agents
|
|
4
|
+
* The event log (`~/.agents/events.jsonl`) records every
|
|
5
5
|
* `agents <module> <cmd>` invocation plus richer typed events (secrets access,
|
|
6
6
|
* version installs, ...), each stamped with who ran it and from where (OS user,
|
|
7
7
|
* local vs SSH, remote client IP). This command reads it back — the audit trail
|
|
@@ -125,8 +125,7 @@ function collect(value, previous) {
|
|
|
125
125
|
}
|
|
126
126
|
/** Tail today's event file, printing new lines as they land. */
|
|
127
127
|
async function followLog() {
|
|
128
|
-
const
|
|
129
|
-
const file = `${getLogsPath()}/events-${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}.jsonl`;
|
|
128
|
+
const file = getLogsPath();
|
|
130
129
|
let offset = 0;
|
|
131
130
|
try {
|
|
132
131
|
offset = fs.statSync(file).size;
|