@phnx-labs/agents-cli 1.20.35 → 1.20.37
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 +91 -0
- package/LICENSE +185 -21
- package/README.md +8 -4
- package/dist/commands/audit.d.ts +14 -0
- package/dist/commands/audit.js +68 -0
- package/dist/commands/browser.js +82 -8
- package/dist/commands/check.d.ts +15 -0
- package/dist/commands/check.js +84 -0
- package/dist/commands/cloud.js +143 -3
- package/dist/commands/computer.js +81 -0
- package/dist/commands/daemon.js +4 -1
- package/dist/commands/doctor.js +1 -89
- package/dist/commands/events.js +3 -3
- package/dist/commands/exec.d.ts +19 -0
- package/dist/commands/exec.js +277 -11
- package/dist/commands/hosts.js +10 -6
- package/dist/commands/inspect.js +8 -9
- package/dist/commands/lock.d.ts +12 -0
- package/dist/commands/lock.js +70 -0
- package/dist/commands/message.d.ts +15 -0
- package/dist/commands/message.js +56 -0
- package/dist/commands/routines.js +101 -5
- package/dist/commands/secrets-migrate.js +106 -57
- package/dist/commands/secrets.d.ts +31 -18
- package/dist/commands/secrets.js +156 -75
- package/dist/commands/serve.d.ts +10 -0
- package/dist/commands/serve.js +37 -0
- package/dist/commands/sessions-inject.d.ts +14 -0
- package/dist/commands/sessions-inject.js +111 -0
- package/dist/commands/sessions-picker.d.ts +2 -0
- package/dist/commands/sessions-picker.js +24 -3
- package/dist/commands/sessions-resume.js +20 -8
- package/dist/commands/sessions-sync.d.ts +3 -0
- package/dist/commands/sessions-sync.js +44 -4
- package/dist/commands/sessions.d.ts +78 -1
- package/dist/commands/sessions.js +506 -66
- package/dist/commands/setup.js +4 -2
- package/dist/commands/sync.d.ts +3 -1
- package/dist/commands/sync.js +156 -4
- package/dist/commands/teams.js +217 -0
- package/dist/commands/versions.js +2 -4
- package/dist/commands/watchdog.d.ts +18 -0
- package/dist/commands/watchdog.js +238 -0
- package/dist/index.js +25 -2
- package/dist/lib/audit/log.d.ts +92 -0
- package/dist/lib/audit/log.js +177 -0
- package/dist/lib/auto-pull.js +2 -1
- package/dist/lib/browser/chrome.d.ts +10 -0
- package/dist/lib/browser/chrome.js +18 -7
- package/dist/lib/browser/drivers/ssh.js +2 -1
- package/dist/lib/browser/har.d.ts +84 -0
- package/dist/lib/browser/har.js +77 -0
- package/dist/lib/browser/ipc.js +24 -3
- package/dist/lib/browser/profiles.d.ts +1 -1
- package/dist/lib/browser/profiles.js +8 -10
- package/dist/lib/browser/refs.d.ts +65 -0
- package/dist/lib/browser/refs.js +73 -1
- package/dist/lib/browser/runtime-state.js +1 -0
- package/dist/lib/browser/service.d.ts +38 -2
- package/dist/lib/browser/service.js +112 -8
- package/dist/lib/browser/types.d.ts +14 -1
- package/dist/lib/budget/live-cloud.d.ts +42 -0
- package/dist/lib/budget/live-cloud.js +79 -0
- package/dist/lib/budget/live-team.d.ts +31 -0
- package/dist/lib/budget/live-team.js +115 -0
- package/dist/lib/cloud/codex.js +4 -0
- package/dist/lib/cloud/rush.d.ts +12 -1
- package/dist/lib/cloud/rush.js +13 -3
- package/dist/lib/cloud/types.d.ts +9 -0
- package/dist/lib/computer/dispatch.d.ts +8 -0
- package/dist/lib/computer/dispatch.js +125 -0
- package/dist/lib/computer/loop.d.ts +62 -0
- package/dist/lib/computer/loop.js +98 -0
- package/dist/lib/computer/model.d.ts +44 -0
- package/dist/lib/computer/model.js +157 -0
- package/dist/lib/concurrency.d.ts +19 -0
- package/dist/lib/concurrency.js +33 -0
- package/dist/lib/daemon.d.ts +57 -0
- package/dist/lib/daemon.js +196 -18
- package/dist/lib/devices/registry.d.ts +7 -0
- package/dist/lib/devices/registry.js +24 -0
- package/dist/lib/devices/resolve-target.d.ts +24 -0
- package/dist/lib/devices/resolve-target.js +80 -0
- package/dist/lib/devices/tailscale.js +1 -1
- package/dist/lib/drift.d.ts +52 -0
- package/dist/lib/drift.js +112 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/events.js +31 -13
- package/dist/lib/exec.d.ts +17 -0
- package/dist/lib/exec.js +79 -13
- package/dist/lib/git.d.ts +27 -0
- package/dist/lib/git.js +56 -1
- package/dist/lib/hooks/cache.d.ts +6 -0
- package/dist/lib/hooks/cache.js +54 -12
- package/dist/lib/hooks.d.ts +27 -0
- package/dist/lib/hooks.js +127 -8
- package/dist/lib/hosts/dispatch.d.ts +15 -0
- package/dist/lib/hosts/dispatch.js +39 -6
- package/dist/lib/hosts/logs.js +30 -1
- package/dist/lib/hosts/option.js +1 -1
- package/dist/lib/hosts/passthrough.js +3 -1
- package/dist/lib/hosts/ready.d.ts +29 -6
- package/dist/lib/hosts/ready.js +66 -15
- package/dist/lib/hosts/registry.d.ts +19 -2
- package/dist/lib/hosts/registry.js +58 -2
- package/dist/lib/hosts/remote-cmd.d.ts +62 -1
- package/dist/lib/hosts/remote-cmd.js +70 -1
- package/dist/lib/hosts/remote-os.d.ts +17 -0
- package/dist/lib/hosts/remote-os.js +30 -0
- package/dist/lib/hosts/session-index.d.ts +34 -0
- package/dist/lib/hosts/session-index.js +56 -0
- package/dist/lib/hosts/tasks.d.ts +14 -0
- package/dist/lib/hosts/tasks.js +15 -0
- package/dist/lib/lock.d.ts +93 -0
- package/dist/lib/lock.js +207 -0
- package/dist/lib/loop.js +16 -1
- package/dist/lib/machine-id.d.ts +21 -0
- package/dist/lib/machine-id.js +26 -0
- package/dist/lib/mailbox-target.d.ts +36 -0
- package/dist/lib/mailbox-target.js +45 -0
- package/dist/lib/mailbox.d.ts +47 -0
- package/dist/lib/mailbox.js +194 -0
- package/dist/lib/mcp.d.ts +5 -0
- package/dist/lib/mcp.js +24 -8
- package/dist/lib/migrate.d.ts +19 -0
- package/dist/lib/migrate.js +134 -26
- package/dist/lib/overdue.js +3 -0
- package/dist/lib/picker.d.ts +2 -0
- package/dist/lib/picker.js +4 -1
- package/dist/lib/platform/exec.d.ts +46 -0
- package/dist/lib/platform/exec.js +74 -0
- package/dist/lib/platform/process.d.ts +31 -0
- package/dist/lib/platform/process.js +34 -1
- package/dist/lib/platform/winpath.js +2 -0
- package/dist/lib/plugins.js +16 -6
- package/dist/lib/profiles.d.ts +25 -0
- package/dist/lib/profiles.js +22 -6
- package/dist/lib/pty-client.js +2 -1
- package/dist/lib/rotate.d.ts +61 -0
- package/dist/lib/rotate.js +52 -0
- package/dist/lib/routines.d.ts +40 -2
- package/dist/lib/routines.js +66 -8
- package/dist/lib/runner.d.ts +11 -2
- package/dist/lib/runner.js +49 -7
- package/dist/lib/scheduler.js +6 -1
- package/dist/lib/secrets/bundles.d.ts +60 -4
- package/dist/lib/secrets/bundles.js +131 -12
- package/dist/lib/secrets/filestore.d.ts +3 -0
- package/dist/lib/secrets/filestore.js +42 -16
- package/dist/lib/secrets/index.d.ts +43 -2
- package/dist/lib/secrets/index.js +102 -3
- package/dist/lib/secrets/mcp.d.ts +93 -0
- package/dist/lib/secrets/mcp.js +205 -0
- package/dist/lib/secrets/remote.js +12 -5
- package/dist/lib/secrets/sync.js +83 -4
- package/dist/lib/secrets/windows.js +14 -3
- package/dist/lib/serve/data.d.ts +81 -0
- package/dist/lib/serve/data.js +91 -0
- package/dist/lib/serve/page.d.ts +7 -0
- package/dist/lib/serve/page.js +140 -0
- package/dist/lib/serve/server.d.ts +46 -0
- package/dist/lib/serve/server.js +115 -0
- package/dist/lib/session/active.d.ts +73 -0
- package/dist/lib/session/active.js +199 -22
- package/dist/lib/session/db.d.ts +2 -1
- package/dist/lib/session/db.js +41 -5
- package/dist/lib/session/discover.d.ts +39 -0
- package/dist/lib/session/discover.js +127 -29
- package/dist/lib/session/ghostty-tabs.d.ts +33 -0
- package/dist/lib/session/ghostty-tabs.js +126 -0
- package/dist/lib/session/inject.d.ts +18 -0
- package/dist/lib/session/inject.js +21 -0
- package/dist/lib/session/parse.js +23 -20
- package/dist/lib/session/pid-registry.d.ts +1 -0
- package/dist/lib/session/pid-registry.js +24 -0
- package/dist/lib/session/provenance.d.ts +14 -2
- package/dist/lib/session/provenance.js +39 -8
- package/dist/lib/session/relative-time.js +6 -2
- package/dist/lib/session/remote-active.js +19 -17
- package/dist/lib/session/remote-list.d.ts +51 -0
- package/dist/lib/session/remote-list.js +205 -0
- package/dist/lib/session/remote.d.ts +7 -1
- package/dist/lib/session/remote.js +20 -4
- package/dist/lib/session/sync/config.d.ts +14 -15
- package/dist/lib/session/sync/config.js +60 -20
- package/dist/lib/session/types.d.ts +23 -0
- package/dist/lib/shims.d.ts +36 -6
- package/dist/lib/shims.js +91 -29
- package/dist/lib/ssh-exec.js +2 -0
- package/dist/lib/ssh-tunnel.js +2 -1
- package/dist/lib/startup/command-registry.d.ts +6 -0
- package/dist/lib/startup/command-registry.js +13 -1
- package/dist/lib/state.d.ts +13 -0
- package/dist/lib/state.js +103 -9
- package/dist/lib/sync-umbrella.d.ts +14 -7
- package/dist/lib/sync-umbrella.js +21 -13
- package/dist/lib/teams/forEach.d.ts +110 -0
- package/dist/lib/teams/forEach.js +186 -0
- package/dist/lib/teams/index.d.ts +1 -0
- package/dist/lib/teams/index.js +1 -0
- package/dist/lib/teams/pr-watch.d.ts +226 -0
- package/dist/lib/teams/pr-watch.js +371 -0
- package/dist/lib/teams/supervisor.d.ts +14 -1
- package/dist/lib/teams/supervisor.js +19 -0
- package/dist/lib/teams/worktree.d.ts +9 -0
- package/dist/lib/teams/worktree.js +32 -0
- package/dist/lib/terminal/backends/index.d.ts +2 -1
- package/dist/lib/terminal/backends/index.js +3 -1
- package/dist/lib/terminal/backends/vscodium-agent.d.ts +31 -0
- package/dist/lib/terminal/backends/vscodium-agent.js +72 -0
- package/dist/lib/terminal/index.d.ts +4 -1
- package/dist/lib/terminal/index.js +4 -1
- package/dist/lib/terminal/inject.d.ts +204 -0
- package/dist/lib/terminal/inject.js +247 -0
- package/dist/lib/terminal/resolve.d.ts +64 -0
- package/dist/lib/terminal/resolve.js +90 -0
- package/dist/lib/terminal/types.d.ts +1 -1
- package/dist/lib/tmux/session.d.ts +10 -0
- package/dist/lib/tmux/session.js +31 -0
- package/dist/lib/triggers/webhook.d.ts +85 -0
- package/dist/lib/triggers/webhook.js +141 -0
- package/dist/lib/versions.d.ts +23 -0
- package/dist/lib/versions.js +119 -13
- package/dist/lib/watchdog/index.d.ts +3 -0
- package/dist/lib/watchdog/index.js +5 -0
- package/dist/lib/watchdog/read.d.ts +35 -0
- package/dist/lib/watchdog/read.js +149 -0
- package/dist/lib/watchdog/runner.d.ts +127 -0
- package/dist/lib/watchdog/runner.js +322 -0
- package/dist/lib/watchdog/watchdog.d.ts +40 -0
- package/dist/lib/watchdog/watchdog.js +166 -0
- package/dist/lib/watchdog/watchdogTail.d.ts +5 -0
- package/dist/lib/watchdog/watchdogTail.js +154 -0
- package/dist/lib/workflows.d.ts +166 -0
- package/dist/lib/workflows.js +193 -0
- package/package.json +5 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,97 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 1.20.36
|
|
6
|
+
|
|
7
|
+
**[windows] `agents sessions --active` detects sessions on Windows, and shim launches carry cwd + session identity everywhere**
|
|
8
|
+
|
|
9
|
+
- The active listing found nothing on Windows: the headless scan shelled out to `ps -A` and per-pid `lsof` — both POSIX-only, both failing silently into "No active agent sessions" with a dozen live `claude.exe` processes running. The process table now comes from one CIM query on win32 (`powershell.exe Get-CimInstance Win32_Process`; `wmic` is removed on current Windows 11) parsed into the same pid/ppid/comm rows, agent-kind matching strips the `.exe` image suffix case-insensitively (POSIX comms stay exact-match — macOS's Claude *desktop app* process is named `Claude` and 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 (no `lsof` on Windows), same-kind child agent processes — Claude runs subagents and its bundled ripgrep as child `claude` processes — 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 root `claude.exe` processes render as 6 rows (previously zero). Source: `src/lib/session/active.ts`.
|
|
10
|
+
- Those Windows rows grouped under `unknown` with no topic because only `ag run` recorded a pid → session/cwd registry entry. The transparent shim delegate (`execShimPassthrough`) — the path every `claude`/`codex` typed into a terminal actually takes — now writes the same `by-pid` registry 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 `.cmd` shell 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 of `unknown`. (POSIX bash shims `exec` the 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`.
|
|
11
|
+
**[windows] `browser profiles create` no longer hands out a port an already-running browser is listening on**
|
|
12
|
+
|
|
13
|
+
- `findFreeProfilePort` probed candidate ports by shelling out to `lsof`, 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 `--endpoint` was assigned `cdp://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 through `isPortInUse` (`chrome.ts`, newly exported), the same platform-aware probe the launcher already used: `lsof` on POSIX, `netstat -ano` on 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`.
|
|
14
|
+
|
|
15
|
+
**[windows] Background spawns no longer flash console windows while agents run**
|
|
16
|
+
|
|
17
|
+
- 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: true` without `windowsHide`. On Windows `detached` maps to `DETACHED_PROCESS`, under which CreateProcess ignores `CREATE_NO_WINDOW` and the child runs console-less, so every console-subsystem descendant (powershell.exe for a Credential Manager read, git, node, a `.cmd` shim'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 through `powershell.exe` on every session-sync cycle (90s). The new `backgroundSpawnOptions()` (`src/lib/platform/process.ts`) is the single place that decides the pattern: POSIX keeps `detached: true` (own process group, group-kill still works); Windows switches to `windowsHide: true` with 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 yields `VISIBLE=True`, the new pattern allocates no console window at all. Leaf spawns of console tools reachable from a console-less parent (powershell in `secrets/windows.ts` + `platform/winpath.ts`, tasklist/netstat/taskkill in the browser runtime probes, `tailscale status`, ssh, ffmpeg) now pass `windowsHide: true` as 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.
|
|
18
|
+
- Fallout the hidden console surfaced (caught by the real-advapi32 round-trip test): the Credential Manager driver's `set` read 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 as `caf├⌐ Γÿò`). The static PS script now reads stdin as raw bytes (`[Console]::OpenStandardInput()` → `MemoryStream`) and pins `[Console]::OutputEncoding` to UTF-8, so the round-trip is codepage-independent in every calling context. Source: `src/lib/secrets/windows.ts`.
|
|
19
|
+
- Correction for the fd-redirected roots (daemon, runner, PTY server): `windowsHide` is inert whenever a stdio slot is redirected to an fd — libuv skips `CREATE_NO_WINDOW` if 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 moment `agents daemon start` returned (the #556 failure, reproduced live: child `alive-after-launcher-exit=false` under `{detached:false, windowsHide:true}` with fd stdio, `true` under `{detached:true, …}`). `backgroundSpawnOptions({ fdStdio: true })` now keeps `DETACHED_PROCESS` for these roots — the child runs console-less and windowless, and its console-tool spawns stay invisible via the leaf `windowsHide` fixes 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`).
|
|
20
|
+
|
|
21
|
+
**[teams] `agents teams pr-watch <team>` — autonomous PR lifecycle: CI-fix waves + review-comment routing (Closes #338)**
|
|
22
|
+
|
|
23
|
+
- A team's teammates open PRs; `pr-watch` watches them and reacts without a human in the loop. Each poll it resolves the PRs the team opened (from each teammate's `pr_url`, else the `gh pr create` detected in the session it ran), snapshots CI + review comments via the `gh` CLI (`gh pr checks --json`, `gh api …/pulls/{n}/comments`), and decides follow-ups: **RED CI** spawns a fix teammate `--after` the 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 a `bugfix` teammate (the existing `TaskType`) `--after` the source, with the comment body injected. Both slot into the team DAG the supervisor already drains — the loop calls `startReady` each pass so staged fixers launch when their source completes — and every reaction is visible in `agents teams status`. Dedupe is by check-run id / comment id, persisted to `pr-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 in `src/lib/teams/pr-watch.test.ts`, no network); the `gh` collectors and the `handleSpawn`-backed reactor sit on top. **Deferred (documented follow-up):** the event-driven path from #331's webhook receiver — `pollPrSnapshot` is the seam where a `check_run` / `pull_request_review_comment` payload plugs in, producing the same `PrSnapshot` the pure decider already consumes. Source: `src/lib/teams/pr-watch.ts`, `src/commands/teams.ts`.
|
|
24
|
+
|
|
25
|
+
**[hosts] `--host`/`--device` now resolves registered devices and ad-hoc `user@host` — one concept, one flag**
|
|
26
|
+
|
|
27
|
+
- Offloading a run no longer needs a machine enrolled in *two* registries. `agents run --host <name>` (and the new `--device <name>` alias, plus `teams add --host`, and every other `--host` consumer via the shared resolver) now resolves in order: the `agents hosts` registry (unchanged), then the **`agents devices`** registry, then an ad-hoc **`user@host`**. A machine registered once with `agents devices sync` is reachable immediately — previously it errored `Unknown host` unless you *also* ran `agents 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 by `assertValidSshTarget`). A device that authenticates by password can't offload over the BatchMode ssh path, so it throws a typed, actionable `DeviceOffloadUnsupportedError` (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`.
|
|
28
|
+
|
|
29
|
+
**[secrets] recover credentials orphaned under a stale keychain access group (RUSH-1413)**
|
|
30
|
+
|
|
31
|
+
- 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 concrete `2HTP252L87.com.phnx-labs.agents-keychain` that every query now pins (`keychain-helper.swift` `dpBase`). Those items are intact and the wildcard entitlement authorizes reading them, but the pinned queries never ask for that group, so `has`/`get`/`list` reported them **missing** and whole bundles vanished from `secrets 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`/`has` add an **un-pinned data-protection fallback pass** after the pinned miss, so an orphan reads and reports present instead of missing; (2) `get`/`get-batch` **re-home** an orphan inline the first time it's read — reusing the read's Touch ID, add-before-delete, deleting the exact orphan by `kSecValuePersistentRef` — mirroring the existing file-based `migrateInline`; (3) a new `migrate-orphans` helper verb bulk re-homes every orphan behind a single Touch ID. `list` is now un-pinned so orphaned bundle metadata reappears, and `set`/`delete` clear across all groups so a rotate/delete can't leave a shadow copy. New `list-orphans` verb enumerates orphans prompt-free. Source: `src/lib/secrets/keychain-helper.swift`, `src/lib/secrets/index.ts`.
|
|
32
|
+
- `agents secrets migrate-acl` now sweeps orphaned-access-group items in addition to legacy-ACL stragglers: the dry-run lists both classes, `--commit` re-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 team `2HTP252L87` and 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.
|
|
33
|
+
|
|
34
|
+
**CI: audit-event tests are green on Windows; the release re-gates on the windows-latest matrix legs (RUSH-1412)**
|
|
35
|
+
|
|
36
|
+
- The cross-platform matrix (`ci.yml`, runs only on `release/**` + `v*`) had both `build (windows-latest, …)` legs red: `tests/events-audit.test.ts` and `tests/teams-events.test.ts` spawn the CLI with a redirected `HOME` and then read the audit trail under it, but the events writer rooted its log dir at a bare `os.homedir()` (`src/lib/events.ts:24`). On Windows `os.homedir()` resolves from `USERPROFILE` and ignores a `HOME` override, so every `command.start`/`command.end` record was silently written to the real profile instead of the test's temp home — the events array came back empty and the log file `ENOENT`'d (macOS/Ubuntu were green because `os.homedir()` honors `$HOME` on POSIX). The writer now roots its log dir through `state.getLogsDir()`, the single canonical home anchor (`process.env.HOME ?? os.homedir()`), which honors an explicit `HOME` on every platform and still resolves to `USERPROFILE` in production on Windows (where `HOME` is unset), so real users are unaffected. One `events-audit` case also reconstructed its log filename from a UTC `toISOString()` while the writer names files from the local date, so it `ENOENT`'d whenever a runner's local and UTC dates straddled midnight; it now globs the log dir like the other assertions. `scripts/release.sh` restores both `build (windows-latest, 22|24)` entries to `EXPECTED_CHECKS`, so Windows is a release gate again. Source: `src/lib/events.ts`, `tests/events-audit.test.ts`, `scripts/release.sh`.
|
|
37
|
+
- Three more `build (windows-latest, …)` failures fixed. (1) **Antigravity sessions were invisible to Windows users, not just tests.** `parseAntigravity` read its conversation SQLite DBs by shelling out to the `sqlite3` CLI (`src/lib/session/parse.ts:893`), which is absent on Windows — so `execFileSync('sqlite3', …)` threw `spawnSync sqlite3 ENOENT` and the parser silently returned `[]` for every real Antigravity session on Windows. It now reads the `step_payload` BLOBs through the runtime-agnostic `src/lib/sqlite.ts` wrapper (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.ts` builds its fixture DB through the same wrapper instead of the CLI. (2) `parse-droid.test.ts` derived its `testdata` dir from `new URL(import.meta.url).pathname`, which on Windows yields `/C:/…` — so `path.join` produced a doubled-drive `C:\C:\…` that `ENOENT`'d; it now uses `fileURLToPath(import.meta.url)`. (3) `git.test.ts`'s `syncRepoGit` "pull-only" case failed because the Windows runner's `core.autocrlf=true` converted the freshly-cloned `README.md` to CRLF *during* `git clone` — before `configIdentity()` could set `autocrlf=false` on the clone — so `status.isClean()` saw a phantom modification and `syncRepoGit` refused with "Working tree has uncommitted changes." The test seed now commits a `.gitattributes` (`* -text`), which wins over `autocrlf` at checkout time so every clone lands byte-identical LF content. (`parseOpenCode` shells out to `sqlite3` the same way and has the same latent Windows gap, but its test mocks `execFileSync` so 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`.
|
|
38
|
+
|
|
39
|
+
**`agents message <target> <text>`: deliver a message to an already-running agent mid-flight [RUSH-1415]**
|
|
40
|
+
|
|
41
|
+
- 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 (was `agents cloud message`); a **live local/teams/loop agent** gets the text enqueued into a per-agent file-spool mailbox that a `PreToolUse` hook 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 (via `REMOTE_PASSTHROUGH`) to the box that owns the agent, and `message` registers as a lazy SQLite-backed command like `cloud`/`sessions`/`teams`. Source: `src/commands/message.ts`, `src/lib/mailbox-target.ts`, `src/lib/hosts/passthrough.ts`, `src/lib/startup/command-registry.ts`.
|
|
42
|
+
- 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 by `msgId`). Every message stamps a `to` field and a monotonic FIFO `msgId`; 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-time `to` stamp so a traversal-bearing id fails loud instead of silently misrouting. At spawn, `buildExecEnv` points each agent at its own box via `AGENTS_MAILBOX_DIR` (keyed by session id); a loop overrides it to the run-level box so every iteration shares one inbox, and prints `agents 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`.
|
|
43
|
+
|
|
44
|
+
**Watchdog core: stall detection + nudge decision for a stalled agent (#612) [RUSH-1415]**
|
|
45
|
+
|
|
46
|
+
- 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 tolerant `parseWatchdogResponse`. `summarizeWatchdogTail` extracts 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 from `sessionId + agent` by reusing `getAgentSessionDirs()` rather than hardcoding paths — including the recursive `walkForFiles` walk that reaches Codex's deep `sessions/YYYY/MM/DD/rollout-…jsonl` layout and Gemini's tmp layout, driven per-agent by `WATCHDOG_SESSION_LAYOUT`. Source: `src/lib/watchdog/watchdog.ts`, `src/lib/watchdog/watchdogTail.ts`, `src/lib/watchdog/read.ts`, `src/lib/watchdog/index.ts`.
|
|
47
|
+
|
|
48
|
+
**Terminal injection: type into an already-running agent's exact terminal (#611, #616) [RUSH-1415]**
|
|
49
|
+
|
|
50
|
+
- `injectIntoTerminal` extends 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 a `LaunchSpec` run through the same `runSpec` transport, so injection inherits local/remote (`--host` over SSH) execution for free. Backends: **tmux** `send-keys -t <pane>` (socket-addressed), **iterm** `tell session id "<uuid>" to write text` (no `activate`, so it addresses the exact split without stealing focus), **vscodium** (VSCodium/Cursor/VS Code) over the editor CLI's `--open-url` into the extension's `/inject` verb, and **pty** via the agents-pty sidecar (local-only). Ink-TUI Enter semantics: text and Enter are two separate writes by default (a fused `text\r` is swallowed by Claude's Ink TUI), and `combined` opts into the single fused write for plain shells. Source: `src/lib/terminal/inject.ts`, `src/lib/terminal/index.ts`.
|
|
51
|
+
- `resolveInjectTarget` is the single resolver the watchdog calls: `sessionId →` a precise `InjectTarget` or an honest `{ addressable: false, reason }`, with precedence tmux > iterm > vscodium > pty and a deliberate safe skip for Ghostty (no addressable split API). `deriveProvenance` now captures `$ITERM_SESSION_ID` and, absent tmux, exposes an `iterm` reply 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`/`--pty` to target a backend directly, `--combined` to toggle the Ink-safe two-write default, `--no-enter` to send without submitting, and `--host` to inject over SSH. Source: `src/lib/terminal/resolve.ts`, `src/lib/session/provenance.ts`, `src/lib/session/inject.ts`, `src/commands/sessions-inject.ts`.
|
|
52
|
+
|
|
53
|
+
**Watchdog consumer + `agents watchdog`: run one stall-detection tick end to end (#619, #622) [RUSH-1415]**
|
|
54
|
+
|
|
55
|
+
- `runWatchdogTick` ties the pure pieces together into one pass over `getActiveSessions()`: `classifyTerminal()` finds stalls, `readWatchdogTail()` reads the transcript, `isLikelyTrulyBlocked()` gates on the promise-without-toolcall heuristic (deterministic v1) or an optional `--smart` LLM decider, `resolveInjectTargetForSession()` is the absolute safety gate, and `injectIntoTerminal()` delivers `Continue.` into the EXACT split. A nudge fires ONLY on `addressable:true`; an `addressable:false` stall is flagged to a tray-readable state file and skipped — never a guessed target. Per-session policy is `off|keep|handsoff` (handsoff detects and flags but never injects); cooldown and un-addressable flags persist under `~/.agents/.cache/state/watchdog/`. The `agents watchdog` command runs it without the menu-bar: bare = one dry tick (reports would-nudge/skip + why), `--nudge` injects for real, `--watch` is a daemon loop (`--interval`, default 30s), `--json` is machine-readable, and `--stall/--cooldown/--dormant` override thresholds. `runner.test.ts` drives 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`.
|
|
56
|
+
- 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 via `agents watchdog enable|disable` and shows `N stalled · M nudged`, and `AgentsCLI` gains `watchdogStatus()/watchdogTick(nudge:)/watchdogSetEnabled()` mirroring the `doctorOverview()` 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`.
|
|
57
|
+
|
|
58
|
+
**VSCodium / Cursor / VS Code terminal backend (#608, #620)**
|
|
59
|
+
|
|
60
|
+
- A new `vscodium-agent` terminal backend opens each resumed session as an agent-terminal tab in a running VSCodium / Cursor / VS Code window — via the `swarm-ext` extension's `/spawn` URI 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 no `zsh -ilc` wrap 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-decodes `uri.query` once before the handler parses it (a bare `echo a && touch b` was otherwise truncated at the `&`). Wired into `sessions resume` as `--vscodium`; auto-detect is intentionally omitted (`TERM_PROGRAM=vscode` can'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 (`--tabs` still forces tabs elsewhere). Source: `src/lib/terminal/backends/vscodium-agent.ts`, `src/lib/terminal/index.ts`, `src/commands/sessions-resume.ts`.
|
|
61
|
+
|
|
62
|
+
**`agents sync <repo>`: git-sync a single DotAgent repo (#535)**
|
|
63
|
+
|
|
64
|
+
- 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 new `syncRepoGit` refuses on a dirty working tree (commit or discard first), otherwise `git fetch origin` + `git pull --rebase origin <branch>` against the repo's own HEAD branch (falling back to `main`), reinstalls the git hooks, and reports the resulting short commit. The `user` repo and enabled extra-repo aliases also `git push` local commits up; `system` is a pull-only mirror of the npm-shipped upstream (`push: false`). `project` and 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 like `system`/`user` would otherwise fail `parseAgentSpec`. Source: `src/lib/git.ts`, `src/commands/sync.ts`.
|
|
65
|
+
- Bare `agents sync` no 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 via `agents sessions --host <machine>`). Interactive bare `agents 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 via `mergeRepoScopedSelections` / `unionResourceSelections`. Source: `src/lib/sync-umbrella.ts`, `src/lib/versions.ts`, `src/commands/sync.ts`.
|
|
66
|
+
|
|
67
|
+
**Split `agents.yaml` into portable, per-device, and machine-local files (#538)**
|
|
68
|
+
|
|
69
|
+
- The committed central `~/.agents/agents.yaml` used to carry machine-specific fields and was held back with a `git skip-worktree` band-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 central `agents.yaml` is left portable. `writeMetaUnlocked` writes the device and history files BEFORE stripping and rewriting central, so a crash mid-write never drops pins/versions before they persist; `readMeta` overlays the machine-local files back on via `overlayMachineLocal` (device pins win and self-heal a pre-migration central). Source: `src/lib/state.ts`, `src/lib/machine-id.ts`.
|
|
70
|
+
- Migration `migrateSplitDeviceLocalMeta` (sentinel bumped to `v11`) performs the one-time split on raw YAML, merging into any existing device/history files (existing entries win) via `atomicWriteFileSync`, and only rewrites central when it actually carries machine-local fields — a portable-only `agents.yaml` is left byte-untouched — while always clearing the `skip-worktree` bit 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-level `state.ts` can 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`.
|
|
71
|
+
|
|
72
|
+
**`agents sessions`: the interactive picker now shows origin machine, PR/ticket, and worktree columns**
|
|
73
|
+
|
|
74
|
+
- 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 on `SessionMeta.machine` by `discoverSessions`. The picker row, previously stuck on `shortId · 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 so `yosemite-s0`/`yosemite-s1` read as `s0`/`s1`), a blue `PR#`/ticket column (only when some row carries a ref), and a magenta `wt:<slug>` worktree badge. Column flags are computed once over the whole pool via `pickerColumnsFor` and 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.
|
|
75
|
+
- A dim `subtitle` hint line renders between the header and the rows (new `subtitle` field on `PickerConfig`/`SessionPickerConfig`), rotating a `Tip:` 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 but `formatPickerLabel` reserved 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 through `PickerColumns` and 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`.
|
|
76
|
+
|
|
77
|
+
**Reach Windows peers over `--host` (RUSH-1429)**
|
|
78
|
+
|
|
79
|
+
- The SSH command layer gained a PowerShell dialect so `--host` operations can target Windows remotes, where ssh lands in `cmd.exe`/PowerShell and `bash -lc` does not exist. `remoteShellFor(os)` routes `windows → powershell` and everything else (including unknown/absent) → posix, so linux/macOS never regress; `buildWindowsAgentsCommand` emits `powershell -NoProfile -EncodedCommand <base64-utf16le>`, which survives `cmd.exe` re-parsing with zero quoting hazards. The peer OS is resolved from the tailscale-synced device registry (fleet fan-out) or the enrolled `HostEntry.os` (explicit `--host`). This fixes `agents sessions --host` / `--active` and remote secrets reads (browse + use-a-remote-bundle), which previously wrapped the remote invocation in `bash -lc` and got `'bash' is not recognized` from a Windows peer. The `secrets export/import --host` write 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`.
|
|
80
|
+
|
|
81
|
+
**Windows portability + CI hardening**
|
|
82
|
+
|
|
83
|
+
- `agents sessions … resume` no longer crashes on Windows with `spawn EFTYPE`: `resumeSessionInPlace` spawned the version-pinned launcher (`claude@2.1.196`) with `shell: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 via `needsWindowsShell` and reports a synchronous launch failure truthfully. The generated hook-cache shim also hardcoded `python3` for its hash/timer/mtime, but on Windows `python3` is often the Microsoft Store execution-alias stub (prints to stderr, exits non-zero, 0 bytes) — silently emptying `mtime` so every call missed the cache and re-ran the hook; it now probes for a runnable interpreter (`python3`, then `python`) by executing `-c 'import sys'`. Source: `src/commands/sessions.ts`, `src/lib/hooks/cache.ts`.
|
|
84
|
+
- Two new CI guards keep these Windows-only, separator-prone bugs from reaching a release: a path-filtered `test-windows` job runs the suite on `windows-latest` for changes under hooks/platform/shims (the required `test` gate runs on `ubuntu-latest`, where `path.sep` is `/`, so a backslash-path bug is invisible), and `toPortableCommand` is now pure/exported with injectable home + separator so a unit test can assert Windows `C:\…` → `~/…` folding on any host. Separately, a `prepare: npm run build` hook rebuilds the gitignored `dist/` on every install/link (and before `npm publish`), so a dev-linked checkout can't silently run a stale `dist/` behind a source fix. Source: `.github/workflows/tests-windows.yml`, `package.json`.
|
|
85
|
+
|
|
86
|
+
**License: MIT → Apache-2.0 (#504)**
|
|
87
|
+
|
|
88
|
+
- The project relicenses from MIT to Apache-2.0. `LICENSE`, `README`, and `package.json` carry the new license, and the human-facing docs (the `AGENTS.md` brand lines, the `CONTRIBUTING.md` CLA clause, `DESIGN.md`) were aligned so the stated license is consistent everywhere.
|
|
89
|
+
|
|
90
|
+
**Security hardening batch (#474–#478)**
|
|
91
|
+
|
|
92
|
+
- **Shell / option injection.** `agents inspect` no longer builds its `git` call as a shell string: a crafted repo path could inject via `$(…)` or other shell syntax through `execSync(\`git -C ${…} ${args}\`)`. It now uses argv-form `execFileSync('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`.
|
|
93
|
+
- **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`.
|
|
94
|
+
- **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 an `agents` version install (#476). Source: `src/lib/versions.ts`.
|
|
95
|
+
|
|
5
96
|
## 1.20.35
|
|
6
97
|
|
|
7
98
|
**CI: build node-pty's native binary on macOS/Windows so the release matrix is green cross-platform**
|
package/LICENSE
CHANGED
|
@@ -1,21 +1,185 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction, and
|
|
10
|
+
distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
|
13
|
+
owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all other entities
|
|
16
|
+
that control, are controlled by, or are under common control with that entity.
|
|
17
|
+
For the purposes of this definition, "control" means (i) the power, direct or
|
|
18
|
+
indirect, to cause the direction or management of such entity, whether by
|
|
19
|
+
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
20
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
21
|
+
|
|
22
|
+
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
|
23
|
+
permissions granted by this License.
|
|
24
|
+
|
|
25
|
+
"Source" form shall mean the preferred form for making modifications, including
|
|
26
|
+
but not limited to software source code, documentation source, and configuration
|
|
27
|
+
files.
|
|
28
|
+
|
|
29
|
+
"Object" form shall mean any form resulting from mechanical transformation or
|
|
30
|
+
translation of a Source form, including but not limited to compiled object code,
|
|
31
|
+
generated documentation, and conversions to other media types.
|
|
32
|
+
|
|
33
|
+
"Work" shall mean the work of authorship, whether in Source or Object form,
|
|
34
|
+
made available under the License, as indicated by a copyright notice that is
|
|
35
|
+
included in or attached to the work (an example is provided in the Appendix
|
|
36
|
+
below).
|
|
37
|
+
|
|
38
|
+
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
|
39
|
+
is based on (or derived from) the Work and for which the editorial revisions,
|
|
40
|
+
annotations, elaborations, or other modifications represent, as a whole, an
|
|
41
|
+
original work of authorship. For the purposes of this License, Derivative Works
|
|
42
|
+
shall not include works that remain separable from, or merely link (or bind by
|
|
43
|
+
name) to the interfaces of, the Work and Derivative Works thereof.
|
|
44
|
+
|
|
45
|
+
"Contribution" shall mean any work of authorship, including the original
|
|
46
|
+
version of the Work and any modifications or additions to that Work or
|
|
47
|
+
Derivative Works thereof, that is intentionally submitted to Licensor for
|
|
48
|
+
inclusion in the Work by the copyright owner or by an individual or Legal Entity
|
|
49
|
+
authorized to submit on behalf of the copyright owner. For the purposes of this
|
|
50
|
+
definition, "submitted" means any form of electronic, verbal, or written
|
|
51
|
+
communication sent to the Licensor or its representatives, including but not
|
|
52
|
+
limited to communication on electronic mailing lists, source code control
|
|
53
|
+
systems, and issue tracking systems that are managed by, or on behalf of, the
|
|
54
|
+
Licensor for the purpose of discussing and improving the Work, but excluding
|
|
55
|
+
communication that is conspicuously marked or otherwise designated in writing by
|
|
56
|
+
the copyright owner as "Not a Contribution."
|
|
57
|
+
|
|
58
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
|
59
|
+
of whom a Contribution has been received by Licensor and subsequently
|
|
60
|
+
incorporated within the Work.
|
|
61
|
+
|
|
62
|
+
2. Grant of Copyright License. Subject to the terms and conditions of this
|
|
63
|
+
License, each Contributor hereby grants to You a perpetual, worldwide,
|
|
64
|
+
non-exclusive, no-charge, royalty-free, irrevocable copyright license to
|
|
65
|
+
reproduce, prepare Derivative Works of, publicly display, publicly perform,
|
|
66
|
+
sublicense, and distribute the Work and such Derivative Works in Source or
|
|
67
|
+
Object form.
|
|
68
|
+
|
|
69
|
+
3. Grant of Patent License. Subject to the terms and conditions of this License,
|
|
70
|
+
each Contributor hereby grants to You a perpetual, worldwide, non-exclusive,
|
|
71
|
+
no-charge, royalty-free, irrevocable (except as stated in this section) patent
|
|
72
|
+
license to make, have made, use, offer to sell, sell, import, and otherwise
|
|
73
|
+
transfer the Work, where such license applies only to those patent claims
|
|
74
|
+
licensable by such Contributor that are necessarily infringed by their
|
|
75
|
+
Contribution(s) alone or by combination of their Contribution(s) with the Work
|
|
76
|
+
to which such Contribution(s) was submitted. If You institute patent litigation
|
|
77
|
+
against any entity (including a cross-claim or counterclaim in a lawsuit)
|
|
78
|
+
alleging that the Work or a Contribution incorporated within the Work
|
|
79
|
+
constitutes direct or contributory patent infringement, then any patent licenses
|
|
80
|
+
granted to You under this License for that Work shall terminate as of the date
|
|
81
|
+
such litigation is filed.
|
|
82
|
+
|
|
83
|
+
4. Redistribution. You may reproduce and distribute copies of the Work or
|
|
84
|
+
Derivative Works thereof in any medium, with or without modifications, and in
|
|
85
|
+
Source or Object form, provided that You meet the following conditions:
|
|
86
|
+
|
|
87
|
+
(a) You must give any other recipients of the Work or Derivative Works a copy of
|
|
88
|
+
this License; and
|
|
89
|
+
|
|
90
|
+
(b) You must cause any modified files to carry prominent notices stating that
|
|
91
|
+
You changed the files; and
|
|
92
|
+
|
|
93
|
+
(c) You must retain, in the Source form of any Derivative Works that You
|
|
94
|
+
distribute, all copyright, patent, trademark, and attribution notices from the
|
|
95
|
+
Source form of the Work, excluding those notices that do not pertain to any part
|
|
96
|
+
of the Derivative Works; and
|
|
97
|
+
|
|
98
|
+
(d) If the Work includes a "NOTICE" text file as part of its distribution, then
|
|
99
|
+
any Derivative Works that You distribute must include a readable copy of the
|
|
100
|
+
attribution notices contained within such NOTICE file, excluding those notices
|
|
101
|
+
that do not pertain to any part of the Derivative Works, in at least one of the
|
|
102
|
+
following places: within a NOTICE text file distributed as part of the
|
|
103
|
+
Derivative Works; within the Source form or documentation, if provided along
|
|
104
|
+
with the Derivative Works; or, within a display generated by the Derivative
|
|
105
|
+
Works, if and wherever such third-party notices normally appear.
|
|
106
|
+
|
|
107
|
+
The contents of the NOTICE file are for informational purposes only and do not
|
|
108
|
+
modify the License. You may add Your own attribution notices within Derivative
|
|
109
|
+
Works that You distribute, alongside or as an addendum to the NOTICE text from
|
|
110
|
+
the Work, provided that such additional attribution notices cannot be construed
|
|
111
|
+
as modifying the License.
|
|
112
|
+
|
|
113
|
+
You may add Your own copyright statement to Your modifications and may provide
|
|
114
|
+
additional or different license terms and conditions for use, reproduction, or
|
|
115
|
+
distribution of Your modifications, or for any such Derivative Works as a whole,
|
|
116
|
+
provided Your use, reproduction, and distribution of the Work otherwise complies
|
|
117
|
+
with the conditions stated in this License.
|
|
118
|
+
|
|
119
|
+
5. Submission of Contributions. Unless You explicitly state otherwise, any
|
|
120
|
+
Contribution intentionally submitted for inclusion in the Work by You to the
|
|
121
|
+
Licensor shall be under the terms and conditions of this License, without any
|
|
122
|
+
additional terms or conditions. Notwithstanding the above, nothing herein shall
|
|
123
|
+
supersede or modify the terms of any separate license agreement you may have
|
|
124
|
+
executed with Licensor regarding such Contributions.
|
|
125
|
+
|
|
126
|
+
6. Trademarks. This License does not grant permission to use the trade names,
|
|
127
|
+
trademarks, service marks, or product names of the Licensor, except as required
|
|
128
|
+
for reasonable and customary use in describing the origin of the Work and
|
|
129
|
+
reproducing the content of the NOTICE file.
|
|
130
|
+
|
|
131
|
+
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in
|
|
132
|
+
writing, Licensor provides the Work (and each Contributor provides its
|
|
133
|
+
Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
134
|
+
KIND, either express or implied, including, without limitation, any warranties or
|
|
135
|
+
conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
136
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
137
|
+
appropriateness of using or redistributing the Work and assume any risks
|
|
138
|
+
associated with Your exercise of permissions under this License.
|
|
139
|
+
|
|
140
|
+
8. Limitation of Liability. In no event and under no legal theory, whether in
|
|
141
|
+
tort (including negligence), contract, or otherwise, unless required by
|
|
142
|
+
applicable law (such as deliberate and grossly negligent acts) or agreed to in
|
|
143
|
+
writing, shall any Contributor be liable to You for damages, including any
|
|
144
|
+
direct, indirect, special, incidental, or consequential damages of any character
|
|
145
|
+
arising as a result of this License or out of the use or inability to use the
|
|
146
|
+
Work (including but not limited to damages for loss of goodwill, work stoppage,
|
|
147
|
+
computer failure or malfunction, or any and all other commercial damages or
|
|
148
|
+
losses), even if such Contributor has been advised of the possibility of such
|
|
149
|
+
damages.
|
|
150
|
+
|
|
151
|
+
9. Accepting Warranty or Additional Liability. While redistributing the Work or
|
|
152
|
+
Derivative Works thereof, You may choose to offer, and charge a fee for,
|
|
153
|
+
acceptance of support, warranty, indemnity, or other liability obligations
|
|
154
|
+
and/or rights consistent with this License. However, in accepting such
|
|
155
|
+
obligations, You may act only on Your own behalf and on Your sole
|
|
156
|
+
responsibility, not on behalf of any other Contributor, and only if You agree to
|
|
157
|
+
indemnify, defend, and hold each Contributor harmless for any liability incurred
|
|
158
|
+
by, or claims asserted against, such Contributor by reason of your accepting any
|
|
159
|
+
such warranty or additional liability.
|
|
160
|
+
|
|
161
|
+
END OF TERMS AND CONDITIONS
|
|
162
|
+
|
|
163
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
164
|
+
|
|
165
|
+
To apply the Apache License to your work, attach the following boilerplate
|
|
166
|
+
notice, with the fields enclosed by brackets "[]" replaced with your own
|
|
167
|
+
identifying information. (Don't include the brackets!) The text should be
|
|
168
|
+
enclosed in the appropriate comment syntax for the file format. We also
|
|
169
|
+
recommend that a file or class name and description of purpose be included on
|
|
170
|
+
the same "printed page" as the copyright notice for easier identification
|
|
171
|
+
within third-party archives.
|
|
172
|
+
|
|
173
|
+
Copyright [yyyy] [name of copyright owner]
|
|
174
|
+
|
|
175
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
176
|
+
you may not use this file except in compliance with the License.
|
|
177
|
+
You may obtain a copy of the License at
|
|
178
|
+
|
|
179
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
180
|
+
|
|
181
|
+
Unless required by applicable law or agreed to in writing, software
|
|
182
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
183
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
184
|
+
See the License for the specific language governing permissions and
|
|
185
|
+
limitations under the License.
|
package/README.md
CHANGED
|
@@ -41,6 +41,8 @@ npm install -g @phnx-labs/agents-cli
|
|
|
41
41
|
bun install -g @phnx-labs/agents-cli
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
Already installed? `agents upgrade` updates agents-cli itself to the latest version (`agents upgrade 1.2.3` for a specific version or dist-tag, `-y` to skip the confirm prompt). The command is `upgrade` on every platform -- there is no `agents update` (on macOS, `agents helper update` is a different command that reinstalls the keychain helper, not agents-cli).
|
|
45
|
+
|
|
44
46
|
Source: [github.com/phnx-labs/agents-cli](https://github.com/phnx-labs/agents-cli)
|
|
45
47
|
|
|
46
48
|
Also available as `ag` -- all commands work with both `agents` and `ag`.
|
|
@@ -192,7 +194,7 @@ Backed by a SQLite + FTS5 index at `~/.agents/.history/sessions/sessions.db` wit
|
|
|
192
194
|
|
|
193
195
|
## Run open models through Claude Code (experimental)
|
|
194
196
|
|
|
195
|
-
> **Note:** Profiles are experimental
|
|
197
|
+
> **Note:** Profiles are experimental, but available by default — no enable step needed.
|
|
196
198
|
|
|
197
199
|
```bash
|
|
198
200
|
# Kimi K2.5 responding inside Claude Code's UI, tools, and skills.
|
|
@@ -387,7 +389,7 @@ Give agents access to a real browser — no relay extension, no cloud service, n
|
|
|
387
389
|
# First run: omit --profile and we auto-pick the first installed Chromium-family
|
|
388
390
|
# browser. macOS prefers Chrome > Brave > Edge > Chromium > Comet; Linux prefers
|
|
389
391
|
# Chrome > Chromium > Brave > Edge; Windows prefers Edge (always preinstalled) >
|
|
390
|
-
# Chrome > Brave. The auto-picked profile is saved as "default" for later runs.
|
|
392
|
+
# Chrome > Brave > Comet. The auto-picked profile is saved as "default" for later runs.
|
|
391
393
|
export AGENTS_BROWSER_TASK=$(agents browser start --url https://app.example.com)
|
|
392
394
|
|
|
393
395
|
# Or pin a named profile to a specific browser (chrome, comet, brave, chromium,
|
|
@@ -775,6 +777,8 @@ Yes -- `agents run` is non-interactive by default. `--yes` auto-accepts prompts,
|
|
|
775
777
|
|
|
776
778
|
The auto-update prompt is suppressed automatically when stdin or stdout isn't a TTY. For headless environments where TTY detection misfires (k8s pods that allocate a PTY for stdout, cloud sandbox factories), set `AGENTS_CLI_DISABLE_AUTO_UPDATE=1` to skip the update check entirely -- no prompt, no network call.
|
|
777
779
|
|
|
780
|
+
To update on demand instead of waiting for the prompt, run `agents upgrade` (add `-y` to skip the confirmation, or pass a version/dist-tag to install something other than latest).
|
|
781
|
+
|
|
778
782
|
### What happens to my config when I switch versions?
|
|
779
783
|
|
|
780
784
|
Each version has its own isolated config directory. Switching just repoints a symlink — your per-version config stays untouched. On first migration (if you had a real `~/.claude/` directory before using agents-cli), that gets backed up once to `~/.agents-system/backups/`.
|
|
@@ -785,7 +789,7 @@ No — it's a SQLite + FTS5 full-text index. Fast, flexible, and robust. Agents
|
|
|
785
789
|
|
|
786
790
|
### How do I use custom or local models?
|
|
787
791
|
|
|
788
|
-
Profiles (experimental —
|
|
792
|
+
Profiles (experimental — available by default). Works with LiteLLM Proxy, Ollama, or any OpenAI-compatible endpoint. Drop a YAML in `~/.agents/profiles/` pointing to your endpoint.
|
|
789
793
|
|
|
790
794
|
### Can I add support for a new agent?
|
|
791
795
|
|
|
@@ -807,4 +811,4 @@ Commands in [src/commands/](src/commands/), libraries in [src/lib/](src/lib/), t
|
|
|
807
811
|
|
|
808
812
|
## License
|
|
809
813
|
|
|
810
|
-
|
|
814
|
+
Apache-2.0 -- see [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agents audit` — inspect the tamper-evident audit log of dispatched runs.
|
|
3
|
+
*
|
|
4
|
+
* Every run that reaches the exec dispatch chokepoint appends one hash-chained
|
|
5
|
+
* record (see src/lib/audit/log.ts). This command walks that chain:
|
|
6
|
+
*
|
|
7
|
+
* agents audit verify Confirm the chain is intact; report the first break.
|
|
8
|
+
* agents audit list Print recent records (newest last).
|
|
9
|
+
*
|
|
10
|
+
* `verify` exits non-zero when the chain is broken so it can gate CI / governance
|
|
11
|
+
* checks.
|
|
12
|
+
*/
|
|
13
|
+
import type { Command } from 'commander';
|
|
14
|
+
export declare function registerAuditCommands(program: Command): void;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agents audit` — inspect the tamper-evident audit log of dispatched runs.
|
|
3
|
+
*
|
|
4
|
+
* Every run that reaches the exec dispatch chokepoint appends one hash-chained
|
|
5
|
+
* record (see src/lib/audit/log.ts). This command walks that chain:
|
|
6
|
+
*
|
|
7
|
+
* agents audit verify Confirm the chain is intact; report the first break.
|
|
8
|
+
* agents audit list Print recent records (newest last).
|
|
9
|
+
*
|
|
10
|
+
* `verify` exits non-zero when the chain is broken so it can gate CI / governance
|
|
11
|
+
* checks.
|
|
12
|
+
*/
|
|
13
|
+
import chalk from 'chalk';
|
|
14
|
+
import { verifyAuditChain, readAuditLog, getAuditLogPath, } from '../lib/audit/log.js';
|
|
15
|
+
function renderRow(r, i) {
|
|
16
|
+
const idx = chalk.gray(String(i).padStart(4));
|
|
17
|
+
const time = chalk.gray(r.ts.slice(0, 19).replace('T', ' '));
|
|
18
|
+
const who = `${r.agent}@${r.version}`.padEnd(20);
|
|
19
|
+
const outcome = r.outcome === 'ok' ? chalk.green('ok ') : chalk.red('fail');
|
|
20
|
+
const mode = chalk.cyan(r.mode.padEnd(6));
|
|
21
|
+
return `${idx} ${time} ${who} ${mode} ${outcome} exit=${r.exit} ${r.repo}`;
|
|
22
|
+
}
|
|
23
|
+
export function registerAuditCommands(program) {
|
|
24
|
+
const audit = program
|
|
25
|
+
.command('audit')
|
|
26
|
+
.description('Inspect the tamper-evident audit log of dispatched runs');
|
|
27
|
+
audit
|
|
28
|
+
.command('verify')
|
|
29
|
+
.description('Walk the hash chain and report OK or the first broken index')
|
|
30
|
+
.option('--json', 'Output the result as JSON')
|
|
31
|
+
.action((options) => {
|
|
32
|
+
const result = verifyAuditChain();
|
|
33
|
+
if (options.json) {
|
|
34
|
+
console.log(JSON.stringify(result));
|
|
35
|
+
process.exit(result.ok ? 0 : 1);
|
|
36
|
+
}
|
|
37
|
+
if (result.ok) {
|
|
38
|
+
const n = readAuditLog().length;
|
|
39
|
+
console.log(chalk.green(`✓ audit chain intact — ${n} record(s) verified`));
|
|
40
|
+
console.log(chalk.gray(` ${getAuditLogPath()}`));
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
console.error(chalk.red(`✗ audit chain BROKEN at record #${result.brokenAt}`));
|
|
44
|
+
console.error(chalk.gray(` ${getAuditLogPath()}`));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
});
|
|
47
|
+
audit
|
|
48
|
+
.command('list')
|
|
49
|
+
.description('Print recent audit records (oldest-first)')
|
|
50
|
+
.option('--limit <n>', 'Max records to show (default 50)', '50')
|
|
51
|
+
.option('--json', 'Output raw records as JSON')
|
|
52
|
+
.action((options) => {
|
|
53
|
+
const all = readAuditLog();
|
|
54
|
+
if (options.json) {
|
|
55
|
+
console.log(JSON.stringify(all, null, 2));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (all.length === 0) {
|
|
59
|
+
console.log(chalk.gray('No audit records yet.'));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const limit = Math.max(1, parseInt(options.limit ?? '50', 10) || 50);
|
|
63
|
+
const start = Math.max(0, all.length - limit);
|
|
64
|
+
for (let i = start; i < all.length; i++)
|
|
65
|
+
console.log(renderRow(all[i], i));
|
|
66
|
+
console.log(chalk.gray(`\n${all.length} record(s). Log: ${getAuditLogPath()}`));
|
|
67
|
+
});
|
|
68
|
+
}
|