@phnx-labs/agents-cli 1.22.7 → 1.22.9
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 +97 -0
- package/README.md +5 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/browser.js +61 -0
- package/dist/commands/exec.js +89 -21
- package/dist/commands/feed.js +38 -1
- package/dist/commands/harness.d.ts +40 -2
- package/dist/commands/harness.js +316 -20
- package/dist/commands/monitors.js +2 -2
- package/dist/commands/profiles.d.ts +42 -0
- package/dist/commands/profiles.js +91 -3
- package/dist/commands/routines.js +2 -2
- package/dist/commands/run-account-picker.js +2 -0
- package/dist/commands/sessions-picker.js +3 -3
- package/dist/commands/sessions-render.d.ts +12 -0
- package/dist/commands/sessions-render.js +124 -0
- package/dist/commands/sessions.js +28 -1
- package/dist/commands/snapshot.d.ts +11 -0
- package/dist/commands/snapshot.js +107 -0
- package/dist/commands/ssh.js +26 -3
- package/dist/commands/teams.js +14 -2
- package/dist/commands/view.d.ts +7 -0
- package/dist/commands/view.js +1 -1
- package/dist/index.js +11 -1
- package/dist/lib/browser/ipc.js +2 -0
- package/dist/lib/browser/remote-control.d.ts +35 -0
- package/dist/lib/browser/remote-control.js +48 -0
- package/dist/lib/browser/service.d.ts +19 -0
- package/dist/lib/browser/service.js +19 -1
- package/dist/lib/browser/types.d.ts +14 -2
- package/dist/lib/crabbox/cli.d.ts +35 -0
- package/dist/lib/crabbox/cli.js +46 -0
- package/dist/lib/crabbox/config.d.ts +21 -0
- package/dist/lib/crabbox/config.js +43 -0
- package/dist/lib/crabbox/lease.d.ts +15 -5
- package/dist/lib/crabbox/lease.js +57 -17
- package/dist/lib/daemon.js +12 -11
- package/dist/lib/device-config.js +8 -0
- package/dist/lib/devices/resolve-target.d.ts +4 -3
- package/dist/lib/devices/resolve-target.js +4 -3
- package/dist/lib/hosts/passthrough.d.ts +10 -1
- package/dist/lib/hosts/passthrough.js +41 -3
- package/dist/lib/hosts/registry.d.ts +4 -0
- package/dist/lib/hosts/registry.js +16 -0
- package/dist/lib/hosts/remote-cmd.js +2 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/observe-aliases.d.ts +30 -0
- package/dist/lib/observe-aliases.js +56 -0
- package/dist/lib/placement.d.ts +82 -0
- package/dist/lib/placement.js +188 -0
- package/dist/lib/profiles.d.ts +31 -9
- package/dist/lib/profiles.js +83 -13
- package/dist/lib/redact.js +4 -0
- package/dist/lib/rotate.d.ts +19 -4
- package/dist/lib/rotate.js +24 -1
- package/dist/lib/routines.d.ts +2 -0
- package/dist/lib/runner.d.ts +3 -0
- package/dist/lib/runner.js +90 -7
- 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/session/parse.d.ts +5 -1
- package/dist/lib/session/parse.js +23 -13
- package/dist/lib/session/prompt.js +5 -0
- package/dist/lib/session/render.d.ts +3 -0
- package/dist/lib/session/render.js +33 -10
- package/dist/lib/snapshot.d.ts +103 -0
- package/dist/lib/snapshot.js +99 -0
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +17 -1
- package/dist/lib/usage-refresh.d.ts +19 -11
- package/dist/lib/usage-refresh.js +75 -43
- package/dist/lib/usage.d.ts +12 -0
- package/dist/lib/usage.js +37 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,100 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.22.9
|
|
4
|
+
|
|
5
|
+
- **`agents ssh auto` and `agents teams add --device auto` no longer reject with "Unknown device 'auto'" (RUSH-2185).** The `auto` affinity sentinel was a `run`-only preprocessing step (`applyDeviceAutoToOptions` in `smart-launch.ts`, wired only from `agents run`'s exec path) — every other `--host`/`--device` caller went straight to the shared resolver, which had no idea what `auto` meant and reported it as an unregistered device. `matchHost` (the one core every `--host`/`--device` caller shares) now resolves `auto` directly via the same `resolveDeviceAffinity` engine `run` uses, so `agents ssh`, `agents teams add`, and anything else routed through `matchHost`/`resolveHost` (including the generic `--host`/`--device` passthrough) pick a device the same way. `agents teams add --device auto` landing on the local machine now just runs the teammate locally, matching `run`'s "null pick = local" outcome; `agents ssh auto` refuses a local pick with a clear message instead of self-SSHing, since `agents ssh` exists to dial OUT to a remote box. Source: `apps/cli/src/lib/hosts/registry.ts`, `apps/cli/src/lib/devices/resolve-target.ts`, `apps/cli/src/commands/ssh.ts`, `apps/cli/src/commands/teams.ts`, `apps/cli/docs/00-concepts.md`, `apps/cli/docs/hosts.md`, `apps/cli/docs/teams.md`.
|
|
6
|
+
|
|
7
|
+
- **`agents harness edit` gains `--auth-provider`, `--fallback-model`, and `--from-secrets`; `add`/`fork` gain an interactive wizard and `--from-secrets`.** `edit` (already shipped with `--model`/`--base-url`/`--version`/`--description`) now also repoints auth at a different keychain-backed provider, sets or clears (`--fallback-model ""`) the same-host fallback model retried on a rate limit, and — like `add`/`fork` — accepts `--from-secrets <bundle>[:<key>]` to copy a value out of an existing `agents secrets` bundle into the harness's own keychain item once, instead of retyping a key already stored elsewhere (the item it writes to, `agents-cli.<provider>.token`, is never gated behind the biometry-required prefixes, so later reads stay silent). `agents harness add`/`fork` now accept `[name]`/`[source] [name]` as optional positionals — run either with insufficient flags in an interactive terminal and a picker (fork from a native host or existing harness → a built-in preset or "build custom" → the harness's name, pre-filled with the preset's own name → how to get the key) replaces the old hard error; flags remain fully supported for scripts, and a non-interactive shell still gets the original error. Source: `apps/cli/src/commands/harness.ts`, `apps/cli/src/commands/profiles.ts`, `apps/cli/docs/profiles.md`.
|
|
8
|
+
|
|
9
|
+
- **`agents run --lease` is reuse-first against the crabbox profile pool, with a
|
|
10
|
+
`--fresh` opt-out.** A bare `--lease` used to always lease a brand-new box, so
|
|
11
|
+
bursts of runs (e.g. resumed sessions) stacked up idle `keep=true` boxes at
|
|
12
|
+
full monthly cost. Now, before warming a new box, the run looks for a warm box
|
|
13
|
+
carrying the same `profile` label the warmup would use (read from the repo's
|
|
14
|
+
`.crabbox.yaml`, matching `scripts/sandbox.sh`'s `pick_ready_box`) and the same
|
|
15
|
+
network mode — a tailnet box is never handed to a public run or vice versa —
|
|
16
|
+
and reuses the first one `crabbox status` reports SSH-ready, keeping it after
|
|
17
|
+
the run. A not-ready pool box is skipped, never stopped. `--fresh` forces the
|
|
18
|
+
old behavior (brand-new box, torn down after the run); `--box <slug>` is
|
|
19
|
+
unchanged. Source: `apps/cli/src/lib/crabbox/lease.ts`,
|
|
20
|
+
`apps/cli/src/lib/crabbox/cli.ts`, `apps/cli/src/lib/crabbox/config.ts`,
|
|
21
|
+
`apps/cli/src/commands/exec.ts`.
|
|
22
|
+
|
|
23
|
+
- **The menu bar now notices and reports when the scheduler dies — instead of
|
|
24
|
+
staying silent forever.** The only proactive "routines overdue / scheduler
|
|
25
|
+
down" signal was `notifyOverdue` (`src/lib/overdue.ts`), fired from inside
|
|
26
|
+
`runDaemon()` — so it could never fire while the daemon itself was down, the
|
|
27
|
+
exact outage it exists to report. `MenubarHelper` is a separate launchd
|
|
28
|
+
KeepAlive service that stays alive when the daemon dies, so its 10s tick now
|
|
29
|
+
polls daemon liveness independently of the dropdown ever being opened; once
|
|
30
|
+
it has been continuously unreachable for ~30s (debounced past a routine
|
|
31
|
+
restart blip), it fires one native notification ("Scheduler stopped —
|
|
32
|
+
routines won't run") through its own `NSUserNotificationCenter` delivery —
|
|
33
|
+
no daemon, no CLI spawn required — and lights the always-visible menu-bar
|
|
34
|
+
badge (`⏻`) until the scheduler comes back. Source:
|
|
35
|
+
`apps/cli/menubar/Sources/MenubarHelper/StatusItemController.swift`.
|
|
36
|
+
|
|
37
|
+
- **Observe umbrella aliases: `inbox`, `timeline`, `roster` (Phase 3 surface consolidation).**
|
|
38
|
+
Thin doors onto existing readers (no store merge): `agents inbox` ≡ `feed`,
|
|
39
|
+
`agents timeline` ≡ `feed --filter updates`, `agents roster` ≡ `sessions --active`.
|
|
40
|
+
Root help gains an Observe section; `agents audit` stays the tamper-evident run
|
|
41
|
+
log (not an events alias). Source: `apps/cli/src/lib/observe-aliases.ts`,
|
|
42
|
+
`apps/cli/src/commands/feed.ts`, `apps/cli/src/commands/sessions.ts`.
|
|
43
|
+
|
|
44
|
+
- **Daemon usage refresh is a fixed 5-minute per-host schedule, concurrency-safe, and Touch-ID-free.**
|
|
45
|
+
Each machine's daemon still owns its own usage cache (no fleet-wide store). Account live
|
|
46
|
+
fetches are now scheduled every **5 minutes** (was adaptive 90s–15m), with a 60s wake to
|
|
47
|
+
notice due accounts after backoff ends. Cache writes use a file lock + atomic rename so a
|
|
48
|
+
concurrent `agents view` background refresh cannot tear or drop rows. The daemon path
|
|
49
|
+
loads Claude credentials with **`fileOnly`** (setup-token / no-ACL cache / `.credentials.json`
|
|
50
|
+
only) and never opens the ACL-bound macOS keychain item, so a background tick cannot pop
|
|
51
|
+
Touch ID. Refresh still skips a provider under 429 backoff and still never rotates
|
|
52
|
+
single-use Claude refresh tokens.
|
|
53
|
+
|
|
54
|
+
## 1.22.8
|
|
55
|
+
|
|
56
|
+
- **`agents browser` now gates cross-machine drives behind per-device consent.** `agents browser <cmd> --host <device>` already routes a browser command to another fleet machine over SSH and drives its browser — but nothing asked that machine's permission, so any box you could SSH to, you could drive. A new device-local `browser.remote-control` setting (off by default, never synced) fixes that: a fleet-remote `browser --host <this-machine> start` is refused with an actionable message until the owner runs `agents browser remote-control on` here. Local starts (no `--host`) are never gated. The fleet passthrough marks every `--host` dispatch with `AGENTS_FLEET_REMOTE` so the far side can tell a cross-machine drive from a local one. New command: `agents browser remote-control [on|off]` (no arg prints status; `--json` supported). Source: `apps/cli/src/lib/browser/remote-control.ts`, `apps/cli/src/commands/browser.ts`, `apps/cli/src/lib/hosts/passthrough.ts`, `apps/cli/src/lib/device-config.ts`, `apps/cli/docs/browser.md`.
|
|
57
|
+
|
|
58
|
+
- **`agents browser` tasks are now attributed to the caller that ran `start`, not
|
|
59
|
+
to the browser daemon.** `Task.owner` (RUSH-2020) was resolved with
|
|
60
|
+
`resolveActor()` *inside* the shared, long-lived browser daemon, so every task —
|
|
61
|
+
no matter which agent or person opened it — was stamped with the identity of
|
|
62
|
+
whoever happened to start the daemon. The caller's identity is now forwarded over
|
|
63
|
+
IPC: the CLI (the caller's own process) puts `actor` (`resolveActor().id`) and
|
|
64
|
+
`launchId` (`$AGENT_LAUNCH_ID`, the per-run id `exec.ts` injects for every harness)
|
|
65
|
+
on the `start` request, and the daemon stamps exactly those. Adds `Task.launchId` —
|
|
66
|
+
which run created a task — the scope a later `browser status --mine` and the
|
|
67
|
+
no-flag current-task default will filter on. Source:
|
|
68
|
+
`apps/cli/src/lib/browser/types.ts`, `apps/cli/src/lib/browser/service.ts`,
|
|
69
|
+
`apps/cli/src/lib/browser/ipc.ts`, `apps/cli/src/commands/browser.ts`.
|
|
70
|
+
|
|
71
|
+
- **`agents harness edit` and `agents harness rename` are now real commands.** `editProfile` and `renameProfile` already existed in `lib/profiles.ts` but nothing on the CLI surface reached them, so changing a custom harness meant hand-editing its YAML. `agents harness edit <name>` applies `--model`, `--base-url`, `--version`, and `--description` in place, preserving fork lineage (an edit never marks a harness as forked from itself). `agents harness rename <name> <new-name>` renames the YAML file and its `name` field, and rewrites `forkedFrom` on every harness that pointed at the old name so the fork graph stays accurate. There is deliberately no `--label`: the header `agents view` prints is derived from the harness name, so renaming is how you change it.
|
|
72
|
+
|
|
73
|
+
- **Run-time messages call a custom harness a "custom harness", not a "profile".** When you `agents run <name>` a custom harness (created with `agents harness add`), the CLI now says `Resolved custom harness '<name>'` and, for a discarded cost tier, `cost tiers don't apply to custom harness '<name>'` — instead of the legacy internal noun "profile". The `--strategy` and account-picker notices on a custom-harness run are aligned too. Behavior is unchanged; the legacy `agents profiles` alias still works. Source: `apps/cli/src/commands/exec.ts`.
|
|
74
|
+
|
|
75
|
+
- **Placement model + `agents run --where` (Phase 2 surface consolidation).**
|
|
76
|
+
"Where does the body run?" is one shared object (`local | device | fleet | cloud | lease`)
|
|
77
|
+
in `src/lib/placement.ts`. `agents run --where device:<name>|auto|lease[:backend]|local`
|
|
78
|
+
expands into the existing `--host` / `--lease` paths; mixing doors fails loud. Docs
|
|
79
|
+
(`00-concepts.md` § Placement, `hosts.md`) and help on run / routines / monitors teach
|
|
80
|
+
the matrix — including that monitors `--device` is **owner**, not body placement.
|
|
81
|
+
Old flags remain aliases. Source: `apps/cli/src/lib/placement.ts`, `apps/cli/src/commands/exec.ts`.
|
|
82
|
+
|
|
83
|
+
- **`agents harness fork` no longer accepts `--label` (breaking change).** The `--label` flag was used to set a human-facing display name for a custom harness. Display names are now always derived from the profile's `name` via a curated vendor/brand table (`deepseek-flash` → `DeepSeek Flash`, `spark` → `Spark`), so the flag is superfluous. Any script that passes `--label` to `agents harness fork` will receive a CLI error; remove the flag to migrate.
|
|
84
|
+
|
|
85
|
+
- **`agents run` no longer auto-picks an account whose token the server has already rejected.** Account rotation judged an account "signed in" from a local heuristic — a credential file is present and its email decodes — which cannot tell a good token from a revoked-but-unexpired one, so `balanced`/`available`/`run auto` could route into a `revoked` account and die at spawn ("session expired"). Eligibility now also reads the daemon's live auth-health probe (`auth-health.ts`): a `revoked` (401/403) account is excluded from the pick, reported as `revoked` by the pre-flight readiness check, shown as "needs re-login" in the account picker, and named in the teams throttle warning. Fail-open: a missing probe or any non-revoked verdict never blocks a launch (a cached `revoked` keeps gating until the daemon's next probe clears it). Source: `apps/cli/src/lib/rotate.ts`, `apps/cli/src/commands/run-account-picker.ts`, `apps/cli/src/commands/teams.ts`, `apps/cli/docs/hosts.md`.
|
|
86
|
+
|
|
87
|
+
- **Scheduled routines no longer overlap or outlive their configured timeout (RUSH-2186).** Detached cron, catchup, and monitor launches now take a cross-process per-routine claim and refuse a second fire while the prior run is alive. The configured deadline is persisted in run metadata; both the live runner and the restart-recovery monitor kill the owned process tree and record `timeout` when it expires. Source: `apps/cli/src/lib/runner.ts`, `apps/cli/src/lib/routines.ts`, `apps/cli/docs/03-routines.md`.
|
|
88
|
+
|
|
89
|
+
- **`agents snapshot` — one-process poll for inventory + active sessions (Phase 4 surface consolidation).**
|
|
90
|
+
Consumers (Factory, scripts, menubar) were forking `view --json` × N harnesses plus
|
|
91
|
+
`sessions --active --json` (and sometimes feed) on every tick. `agents snapshot --json`
|
|
92
|
+
returns the same shapes in one invocation: `inventory` (view), `sessions` (active rows),
|
|
93
|
+
optional `--with-feed` / `--with-sync`. Default sessions scope is this machine; `--all-hosts`
|
|
94
|
+
matches full `sessions --active` fan-out. Does **not** redefine `agents status`, which stays
|
|
95
|
+
the UnifiedSyncStatus sync contract. Source: `apps/cli/src/commands/snapshot.ts`,
|
|
96
|
+
`apps/cli/src/lib/snapshot.ts`.
|
|
97
|
+
|
|
3
98
|
## 1.22.7
|
|
4
99
|
|
|
5
100
|
- **`agents feed --project <name>` scopes the whole feed to one project.** Open
|
|
@@ -939,6 +1034,8 @@
|
|
|
939
1034
|
|
|
940
1035
|
## 1.20.92
|
|
941
1036
|
|
|
1037
|
+
- **`agents sessions render <id...>` produces shareable, redacted Markdown instead of raw harness JSONL.** Claude, Codex, Kimi, Grok, Cursor, and Droid transcripts flow through the existing normalized `SessionEvent[]` parsers, then render with the same session-browser preview at the top, ordered user/assistant turns, fenced shell commands, JSON tool arguments, and explicitly truncated tool results. Redaction remains on by default through the canonical redactor, now also masking Unix/macOS/Windows home-directory identities and live secret values; `--no-redact` is local-only. Reasoning defaults to omitted and can be folded or included explicitly. One or several selected sessions can be written to a mode-`0600` Markdown file, and `--json` exposes the rendered documents for machine callers. The repo session skill and transcript-sharing guidance now require rendering `.md` before creating a confidential gist. Source: `apps/cli/src/commands/sessions-render.ts`, `apps/cli/src/lib/session/render.ts`, `apps/cli/src/lib/redact.ts`, `.agents/skills/sessions/SKILL.md`.
|
|
1038
|
+
|
|
942
1039
|
- **`agents sessions` rows show creation time as well as last activity (RUSH-2107).**
|
|
943
1040
|
The trailing time cell used to carry one unlabeled "X ago" — last activity — so a
|
|
944
1041
|
row could not say when the session began or how long it had been alive. It now
|
package/README.md
CHANGED
|
@@ -255,6 +255,9 @@ agents sessions --project my-app
|
|
|
255
255
|
# Read a full conversation
|
|
256
256
|
agents sessions a1b2c3d4 --markdown
|
|
257
257
|
|
|
258
|
+
# Render a shareable, redacted Markdown transcript with the session preview on top
|
|
259
|
+
agents sessions render a1b2c3d4 -o session.md
|
|
260
|
+
|
|
258
261
|
# Just the last 3 turns, user messages only
|
|
259
262
|
agents sessions a1b2c3d4 --last 3 --include user
|
|
260
263
|
|
|
@@ -323,6 +326,8 @@ Each live session resolves to `working`, `waiting_input` (with why -- a question
|
|
|
323
326
|
|
|
324
327
|
Landing on a session cold? `agents sessions <id>` prints a catch-up digest: an inferred title, files changed grouped by directory (created / modified / deleted), a histogram of which tools did the work (including parsed Bash commands -- `git`, `npm`, `ffmpeg`, `ssh`, and so on), and the last test verdict -- the signals to reload a task in seconds.
|
|
325
328
|
|
|
329
|
+
Sharing a session uses `agents sessions render <id> -o session.md`, not the raw harness JSONL. The document starts with that same preview, then presents user and assistant turns, fenced commands, structured tool arguments, and bounded tool output. Credential-shaped values and local home paths are redacted by default; `--no-redact` is for local-only inspection.
|
|
330
|
+
|
|
326
331
|
### Resume anywhere — and stay resumed
|
|
327
332
|
|
|
328
333
|
Pick up any past conversation and drop it back into a terminal:
|
package/dist/bin/agents
CHANGED
|
Binary file
|
package/dist/commands/browser.js
CHANGED
|
@@ -2,6 +2,7 @@ import * as fs from 'fs';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { listProfiles, getProfile, createProfile, deleteProfile, ensureDefaultBrowserProfile, getConfiguredDefaultProfileName, DEFAULT_BROWSER_PROFILE_NAME, getProfileRuntimeDir, extractConfiguredPort, findFreeProfilePort, getEndpointPresets, } from '../lib/browser/profiles.js';
|
|
4
4
|
import { updateMeta } from '../lib/state.js';
|
|
5
|
+
import { resolveActor } from '../lib/actor.js';
|
|
5
6
|
import { loginsForProfile, profilesLoggedInto, serviceForUrl, loginsWithAccountsForProfile, accountsForProfile, credKeysForService, AUTH_SIGNATURES, } from '../lib/browser/login-detection.js';
|
|
6
7
|
import { parseSecretRef } from '../lib/browser/secret-ref.js';
|
|
7
8
|
import { readAndResolveBundleEnv, isHeadlessSecretsContext, bundleExists, readBundle, describeBundle } from '../lib/secrets/bundles.js';
|
|
@@ -13,6 +14,8 @@ import { discoverBrowserWsUrl, verifyBrowserIdentity } from '../lib/browser/cdp.
|
|
|
13
14
|
import { parseTargetFilter } from '../lib/browser/service.js';
|
|
14
15
|
import { BrowserDaemonNotRunningError, formatBrowserDaemonNotRunningError, sendIPCRequest, } from '../lib/browser/ipc.js';
|
|
15
16
|
import { browserTaskPicker } from './browser-picker.js';
|
|
17
|
+
import { assertRemoteControlAllowed } from '../lib/browser/remote-control.js';
|
|
18
|
+
import { getConfigValue, setConfigValue } from '../lib/device-config.js';
|
|
16
19
|
import { isInteractiveTerminal } from './utils.js';
|
|
17
20
|
import { registerCommandGroups, setHelpSections } from '../lib/help.js';
|
|
18
21
|
import { buildHar } from '../lib/browser/har.js';
|
|
@@ -76,6 +79,12 @@ export function registerBrowserCommand(program) {
|
|
|
76
79
|
agents browser navigate https://example.com
|
|
77
80
|
agents browser screenshot
|
|
78
81
|
|
|
82
|
+
# Drive another machine's browser (needs its consent — see remote-control)
|
|
83
|
+
agents browser start --host zion
|
|
84
|
+
|
|
85
|
+
# Allow / deny other fleet machines driving THIS machine's browser
|
|
86
|
+
agents browser remote-control on
|
|
87
|
+
|
|
79
88
|
# End the session when done
|
|
80
89
|
agents browser done
|
|
81
90
|
`,
|
|
@@ -609,6 +618,42 @@ function registerProfilesCommands(browser) {
|
|
|
609
618
|
});
|
|
610
619
|
}
|
|
611
620
|
function registerTaskCommands(browser) {
|
|
621
|
+
browser
|
|
622
|
+
.command('remote-control [state]')
|
|
623
|
+
.description("Allow or deny other fleet machines driving THIS machine's browser over `browser --host`. " +
|
|
624
|
+
'`on`/`off` to set (device-local, never synced); no argument prints the current value. Default off.')
|
|
625
|
+
.option('--json', 'Output as JSON')
|
|
626
|
+
.action((state, opts) => {
|
|
627
|
+
const KEY = 'browser.remote-control';
|
|
628
|
+
if (state === undefined) {
|
|
629
|
+
const cur = getConfigValue(KEY).value === true;
|
|
630
|
+
if (opts.json) {
|
|
631
|
+
console.log(JSON.stringify({ remoteControl: cur }));
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
console.log(`Remote browser control (this machine): ${cur ? 'on' : 'off'}`);
|
|
635
|
+
if (!cur)
|
|
636
|
+
console.log('Enable with: agents browser remote-control on');
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
const norm = state.toLowerCase();
|
|
640
|
+
const onWords = ['on', 'true', 'yes', 'allow', 'enable'];
|
|
641
|
+
const offWords = ['off', 'false', 'no', 'deny', 'disable'];
|
|
642
|
+
if (!onWords.includes(norm) && !offWords.includes(norm)) {
|
|
643
|
+
console.error(`Expected "on" or "off", got "${state}".`);
|
|
644
|
+
process.exit(1);
|
|
645
|
+
}
|
|
646
|
+
const value = onWords.includes(norm);
|
|
647
|
+
setConfigValue(KEY, value);
|
|
648
|
+
if (opts.json) {
|
|
649
|
+
console.log(JSON.stringify({ remoteControl: value }));
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
console.log(`Remote browser control (this machine) is now ${value ? 'on' : 'off'}.`);
|
|
653
|
+
console.log(value
|
|
654
|
+
? 'Other fleet machines can now drive this browser via `browser --host <this-device>`.'
|
|
655
|
+
: 'Cross-machine `browser --host` drives to this machine are refused.');
|
|
656
|
+
});
|
|
612
657
|
browser
|
|
613
658
|
.command('start')
|
|
614
659
|
.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.')
|
|
@@ -622,6 +667,16 @@ function registerTaskCommands(browser) {
|
|
|
622
667
|
.option('--duration <sec>', 'Recording duration cap in seconds (with --record; default 60)', (v) => parseInt(v, 10))
|
|
623
668
|
.option('--max-mb <mb>', 'Recording size cap in MB (with --record; default 25)', (v) => parseInt(v, 10))
|
|
624
669
|
.action(async (opts) => {
|
|
670
|
+
// Consent gate: a fleet-remote `browser --host <this-machine> start` may
|
|
671
|
+
// only open a browser here if the owner opted in. Refuse before we resolve
|
|
672
|
+
// or auto-create any profile. Local starts are never gated.
|
|
673
|
+
try {
|
|
674
|
+
assertRemoteControlAllowed();
|
|
675
|
+
}
|
|
676
|
+
catch (err) {
|
|
677
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
678
|
+
process.exit(1);
|
|
679
|
+
}
|
|
625
680
|
let profileName = opts.profile;
|
|
626
681
|
if (!profileName) {
|
|
627
682
|
try {
|
|
@@ -698,6 +753,12 @@ function registerTaskCommands(browser) {
|
|
|
698
753
|
url: opts.url,
|
|
699
754
|
endpoint: opts.endpoint,
|
|
700
755
|
skipDomainSkill: opts.skills === false,
|
|
756
|
+
// Forward the caller's identity: the browser daemon is shared, so it
|
|
757
|
+
// cannot resolve who/which-run called `start`. `resolveActor()` runs
|
|
758
|
+
// here in the CLI (the caller's process); `$AGENT_LAUNCH_ID` is the
|
|
759
|
+
// per-run id exec.ts injects for every harness.
|
|
760
|
+
actor: resolveActor().id,
|
|
761
|
+
launchId: process.env.AGENT_LAUNCH_ID,
|
|
701
762
|
});
|
|
702
763
|
if (!response.ok) {
|
|
703
764
|
console.error(response.error);
|
package/dist/commands/exec.js
CHANGED
|
@@ -553,15 +553,17 @@ export function registerRunCommand(program) {
|
|
|
553
553
|
.option('--budget <tokens>', 'Loop token hard-cap: stop once cumulative tokens reach this (stoppedBy: budget), enforced outside the agent. Loop only.')
|
|
554
554
|
.option('--until <signal>', 'Loop stop condition. `signal` reads <runDir>/loop-signal.json {continue,reason} each iteration; absent or continue:false stops (fail-closed). Loop only.')
|
|
555
555
|
.option('--interval <dur>', 'Loop delay between iterations ("0" back-to-back, "30m" paces). Loop only.')
|
|
556
|
-
.option('--
|
|
557
|
-
.option('--
|
|
556
|
+
.option('--where <spec>', 'Where this run\'s body executes (one placement door): local | device:<name> | auto | lease[:backend]. Expands to --host/--lease. Do not combine with those flags. See docs/00-concepts.md#placement.')
|
|
557
|
+
.option('--host <name>', 'Offload this run onto another machine over SSH — a device name, registered host, or user@host. Pass "auto" to pick from 14d usage affinity (most-used online device has highest probability). Same as --where device:<name>. See `agents devices`.')
|
|
558
|
+
.option('--device <name>', 'Alias of --host. Pass "auto" for affinity-based device pick (same as --where auto).')
|
|
558
559
|
.option('--remote-cwd <dir>', "Explicit host working directory for --host runs, used VERBATIM (overrides --cwd; usually --cwd suffices — it re-roots a local-home path onto the remote home). Pass a single-quoted '$HOME/…' or a valid remote absolute path; a local ~ expands here and won't exist there (/Users/you vs /home/you).")
|
|
559
560
|
.option('--no-follow', 'With --host, dispatch detached and return immediately (track via `agents hosts ps/logs`).')
|
|
560
561
|
.option('--any', 'With --host <cap> (a capability tag), pick any matching host instead of erroring when several match.')
|
|
561
562
|
.option('--copy-creds', 'With --host, copy the picked runtime credentials (and Claude OAuth token) to the host, then shred them after the run. Opt-in per run.')
|
|
562
|
-
.option('--lease [backend]',
|
|
563
|
+
.option('--lease [backend]', "Run on a cloud box (via crabbox) and tear it down after — reuses a warm box from the repo's profile pool when one is ready (--fresh forces a new box). Optional backend selects the cloud (hetzner/aws/do). Same as --where lease[:backend]. Unlike --host, no machine is registered.")
|
|
563
564
|
.option('--box <slug>', 'Reuse an existing warm crabbox box for this run instead of provisioning a disposable --lease box.')
|
|
564
565
|
.option('--keep-box', 'With --lease, keep the box after the run instead of stopping it.')
|
|
566
|
+
.option('--fresh', "With --lease, always provision a brand-new box (skip the warm profile-pool reuse) and tear it down after the run.")
|
|
565
567
|
.option('--reuse', 'With --lease, reuse the most-recently-used warm box if one exists (else provision fresh). The scriptable form of the interactive reuse picker.')
|
|
566
568
|
.option('--bare', 'With --lease, skip copying your local ~/.agents setup (skills/hooks/commands/MCP) onto the box.')
|
|
567
569
|
.option('--tailscale', 'Lease the box onto your tailnet (reachable only over Tailscale) rather than a public IP.')
|
|
@@ -601,6 +603,11 @@ export function registerRunCommand(program) {
|
|
|
601
603
|
agents run auto "fix the flaky test" --mode edit
|
|
602
604
|
agents run auto --host yosemite-s0 "fix the flaky test" # pin the host
|
|
603
605
|
|
|
606
|
+
# Placement (one door — where the body runs). Old flags still work.
|
|
607
|
+
agents run claude "…" --where device:yosemite-s0 # = --host yosemite-s0
|
|
608
|
+
agents run claude "…" --where auto # = --device auto
|
|
609
|
+
agents run claude "fix CI" --where lease --mode edit
|
|
610
|
+
|
|
604
611
|
# Open the session in a terminal tab — detected from where your sessions
|
|
605
612
|
# already run (Ghostty / iTerm / Terminal.app); force one with a value
|
|
606
613
|
agents run claude --terminal
|
|
@@ -615,6 +622,14 @@ export function registerRunCommand(program) {
|
|
|
615
622
|
# Inject a keychain-backed secrets bundle
|
|
616
623
|
agents run claude "deploy the worker" --secrets prod --mode edit
|
|
617
624
|
|
|
625
|
+
# Run on a cloud box — reuses a warm box from the repo's profile pool when
|
|
626
|
+
# one is ready (kept after the run), else leases a fresh box, torn down after
|
|
627
|
+
agents run claude "fix the failing tests" --lease
|
|
628
|
+
|
|
629
|
+
# Force a brand-new box (destroyed after), or target a warm box by slug
|
|
630
|
+
agents run claude "fix the failing tests" --lease --fresh
|
|
631
|
+
agents run claude "fix the failing tests" --box warm-one
|
|
632
|
+
|
|
618
633
|
# Pass arbitrary native flags to the underlying CLI via -- separator
|
|
619
634
|
agents run kimi -- --plan --some-kimi-option value
|
|
620
635
|
agents run claude "fix the bug" -- --custom-flag
|
|
@@ -688,6 +703,36 @@ export function registerRunCommand(program) {
|
|
|
688
703
|
await handleTerminalHandoff(agentSpec, options, prompt);
|
|
689
704
|
return;
|
|
690
705
|
}
|
|
706
|
+
// Placement: --where expands into --host / --lease before any dispatch.
|
|
707
|
+
// One door for "where does the body run?" — old flags remain aliases.
|
|
708
|
+
// See lib/placement.ts and docs/00-concepts.md#placement.
|
|
709
|
+
{
|
|
710
|
+
const { placementFromRunFlags, expandPlacementToRunFlags, PlacementError } = await import('../lib/placement.js');
|
|
711
|
+
try {
|
|
712
|
+
const placement = placementFromRunFlags(options);
|
|
713
|
+
if (options.where) {
|
|
714
|
+
const expanded = expandPlacementToRunFlags(placement);
|
|
715
|
+
if (expanded.host !== undefined)
|
|
716
|
+
options.host = expanded.host;
|
|
717
|
+
if (expanded.device !== undefined)
|
|
718
|
+
options.device = expanded.device;
|
|
719
|
+
if (expanded.lease !== undefined)
|
|
720
|
+
options.lease = expanded.lease;
|
|
721
|
+
if (expanded.box !== undefined)
|
|
722
|
+
options.box = expanded.box;
|
|
723
|
+
// Clear the where flag so remote re-entry (host dispatch) does not
|
|
724
|
+
// re-expand and conflict with the concrete host we just set.
|
|
725
|
+
options.where = undefined;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
catch (err) {
|
|
729
|
+
if (err instanceof PlacementError) {
|
|
730
|
+
console.error(chalk.red(err.message));
|
|
731
|
+
process.exit(1);
|
|
732
|
+
}
|
|
733
|
+
throw err;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
691
736
|
// --notify: post a desktop notification when this run finishes. Armed on
|
|
692
737
|
// process exit so it covers EVERY dispatch path below (local, --host,
|
|
693
738
|
// --lease, the error path) instead of one branch. Only for headless runs
|
|
@@ -788,6 +833,14 @@ export function registerRunCommand(program) {
|
|
|
788
833
|
console.error(chalk.red('Pass either --lease to provision a disposable box, or --box <slug> to reuse a warm box — not both.'));
|
|
789
834
|
process.exit(1);
|
|
790
835
|
}
|
|
836
|
+
if (options.fresh && options.box) {
|
|
837
|
+
console.error(chalk.red('--fresh forces a brand-new box; it cannot be combined with --box <slug> (which reuses one).'));
|
|
838
|
+
process.exit(1);
|
|
839
|
+
}
|
|
840
|
+
if (options.fresh && options.reuse) {
|
|
841
|
+
console.error(chalk.red('--fresh forces a brand-new box; it cannot be combined with --reuse.'));
|
|
842
|
+
process.exit(1);
|
|
843
|
+
}
|
|
791
844
|
const backend = typeof options.lease === 'string' ? options.lease : undefined;
|
|
792
845
|
// crabbox syncs this directory to the box via `git ls-files`; outside a
|
|
793
846
|
// git repo that fails with "build sync file list: exit status 128" — but
|
|
@@ -812,12 +865,21 @@ export function registerRunCommand(program) {
|
|
|
812
865
|
// ── F3 reuse (RUSH-1922) + F5 net-mode (RUSH-1924) ───────────────────
|
|
813
866
|
// Resolve which box this run targets and how it is networked BEFORE any
|
|
814
867
|
// provisioning. `--box` is an explicit reuse; otherwise, on an
|
|
815
|
-
// interactive tty, offer the warm boxes as a reuse picker
|
|
816
|
-
//
|
|
868
|
+
// interactive tty, offer the warm boxes as a reuse picker. Headless runs
|
|
869
|
+
// never block: leaseAndRun itself is reuse-first against the profile
|
|
870
|
+
// pool (a ready pool box is reused; none ready → warm a fresh one).
|
|
871
|
+
// `--fresh` opts out of every reuse path.
|
|
817
872
|
const leaseSecretsBundle = process.env.AGENTS_LEASE_SECRETS_BUNDLE;
|
|
818
873
|
const nowSecs = Math.floor(Date.now() / 1000);
|
|
819
874
|
let reuseSlug = options.box;
|
|
820
|
-
|
|
875
|
+
// The profile this run's pool/box carries: the repo's .crabbox.yaml
|
|
876
|
+
// `profile:` when declared (what crabbox warmup would label a fresh box
|
|
877
|
+
// with), else crabbox's default. Passing it to leaseAndRun makes the
|
|
878
|
+
// pool-reuse match interchangeable with a fresh warmup.
|
|
879
|
+
const repoRoot = gitToplevel(leaseCwd);
|
|
880
|
+
const { readCrabboxRepoProfile } = await import('../lib/crabbox/config.js');
|
|
881
|
+
const poolProfile = repoRoot ? readCrabboxRepoProfile(repoRoot) : undefined;
|
|
882
|
+
if (options.lease && !reuseSlug && !options.fresh) {
|
|
821
883
|
const { crabboxList } = await import('../lib/crabbox/cli.js');
|
|
822
884
|
const { reusableBoxes, formatBoxRow } = await import('./lease.js');
|
|
823
885
|
let warm = [];
|
|
@@ -825,9 +887,8 @@ export function registerRunCommand(program) {
|
|
|
825
887
|
warm = reusableBoxes(crabboxList({ secretsBundle: leaseSecretsBundle }), nowSecs);
|
|
826
888
|
}
|
|
827
889
|
catch {
|
|
828
|
-
warm = []; // crabbox unavailable / no creds →
|
|
890
|
+
warm = []; // crabbox unavailable / no creds → the pool check in leaseAndRun decides
|
|
829
891
|
}
|
|
830
|
-
const repoRoot = gitToplevel(leaseCwd);
|
|
831
892
|
const alwaysFresh = repoRoot ? isAlwaysFreshRepo(readAlwaysFreshRepos(), repoRoot) : false;
|
|
832
893
|
if (warm.length > 0 && !alwaysFresh) {
|
|
833
894
|
if (options.reuse) {
|
|
@@ -860,7 +921,8 @@ export function registerRunCommand(program) {
|
|
|
860
921
|
console.error(chalk.yellow('Selection cancelled — provisioning a fresh box.'));
|
|
861
922
|
}
|
|
862
923
|
}
|
|
863
|
-
// Headless with no --reuse falls through here →
|
|
924
|
+
// Headless with no --reuse falls through here → leaseAndRun's
|
|
925
|
+
// profile-pool check decides (reuse a ready pool box, else warm fresh).
|
|
864
926
|
}
|
|
865
927
|
else if (options.reuse && warm.length > 0) {
|
|
866
928
|
// --reuse still honors a warm box even when the picker is suppressed.
|
|
@@ -979,12 +1041,16 @@ export function registerRunCommand(program) {
|
|
|
979
1041
|
: `${runtime} credentials`;
|
|
980
1042
|
const boxLifecycle = reuseSlug
|
|
981
1043
|
? `Reusing crabbox box ${reuseSlug}`
|
|
982
|
-
:
|
|
1044
|
+
: options.fresh
|
|
1045
|
+
? `Leasing a fresh ${backend ?? 'hetzner'} box${netMode === 'tailscale' ? ' on your tailnet' : ''}`
|
|
1046
|
+
: `Leasing a ${backend ?? 'hetzner'} box${netMode === 'tailscale' ? ' on your tailnet' : ''} (a ready box from the '${poolProfile ?? 'default'}' pool is reused when one exists)`;
|
|
983
1047
|
const boxAfterRun = reuseSlug
|
|
984
1048
|
? 'the box is kept after the run'
|
|
985
1049
|
: options.keepBox
|
|
986
1050
|
? 'the box is kept after the run'
|
|
987
|
-
:
|
|
1051
|
+
: options.fresh
|
|
1052
|
+
? 'the box is destroyed after the run'
|
|
1053
|
+
: 'a fresh box is destroyed after the run; a reused pool box is kept';
|
|
988
1054
|
console.error(chalk.gray(`${boxLifecycle} · shipping ${whatShips}${credentialRuntimes.length > 0 && leaseEmail ? ` (${leaseEmail})` : ''}; ${boxAfterRun}.`));
|
|
989
1055
|
// Read the Claude OAuth token from the local Keychain (silent) so it can be
|
|
990
1056
|
// written to ~/.claude/.credentials.json on the box — otherwise Claude boots
|
|
@@ -1064,6 +1130,8 @@ export function registerRunCommand(program) {
|
|
|
1064
1130
|
secretsBundle: leaseSecretsBundle,
|
|
1065
1131
|
keep: options.keepBox,
|
|
1066
1132
|
reuseBox: reuseSlug,
|
|
1133
|
+
fresh: options.fresh,
|
|
1134
|
+
profile: poolProfile,
|
|
1067
1135
|
copySetup,
|
|
1068
1136
|
netMode,
|
|
1069
1137
|
onData: (chunk) => router.push(chunk),
|
|
@@ -1615,7 +1683,7 @@ export function registerRunCommand(program) {
|
|
|
1615
1683
|
const cwd = options.cwd ?? process.cwd();
|
|
1616
1684
|
if (accountPickerRequested && !isValidAgent(rawAgent)) {
|
|
1617
1685
|
if (profileExists(rawAgent)) {
|
|
1618
|
-
console.error(chalk.red(`Account selection is not available for
|
|
1686
|
+
console.error(chalk.red(`Account selection is not available for custom harness '${rawAgent}'. Run its concrete host agent with @ instead.`));
|
|
1619
1687
|
process.exit(1);
|
|
1620
1688
|
}
|
|
1621
1689
|
if (resolveWorkflowRef(rawAgent, cwd)) {
|
|
@@ -1675,7 +1743,7 @@ export function registerRunCommand(program) {
|
|
|
1675
1743
|
profileEnv = resolved.env;
|
|
1676
1744
|
profileFallbackModel = resolved.fallbackModel;
|
|
1677
1745
|
fromProfile = true;
|
|
1678
|
-
process.stderr.write(chalk.gray(`Resolved
|
|
1746
|
+
process.stderr.write(chalk.gray(`Resolved custom harness '${resolved.profileName}' -> ${agent}${version ? `@${version}` : ''}\n`));
|
|
1679
1747
|
if (resolved.tierNote) {
|
|
1680
1748
|
process.stderr.write(chalk.gray(`[agents] ${resolved.tierNote}\n`));
|
|
1681
1749
|
}
|
|
@@ -1685,7 +1753,7 @@ export function registerRunCommand(program) {
|
|
|
1685
1753
|
// native, HOST-catalog tier block below. When the profile has no
|
|
1686
1754
|
// `models:` opt-in at all, resolvedModel stays undefined and
|
|
1687
1755
|
// options.model is left as the raw tier token on purpose — the
|
|
1688
|
-
// "cost tiers don't apply to
|
|
1756
|
+
// "cost tiers don't apply to custom harness ..." discard guard further
|
|
1689
1757
|
// down this function is the canonical fallback for that case, and
|
|
1690
1758
|
// this block must not race it with a second, differently-worded
|
|
1691
1759
|
// message.
|
|
@@ -1894,7 +1962,7 @@ export function registerRunCommand(program) {
|
|
|
1894
1962
|
else {
|
|
1895
1963
|
console.error(chalk.red(`Unknown agent: ${rawAgent}`));
|
|
1896
1964
|
console.error(chalk.gray(`Available agents: ${ALL_AGENT_IDS.join(', ')}`));
|
|
1897
|
-
console.error(chalk.gray(`Or add a
|
|
1965
|
+
console.error(chalk.gray(`Or add a custom harness: agents harness add <name>`));
|
|
1898
1966
|
process.exit(1);
|
|
1899
1967
|
}
|
|
1900
1968
|
}
|
|
@@ -2062,7 +2130,7 @@ export function registerRunCommand(program) {
|
|
|
2062
2130
|
process.stderr.write(chalk.yellow(`[agents] strategy ${strategy} ignored: version ${version} is pinned\n`));
|
|
2063
2131
|
}
|
|
2064
2132
|
else if (fromProfile) {
|
|
2065
|
-
process.stderr.write(chalk.yellow(`[agents] strategy ${strategy} ignored:
|
|
2133
|
+
process.stderr.write(chalk.yellow(`[agents] strategy ${strategy} ignored: custom harness pins its own version/auth\n`));
|
|
2066
2134
|
}
|
|
2067
2135
|
else {
|
|
2068
2136
|
try {
|
|
@@ -2312,12 +2380,12 @@ export function registerRunCommand(program) {
|
|
|
2312
2380
|
? (workflowModel ?? (options.fallback ? undefined : runDefaults.model))
|
|
2313
2381
|
: undefined);
|
|
2314
2382
|
// Cost tiers (cheap|default|best|ultra) resolve against a harness's own model
|
|
2315
|
-
// catalog. A
|
|
2316
|
-
// tier here would forward an incompatible host-harness model to a
|
|
2317
|
-
// Discard it loudly and let the
|
|
2383
|
+
// catalog. A custom harness's model comes from its endpoint, not the host
|
|
2384
|
+
// harness, so a tier here would forward an incompatible host-harness model to a
|
|
2385
|
+
// different API. Discard it loudly and let the custom harness's own model stand.
|
|
2318
2386
|
if (fromProfile && model && isTierToken(model)) {
|
|
2319
|
-
process.stderr.write(chalk.yellow(`[agents] --model ${model}: cost tiers don't apply to
|
|
2320
|
-
`(its model comes from the endpoint) — ignoring the tier, using the
|
|
2387
|
+
process.stderr.write(chalk.yellow(`[agents] --model ${model}: cost tiers don't apply to custom harness '${rawAgent}' ` +
|
|
2388
|
+
`(its model comes from the endpoint) — ignoring the tier, using the custom harness's configured model\n`));
|
|
2321
2389
|
model = undefined;
|
|
2322
2390
|
}
|
|
2323
2391
|
const execOptions = {
|
package/dist/commands/feed.js
CHANGED
|
@@ -222,7 +222,7 @@ export function sessionHintsFromActive(sessions) {
|
|
|
222
222
|
export function registerFeedCommand(program) {
|
|
223
223
|
const feed = program
|
|
224
224
|
.command('feed')
|
|
225
|
-
.description('
|
|
225
|
+
.description('Operator inbox + agent status posts (aliases: inbox = needs-you; timeline = --filter updates)')
|
|
226
226
|
.option('--json', 'Output as JSON (each block stamped with its outcome + ask class)')
|
|
227
227
|
.option('--filter <view>', 'What to show: needs (default) · updates · all', 'needs')
|
|
228
228
|
.option('--flat', 'List one block per agent instead of grouping by outcome')
|
|
@@ -572,6 +572,43 @@ docs/06-observability.md.
|
|
|
572
572
|
renderOutcomeGroup(g, self);
|
|
573
573
|
await renderTrailingActivity();
|
|
574
574
|
});
|
|
575
|
+
// Observe-umbrella aliases (Phase 3): intentional second names, not deprecations.
|
|
576
|
+
// Re-parse into `feed` so flags/help stay single-sourced. See lib/observe-aliases.ts.
|
|
577
|
+
registerFeedObserveAliases(program);
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* `inbox` / `timeline` → feed. Loaded with the feed module so lazy COMMAND_LOADERS
|
|
581
|
+
* for those names also get the real `feed` command registered for re-parse.
|
|
582
|
+
*/
|
|
583
|
+
function registerFeedObserveAliases(program) {
|
|
584
|
+
const reparse = async (alias) => {
|
|
585
|
+
const { expandObserveAlias } = await import('../lib/observe-aliases.js');
|
|
586
|
+
const rest = process.argv.slice(3);
|
|
587
|
+
const expanded = expandObserveAlias(alias, rest);
|
|
588
|
+
if (!expanded) {
|
|
589
|
+
console.error(chalk.red(`Unknown observe alias: ${alias}`));
|
|
590
|
+
process.exit(1);
|
|
591
|
+
}
|
|
592
|
+
if (process.stderr.isTTY)
|
|
593
|
+
process.stderr.write(chalk.gray(`${expanded.note}\n`));
|
|
594
|
+
await program.parseAsync(['node', 'agents', ...expanded.argv]);
|
|
595
|
+
};
|
|
596
|
+
program
|
|
597
|
+
.command('inbox')
|
|
598
|
+
.description('Needs-you inbox (alias of `agents feed`). Open blocks waiting on you.')
|
|
599
|
+
.allowUnknownOption()
|
|
600
|
+
.allowExcessArguments()
|
|
601
|
+
.action(async () => {
|
|
602
|
+
await reparse('inbox');
|
|
603
|
+
});
|
|
604
|
+
program
|
|
605
|
+
.command('timeline')
|
|
606
|
+
.description('Agent progress stream (alias of `agents feed --filter updates`). What agents posted recently.')
|
|
607
|
+
.allowUnknownOption()
|
|
608
|
+
.allowExcessArguments()
|
|
609
|
+
.action(async () => {
|
|
610
|
+
await reparse('timeline');
|
|
611
|
+
});
|
|
575
612
|
}
|
|
576
613
|
/**
|
|
577
614
|
* Mirror a written post to the configured sinks (`feed.broadcast` in
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* native harness registry. The `agents profiles` tree stays unchanged.
|
|
12
12
|
*/
|
|
13
13
|
import type { Command } from 'commander';
|
|
14
|
-
import { type
|
|
14
|
+
import { type AddProfileOptions } from './profiles.js';
|
|
15
|
+
import { type Profile, type ForkProfileOptions } from '../lib/profiles.js';
|
|
15
16
|
/**
|
|
16
17
|
* Print one custom harness. Shared by `agents harness view <name>` and by
|
|
17
18
|
* `agents view <name>` — a custom harness resolves as an agent type there, so
|
|
@@ -24,11 +25,26 @@ export interface ForkOptions {
|
|
|
24
25
|
baseUrl?: string;
|
|
25
26
|
authProvider?: string;
|
|
26
27
|
version?: string;
|
|
27
|
-
label?: string;
|
|
28
28
|
description?: string;
|
|
29
|
+
/** `<bundle>` or `<bundle>:<key>` — see {@link applyFromSecrets} in ./profiles.js. */
|
|
30
|
+
fromSecrets?: string;
|
|
29
31
|
keyStdin?: boolean;
|
|
30
32
|
force?: boolean;
|
|
31
33
|
}
|
|
34
|
+
/** Options accepted by `agents harness edit`. */
|
|
35
|
+
export interface EditOptions {
|
|
36
|
+
model?: string;
|
|
37
|
+
baseUrl?: string;
|
|
38
|
+
authProvider?: string;
|
|
39
|
+
/** Empty string ('') unpins the host CLI version. */
|
|
40
|
+
version?: string;
|
|
41
|
+
description?: string;
|
|
42
|
+
/** Empty string ('') clears the fallback model. */
|
|
43
|
+
fallbackModel?: string;
|
|
44
|
+
/** `<bundle>` or `<bundle>:<key>` — see {@link applyFromSecrets} in ./profiles.js. */
|
|
45
|
+
fromSecrets?: string;
|
|
46
|
+
keyStdin?: boolean;
|
|
47
|
+
}
|
|
32
48
|
/**
|
|
33
49
|
* Build the new harness for `agents harness fork <source> <name>`.
|
|
34
50
|
*
|
|
@@ -38,4 +54,26 @@ export interface ForkOptions {
|
|
|
38
54
|
* copy a model from.
|
|
39
55
|
*/
|
|
40
56
|
export declare function buildFork(source: string, name: string, opts: ForkOptions): Profile;
|
|
57
|
+
/** Map `agents harness edit` flags onto {@link ForkProfileOptions} — the same
|
|
58
|
+
* override shape `editProfile`/`forkProfile` apply. `--version ''` (unpin) is
|
|
59
|
+
* handled by the caller ({@link buildEdit}), not here: `forkProfile`'s own
|
|
60
|
+
* ternary treats an empty string as "no override" and would otherwise inherit
|
|
61
|
+
* the source's version instead of clearing it. */
|
|
62
|
+
export declare function buildEditOverrides(opts: EditOptions): ForkProfileOptions;
|
|
63
|
+
/** True when at least one recognized edit flag was given. */
|
|
64
|
+
export declare function hasEditFlags(opts: EditOptions): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Build the edited harness for `agents harness edit <name>` — the profile-shape
|
|
67
|
+
* transform only; the caller applies `--from-secrets`/`--auth-provider` (async
|
|
68
|
+
* keychain side effects) and persists with `writeProfile`. Mirrors
|
|
69
|
+
* {@link buildFork}'s split between a pure builder and the action's IO.
|
|
70
|
+
*/
|
|
71
|
+
export declare function buildEdit(name: string, opts: EditOptions): Profile;
|
|
72
|
+
/** True when `agents harness fork` was given too little to proceed without the wizard. */
|
|
73
|
+
export declare function forkNeedsWizard(source: string | undefined, name: string | undefined, opts: ForkOptions): boolean;
|
|
74
|
+
/** True when `agents harness add` was given too little to proceed without the wizard.
|
|
75
|
+
* Mirrors {@link addProfile}'s own error condition in ./profiles.js so a bare
|
|
76
|
+
* `agents harness add <preset-name>` (no flags) still resolves via the preset
|
|
77
|
+
* fallback instead of being routed into the wizard. */
|
|
78
|
+
export declare function addNeedsWizard(name: string | undefined, opts: AddProfileOptions): boolean;
|
|
41
79
|
export declare function registerHarnessCommands(program: Command): void;
|