@phnx-labs/agents-cli 1.22.15 → 1.22.17
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 +52 -0
- package/README.md +13 -2
- package/dist/bin/agents +0 -0
- package/dist/commands/cloud.d.ts +0 -1
- package/dist/commands/cloud.js +19 -185
- package/dist/commands/doctor.js +16 -14
- package/dist/commands/exec.js +153 -16
- package/dist/commands/feed.d.ts +1 -0
- package/dist/commands/feed.js +36 -32
- package/dist/commands/humans.d.ts +10 -0
- package/dist/commands/humans.js +91 -0
- package/dist/commands/resume.d.ts +17 -0
- package/dist/commands/resume.js +80 -0
- package/dist/commands/run-cloud.d.ts +26 -0
- package/dist/commands/run-cloud.js +162 -0
- package/dist/commands/sessions.d.ts +13 -4
- package/dist/commands/sessions.js +37 -19
- package/dist/commands/versions.js +4 -2
- package/dist/commands/view.js +1 -54
- package/dist/index.js +3 -2
- package/dist/lib/agents.js +4 -2
- package/dist/lib/channels/send.d.ts +5 -2
- package/dist/lib/channels/send.js +16 -13
- package/dist/lib/cloud/dispatch.d.ts +27 -0
- package/dist/lib/cloud/dispatch.js +214 -0
- package/dist/lib/devices/doctor-findings.d.ts +14 -0
- package/dist/lib/devices/doctor-findings.js +92 -51
- package/dist/lib/exec.d.ts +6 -2
- package/dist/lib/exec.js +22 -4
- package/dist/lib/feed-post.d.ts +2 -2
- package/dist/lib/feed-post.js +15 -10
- package/dist/lib/hooks.d.ts +4 -2
- package/dist/lib/hooks.js +173 -23
- package/dist/lib/hosts/remote-cmd.js +8 -0
- package/dist/lib/humans.d.ts +26 -0
- package/dist/lib/humans.js +73 -0
- package/dist/lib/memory.js +2 -1
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/migrate.js +112 -4
- package/dist/lib/notify.d.ts +4 -4
- package/dist/lib/notify.js +4 -3
- package/dist/lib/permissions.js +12 -10
- package/dist/lib/placement.d.ts +8 -4
- package/dist/lib/placement.js +14 -8
- package/dist/lib/projects.d.ts +3 -3
- package/dist/lib/projects.js +19 -17
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/bundles.d.ts +11 -0
- package/dist/lib/secrets/bundles.js +122 -5
- package/dist/lib/session/actor-sidecar.d.ts +5 -2
- package/dist/lib/session/actor-sidecar.js +4 -2
- package/dist/lib/session/db.d.ts +2 -1
- package/dist/lib/session/db.js +19 -3
- package/dist/lib/session/types.d.ts +4 -0
- package/dist/lib/settings-manifest.js +7 -1
- package/dist/lib/staleness/writers/sources.d.ts +6 -1
- package/dist/lib/staleness/writers/sources.js +93 -7
- package/dist/lib/startup/command-registry.d.ts +2 -0
- package/dist/lib/startup/command-registry.js +5 -0
- package/dist/lib/state.d.ts +2 -0
- package/dist/lib/state.js +6 -6
- package/dist/lib/types.d.ts +52 -0
- package/dist/lib/versions.js +48 -14
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,57 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.22.17
|
|
4
|
+
|
|
5
|
+
- **Codex versions no longer share one account — each keeps its own login.** Installing a new Codex version used to copy the current default version's `.codex/auth.json` into the new version home, so `agents view` reported the same ChatGPT account for every installed Codex and you could never sign two versions into two accounts. The credential is now excluded from settings carry-forward (config, prompts, and rules still carry), matching how Claude omits `.claude.json`. A fresh Codex version installs signed-out; run `codex login` (or `agents run codex --version <v>`) inside it to authenticate that version's own account. Source: `apps/cli/src/lib/settings-manifest.ts`, `apps/cli/src/commands/versions.ts`.
|
|
6
|
+
|
|
7
|
+
- **`agents feed post` now resolves owner delivery and session identity from their canonical indexes (RUSH-2193).** `agents notify` and `channel: owner` consume the normal addressable channel from `humans.yaml`, so the phone destination is no longer duplicated in `agents.yaml`. When a harness leaves `AGENT_SESSION_ID` empty, feed posting joins `AGENT_LAUNCH_ID` to recent activity before requiring `--session`. `agents feed post --help` now states that the default `milestone` is recorded but a sink gated at `minLevel: important` only texts for `--level important`; `--blocked` remains reserved for work that cannot continue. Source: `apps/cli/src/lib/humans.ts`, `apps/cli/src/lib/feed-post.ts`, `apps/cli/src/commands/feed.ts`.
|
|
8
|
+
|
|
9
|
+
- **The macOS menu bar keeps its ACTIVE project accordion open and opens Quick Dispatch without blocking.** Project headers now mutate their session rows inside the existing `NSMenu` tracking session instead of closing the dropdown and attempting a synthetic reopen. `Cmd-Shift-O` prebuilds and focuses its panel before any session-history, attachment scan, thumbnail decode, or Linear-cache work; those sections hydrate asynchronously from warm data. Source: `apps/cli/menubar/Sources/MenubarHelper/{StatusItemController,PromptPanel,AgentsCLI}.swift`, `apps/cli/docs/menubar.md`. (#2051)
|
|
10
|
+
|
|
11
|
+
- **`agents doctor` now accepts symbolic version qualifiers; `agents view` no longer prompts to sync resources.** `agents doctor claude@latest`, `@oldest`, `@pinned`, `@all`, and exact semver qualifiers are resolved through the shared `resolveAgentTargets` engine instead of falling through to a "not installed" error. Bare `agents doctor <agent>` still sweeps all installed versions with `versionExplicit: false` (so `--fix` excludes isolated copies). Separately, the implicit resource-drift scan and interactive sync prompt that ran at the end of `agents view <agent>` have been removed — `view` is read-only and must not mutate version-home state. Source: `apps/cli/src/commands/doctor.ts`, `apps/cli/src/commands/view.ts`. (#2058)
|
|
12
|
+
|
|
13
|
+
- **`agents run <agent> --cloud` — the vendor cloud becomes a run placement.** `--cloud` dispatches the run to the agent's native cloud through the existing provider registry (claude→rush, codex→codex, droid→factory, antigravity→antigravity) — the exact same dispatch as `agents cloud run --agent <agent>`, tracked by `agents cloud list/status/logs/cancel/message`. It sits alongside `--host`/`--device`/`--lease` as the third placement (local, machine, cloud) and is mutually exclusive with them; `--where cloud[:provider]` is the one-door spelling. `--provider` overrides routing; `--repo` (repeatable), `--branch`, and `--cloud-env` refine the task (run's `--env` stays the KEY=VAL passthrough, so the Codex Cloud environment id gets its own flag). Agents without a native cloud (kimi, grok, cursor, opencode, …) fail loud with the capable list unless `--provider` is given, and local-run flags (`--loop`, `--resume`, `--secrets`, `--terminal`, `--cwd`, account strategy, …) are rejected rather than silently dropped. The dispatch core is now shared: `agents cloud run` and `agents run --cloud` both call `src/lib/cloud/dispatch.ts` (`executeCloudDispatch`), so capability checks, the missing-target picker, persistence, streaming, and the budget kill-switch cannot diverge. Source: `apps/cli/src/commands/run-cloud.ts`, `apps/cli/src/lib/cloud/dispatch.ts`, `apps/cli/src/lib/placement.ts`, `apps/cli/src/commands/exec.ts`.
|
|
14
|
+
|
|
15
|
+
## 1.22.16
|
|
16
|
+
|
|
17
|
+
- **Resume exact sessions locally or across the fleet with `agents resume <id>` and `agents run <agent|auto> --resume <id>`.** Full IDs use the local SQLite index before any SSH fan-out; remote owners route to the recorded device and version home. Session metadata now records launch mode alongside harness, version, account, cwd, and machine so strict resume reconstructs the original run. Claude, Codex, Grok, Kimi, Droid, and Cursor use their verified version-specific native resume syntax; `run auto --resume` can select another healthy harness/account and continue through `/continue` when native resume is unavailable. Source: `apps/cli/src/commands/{exec,resume,sessions}.ts`, `apps/cli/src/lib/{exec,session/db}.ts`, `packages/session-tracker/src/hook.sh`.
|
|
18
|
+
|
|
19
|
+
- **Hooks: one-level event dirs (`hooks/<event-name>/<script>`) are first-class.**
|
|
20
|
+
System hooks organize by harness event (`session-start/`, `pre-tool-use/`, …).
|
|
21
|
+
Install names stay the file basename. Dirs with top-level scripts expand into
|
|
22
|
+
individual hooks; fixture-only dirs remain directory bundles. Manifest `script:`
|
|
23
|
+
may be a relative path under `hooks/`. Source: `apps/cli/src/lib/hooks.ts`,
|
|
24
|
+
`apps/cli/src/lib/staleness/writers/sources.ts`, `apps/cli/src/lib/versions.ts`,
|
|
25
|
+
`apps/cli/src/lib/__tests__/hooks-nested-groups.test.ts`.
|
|
26
|
+
|
|
27
|
+
- **`agents humans show owner [--json]`** — new command to display the owner config from `~/.agents/humans.yaml`. The file is written automatically on first run when `notify.owner` exists in `agents.yaml`. Source: `apps/cli/src/lib/humans.ts`, `apps/cli/src/commands/humans.ts`.
|
|
28
|
+
|
|
29
|
+
- **`humans.yaml` — typed, versioned owner config.** `~/.agents/humans.yaml` (`version: 1`) now stores owner identity (name, timezone, quiet hours, severity), notification channels, and escalation policy. `notify.owner` in `agents.yaml` is migrated into it on first run and the `notify.owner` key is removed from `agents.yaml`; unrelated keys are preserved. `agents send --to owner` / `agents notify` prefer `humans.yaml` with a fallback to `agents.yaml` during the migration window. Source: `apps/cli/src/lib/humans.ts`, `apps/cli/src/commands/humans.ts`, `apps/cli/src/lib/migrate.ts`.
|
|
30
|
+
|
|
31
|
+
- **`agents memory` ignores `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, and `MEMORY.md`.** These rule/index files lived in `~/.agents/memory/` but were incorrectly surfaced as memory facts. `isFactFile()` now excludes them by name (case-insensitive). Source: `apps/cli/src/lib/memory.ts`.
|
|
32
|
+
|
|
33
|
+
- **Permissions write path fixed — `groups/` subdirectory.** `installPermissionSet`, `removePermissionSet`, and `savePermissionSet` now all write to the `groups/` subdirectory (matching `discoverPermissionGroups()` which already reads from `groups/`). Source: `apps/cli/src/lib/permissions.ts`.
|
|
34
|
+
|
|
35
|
+
- **Stop eagerly creating webhooks directories.** `ensureAgentsDir()` no longer creates `~/.agents/webhooks/` or `~/.agents/.system/webhooks/` on startup — both dirs are created on first actual use. Source: `apps/cli/src/lib/state.ts`.
|
|
36
|
+
|
|
37
|
+
- **Terminals canonically under `.cache/`.** The stale migration comment that blocked `terminals/` from moving to `~/.agents/.cache/terminals/` is replaced by the actual move. Factory already writes to `.cache/terminals/` (`foreman.registry.ts:9`), so no app-level change is needed. Source: `apps/cli/src/lib/migrate.ts`.
|
|
38
|
+
|
|
39
|
+
- **Menu bar warns when a device is under high load (local or remote).** The
|
|
40
|
+
agents-cli menu bar now shows a `⚠ <device> — high load N%` row in NEEDS YOU when
|
|
41
|
+
a machine's load or memory crosses the `headroom()` "loaded" threshold (≥75%), and
|
|
42
|
+
a red `✕` when critical. The local machine is probed natively via `getloadavg`
|
|
43
|
+
(zero subprocess); fleet peers come from the daemon-warmed `.fleet-stats.json`
|
|
44
|
+
cache with a freshness guard — never the slow `agents doctor` path. Action-required
|
|
45
|
+
rows are now emphasized so items that need you stand out. Source:
|
|
46
|
+
`apps/cli/menubar/Sources/MenubarHelper/LocalState.swift`,
|
|
47
|
+
`apps/cli/menubar/Sources/MenubarHelper/StatusItemController.swift`.
|
|
48
|
+
|
|
49
|
+
- **Projects canonicalization contract.** `agents projects import --from-factory` / `--min-confidence` / `--all` are gone; import is `--from-linear` only, and `~/.agents/factory/projects.json` is never read or migrated. `agents projects list --json` returns definitions only (zero session scan / SSH); `--with-agents` is an explicit opt-in for local active counts. New `agents projects save --json` reads one complete `ProjectDef` from stdin, validates, writes atomically under `~/.agents/projects/`, and prints the saved def. `agents projects rm <name> --json` returns machine-readable success/error. Factory's `managedProjects.ts` shells only through `agents projects list|save|rm` — never reads or writes project YAML/JSON directly, never seeds or migrates legacy Factory state; errors stay explicit for inline UI display. Source: `apps/cli/src/commands/projects.ts`, `apps/cli/src/lib/projects.ts`, `apps/factory/src/core/managedProjects.ts`, `apps/cli/docs/11-projects.md`.
|
|
50
|
+
|
|
51
|
+
- **`ProjectDef` YAML gains `dispatch` block and `linear.name`.** `~/.agents/projects/<name>.yaml` now accepts a `dispatch:` block (`enabled`, `maxAgents`, `provider`, `host`) that opts a project into auto-dispatch and is read directly by `agents __auto-dispatch` — previously these fields lived only in Factory's own registry. `linear.name` stores the Linear project display name alongside the existing `projectId` and `url`. Both fields are optional; existing YAMLs are unchanged. Source: `apps/cli/src/lib/projects.ts`, `apps/cli/src/lib/auto-dispatch.ts`.
|
|
52
|
+
|
|
53
|
+
- **`agents secrets` no longer pops a generic "Agents CLI needs to authenticate" Touch ID sheet on every agent launch.** `listBundles` — which runs on essentially every secrets touch (session-title generation, `agents devices list`, every `agents run`, every remote launch that resolves secrets on the host) — could not ask the keychain for just the bundle *metadata* items: with hashed service names (#316) those names are opaque, so it fell back to a **broad `agents-cli.` keychain scan** that also matched the ACL'd secret *value* items. On machines where a bundle carries a biometric ACL (e.g. a `hold`-tier bundle holding an SSN or a password), macOS evaluated that value ACL during the attributes-only scan and raised a **generic, context-less** Touch ID prompt — on every launch, so a busy fleet felt like a machine-wide bombardment. Neither `kSecUseAuthenticationUIFail` nor `LAContext.interactionNotAllowed` can list the no-ACL items while skipping the ACL'd ones (both return nothing), so the fix is to stop doing the broad scan: a per-machine **no-ACL metadata-name index** (opaque hashes only, in the regenerable helpers dir — it leaks nothing #316 didn't) is read as a silent file instead. The write paths keep it current; an absent/stale index self-heals by rebuilding from the one-time scan, and a missing entry only makes `secrets list` cosmetically incomplete — it never affects a resolve-by-name. Your sensitive bundles keep their biometric gate on real value reads; only the bundle *listing* goes silent. Source: `apps/cli/src/lib/secrets/bundles.ts`.
|
|
54
|
+
|
|
3
55
|
## 1.22.15
|
|
4
56
|
|
|
5
57
|
- **Separate routine definitions from device activation (#2023).** Enable a routine by listing its name in `~/.agents/devices/<hostname>/agents.yaml`; built-in Watchdog setup and `watchdog on|off` now update that host-owned manifest without rewriting the routine definition. Source: `apps/cli/src/lib/routine-activation.ts`, `apps/cli/src/commands/setup-watchdog.ts`.
|
package/README.md
CHANGED
|
@@ -341,9 +341,11 @@ agents sessions resume # multi-select; packs two sessions pe
|
|
|
341
341
|
agents sessions resume "auth middleware" # pre-filter the pool, then choose
|
|
342
342
|
agents sessions resume --tmux # into persistent tmux — survives editor restarts
|
|
343
343
|
agents sessions resume --host zion --tmux # resume on another machine over SSH
|
|
344
|
+
agents resume 019fd0c8-b3e9-77a2-a1a4-444698c4d897 # original harness/version/device/mode
|
|
345
|
+
agents run auto --resume 019fd0c8-b3e9-77a2-a1a4-444698c4d897 # adapt if its account is unavailable
|
|
344
346
|
```
|
|
345
347
|
|
|
346
|
-
`agents sessions resume` reopens sessions in whatever terminal you're in -- auto-detected across iTerm, Ghostty, tmux, and the VSCodium agent-terminal, or forced with `--iterm` / `--ghostty` / `--tmux` / `--vscodium`. Back them with **tmux** and the runs turn durable: detach, close your editor, reboot the GUI -- the session is still alive to `agents tmux attach`. The whole `agents tmux` subsystem (persistent multiplexer sessions that survive editor restarts and can be shared with other tools) sits underneath.
|
|
348
|
+
`agents sessions resume` reopens several sessions in whatever terminal you're in -- auto-detected across iTerm, Ghostty, tmux, and the VSCodium agent-terminal, or forced with `--iterm` / `--ghostty` / `--tmux` / `--vscodium`. `agents resume <id>` resumes one session without requiring you to name its harness: exact IDs take a local SQLite fast path, then resolve fleet-wide and restore the source harness, version, device, cwd, and recorded launch mode. Back them with **tmux** and the runs turn durable: detach, close your editor, reboot the GUI -- the session is still alive to `agents tmux attach`. The whole `agents tmux` subsystem (persistent multiplexer sessions that survive editor restarts and can be shared with other tools) sits underneath.
|
|
347
349
|
|
|
348
350
|
### Send an agent to the background — and bring it back
|
|
349
351
|
|
|
@@ -505,6 +507,13 @@ agents logs <id> --full # the full raw transcript / stdout (toke
|
|
|
505
507
|
agents logs <id> -f # re-attach to a running one and follow
|
|
506
508
|
agents view claude --host gpu-box # inspect the remote install
|
|
507
509
|
agents sync --host gpu-box # make the remote machine current
|
|
510
|
+
agents doctor claude # diagnose every installed claude version
|
|
511
|
+
agents doctor claude@latest # diagnose only the newest installed version
|
|
512
|
+
agents doctor claude@oldest # diagnose only the oldest installed version
|
|
513
|
+
agents doctor claude@pinned # diagnose the global-default (pinned) version
|
|
514
|
+
agents doctor claude@all # diagnose all versions, including isolated copies
|
|
515
|
+
agents doctor claude@latest --fix # auto-fix the newest installed version
|
|
516
|
+
agents doctor claude@latest --device mac-mini # diagnose newest claude on mac-mini
|
|
508
517
|
agents doctor --devices # readiness matrix for every registered device
|
|
509
518
|
agents doctor --devices --json # machine-readable fleet readiness
|
|
510
519
|
agents doctor --device mac-mini # same matrix, scoped to one device
|
|
@@ -621,6 +630,8 @@ Four managed backends behind one interface (`agents cloud providers`):
|
|
|
621
630
|
|
|
622
631
|
Auto-routes each `--agent` to its native cloud, or pin the backend with `--provider`. Instead of dispatching now, register a run as an **event trigger** with `--on pull_request` (also `push`, `issue_comment`, `workflow_run`) -- it persists as a trigger-bound routine that fires on the event. `--json` on every subcommand for scripting.
|
|
623
632
|
|
|
633
|
+
The same dispatch is a placement on `agents run`: `agents run claude "fix the flaky e2e" --cloud --repo acme/api` routes through the identical provider registry and tracks in `agents cloud list/status/logs` alike. `--cloud` sits alongside `--host`/`--device`/`--lease` as one of three placements (local, machine, cloud) and is mutually exclusive with them; `--where cloud[:provider]` is the one-door spelling. Agents without a native cloud fail loud unless `--provider` is given.
|
|
634
|
+
|
|
624
635
|
---
|
|
625
636
|
|
|
626
637
|
## Workflows
|
|
@@ -1070,7 +1081,7 @@ The dropdown surfaces a **NEEDS YOU** queue (agents waiting on a question, a pla
|
|
|
1070
1081
|
|
|
1071
1082
|
### Quick-issue bar (⌘⇧O)
|
|
1072
1083
|
|
|
1073
|
-
Press `Cmd-Shift-O` anywhere for a thin capture surface:
|
|
1084
|
+
Press `Cmd-Shift-O` anywhere for a thin capture surface: the prepared text field appears immediately while repo, thumbnail, and ticket rows hydrate in the background. Type a one-line note, `Cmd-V` to paste, and attach one or more recent screenshots (double-click a thumbnail to preview it in full). Submit, and a headless agent picks the right project from your recent sessions, investigates, and files the Linear ticket itself -- you never leave what you were doing.
|
|
1074
1085
|
|
|
1075
1086
|
The bar also lists the **open Linear tickets of the repo you picked**, urgent first. Switching the repo switches the Linear project; typing filters the list, so an existing ticket shows up before you file a duplicate; and clicking a row (or `⌘1`-`⌘5`) dispatches that ticket to the selected agents -- **Run** implements it, **Plan** posts a plan as a ticket comment.
|
|
1076
1087
|
|
package/dist/bin/agents
CHANGED
|
Binary file
|
package/dist/commands/cloud.d.ts
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
* All tasks are tracked locally in a SQLite database for cross-provider listing.
|
|
7
7
|
*/
|
|
8
8
|
import type { Command } from 'commander';
|
|
9
|
-
/** Print an error message to stderr and exit. */
|
|
10
9
|
/** Return a chalk color function appropriate for the given task status. */
|
|
11
10
|
export declare function statusColor(status: string): (s: string) => string;
|
|
12
11
|
/** Register the `agents cloud` command tree (run, list, status, logs, cancel, message, providers). */
|
package/dist/commands/cloud.js
CHANGED
|
@@ -1,43 +1,12 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { die, relTime, truncate, isJsonMode } from '../lib/format.js';
|
|
3
|
-
import * as fs from 'fs';
|
|
4
|
-
import * as path from 'path';
|
|
5
|
-
import ora from 'ora';
|
|
6
3
|
import { resolveProvider, getAllProviders, getDefaultProviderId } from '../lib/cloud/registry.js';
|
|
7
4
|
import { insertTask, updateTaskStatus, getTaskById, listTasks as listStoredTasks, listActiveTasks } from '../lib/cloud/store.js';
|
|
8
5
|
import { renderStream } from '../lib/cloud/stream.js';
|
|
9
|
-
import {
|
|
6
|
+
import { MAX_IMAGES_PER_DISPATCH } from '../lib/cloud/types.js';
|
|
7
|
+
import { resolveCloudPrompt, executeCloudDispatch } from '../lib/cloud/dispatch.js';
|
|
10
8
|
import { normalizeTriggerEvent, validateTrigger, writeJob, setJobEnabled, jobExists, GITHUB_TRIGGER_EVENTS } from '../lib/routines.js';
|
|
11
9
|
import { emit } from '../lib/events.js';
|
|
12
|
-
import { shareRuntimeEnv } from '../lib/share/config.js';
|
|
13
|
-
/** Map a supported image file extension to its wire mimeType. Rejects anything else. */
|
|
14
|
-
function imageMimeFromPath(file) {
|
|
15
|
-
const ext = path.extname(file).toLowerCase();
|
|
16
|
-
if (ext === '.png')
|
|
17
|
-
return 'image/png';
|
|
18
|
-
if (ext === '.jpg' || ext === '.jpeg')
|
|
19
|
-
return 'image/jpeg';
|
|
20
|
-
if (ext === '.webp')
|
|
21
|
-
return 'image/webp';
|
|
22
|
-
die(`Unsupported image type ${JSON.stringify(ext || file)}. Use .png, .jpg/.jpeg, or .webp.`);
|
|
23
|
-
}
|
|
24
|
-
/** Read one image file into a base64 ImageAttachment, dying with a clear error if it's missing. */
|
|
25
|
-
function readImageAttachment(file) {
|
|
26
|
-
if (!fs.existsSync(file) || !fs.statSync(file).isFile()) {
|
|
27
|
-
die(`Image not found: ${file}`);
|
|
28
|
-
}
|
|
29
|
-
const mimeType = imageMimeFromPath(file);
|
|
30
|
-
return { data: fs.readFileSync(file).toString('base64'), mimeType };
|
|
31
|
-
}
|
|
32
|
-
/** Parse a `--skill <id>` value (`id` or `id@version`) into a SkillRef. */
|
|
33
|
-
function parseSkillRef(raw) {
|
|
34
|
-
const at = raw.lastIndexOf('@');
|
|
35
|
-
if (at > 0) {
|
|
36
|
-
return { id: raw.slice(0, at), version: raw.slice(at + 1) };
|
|
37
|
-
}
|
|
38
|
-
return { id: raw };
|
|
39
|
-
}
|
|
40
|
-
/** Print an error message to stderr and exit. */
|
|
41
10
|
/** Return a chalk color function appropriate for the given task status. */
|
|
42
11
|
export function statusColor(status) {
|
|
43
12
|
switch (status) {
|
|
@@ -52,47 +21,6 @@ export function statusColor(status) {
|
|
|
52
21
|
default: return chalk.white;
|
|
53
22
|
}
|
|
54
23
|
}
|
|
55
|
-
/**
|
|
56
|
-
* After a `MissingTargetError`, try to resolve the target interactively.
|
|
57
|
-
* Returns the chosen id, or undefined when no interactive resolution is
|
|
58
|
-
* possible (non-TTY/JSON, provider can't enumerate, or user cancels) — the
|
|
59
|
-
* caller then prints the error's guidance.
|
|
60
|
-
*
|
|
61
|
-
* Codex has no `listTargets` (no list-environments CLI), so it always returns
|
|
62
|
-
* undefined here and the user sees the `codex cloud` guidance. Factory lists
|
|
63
|
-
* Droid Computers; if listing fails (not signed in) or parses to nothing, we
|
|
64
|
-
* fall back to a free-text prompt so a dispatch is never hard-blocked.
|
|
65
|
-
*/
|
|
66
|
-
async function pickMissingTarget(provider, err, json) {
|
|
67
|
-
if (json || !process.stdout.isTTY)
|
|
68
|
-
return undefined;
|
|
69
|
-
if (!provider.listTargets)
|
|
70
|
-
return undefined;
|
|
71
|
-
const { select, input } = await import('@inquirer/prompts');
|
|
72
|
-
const promptName = err.kind === 'env' ? 'environment' : 'computer';
|
|
73
|
-
let targets;
|
|
74
|
-
try {
|
|
75
|
-
targets = await provider.listTargets();
|
|
76
|
-
}
|
|
77
|
-
catch (listErr) {
|
|
78
|
-
process.stderr.write(chalk.dim(`Could not list ${promptName}s: ${listErr.message}\n`));
|
|
79
|
-
targets = [];
|
|
80
|
-
}
|
|
81
|
-
try {
|
|
82
|
-
if (targets.length > 0) {
|
|
83
|
-
return await select({
|
|
84
|
-
message: `Select a ${promptName}`,
|
|
85
|
-
choices: targets.map((t) => ({ value: t.id, name: t.label ? `${t.id} ${chalk.dim(t.label)}` : t.id })),
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
const typed = (await input({ message: `No ${promptName}s found. Enter a ${promptName} name (blank to cancel):` })).trim();
|
|
89
|
-
return typed || undefined;
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
92
|
-
// User hit Ctrl-C / Esc on the prompt.
|
|
93
|
-
return undefined;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
24
|
/** Register the `agents cloud` command tree (run, list, status, logs, cancel, message, providers). */
|
|
97
25
|
export function registerCloudCommands(program) {
|
|
98
26
|
const cloud = program
|
|
@@ -201,23 +129,16 @@ Examples:
|
|
|
201
129
|
|
|
202
130
|
# Default provider (set in ~/.agents/agents.yaml)
|
|
203
131
|
agents cloud run "refactor auth module" --repo user/repo
|
|
132
|
+
|
|
133
|
+
# Same dispatch as a run placement: agents run <agent> "<task>" --cloud
|
|
204
134
|
`)
|
|
205
135
|
.action(async (positionalPrompt, options) => {
|
|
206
136
|
const json = isJsonMode(options);
|
|
207
137
|
// Resolve prompt: --prompt flag, positional arg, or file
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (fs.existsSync(prompt) && fs.statSync(prompt).isFile()) {
|
|
213
|
-
const filePath = prompt;
|
|
214
|
-
const stat = fs.statSync(filePath);
|
|
215
|
-
const sizeKB = (stat.size / 1024).toFixed(1);
|
|
216
|
-
prompt = fs.readFileSync(filePath, 'utf-8').trim();
|
|
217
|
-
if (process.stderr.isTTY) {
|
|
218
|
-
process.stderr.write(chalk.dim(`Reading prompt from ${filePath} (${sizeKB} KB)\n`));
|
|
219
|
-
}
|
|
220
|
-
}
|
|
138
|
+
const prompt = resolveCloudPrompt(options.prompt || positionalPrompt, {
|
|
139
|
+
json,
|
|
140
|
+
hint: 'agents cloud run "<task>" --repo <owner/repo>',
|
|
141
|
+
});
|
|
221
142
|
// --host names one of YOUR machines as the target — that only means
|
|
222
143
|
// something to the host provider, so it implies --provider host rather
|
|
223
144
|
// than silently riding along to a cloud backend that would ignore it.
|
|
@@ -247,9 +168,6 @@ Examples:
|
|
|
247
168
|
model: options.model,
|
|
248
169
|
providerOptions: {},
|
|
249
170
|
};
|
|
250
|
-
const shareEnv = shareRuntimeEnv();
|
|
251
|
-
if (shareEnv)
|
|
252
|
-
dispatchOptions.env = shareEnv;
|
|
253
171
|
if (options.env)
|
|
254
172
|
dispatchOptions.providerOptions.env = options.env;
|
|
255
173
|
if (options.computer)
|
|
@@ -329,101 +247,17 @@ Examples:
|
|
|
329
247
|
}
|
|
330
248
|
return;
|
|
331
249
|
}
|
|
332
|
-
//
|
|
333
|
-
//
|
|
334
|
-
//
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
}
|
|
344
|
-
dispatchOptions.images = imagePaths.map(readImageAttachment);
|
|
345
|
-
}
|
|
346
|
-
if (skillIds.length > 0) {
|
|
347
|
-
if (!caps.skills)
|
|
348
|
-
die(`${provider.name} does not support ride-along skills.`, 1, { json });
|
|
349
|
-
dispatchOptions.skills = skillIds.map(parseSkillRef);
|
|
350
|
-
}
|
|
351
|
-
// Dispatch. On a missing pre-provisioned target (Codex env / Factory
|
|
352
|
-
// computer), offer an interactive picker instead of a raw error.
|
|
353
|
-
const dispatchOnce = async () => {
|
|
354
|
-
const spinner = ora({ text: `Dispatching to ${provider.name}...`, stream: process.stderr }).start();
|
|
355
|
-
try {
|
|
356
|
-
const t = await provider.dispatch(dispatchOptions);
|
|
357
|
-
spinner.succeed(`Task ${t.id} dispatched to ${provider.name}`);
|
|
358
|
-
return t;
|
|
359
|
-
}
|
|
360
|
-
catch (err) {
|
|
361
|
-
spinner.fail('Dispatch failed');
|
|
362
|
-
throw err;
|
|
363
|
-
}
|
|
364
|
-
};
|
|
365
|
-
let task;
|
|
366
|
-
try {
|
|
367
|
-
task = await dispatchOnce();
|
|
368
|
-
}
|
|
369
|
-
catch (err) {
|
|
370
|
-
if (err instanceof MissingTargetError) {
|
|
371
|
-
const picked = await pickMissingTarget(provider, err, json);
|
|
372
|
-
if (!picked) {
|
|
373
|
-
die(err.guidance ? `${err.message}\n\n${err.guidance}` : err.message, 1, { json });
|
|
374
|
-
}
|
|
375
|
-
dispatchOptions.providerOptions[err.kind] = picked;
|
|
376
|
-
try {
|
|
377
|
-
task = await dispatchOnce();
|
|
378
|
-
}
|
|
379
|
-
catch (err2) {
|
|
380
|
-
die(err2.message, 1, { json });
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
else {
|
|
384
|
-
die(err.message, 1, { json });
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
// Persist locally
|
|
388
|
-
insertTask(task);
|
|
389
|
-
emit('cloud.dispatch', { module: 'cloud', taskId: task.id, agent: task.agent, provider: task.provider, status: task.status });
|
|
390
|
-
if (json) {
|
|
391
|
-
process.stdout.write(JSON.stringify(task) + '\n');
|
|
392
|
-
}
|
|
393
|
-
// Stream output unless --no-follow
|
|
394
|
-
if (options.follow === false)
|
|
395
|
-
return;
|
|
396
|
-
try {
|
|
397
|
-
// Live budget kill-switch (issue #399). Reuses makeLiveSpendWatcher to
|
|
398
|
-
// feed the provider's `usage` events into a shared watcher; on a cap
|
|
399
|
-
// breach we call provider.cancel(task.id) mid-stream. Dormant (returns
|
|
400
|
-
// null) when no caps are configured, so the raw stream flows unchanged.
|
|
401
|
-
const { wrapStreamWithBudgetGate } = await import('../lib/budget/live-cloud.js');
|
|
402
|
-
const gated = wrapStreamWithBudgetGate({
|
|
403
|
-
provider,
|
|
404
|
-
taskId: task.id,
|
|
405
|
-
project: task.repo ?? task.repos?.[0] ?? process.cwd(),
|
|
406
|
-
agent: task.agent ?? 'cloud',
|
|
407
|
-
cwd: process.cwd(),
|
|
408
|
-
});
|
|
409
|
-
const eventSource = gated ? gated.wrap(provider.stream(task.id)) : provider.stream(task.id);
|
|
410
|
-
const result = await renderStream(eventSource, { json });
|
|
411
|
-
updateTaskStatus(task.id, result.status, {
|
|
412
|
-
summary: result.summary,
|
|
413
|
-
prUrl: result.prUrl,
|
|
414
|
-
});
|
|
415
|
-
emit('cloud.complete', { module: 'cloud', taskId: task.id, status: result.status, prUrl: result.prUrl });
|
|
416
|
-
if (gated?.gate.breached()) {
|
|
417
|
-
const b = gated.gate.breach();
|
|
418
|
-
process.stderr.write(`[budget] cap ${b?.cap} exceeded — cancelled cloud task ${task.id}\n`);
|
|
419
|
-
process.exitCode = 7; // Mirrors BUDGET_KILL_EXIT_CODE for CI/headless.
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
catch (err) {
|
|
423
|
-
// Stream disconnect is OK — task keeps running
|
|
424
|
-
process.stderr.write(chalk.dim(`\nStream disconnected. Task ${task.id} continues running.\n`));
|
|
425
|
-
process.stderr.write(chalk.dim(`Check status: agents cloud status ${task.id}\n`));
|
|
426
|
-
}
|
|
250
|
+
// One dispatch path for every cloud surface — lib/cloud/dispatch.ts owns
|
|
251
|
+
// capability checks, the missing-target picker, persistence, streaming,
|
|
252
|
+
// and the budget kill-switch.
|
|
253
|
+
await executeCloudDispatch({
|
|
254
|
+
provider,
|
|
255
|
+
dispatchOptions,
|
|
256
|
+
imagePaths: Array.isArray(options.image) ? options.image : [],
|
|
257
|
+
skillIds: Array.isArray(options.skill) ? options.skill : [],
|
|
258
|
+
follow: options.follow !== false,
|
|
259
|
+
json,
|
|
260
|
+
});
|
|
427
261
|
});
|
|
428
262
|
// ── agents cloud list ─────────────────────────────────────────────────
|
|
429
263
|
cloud
|
package/dist/commands/doctor.js
CHANGED
|
@@ -18,7 +18,8 @@ import { findAmbiguousDevicePins } from '../lib/routines.js';
|
|
|
18
18
|
import chalk from 'chalk';
|
|
19
19
|
import { checkAllClis, collectTeamsDoctorData } from '../lib/teams/agents.js';
|
|
20
20
|
import { AGENTS, ALL_AGENT_IDS, resolveAgentName, formatAgentError, getAccountInfo } from '../lib/agents.js';
|
|
21
|
-
import {
|
|
21
|
+
import { getVersionHomePath, listInstalledVersions, } from '../lib/versions.js';
|
|
22
|
+
import { resolveAgentTargets, AgentSpecError } from '../lib/agent-spec/index.js';
|
|
22
23
|
import { loadManifest, isStale } from '../lib/staleness/index.js';
|
|
23
24
|
import { diffVersionResources, DOCTOR_ALL_KINDS, } from '../lib/doctor-diff.js';
|
|
24
25
|
import { checkVersionHookWiring, inspectDuplicateVersionHooks, registerHooksToSettings } from '../lib/hooks.js';
|
|
@@ -370,29 +371,30 @@ async function runDevicesDoctor(opts) {
|
|
|
370
371
|
function parseTargetArg(arg) {
|
|
371
372
|
const at = arg.indexOf('@');
|
|
372
373
|
const agentPart = at === -1 ? arg : arg.slice(0, at);
|
|
373
|
-
const
|
|
374
|
+
const qualifier = at === -1 ? '' : arg.slice(at + 1);
|
|
374
375
|
const agent = resolveAgentName(agentPart);
|
|
375
376
|
if (!agent)
|
|
376
377
|
return { error: formatAgentError(agentPart) };
|
|
377
|
-
|
|
378
|
+
// No qualifier → non-isolated sweep of every installed version; --fix uses the non-isolated path
|
|
379
|
+
if (!qualifier) {
|
|
378
380
|
const versions = listInstalledVersions(agent);
|
|
379
381
|
if (versions.length === 0)
|
|
380
382
|
return { error: `${AGENTS[agent].name} has no installed versions. Run \`agents add ${agent}@<version>\` first.` };
|
|
381
383
|
return { agent, versions, versionExplicit: false };
|
|
382
384
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
385
|
+
// All explicit qualifiers (@all, @latest, @oldest, @default, @pinned, @x.y.z) → shared resolver
|
|
386
|
+
try {
|
|
387
|
+
const targets = resolveAgentTargets(`${agent}@${qualifier}`, { availableAgents: [agent] });
|
|
388
|
+
const versions = targets.map((t) => t.version).filter((v) => v !== null);
|
|
389
|
+
if (versions.length === 0)
|
|
390
|
+
return { error: `${AGENTS[agent].name} has no installed versions. Run \`agents add ${agent}@<version>\` first.` };
|
|
391
|
+
return { agent, versions, versionExplicit: true };
|
|
388
392
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
return { error: `${AGENTS[agent].name}@${versionPart} is not installed. Installed: ${listInstalledVersions(agent).join(', ') || '(none)'}` };
|
|
393
|
+
catch (e) {
|
|
394
|
+
if (e instanceof AgentSpecError)
|
|
395
|
+
return { error: e.message };
|
|
396
|
+
throw e;
|
|
394
397
|
}
|
|
395
|
-
return { agent, versions: [versionPart], versionExplicit: true };
|
|
396
398
|
}
|
|
397
399
|
function parseKindFilter(arg) {
|
|
398
400
|
if (!arg)
|