@phnx-labs/agents-cli 1.22.6 → 1.22.8

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.
Files changed (104) hide show
  1. package/CHANGELOG.md +131 -1
  2. package/README.md +7 -0
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/browser.js +61 -0
  5. package/dist/commands/exec.js +83 -16
  6. package/dist/commands/feed.d.ts +2 -1
  7. package/dist/commands/feed.js +47 -20
  8. package/dist/commands/focus.js +22 -1
  9. package/dist/commands/harness.d.ts +0 -1
  10. package/dist/commands/harness.js +60 -4
  11. package/dist/commands/models.js +2 -2
  12. package/dist/commands/monitors.js +2 -2
  13. package/dist/commands/projects.js +122 -107
  14. package/dist/commands/routines.js +2 -2
  15. package/dist/commands/run-account-picker.js +2 -0
  16. package/dist/commands/secrets.js +1 -1
  17. package/dist/commands/sessions-backfill.d.ts +33 -0
  18. package/dist/commands/sessions-backfill.js +83 -1
  19. package/dist/commands/sessions-stats.d.ts +36 -0
  20. package/dist/commands/sessions-stats.js +263 -0
  21. package/dist/commands/sessions.d.ts +1 -1
  22. package/dist/commands/sessions.js +21 -1
  23. package/dist/commands/snapshot.d.ts +11 -0
  24. package/dist/commands/snapshot.js +107 -0
  25. package/dist/commands/teams.js +2 -1
  26. package/dist/commands/view.d.ts +9 -1
  27. package/dist/commands/view.js +63 -12
  28. package/dist/index.js +4 -2
  29. package/dist/lib/activity.js +2 -2
  30. package/dist/lib/agents.js +68 -11
  31. package/dist/lib/analytics/recipes.js +11 -5
  32. package/dist/lib/browser/ipc.js +2 -0
  33. package/dist/lib/browser/profiles.d.ts +15 -7
  34. package/dist/lib/browser/profiles.js +53 -12
  35. package/dist/lib/browser/remote-control.d.ts +35 -0
  36. package/dist/lib/browser/remote-control.js +48 -0
  37. package/dist/lib/browser/service.d.ts +19 -0
  38. package/dist/lib/browser/service.js +19 -1
  39. package/dist/lib/browser/types.d.ts +14 -2
  40. package/dist/lib/byok-usage.d.ts +38 -0
  41. package/dist/lib/byok-usage.js +117 -0
  42. package/dist/lib/capabilities.js +1 -1
  43. package/dist/lib/device-config.js +8 -0
  44. package/dist/lib/exec.d.ts +20 -0
  45. package/dist/lib/exec.js +73 -8
  46. package/dist/lib/feed-outcome.d.ts +1 -0
  47. package/dist/lib/feed-outcome.js +2 -0
  48. package/dist/lib/feed-post.js +1 -1
  49. package/dist/lib/feed-ranking.d.ts +1 -0
  50. package/dist/lib/feed-ranking.js +4 -0
  51. package/dist/lib/feed.d.ts +4 -1
  52. package/dist/lib/feed.js +25 -0
  53. package/dist/lib/hosts/passthrough.d.ts +10 -1
  54. package/dist/lib/hosts/passthrough.js +23 -3
  55. package/dist/lib/hosts/remote-cmd.js +1 -0
  56. package/dist/lib/mcp.js +6 -1
  57. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  58. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  59. package/dist/lib/model-tiers.js +4 -1
  60. package/dist/lib/models.js +63 -0
  61. package/dist/lib/placement.d.ts +82 -0
  62. package/dist/lib/placement.js +188 -0
  63. package/dist/lib/profiles.d.ts +73 -10
  64. package/dist/lib/profiles.js +133 -15
  65. package/dist/lib/project-focus.d.ts +9 -0
  66. package/dist/lib/project-focus.js +23 -0
  67. package/dist/lib/project-key.d.ts +1 -1
  68. package/dist/lib/project-key.js +1 -1
  69. package/dist/lib/project-probe.d.ts +18 -0
  70. package/dist/lib/project-probe.js +46 -0
  71. package/dist/lib/project-status.d.ts +56 -0
  72. package/dist/lib/project-status.js +125 -18
  73. package/dist/lib/resources/mcp.js +3 -0
  74. package/dist/lib/resources/types.d.ts +1 -1
  75. package/dist/lib/rotate.d.ts +19 -4
  76. package/dist/lib/rotate.js +24 -1
  77. package/dist/lib/routines.d.ts +2 -0
  78. package/dist/lib/runner.d.ts +3 -0
  79. package/dist/lib/runner.js +90 -7
  80. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  81. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  82. package/dist/lib/secrets/audit.js +1 -1
  83. package/dist/lib/secrets/index.d.ts +1 -0
  84. package/dist/lib/secrets/index.js +29 -15
  85. package/dist/lib/secrets/remote.d.ts +1 -1
  86. package/dist/lib/secrets/remote.js +1 -1
  87. package/dist/lib/session/active.d.ts +2 -0
  88. package/dist/lib/session/bash-command.d.ts +2 -3
  89. package/dist/lib/session/db.d.ts +92 -1
  90. package/dist/lib/session/db.js +230 -1
  91. package/dist/lib/session/digest.d.ts +1 -1
  92. package/dist/lib/session/digest.js +1 -1
  93. package/dist/lib/share/publish.js +24 -0
  94. package/dist/lib/snapshot.d.ts +103 -0
  95. package/dist/lib/snapshot.js +99 -0
  96. package/dist/lib/startup/command-registry.d.ts +1 -1
  97. package/dist/lib/startup/command-registry.js +2 -2
  98. package/dist/lib/subagents-registry.js +3 -0
  99. package/dist/lib/types.d.ts +11 -2
  100. package/dist/lib/usage.d.ts +5 -0
  101. package/dist/lib/usage.js +3 -3
  102. package/package.json +1 -1
  103. package/dist/commands/activity.d.ts +0 -87
  104. package/dist/commands/activity.js +0 -346
