@phnx-labs/agents-cli 1.20.48 → 1.20.50
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 +12 -0
- package/README.md +3 -0
- package/dist/commands/doctor.js +133 -2
- package/dist/commands/exec.js +27 -17
- package/dist/commands/teams.js +217 -70
- package/dist/lib/cloud/rush.js +5 -1
- package/dist/lib/exec.d.ts +9 -5
- package/dist/lib/exec.js +29 -10
- package/dist/lib/hosts/passthrough.js +30 -1
- package/dist/lib/hosts/progress.d.ts +31 -0
- package/dist/lib/hosts/progress.js +35 -0
- package/dist/lib/hosts/remote-cmd.d.ts +1 -1
- package/dist/lib/hosts/remote-cmd.js +13 -3
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/shims.d.ts +17 -0
- package/dist/lib/shims.js +29 -0
- package/dist/lib/teams/agents.d.ts +108 -1
- package/dist/lib/teams/agents.js +511 -11
- package/dist/lib/teams/api.d.ts +7 -1
- package/dist/lib/teams/api.js +5 -2
- package/dist/lib/teams/registry.d.ts +17 -0
- package/dist/lib/teams/registry.js +2 -0
- package/dist/lib/teams/remoteWorktree.d.ts +57 -0
- package/dist/lib/teams/remoteWorktree.js +213 -0
- package/dist/lib/teams/scheduler.d.ts +29 -0
- package/dist/lib/teams/scheduler.js +78 -0
- package/dist/lib/teams/supervisor.js +7 -0
- package/dist/lib/versions.d.ts +7 -0
- package/dist/lib/versions.js +42 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 1.20.50
|
|
6
|
+
|
|
7
|
+
- **Distributed agent teams: teammates can now run on different machines across your fleet, not just the box running `teams start`.** A single team can place the backend teammate on a Linux box and the UI teammate on a Mac while one orchestrator still drives the DAG, polls status, and cleans up. One vocabulary, all optional (omit it and teams stay 100% local as before): `teams create --devices a,b,c` (alias `--hosts`) declares a pool the team may auto-schedule onto, `--repo <url|path>` (defaults to the local checkout's `origin`) says how each device gets the code, and `teams add --device X` (alias `--host`) pins one teammate to a host — which needs **no** pool, so "send just one teammate elsewhere" is zero-setup. Placement resolves top-down at launch: explicit `--device` pin → single-device pool (whole team there) → multi-device pool (least-loaded auto-schedule) → local. Remote teammates dispatch over SSH via the existing `agents devices`/host machinery (a third teammate backend beside local and cloud), are monitored by offset-tailing the remote log + `.exit` sentinel, and get the repo auto-provisioned per device (reuse an existing checkout, else clone into `~/.agents/repos/<team>`) with an optional per-teammate git worktree on the host. `teams status`/`teams logs` show each teammate's host and stream its output back with the local mirror capped (~512KB rolling tail) so a 10+-teammate fleet can't blow up the orchestrator. POSIX hosts only in v1 (Windows rejected with a clear message). Source: `apps/cli/src/lib/teams/{scheduler,remoteWorktree,agents,api,supervisor,registry}.ts`, `apps/cli/src/lib/hosts/{progress,passthrough}.ts`, `apps/cli/src/commands/teams.ts`, `apps/cli/docs/teams.md`.
|
|
8
|
+
- **NEW: `agents doctor --devices` shows a cross-device agent-readiness matrix.** `agents doctor` could already run on one remote machine via `--host`, but checking the whole fleet meant running the command once per box. `--devices` fans out `agents teams doctor --json` to every registered device (plus the local machine), renders a device × agent matrix, and emits a stable JSON contract with `--json`. `--device <name>` or `--host <name>` scopes the same matrix to a single machine. The remote probe now bootstraps `PATH` with the canonical shim directories before running, so login shells that haven't sourced interactive rc files no longer report false "not installed" negatives. Source: `apps/cli/src/commands/doctor.ts`, `apps/cli/src/lib/teams/agents.ts`, `apps/cli/src/lib/hosts/{passthrough,remote-cmd}.ts`.
|
|
9
|
+
- **`agents run codex` / `agents teams` now honor your configured Codex model instead of silently defaulting to `gpt-5.3-codex`.** Codex runs under a per-version `CODEX_HOME`, and your `model` preference (`~/.codex/config.toml`) lives only in the version-home that was active when you set it. A dispatch pinned to a different version read a home with no top-level `model`, so Codex fell back to its built-in default — which a ChatGPT-tier account isn't entitled to use, so the run died with `400: The 'gpt-5.3-codex' model is not supported when using Codex with a ChatGPT account` before doing any work, even though `ag view` reported Codex "signed in". When no explicit `--model` is passed, the model is now defaulted (for Codex) to the top-level `model` in your active `~/.codex/config.toml` and forwarded via `--model`; it's read-only (no file writes), so fanning out many parallel runs to one version-home can't race. Verified live on a box where Codex was 100% unusable: the request model changed `gpt-5.3-codex` → `gpt-5.5` and codex@0.142.0 returned successfully. Source: `apps/cli/src/lib/exec.ts`, `apps/cli/src/lib/shims.ts` (`readCodexConfiguredModel`).
|
|
10
|
+
- **Fix: `agents add claude@<version>` now produces a runnable install — it no longer ships a half-built binary that dies with "claude native binary not installed."** `installVersion` runs `npm install --ignore-scripts` (the right posture for the dependency *tree* — never run arbitrary transitive postinstalls), but that also skipped the agent package's OWN postinstall, which for `@anthropic-ai/claude-code` is a required step: the package ships a ~500-byte stub at `bin/claude.exe` plus per-arch native binaries as optional deps, and its `postinstall` (`node install.cjs`) is what copies the correct ~231 MB native binary over the stub. Skipped, every launch died with `Error: claude native binary not installed`. The existing launch-health self-heal (#764, and its Windows/daemon extension) couldn't save it on two counts: the stub reports its breakage *politely* rather than with a raw `ENOENT`, so the probe's missing-binary signature didn't match and the gutted install read as healthy; and the repair path (`ensureAgentRunnable` → clean reinstall) re-ran the same `--ignore-scripts` install, so it never copied the binary either. `installVersion` now runs the **first-party** package's declared `postinstall` after the npm install (scoped to that one package — never the dependency tree, never claude-code's `exit 1` `prepare` guard), best-effort, before the integrity gate. Because `installVersion` is the single choke point for `agents add`, config refresh, run-time heal, and the daemon's proactive heal, this also revives the repair path for the whole class. `isMissingBinarySignature` was additionally widened to recognize the stub's polite phrases (`native binary not installed`, `postinstall did not run`, `optional dependency was not downloaded`) so the self-heal catches this failure mode if a postinstall ever silently no-ops. Verified end-to-end on linux-arm64: `installVersion('claude','2.1.186')` into a clean HOME runs the postinstall automatically, lands the 231,782,112-byte binary (not the stub), and `claude.exe --version` returns `2.1.186 (Claude Code)` — with no manual `install.cjs` step. Source: `apps/cli/src/lib/versions.ts` (`installVersion`, `isMissingBinarySignature`).
|
|
11
|
+
|
|
12
|
+
## 1.20.49
|
|
13
|
+
|
|
14
|
+
- **`agents run --mode plan` no longer hard-fails on agents without a read-only mode (antigravity, cursor, kiro, …).** Those agents have no plan flag, so an explicit or default `--mode plan` used to abort with `does not support 'plan' mode` — breaking multi-agent scripts that pass a uniform plan flag, and diverging from `agents teams add` (default mode `edit`). `resolveMode` now degrades unsupported `plan` to the agent's safest native mode (`capabilities.modes[0]`, typically `edit`), matching the existing `auto` → `edit` degrade. The CLI prints a yellow warning when the user explicitly asked for plan (gray for the implicit default) so the elevation is never silent. `skip` still hard-fails when unsupported. Source: `apps/cli/src/lib/exec.ts`, `apps/cli/src/commands/exec.ts`.
|
|
15
|
+
- **`agents cloud cancel` now actually cancels paused runs.** `RushProvider.cancel()` issued `DELETE /api/v1/cloud-runs/{id}`, which the backend doesn't implement — it 404s — so `agents cloud cancel` (and the Factory Floor's cancel affordance) silently failed on any run that wasn't actively running: `queued`, `needs_review`, and `input_required` runs stayed stuck (e.g. a 14-day-old input-required run lingering in the Floor's "NEEDS YOU" bucket forever). Switched to the cancel action endpoint `POST /api/v1/cloud-runs/{id}/cancel`, which the backend implements and which cancels paused runs too. Verified live against `api.prix.dev` (the POST returned `{"ok":true,"status":"cancelled"}` and the stuck run transitioned `needs_review` → `cancelled`). Source: `apps/cli/src/lib/cloud/rush.ts`.
|
|
16
|
+
|
|
5
17
|
## 1.20.48
|
|
6
18
|
|
|
7
19
|
- **Menu-bar helper: a RECENT TICKETS section shows the issues you filed via the quick-issue bar, each clickable to open in Linear.** The completion notification is transient, so the tickets the `Cmd-Shift-O` bar creates now also persist to a small local ledger (`~/.agents/.history/menubar/recent-tickets.json`, newest-first, deduped by id, capped at 10) that the menu-bar dropdown surfaces below RECENT sessions — click a row to open the ticket. The dispatch records the id + note + Linear URL on a successful create; the section renders nothing when the ledger is empty. Source: `apps/cli/menubar/Sources/MenubarHelper/{RecentTickets,StatusItemController,AgentsCLI,IssueSelfTest}.swift`.
|
package/README.md
CHANGED
|
@@ -307,6 +307,9 @@ agents logs <id> --full # the full raw transcript / stdout (toke
|
|
|
307
307
|
agents logs <id> -f # re-attach to a running one and follow
|
|
308
308
|
agents view claude --host gpu-box # inspect the remote install
|
|
309
309
|
agents sync --host gpu-box # make the remote machine current
|
|
310
|
+
agents doctor --devices # readiness matrix for every registered device
|
|
311
|
+
agents doctor --devices --json # machine-readable fleet readiness
|
|
312
|
+
agents doctor --device mac-mini # same matrix, scoped to one device
|
|
310
313
|
|
|
311
314
|
# Your Tailscale fleet, auto-discovered
|
|
312
315
|
agents devices sync # ingest `tailscale status`
|
package/dist/commands/doctor.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { addHostOption } from '../lib/hosts/option.js';
|
|
2
|
+
import { buildRemoteAgentsInvocation } from '../lib/hosts/remote-cmd.js';
|
|
3
|
+
import { loadDevices } from '../lib/devices/registry.js';
|
|
4
|
+
import { resolveHost } from '../lib/hosts/registry.js';
|
|
5
|
+
import { sshExec } from '../lib/ssh-exec.js';
|
|
6
|
+
import { sshTargetFor } from '../lib/hosts/types.js';
|
|
7
|
+
import { machineId } from '../lib/session/sync/config.js';
|
|
2
8
|
import chalk from 'chalk';
|
|
3
|
-
import { checkAllClis } from '../lib/teams/agents.js';
|
|
9
|
+
import { checkAllClis, collectTeamsDoctorData } from '../lib/teams/agents.js';
|
|
4
10
|
import { AGENTS, ALL_AGENT_IDS, resolveAgentName, formatAgentError } from '../lib/agents.js';
|
|
5
11
|
import { getGlobalDefault, getVersionHomePath, isVersionInstalled, listInstalledVersions, parseAgentSpec, } from '../lib/versions.js';
|
|
6
12
|
import { loadManifest, isStale } from '../lib/staleness/index.js';
|
|
@@ -203,6 +209,121 @@ function renderExecPolicyAdvisory() {
|
|
|
203
209
|
console.log(chalk.gray(` ${line}`));
|
|
204
210
|
}
|
|
205
211
|
}
|
|
212
|
+
const AGENT_ORDER = ['claude', 'codex', 'kimi', 'grok', 'antigravity', 'opencode', 'cursor', 'gemini', 'droid'];
|
|
213
|
+
function shortAgentHeader(name) {
|
|
214
|
+
return name.slice(0, 4).padEnd(4);
|
|
215
|
+
}
|
|
216
|
+
function agentCell(entry) {
|
|
217
|
+
if (!entry)
|
|
218
|
+
return chalk.gray('- ');
|
|
219
|
+
if (entry.installed) {
|
|
220
|
+
const signedInHint = entry.signedIn ? '*' : ' ';
|
|
221
|
+
return chalk.green(`rdy${signedInHint}`.padEnd(4));
|
|
222
|
+
}
|
|
223
|
+
if (entry.error)
|
|
224
|
+
return chalk.red('err '.padEnd(4));
|
|
225
|
+
return chalk.gray('no '.padEnd(4));
|
|
226
|
+
}
|
|
227
|
+
async function resolveFleetTargets(opts) {
|
|
228
|
+
const singleName = opts.host || opts.device;
|
|
229
|
+
if (singleName) {
|
|
230
|
+
// --device / --host as a single-device filter: resolve through the device
|
|
231
|
+
// registry first, then the general host registry, then ad-hoc user@host.
|
|
232
|
+
const registry = await loadDevices();
|
|
233
|
+
const deviceProfile = registry[singleName];
|
|
234
|
+
if (deviceProfile) {
|
|
235
|
+
return [{
|
|
236
|
+
name: deviceProfile.name,
|
|
237
|
+
sshTarget: deviceProfile.name,
|
|
238
|
+
os: deviceProfile.platform !== 'unknown' ? deviceProfile.platform : undefined,
|
|
239
|
+
}];
|
|
240
|
+
}
|
|
241
|
+
const host = await resolveHost(singleName);
|
|
242
|
+
if (host) {
|
|
243
|
+
return [{ name: singleName, sshTarget: sshTargetFor(host), os: host.os }];
|
|
244
|
+
}
|
|
245
|
+
console.error(chalk.red(`Unknown host or device '${singleName}'.`));
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
const registry = await loadDevices();
|
|
249
|
+
const localName = machineId();
|
|
250
|
+
return Object.values(registry)
|
|
251
|
+
.filter((d) => d.name.toLowerCase() !== localName)
|
|
252
|
+
.map((d) => ({
|
|
253
|
+
name: d.name,
|
|
254
|
+
sshTarget: d.name,
|
|
255
|
+
os: d.platform !== 'unknown' ? d.platform : undefined,
|
|
256
|
+
}));
|
|
257
|
+
}
|
|
258
|
+
async function probeFleetTarget(target) {
|
|
259
|
+
const forwarded = ['teams', 'doctor', '--json'];
|
|
260
|
+
const isWin = /^win/i.test((target.os ?? '').trim());
|
|
261
|
+
const remoteCmd = buildRemoteAgentsInvocation(forwarded, undefined, isWin ? 'windows' : undefined,
|
|
262
|
+
// POSIX login shells often lack the shims dir; Windows PowerShell usually
|
|
263
|
+
// has it via the install profile, and our single-quote escaping would
|
|
264
|
+
// prevent $HOME expansion there, so skip the bootstrap on Windows.
|
|
265
|
+
isWin ? undefined : { PATH: '$HOME/.agents/.cache/shims:$HOME/.local/bin:$PATH' });
|
|
266
|
+
const res = sshExec(target.sshTarget, remoteCmd, { timeoutMs: 30000, multiplex: true });
|
|
267
|
+
if (res.code !== 0) {
|
|
268
|
+
return {
|
|
269
|
+
name: target.name,
|
|
270
|
+
online: false,
|
|
271
|
+
error: res.timedOut ? 'timed out' : (res.stderr || `exit ${res.code ?? 'unknown'}`),
|
|
272
|
+
agents: {},
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
try {
|
|
276
|
+
const agents = JSON.parse(res.stdout);
|
|
277
|
+
return { name: target.name, online: true, agents };
|
|
278
|
+
}
|
|
279
|
+
catch (err) {
|
|
280
|
+
const stderrHint = res.stderr ? ` stderr: ${res.stderr.trim()}` : '';
|
|
281
|
+
return {
|
|
282
|
+
name: target.name,
|
|
283
|
+
online: true,
|
|
284
|
+
error: `invalid JSON (${err?.message ?? 'parse error'})${stderrHint}`,
|
|
285
|
+
agents: {},
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
async function runDevicesDoctor(opts) {
|
|
290
|
+
const singleName = opts.host || opts.device;
|
|
291
|
+
const targets = await resolveFleetTargets(opts);
|
|
292
|
+
const localName = machineId();
|
|
293
|
+
const results = [];
|
|
294
|
+
// Local machine first, directly.
|
|
295
|
+
if (!singleName) {
|
|
296
|
+
results.push({ name: localName, online: true, agents: await collectTeamsDoctorData() });
|
|
297
|
+
}
|
|
298
|
+
// Remote targets in parallel.
|
|
299
|
+
const remoteResults = await Promise.all(targets.map(probeFleetTarget));
|
|
300
|
+
results.push(...remoteResults);
|
|
301
|
+
if (opts.json) {
|
|
302
|
+
console.log(JSON.stringify({ devices: results }, null, 2));
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (results.length === 0) {
|
|
306
|
+
console.log(chalk.gray('No registered devices. Run `agents devices` to register some.'));
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const agentsToShow = AGENT_ORDER.filter((a) => results.some((r) => r.agents[a] !== undefined));
|
|
310
|
+
console.log(chalk.bold('Agent readiness by device'));
|
|
311
|
+
if (agentsToShow.length === 0) {
|
|
312
|
+
console.log(chalk.gray(' (no agent data collected)'));
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const nameWidth = Math.max(...results.map((r) => r.name.length));
|
|
316
|
+
const header = ` ${'Device'.padEnd(nameWidth)} ${agentsToShow.map(shortAgentHeader).join(' ')}`;
|
|
317
|
+
console.log(chalk.gray(header));
|
|
318
|
+
for (const row of results) {
|
|
319
|
+
const status = row.online ? chalk.green('online ') : chalk.red('offline');
|
|
320
|
+
const errorSuffix = row.error ? ` ${chalk.gray(row.error)}` : '';
|
|
321
|
+
const cells = agentsToShow.map((a) => agentCell(row.agents[a])).join(' ');
|
|
322
|
+
console.log(` ${row.name.padEnd(nameWidth)} ${status} ${cells}${errorSuffix}`);
|
|
323
|
+
}
|
|
324
|
+
console.log();
|
|
325
|
+
console.log(chalk.gray(' rdy* = installed and signed in · rdy = installed · no = not installed · err = probe failed · - = offline'));
|
|
326
|
+
}
|
|
206
327
|
function parseTargetArg(arg) {
|
|
207
328
|
const at = arg.indexOf('@');
|
|
208
329
|
const agentPart = at === -1 ? arg : arg.slice(0, at);
|
|
@@ -484,7 +605,9 @@ export function registerDoctorCommand(program) {
|
|
|
484
605
|
.option('--kind <kinds>', 'Restrict to comma-separated resource kinds (commands,skills,hooks,rules,mcp,permissions,subagents,plugins,promptcuts)')
|
|
485
606
|
.option('--cwd <path>', 'Resolution cwd for project layer detection (default: process.cwd())')
|
|
486
607
|
.option('--adopt <agent>', "Take over the agent's native launcher that shadows the shim (symlink it to the version-managed shim; reversible with --release)")
|
|
487
|
-
.option('--release <agent>', 'Undo --adopt: restore the native launcher agents-cli previously adopted')
|
|
608
|
+
.option('--release <agent>', 'Undo --adopt: restore the native launcher agents-cli previously adopted')
|
|
609
|
+
.option('--devices', 'Check agent readiness on every registered device (alias --hosts)')
|
|
610
|
+
.option('--hosts', 'Alias of --devices');
|
|
488
611
|
setHelpSections(doctorCmd, {
|
|
489
612
|
examples: `
|
|
490
613
|
# Overview: CLI availability + sync status + orphans across all defaults
|
|
@@ -511,6 +634,14 @@ export function registerDoctorCommand(program) {
|
|
|
511
634
|
});
|
|
512
635
|
doctorCmd.action(async (target, opts) => {
|
|
513
636
|
const cwd = opts.cwd ? opts.cwd : process.cwd();
|
|
637
|
+
if (opts.devices || opts.hosts) {
|
|
638
|
+
if (target) {
|
|
639
|
+
console.error(chalk.red('Cannot combine --devices with a target argument.'));
|
|
640
|
+
process.exit(1);
|
|
641
|
+
}
|
|
642
|
+
await runDevicesDoctor(opts);
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
514
645
|
// Launcher adoption escape hatch. `--adopt <agent>` forces the take-over
|
|
515
646
|
// even for a non-default agent; `--release <agent>` reverses it.
|
|
516
647
|
if (opts.adopt || opts.release) {
|
package/dist/commands/exec.js
CHANGED
|
@@ -570,7 +570,7 @@ export function registerRunCommand(program) {
|
|
|
570
570
|
});
|
|
571
571
|
process.exit(resumeExit);
|
|
572
572
|
}
|
|
573
|
-
const [{ buildExecCommand, parseExecEnv, execAgent, runWithFallback, normalizeMode, resolveMode,
|
|
573
|
+
const [{ buildExecCommand, parseExecEnv, execAgent, runWithFallback, normalizeMode, resolveMode, headlessPlanStallCommand, nativeResume, resolveInteractive }, { ALL_AGENT_IDS }, { profileExists, resolveProfileForRun }, { readAndResolveBundleEnv, describeBundle, assertRemoteBundleFlagsUnsupported }, { splitBundleRef, resolveSshTarget, remoteResolveEnv }, { getConfiguredRunStrategy, normalizeRunStrategy, resolveRunVersion, rotationFailoverChain, shouldArmRotationFailover, RUN_STRATEGIES }, { getGlobalDefault, getVersionHomePath, resolveVersion, resolveVersionAlias, ensureAgentRunnable }, { buildDiscoveredPlugin, loadPluginManifest, syncPluginToVersion }, { parseWorkflowFrontmatter, resolveWorkflowRef, resolveAllowedSubagents, pruneStaleWorkflowSubagents }, { resolveRunDefaults }, { getMcpServersByName, buildWorkflowMcpConfig }, { supports },] = await Promise.all([
|
|
574
574
|
import('../lib/exec.js'),
|
|
575
575
|
import('../lib/agents.js'),
|
|
576
576
|
import('../lib/profiles.js'),
|
|
@@ -1017,28 +1017,38 @@ export function registerRunCommand(program) {
|
|
|
1017
1017
|
console.error(chalk.red(`Invalid mode: ${mode}. Use plan, edit, auto, or skip ('full' accepted as alias for skip).`));
|
|
1018
1018
|
process.exit(1);
|
|
1019
1019
|
}
|
|
1020
|
-
//
|
|
1021
|
-
//
|
|
1022
|
-
//
|
|
1023
|
-
//
|
|
1024
|
-
//
|
|
1025
|
-
//
|
|
1026
|
-
//
|
|
1020
|
+
// Default CLI mode is the generic 'plan'. Agents without a read-only
|
|
1021
|
+
// mode (antigravity, cursor, kiro, …) degrade via resolveMode to their
|
|
1022
|
+
// safest native mode (modes[0], typically edit). That covers both the
|
|
1023
|
+
// implicit default and an explicit `--mode plan`, so multi-agent
|
|
1024
|
+
// scripts can pass a uniform plan flag without per-agent branching.
|
|
1025
|
+
// Elevation is never silent: we warn on stderr (yellow when the user
|
|
1026
|
+
// explicitly asked for plan; gray for the implicit default / auto).
|
|
1027
|
+
// `skip` still hard-fails when unsupported — pretending we bypassed
|
|
1028
|
+
// permissions would be unsafe.
|
|
1027
1029
|
const modeIsDefault = modeSource === 'default';
|
|
1030
|
+
const requestedMode = normalizeMode(mode);
|
|
1031
|
+
let resolvedMode;
|
|
1028
1032
|
try {
|
|
1029
|
-
resolveMode(agent,
|
|
1033
|
+
resolvedMode = resolveMode(agent, requestedMode);
|
|
1030
1034
|
}
|
|
1031
1035
|
catch (err) {
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
+
console.error(chalk.red(err.message));
|
|
1037
|
+
process.exit(1);
|
|
1038
|
+
}
|
|
1039
|
+
if (resolvedMode !== requestedMode) {
|
|
1040
|
+
mode = resolvedMode;
|
|
1041
|
+
if (!options.quiet) {
|
|
1042
|
+
if (requestedMode === 'plan' && !modeIsDefault) {
|
|
1043
|
+
process.stderr.write(chalk.yellow(`[agents] ${agent} has no read-only 'plan' mode; using '${mode}' (writable) instead. Pass --mode ${mode} to silence this.\n`));
|
|
1044
|
+
}
|
|
1045
|
+
else {
|
|
1046
|
+
process.stderr.write(chalk.gray(`[agents] ${agent} has no '${requestedMode}' mode; using '${mode}'\n`));
|
|
1036
1047
|
}
|
|
1037
1048
|
}
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
}
|
|
1049
|
+
}
|
|
1050
|
+
else {
|
|
1051
|
+
mode = resolvedMode;
|
|
1042
1052
|
}
|
|
1043
1053
|
// Fail fast on the headless-plan stall footgun: a slash command run
|
|
1044
1054
|
// headless under the implicit default 'plan' mode hangs forever at
|