package/CHANGELOG.md CHANGED
@@ -1,9 +1,139 @@
1
1
  # Changelog
2
2
 
3
- ## 1.22.6
3
+ ## 1.22.8
4
+
5
+ - **`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`.
6
+
7
+ - **`agents browser` tasks are now attributed to the caller that ran `start`, not
8
+ to the browser daemon.** `Task.owner` (RUSH-2020) was resolved with
9
+ `resolveActor()` *inside* the shared, long-lived browser daemon, so every task —
10
+ no matter which agent or person opened it — was stamped with the identity of
11
+ whoever happened to start the daemon. The caller's identity is now forwarded over
12
+ IPC: the CLI (the caller's own process) puts `actor` (`resolveActor().id`) and
13
+ `launchId` (`$AGENT_LAUNCH_ID`, the per-run id `exec.ts` injects for every harness)
14
+ on the `start` request, and the daemon stamps exactly those. Adds `Task.launchId` —
15
+ which run created a task — the scope a later `browser status --mine` and the
16
+ no-flag current-task default will filter on. Source:
17
+ `apps/cli/src/lib/browser/types.ts`, `apps/cli/src/lib/browser/service.ts`,
18
+ `apps/cli/src/lib/browser/ipc.ts`, `apps/cli/src/commands/browser.ts`.
19
+
20
+ - **`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.
21
+
22
+ - **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`.
23
+
24
+ - **Placement model + `agents run --where` (Phase 2 surface consolidation).**
25
+ "Where does the body run?" is one shared object (`local | device | fleet | cloud | lease`)
26
+ in `src/lib/placement.ts`. `agents run --where device:<name>|auto|lease[:backend]|local`
27
+ expands into the existing `--host` / `--lease` paths; mixing doors fails loud. Docs
28
+ (`00-concepts.md` § Placement, `hosts.md`) and help on run / routines / monitors teach
29
+ the matrix — including that monitors `--device` is **owner**, not body placement.
30
+ Old flags remain aliases. Source: `apps/cli/src/lib/placement.ts`, `apps/cli/src/commands/exec.ts`.
31
+
32
+ - **`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.
33
+
34
+ - **`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`.
35
+
36
+ - **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`.
37
+
38
+ - **`agents snapshot` — one-process poll for inventory + active sessions (Phase 4 surface consolidation).**
39
+ Consumers (Factory, scripts, menubar) were forking `view --json` × N harnesses plus
40
+ `sessions --active --json` (and sometimes feed) on every tick. `agents snapshot --json`
41
+ returns the same shapes in one invocation: `inventory` (view), `sessions` (active rows),
42
+ optional `--with-feed` / `--with-sync`. Default sessions scope is this machine; `--all-hosts`
43
+ matches full `sessions --active` fan-out. Does **not** redefine `agents status`, which stays
44
+ the UnifiedSyncStatus sync contract. Source: `apps/cli/src/commands/snapshot.ts`,
45
+ `apps/cli/src/lib/snapshot.ts`.
46
+
47
+ ## 1.22.7
48
+
49
+ - **`agents feed --project <name>` scopes the whole feed to one project.** Open
50
+ blocks, the updates view (`--filter updates`), and the trailing activity lane
51
+ are all filtered to the requested repo/project using the same worktree-aware
52
+ project key as `agents perf` (`lib/project-key.ts`). The masthead becomes
53
+ `<project> needs you` / `<project> updates`. Filtering is applied locally after
54
+ the fleet fan-out, so older peers that do not recognize `--project` still
55
+ contribute correctly. Source: `apps/cli/src/commands/feed.ts`,
56
+ `apps/cli/src/lib/feed-ranking.ts`.
57
+
58
+ - **Feed blocks are now stamped with their project.** The `feed-publish` hook
59
+ derives project from the session cwd, and `agents feed post --blocked` stamps it
60
+ on the declared block. Live-session enrichment backfills `project` onto older
61
+ blocks that lack it. Source: `apps/cli/src/lib/feed.ts`,
62
+ `apps/cli/src/lib/feed-outcome.ts`, `apps/cli/src/lib/session/active.ts`.
63
+
64
+ - **`agents activity` is removed.** The standalone milestone timeline is gone;
65
+ its stream is now read through `agents feed --filter all` (blocks + updates) or
66
+ `agents feed --filter updates` (updates only). `activity --project <name>` is
67
+ replaced by `feed --project <name>`. Source: `apps/cli/src/index.ts`,
68
+ `apps/cli/src/startup/command-registry.ts`, `apps/cli/src/commands/activity.ts`
69
+ (deleted), `apps/cli/docs/06-observability.md`,
70
+ `apps/cli/docs/11-projects.md`.
71
+
72
+ - **`agents browser start` no longer fails with "Custom binary not found" when the `default` profile came from another OS.** `~/.agents/agents.yaml` syncs across the fleet, so a `default` profile auto-created on macOS carried a `/Applications/Google Chrome.app/...` binary path that doesn't exist on a Linux box — a bare `browser start` there died with `Custom binary not found`, the top browser roadblock (one session burned six commands working around it). `ensureDefaultBrowserProfile` now validates that the resolved default can actually launch on THIS machine and, if its browser/binary is missing, regenerates the `default` from the installed-browser auto-detect instead of handing back the broken profile. A configured default (`profiles set-default`) that can't launch here warns and falls through to auto-detect; remote (`ssh://`) defaults skip the local binary check since their browser lives on the far host. Source: `apps/cli/src/lib/browser/profiles.ts`, `apps/cli/docs/browser.md`.
73
+
74
+ - **The stray "Agents CLI needs to authenticate to continue" Touch ID sheet now actually heals on an already-hashed machine (SEC-13/#1938 follow-up).** 1.22.5 added a one-time no-ACL re-store for a stale-ACL'd `agents-cli.hmackey` item — the internal HMAC key read *before every hashed keychain lookup*, whose damaged copy pops a generic, context-less Touch ID sheet on nearly any command that touches secrets. But it wired the heal only into `maybeAutoRekey`, which is bypassed for the hmackey and hashed-name lookups themselves (`prepareServiceName` returns early for `HMAC_KEY_ITEM` before `maybeAutoRekey` runs). So the exact hot paths that read the key — the `agents devices list` stats probe a SessionStart hook runs, and every background hashed read — never triggered the heal, and an already-migrated machine prompted forever. The documented `agents secrets rekey` remedy is also a no-op on such a machine: with no cleartext names left to re-key, it returns without re-storing the key. The heal now runs on the read path itself (`readHmacKeyRecord`): the first hashed lookup in the first process re-stores the record no-ACL exactly once (guarded by `healedNoAcl`, so it never churns the keychain afterward) — one last prompt on the read that heals it, then silent forever, on every path. Source: `apps/cli/src/lib/secrets/index.ts`.
75
+
76
+ - **Add Pi (Oh My Pi, `omp`) as a native harness.** agents-cli now installs, runs, and
77
+ syncs resources for [Oh My Pi](https://omp.sh) (`@oh-my-pi/pi-coding-agent`, binary
78
+ `omp`) under id `pi`. Pi is a Bun-based, terminal-first, multi-provider coding agent;
79
+ its cross-provider model catalog (OpenRouter, OpenAI, Anthropic, xAI, DeepSeek, …)
80
+ surfaces in `agents view` and `agents models pi` via `omp models --json`. It is
81
+ Claude-compatible: MCP (`.mcp.json`, stdio + http + headers), skills, file commands, and
82
+ Claude-shaped subagents all sync into `~/.omp/agent/`. Hooks, allowlist, and plugins are
83
+ intentionally off (omp's hook/approval/plugin models don't map to agents-cli's).
84
+ Source: `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/exec.ts`, `apps/cli/src/lib/models.ts`.
85
+
86
+ - **`agents run <profile> --model <tier>` now resolves the cost tier against the profile's own harness, not its host's.** A custom-harness profile (e.g. a DeepSeek model routed through the `claude` host binary) resolved a tier token (`cheap|default|best|ultra`) by calling `resolveTier(options.agent, ...)` with `options.agent` already overwritten to the HOST agent's id — so `best` resolved against Claude's own catalog and could push a real Claude model id as the `--model` flag, clobbering the profile's own `ANTHROPIC_MODEL` env value. Profiles can now declare a `models:` block (per-tier model ids for the harness's own catalog); a requested tier resolves against it first — clamping an unset tier down to the next cheaper one that IS set — and the concrete id is substituted into both the env and the forwarded `--model` value before exec.ts's native (host-catalog) tier logic ever runs. A profile with no `models:` configured degrades gracefully to today's behavior — the harness's single pinned model, with an informational note instead of an error. Source: `apps/cli/src/lib/profiles.ts`, `apps/cli/src/commands/exec.ts`.
87
+
88
+ - **`agents projects status` card: host-grouped agents, focus units, and a warnings footer.** Live agents render under `@host` rows so the same harness on two machines is not collapsed into one cell. Focus counts are labeled `file-touches (Nd)` instead of bare integers. Repo drift, dirty trees, missing checkouts, slug mismatch, unmeasurable schedule, and crash piles land at the bottom with 🔴 critical / ⚠️ continue. Local workspace probe always feeds the footer (full fleet table still requires `--fleet`). Source: `apps/cli/src/lib/project-status.ts`, `project-focus.ts`, `project-probe.ts`, `commands/projects.ts`.
89
+
90
+ - **`agents projects status` and `view`/`show` share one body.** Named form is the full card (every milestone + definition); unnamed is the multi-project rollup. No second implementation to drift. Source: `apps/cli/src/commands/projects.ts`.
91
+
92
+ - **`agents sessions --help` and `05-sessions.md` now teach one session-lifecycle
93
+ matrix.** `focus` / `focus --attach-only` / `detach` / `attach` / `resume` are
94
+ listed as distinct intents (not synonyms), so operators stop guessing among
95
+ `go` / `focus` / `attach` / `resume`. Source: `apps/cli/src/commands/sessions.ts`,
96
+ `apps/cli/src/commands/focus.ts`, `apps/cli/docs/05-sessions.md`.
4
97
 
5
98
  - **Cost tiers are ignored (with a clear warning) for profile runs.** A profile's model comes from its endpoint (e.g. Kimi/DeepSeek/GLM via `agents run <profile>`), not the host harness's catalog — so passing `--model cheap|default|best|ultra` to a profile used to resolve against the *host* harness and forward an incompatible model id to the profile's endpoint. Now a tier on a profile run is discarded with a standout warning and the profile's configured model is used. Concrete `--model <id>` on a profile is unchanged. Source: `apps/cli/src/commands/exec.ts`.
6
99
 
100
+ - **`agents view` harness rows now lead with the version number.** Custom harness rows previously showed `via <host> <version>` — the host CLI name came first, which buried the version in the middle of the line. The format is now `<version> (forked from <host>)` for pinned harnesses and `<version> (forked from <host>, tracks default)` for unpinned ones that follow the host's global default. The `tracks default` label is shown in green so it stands out at a glance.
101
+
102
+ - **Chained fork lineage in harness headers.** When a custom harness is itself a fork of another custom harness (which in turn forks a native host), the block header now shows the full two-hop chain: `custom · forked from <intermediate> -> <native-host>`. Single-hop forks continue to show `custom · forked from <parent>`.
103
+
104
+ - **BYOK budget bar in `agents view`.** Custom harnesses backed by an OpenRouter key now show a live spend bar (amount used, remaining, and limit) inline on the model/auth row. Keys are deduplicated so multiple harnesses sharing the same keychain entry trigger exactly one API call. The bar is rendered only when a budget is available; harnesses without a BYOK key are unaffected. Source: `apps/cli/src/lib/byok-usage.ts`, `apps/cli/src/commands/view.ts`.
105
+
106
+ - **`agents run auto` with no prompt no longer silently attaches a dead pane or leaves an orphan session (RUSH-2185 / EXEC-23a).** Three latent bugs combined to produce this failure when `auto` picked a harness like `cursor-agent` that exits immediately without a prompt: (F1) the auto-picker had no gate for whether a harness can open a bare interactive REPL — `cursor-agent` was a valid candidate even though its CLI requires a prompt and exits on `argv = []`; (F2) `surfacePaneFailure` was guarded by `status !== 0`, so a clean exit-0 death produced only a bare `[detached]` line with no diagnostic; (F3) the "pane still alive → keep session" fall-through relied on `paneExitStatus` returning `{dead:false}`, which it also returns on any query error (a race right after the pane-died hook), leaving the session alive as an orphan. Fixed: (F1) a new `interactiveRepl` capability bit in `AgentConfig.capabilities` marks every harness; `auto` now filters to REPL-capable candidates before picking, and fails loud naming the installed harnesses when none qualify; (F2) `shouldRecapDeadPane(status, interactive)` surfaces the pane tail any time the run is interactive, regardless of exit code; (F3) `isPaneKnownAliveFromQueryResult(code, stdout)` is now required as positive proof before keeping a session — an ambiguous result tears the session down via `killSession` instead. Source: `apps/cli/src/lib/exec.ts`, `apps/cli/src/lib/agents.ts`, `apps/cli/src/lib/types.ts`, `apps/cli/src/lib/capabilities.ts`, `apps/cli/src/commands/exec.ts`, `apps/cli/docs/specifications.md` (EXEC-23a).
107
+
108
+ - **`agents run auto` can pick the Pi (`omp`) harness for prompt-less interactive runs again, and `main` builds green.** The `interactiveRepl` capability bit added by the RUSH-2185 / EXEC-23a fix landed at the same time as the new Pi harness, and neither change saw the other — so `pi` was the one agent in `AGENTS` that never declared the bit, and the completeness test that pins the registry to the capability list went red on `main` (`pi missing interactiveRepl`). Pi now declares `interactiveRepl: true`: bare `omp` runs the TUI, and `omp -p` is the one-shot form that answers a prompt and exits.
109
+
110
+ - **`projects status --fleet` no longer labels this box `@local`.** Local sessions from `getActiveSessions()` lacked `machine`; host-grouped agents stamped remotes only. Locals are now filled with `machineId()` before rollup so the agents roster and fleet lines agree. Source: `apps/cli/src/lib/project-status.ts`, `commands/projects.ts`.
111
+
112
+ - **`agents sessions stats` — which skills/commands you actually invoke, and which are dead weight.** A cheap, db-backed rollup of `session_resource_usage` (the skill/`Skill`-tool + slash-command tallies already recorded at index time), joined to `sessions` for attribution so `--agent`/`--project`/`--since`/`--machine` narrow the window and `--kind`/`--plugin` narrow the resources. A both-ends view: the most-invoked resources (`--bottom` flips to least-invoked, `--top <n>` caps), and the installed-but-never-invoked ones (cross-referenced against `listResources`/`discoverPlugins`) — the productized form of a manual transcript-scan audit. `--json` emits a versioned `sessions-stats` envelope. The signal captures EXPLICIT invocations only (slash commands + `Skill` tool calls) — an auto-triggered skill emits no event and reads as 0, and only Claude transcripts expose the signal today; both caveats are surfaced in help and output. A new `agents sessions backfill resources` folds historical sessions (indexed before the signal shipped) into the usage index, re-parsing each transcript from byte 0, gated by a new `resource_scan_ledger` (schema v31) so reruns skip completed transcripts — mirroring `agents sessions backfill tools`. Source: `apps/cli/src/commands/sessions-stats.ts`, `apps/cli/src/commands/sessions-backfill.ts`, `apps/cli/src/lib/session/db.ts`, `apps/cli/docs/05-sessions.md`, `apps/cli/docs/specifications.md` (SES-IF-4b).
113
+
114
+ - **`agents share` serves screenshots and recordings with a real content-type.**
115
+ Publishing a PNG/JPEG/GIF/WebP/AVIF image, an MP4/MOV/WebM video, or a PDF now
116
+ sets the matching `content-type` instead of `application/octet-stream`. GitHub's
117
+ image proxy (camo) only renders an inline `![](url)` when the asset is served as
118
+ a real image/video type, so this is what lets an agent drop a screenshot or a
119
+ screen recording straight into a PR body via `agents share <file>`. HTML, SVG,
120
+ CSS, JS, JSON, and text were already typed correctly. Source:
121
+ `apps/cli/src/lib/share/publish.ts`.
122
+
123
+ - **`agents trends tools-per-session` now counts every scanned session, not just `agents teams`
124
+ runs.** The recipe read `sessions.tool_call_count`, a column nothing populates except the
125
+ teams summarizer (`apps/cli/src/lib/teams/summarizer.ts`) — the general session indexer never
126
+ computes it. So every session that did not come from a team was scored 0 or excluded outright
127
+ by `WHERE tool_call_count IS NOT NULL`, pinning the fleet-wide p50 at 0 however many tools ran
128
+ and leaving only `claude` in the table. It now reads `tool_scan_ledger.call_count`, the
129
+ per-session count the tool indexer writes for every session it scans — the same index behind
130
+ `agents sessions --include tools`, so the two surfaces stop disagreeing. Sessions with
131
+ genuinely zero tool calls still count as 0 instead of vanishing. On a real 7-day window this
132
+ took the sample from 400 to 570 sessions and surfaced `grok`, `rush`, `codex`, `kimi`,
133
+ `droid` and `antigravity`, none of which had ever appeared. Run `agents sessions backfill
134
+ tools` once if historical sessions were never indexed. Source:
135
+ `apps/cli/src/lib/analytics/recipes.ts`.
136
+
7
137
  ## 1.22.5
8
138
 
9
139
  - **`agents events` can now filter by `--session <id>` and `--bundle <name>` — trace which agent/session triggered a secret access.** Every event already carries the provenance `sessionId`, and secrets events carry the `bundle` in their payload, but neither was queryable: you could see *that* the `share` bundle was read, not *which session* read it. `--session` (wired to the engine's existing `sessionId` filter) and `--bundle` (a new payload filter across both the operational log and the activity stream) close that gap. `agents events --module secrets --bundle share --session <id>` answers "which agent read the share bundle" — the attribution the Touch ID storm investigation needed, since the macOS biometric sheet itself emits no event. Source: `apps/cli/src/lib/event-stream.ts`, `apps/cli/src/commands/events.ts`, `apps/cli/docs/06-observability.md`.
package/README.md CHANGED
@@ -31,6 +31,8 @@
31
31
  <a href="https://x.ai" title="Grok Build (xAI)"><strong>Grok</strong></a>
32
32
  &nbsp;&nbsp;&nbsp;&nbsp;
33
33
  <a href="https://factory.ai" title="Factory AI Droid"><strong>Droid</strong></a>
34
+ &nbsp;&nbsp;&nbsp;&nbsp;
35
+ <a href="https://omp.sh" title="Oh My Pi"><strong>Pi</strong></a>
34
36
  </p>
35
37
 
36
38
  https://agents-cli.sh/demo.mp4
@@ -270,6 +272,11 @@ agents sessions --include tools --query 'program:git' --count --fleet --json
270
272
 
271
273
  # Populate historical tool rows once on each device
272
274
  agents sessions backfill tools --fleet
275
+
276
+ # Which skills/commands you actually invoke -- and which installed ones are dead weight
277
+ agents sessions stats
278
+ agents sessions stats --zero # only the never-invoked (dead weight)
279
+ agents sessions backfill resources # fold historical sessions into the usage index
273
280
  ```
274
281
 
275
282
  Interactive picker when you're in a terminal. Structured output (`--json`, `--markdown`, filtered by role or turn count) when piped.
package/dist/bin/agents CHANGED
Binary file
@@ -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);
@@ -553,13 +553,14 @@ 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('--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). See `agents devices`.')
557
- .option('--device <name>', 'Alias of --host. Pass "auto" for affinity-based device pick (same as --host auto).')
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]', 'Invent a disposable cloud box for this run and tear it down after (via crabbox). Optional backend selects the cloud (hetzner/aws/do). Unlike --host, no machine is registered.')
563
+ .option('--lease [backend]', 'Invent a disposable cloud box for this run and tear it down after (via crabbox). 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.')
565
566
  .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.')
@@ -601,6 +602,11 @@ export function registerRunCommand(program) {
601
602
  agents run auto "fix the flaky test" --mode edit
602
603
  agents run auto --host yosemite-s0 "fix the flaky test" # pin the host
603
604
 
605
+ # Placement (one door — where the body runs). Old flags still work.
606
+ agents run claude "…" --where device:yosemite-s0 # = --host yosemite-s0
607
+ agents run claude "…" --where auto # = --device auto
608
+ agents run claude "fix CI" --where lease --mode edit
609
+
604
610
  # Open the session in a terminal tab — detected from where your sessions
605
611
  # already run (Ghostty / iTerm / Terminal.app); force one with a value
606
612
  agents run claude --terminal
@@ -688,6 +694,36 @@ export function registerRunCommand(program) {
688
694
  await handleTerminalHandoff(agentSpec, options, prompt);
689
695
  return;
690
696
  }
697
+ // Placement: --where expands into --host / --lease before any dispatch.
698
+ // One door for "where does the body run?" — old flags remain aliases.
699
+ // See lib/placement.ts and docs/00-concepts.md#placement.
700
+ {
701
+ const { placementFromRunFlags, expandPlacementToRunFlags, PlacementError } = await import('../lib/placement.js');
702
+ try {
703
+ const placement = placementFromRunFlags(options);
704
+ if (options.where) {
705
+ const expanded = expandPlacementToRunFlags(placement);
706
+ if (expanded.host !== undefined)
707
+ options.host = expanded.host;
708
+ if (expanded.device !== undefined)
709
+ options.device = expanded.device;
710
+ if (expanded.lease !== undefined)
711
+ options.lease = expanded.lease;
712
+ if (expanded.box !== undefined)
713
+ options.box = expanded.box;
714
+ // Clear the where flag so remote re-entry (host dispatch) does not
715
+ // re-expand and conflict with the concrete host we just set.
716
+ options.where = undefined;
717
+ }
718
+ }
719
+ catch (err) {
720
+ if (err instanceof PlacementError) {
721
+ console.error(chalk.red(err.message));
722
+ process.exit(1);
723
+ }
724
+ throw err;
725
+ }
726
+ }
691
727
  // --notify: post a desktop notification when this run finishes. Armed on
692
728
  // process exit so it covers EVERY dispatch path below (local, --host,
693
729
  // --lease, the error path) instead of one branch. Only for headless runs
@@ -1568,7 +1604,7 @@ export function registerRunCommand(program) {
1568
1604
  await warnUnpushedWork(resumeExec.cwd ?? process.cwd());
1569
1605
  process.exit(resumeExit);
1570
1606
  }
1571
- const [{ buildExecCommand, parseExecEnv, execAgent, runWithFallback, normalizeMode, resolveMode, headlessPlanStallCommand, nativeResume, resolveInteractive, inferredInteractiveWithoutTty }, { ALL_AGENT_IDS, ACCOUNT_INSPECTION_AGENT_IDS, agentLabel, supportsAccountInspection }, { profileExists, resolveProfileForRun }, { readAndResolveBundleEnv, describeBundle, assertRemoteBundleFlagsUnsupported }, { splitBundleRef, resolveHostSshTarget, remoteResolveEnv }, { getConfiguredRunStrategy, normalizeRunStrategy, resolveRunVersion, rotationFailoverChain, shouldArmRotationFailover, RUN_STRATEGIES, collectHarnessCandidates, pickHarnessWeighted, classifyHarnessCandidates, formatHarnessPickBanner, formatNoHealthyHarnessError, formatNoHealthyAccountError }, { getGlobalDefault, getVersionHomePath, resolveVersion, resolveVersionAlias, ensureAgentRunnable }, { buildDiscoveredPlugin, loadPluginManifest, syncPluginToVersion }, { parseWorkflowFrontmatter, resolveWorkflowRef, resolveAllowedSubagents, pruneStaleWorkflowSubagents, ensureSubagentDispatchTool }, { resolveRunDefaults }, { getMcpServersByName, buildWorkflowMcpConfig }, { supports }, { shareRuntimeEnv },] = await Promise.all([
1607
+ const [{ buildExecCommand, parseExecEnv, execAgent, runWithFallback, normalizeMode, resolveMode, headlessPlanStallCommand, nativeResume, resolveInteractive, inferredInteractiveWithoutTty }, { ALL_AGENT_IDS, ACCOUNT_INSPECTION_AGENT_IDS, agentLabel, supportsAccountInspection }, { profileExists, resolveProfileForRun }, { readAndResolveBundleEnv, describeBundle, assertRemoteBundleFlagsUnsupported }, { splitBundleRef, resolveHostSshTarget, remoteResolveEnv }, { getConfiguredRunStrategy, normalizeRunStrategy, resolveRunVersion, rotationFailoverChain, shouldArmRotationFailover, RUN_STRATEGIES, collectHarnessCandidates, pickHarnessWeighted, classifyHarnessCandidates, formatHarnessPickBanner, formatNoHealthyHarnessError, formatNoHealthyAccountError }, { getGlobalDefault, getVersionHomePath, resolveVersion, resolveVersionAlias, ensureAgentRunnable }, { buildDiscoveredPlugin, loadPluginManifest, syncPluginToVersion }, { parseWorkflowFrontmatter, resolveWorkflowRef, resolveAllowedSubagents, pruneStaleWorkflowSubagents, ensureSubagentDispatchTool }, { resolveRunDefaults }, { getMcpServersByName, buildWorkflowMcpConfig }, { supports, capableAgents }, { shareRuntimeEnv },] = await Promise.all([
1572
1608
  import('../lib/exec.js'),
1573
1609
  import('../lib/agents.js'),
1574
1610
  import('../lib/profiles.js'),
@@ -1615,7 +1651,7 @@ export function registerRunCommand(program) {
1615
1651
  const cwd = options.cwd ?? process.cwd();
1616
1652
  if (accountPickerRequested && !isValidAgent(rawAgent)) {
1617
1653
  if (profileExists(rawAgent)) {
1618
- console.error(chalk.red(`Account selection is not available for profile '${rawAgent}'. Run its concrete host agent with @ instead.`));
1654
+ console.error(chalk.red(`Account selection is not available for custom harness '${rawAgent}'. Run its concrete host agent with @ instead.`));
1619
1655
  process.exit(1);
1620
1656
  }
1621
1657
  if (resolveWorkflowRef(rawAgent, cwd)) {
@@ -1629,9 +1665,24 @@ export function registerRunCommand(program) {
1629
1665
  // — launching a default "because it's there" is how a rotate loop
1630
1666
  // hammers an exhausted account.
1631
1667
  const byHarness = await collectHarnessCandidates();
1632
- const harnessPick = pickHarnessWeighted(byHarness);
1668
+ // F1 (RUSH-2185 / EXEC-23a): a prompt-less run is interactive; only
1669
+ // harnesses that can open a REPL with no argv are valid candidates.
1670
+ // cursor-agent and similar exit immediately without a prompt, which
1671
+ // leaves a silent [detached] pane and an orphan session.
1672
+ const interactive = prompt === undefined && options.headless !== true;
1673
+ const replCapable = interactive ? new Set(capableAgents('interactiveRepl')) : null;
1674
+ const candidateHarness = replCapable
1675
+ ? new Map([...byHarness].filter(([id]) => replCapable.has(id)))
1676
+ : byHarness;
1677
+ const harnessPick = pickHarnessWeighted(candidateHarness);
1633
1678
  if (!harnessPick) {
1634
- console.error(chalk.red(formatNoHealthyHarnessError(classifyHarnessCandidates(byHarness))));
1679
+ if (replCapable && byHarness.size > 0 && candidateHarness.size === 0) {
1680
+ const installed = [...byHarness.keys()].join(', ');
1681
+ console.error(chalk.red(`No installed harness supports a prompt-less interactive REPL (installed: ${installed}). Pass a prompt (-p) or install claude, codex, or another REPL-capable harness.`));
1682
+ }
1683
+ else {
1684
+ console.error(chalk.red(formatNoHealthyHarnessError(classifyHarnessCandidates(candidateHarness))));
1685
+ }
1635
1686
  process.exit(1);
1636
1687
  }
1637
1688
  agent = harnessPick.picked.agent;
@@ -1653,14 +1704,30 @@ export function registerRunCommand(program) {
1653
1704
  // so Chinese models (Kimi, DeepSeek, Qwen, GLM) can run inside
1654
1705
  // Claude Code without a local proxy.
1655
1706
  try {
1656
- const resolved = resolveProfileForRun(rawAgent);
1707
+ const resolved = resolveProfileForRun(rawAgent, options.model);
1657
1708
  agent = resolved.agent;
1658
1709
  if (!version)
1659
1710
  version = resolved.version;
1660
1711
  profileEnv = resolved.env;
1661
1712
  profileFallbackModel = resolved.fallbackModel;
1662
1713
  fromProfile = true;
1663
- process.stderr.write(chalk.gray(`Resolved profile '${resolved.profileName}' -> ${agent}${version ? `@${version}` : ''}\n`));
1714
+ process.stderr.write(chalk.gray(`Resolved custom harness '${resolved.profileName}' -> ${agent}${version ? `@${version}` : ''}\n`));
1715
+ if (resolved.tierNote) {
1716
+ process.stderr.write(chalk.gray(`[agents] ${resolved.tierNote}\n`));
1717
+ }
1718
+ // A tier token (cheap/default/best/ultra) already resolved against
1719
+ // this PROFILE's own `models:` map above, when the profile opts in.
1720
+ // Replace the raw --model value here so the tier never reaches the
1721
+ // native, HOST-catalog tier block below. When the profile has no
1722
+ // `models:` opt-in at all, resolvedModel stays undefined and
1723
+ // options.model is left as the raw tier token on purpose — the
1724
+ // "cost tiers don't apply to custom harness ..." discard guard further
1725
+ // down this function is the canonical fallback for that case, and
1726
+ // this block must not race it with a second, differently-worded
1727
+ // message.
1728
+ if (resolved.resolvedModel !== undefined) {
1729
+ options.model = resolved.resolvedModel;
1730
+ }
1664
1731
  }
1665
1732
  catch (err) {
1666
1733
  console.error(chalk.red(err.message));
@@ -1863,7 +1930,7 @@ export function registerRunCommand(program) {
1863
1930
  else {
1864
1931
  console.error(chalk.red(`Unknown agent: ${rawAgent}`));
1865
1932
  console.error(chalk.gray(`Available agents: ${ALL_AGENT_IDS.join(', ')}`));
1866
- console.error(chalk.gray(`Or add a profile: agents profiles add <name>`));
1933
+ console.error(chalk.gray(`Or add a custom harness: agents harness add <name>`));
1867
1934
  process.exit(1);
1868
1935
  }
1869
1936
  }
@@ -2031,7 +2098,7 @@ export function registerRunCommand(program) {
2031
2098
  process.stderr.write(chalk.yellow(`[agents] strategy ${strategy} ignored: version ${version} is pinned\n`));
2032
2099
  }
2033
2100
  else if (fromProfile) {
2034
- process.stderr.write(chalk.yellow(`[agents] strategy ${strategy} ignored: profile pins its own version/auth\n`));
2101
+ process.stderr.write(chalk.yellow(`[agents] strategy ${strategy} ignored: custom harness pins its own version/auth\n`));
2035
2102
  }
2036
2103
  else {
2037
2104
  try {
@@ -2281,12 +2348,12 @@ export function registerRunCommand(program) {
2281
2348
  ? (workflowModel ?? (options.fallback ? undefined : runDefaults.model))
2282
2349
  : undefined);
2283
2350
  // Cost tiers (cheap|default|best|ultra) resolve against a harness's own model
2284
- // catalog. A profile's model comes from its endpoint, not the host harness, so a
2285
- // tier here would forward an incompatible host-harness model to a different API.
2286
- // Discard it loudly and let the profile's configured model stand.
2351
+ // catalog. A custom harness's model comes from its endpoint, not the host
2352
+ // harness, so a tier here would forward an incompatible host-harness model to a
2353
+ // different API. Discard it loudly and let the custom harness's own model stand.
2287
2354
  if (fromProfile && model && isTierToken(model)) {
2288
- process.stderr.write(chalk.yellow(`[agents] --model ${model}: cost tiers don't apply to profile '${rawAgent}' ` +
2289
- `(its model comes from the endpoint) — ignoring the tier, using the profile's configured model\n`));
2355
+ process.stderr.write(chalk.yellow(`[agents] --model ${model}: cost tiers don't apply to custom harness '${rawAgent}' ` +
2356
+ `(its model comes from the endpoint) — ignoring the tier, using the custom harness's configured model\n`));
2290
2357
  model = undefined;
2291
2358
  }
2292
2359
  const execOptions = {
@@ -13,7 +13,7 @@
13
13
  * `agents feed post`: agent-callable free-text progress into the activity
14
14
  * stream (milestone `status.posted`). Session/agent/host/runtime/pid identity
15
15
  * is auto-stamped from env + the pid registry — no domain-specific flags.
16
- * Humans watch via the feed activity lane / `agents activity`.
16
+ * Humans watch via the feed activity lane.
17
17
  */
18
18
  import type { Command } from 'commander';
19
19
  import { type OpenBlock } from '../lib/feed.js';
@@ -47,6 +47,7 @@ export declare function formatOutcomeHeader(group: OutcomeGroup): string;
47
47
  export declare function sessionHintsFromActive(sessions: Array<{
48
48
  sessionId?: string;
49
49
  agentId?: string;
50
+ cwd?: string;
50
51
  ticket?: {
51
52
  id?: string;
52
53
  };