@phnx-labs/agents-cli 1.20.64 → 1.20.65
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 +39 -3
- package/README.md +37 -2
- package/dist/bin/agents +0 -0
- package/dist/commands/apply.d.ts +12 -0
- package/dist/commands/apply.js +274 -0
- package/dist/commands/browser.js +2 -2
- package/dist/commands/cloud.js +32 -2
- package/dist/commands/doctor.js +4 -1
- package/dist/commands/exec.js +94 -49
- package/dist/commands/feed.js +25 -11
- package/dist/commands/hosts.js +44 -6
- package/dist/commands/mcp.js +55 -5
- package/dist/commands/monitors.d.ts +12 -0
- package/dist/commands/monitors.js +740 -0
- package/dist/commands/output.js +2 -2
- package/dist/commands/routines.js +23 -2
- package/dist/commands/secrets.d.ts +16 -0
- package/dist/commands/secrets.js +215 -64
- package/dist/commands/serve.js +31 -0
- package/dist/commands/sessions-export.js +8 -3
- package/dist/commands/sessions.d.ts +16 -0
- package/dist/commands/sessions.js +36 -6
- package/dist/commands/ssh.js +45 -2
- package/dist/commands/versions.js +7 -3
- package/dist/commands/view.d.ts +26 -0
- package/dist/commands/view.js +32 -9
- package/dist/commands/webhook.js +10 -2
- package/dist/index.js +34 -14
- package/dist/lib/agents.d.ts +18 -0
- package/dist/lib/agents.js +53 -3
- package/dist/lib/auto-dispatch-provider.js +7 -2
- package/dist/lib/auto-dispatch.d.ts +3 -0
- package/dist/lib/auto-dispatch.js +3 -0
- package/dist/lib/browser/chrome.js +2 -2
- package/dist/lib/cloud/antigravity.js +2 -2
- package/dist/lib/cloud/host.d.ts +59 -0
- package/dist/lib/cloud/host.js +224 -0
- package/dist/lib/cloud/registry.js +4 -0
- package/dist/lib/cloud/types.d.ts +6 -4
- package/dist/lib/computer-rpc.js +3 -1
- package/dist/lib/crabbox/cli.js +5 -1
- package/dist/lib/crabbox/runtimes.js +11 -2
- package/dist/lib/daemon.d.ts +20 -4
- package/dist/lib/daemon.js +62 -19
- package/dist/lib/devices/fleet.d.ts +3 -2
- package/dist/lib/devices/fleet.js +9 -0
- package/dist/lib/devices/registry.d.ts +15 -0
- package/dist/lib/devices/registry.js +9 -0
- package/dist/lib/exec.d.ts +19 -2
- package/dist/lib/exec.js +41 -13
- package/dist/lib/fleet/apply.d.ts +63 -0
- package/dist/lib/fleet/apply.js +214 -0
- package/dist/lib/fleet/auth-sync.d.ts +67 -0
- package/dist/lib/fleet/auth-sync.js +142 -0
- package/dist/lib/fleet/manifest.d.ts +29 -0
- package/dist/lib/fleet/manifest.js +127 -0
- package/dist/lib/fleet/types.d.ts +129 -0
- package/dist/lib/fleet/types.js +13 -0
- package/dist/lib/git.d.ts +27 -0
- package/dist/lib/git.js +34 -2
- package/dist/lib/hosts/dispatch.d.ts +29 -8
- package/dist/lib/hosts/dispatch.js +46 -18
- package/dist/lib/hosts/passthrough.js +2 -0
- package/dist/lib/hosts/providers/devices.d.ts +27 -0
- package/dist/lib/hosts/providers/devices.js +98 -0
- package/dist/lib/hosts/registry.d.ts +10 -16
- package/dist/lib/hosts/registry.js +17 -50
- package/dist/lib/hosts/remote-cmd.d.ts +23 -0
- package/dist/lib/hosts/remote-cmd.js +71 -0
- package/dist/lib/hosts/run-target.d.ts +84 -0
- package/dist/lib/hosts/run-target.js +99 -0
- package/dist/lib/hosts/types.d.ts +23 -5
- package/dist/lib/hosts/types.js +22 -4
- package/dist/lib/linear-autoclose.d.ts +30 -0
- package/dist/lib/linear-autoclose.js +22 -0
- package/dist/lib/mcp.d.ts +27 -1
- package/dist/lib/mcp.js +126 -12
- package/dist/lib/monitors/config.d.ts +161 -0
- package/dist/lib/monitors/config.js +372 -0
- package/dist/lib/monitors/dispatch.d.ts +28 -0
- package/dist/lib/monitors/dispatch.js +91 -0
- package/dist/lib/monitors/engine.d.ts +61 -0
- package/dist/lib/monitors/engine.js +201 -0
- package/dist/lib/monitors/sources/command.d.ts +11 -0
- package/dist/lib/monitors/sources/command.js +31 -0
- package/dist/lib/monitors/sources/device.d.ts +13 -0
- package/dist/lib/monitors/sources/device.js +35 -0
- package/dist/lib/monitors/sources/file.d.ts +14 -0
- package/dist/lib/monitors/sources/file.js +57 -0
- package/dist/lib/monitors/sources/http.d.ts +10 -0
- package/dist/lib/monitors/sources/http.js +34 -0
- package/dist/lib/monitors/sources/index.d.ts +14 -0
- package/dist/lib/monitors/sources/index.js +31 -0
- package/dist/lib/monitors/sources/poll.d.ts +9 -0
- package/dist/lib/monitors/sources/poll.js +9 -0
- package/dist/lib/monitors/sources/types.d.ts +18 -0
- package/dist/lib/monitors/sources/types.js +9 -0
- package/dist/lib/monitors/sources/webhook.d.ts +23 -0
- package/dist/lib/monitors/sources/webhook.js +47 -0
- package/dist/lib/monitors/sources/ws.d.ts +14 -0
- package/dist/lib/monitors/sources/ws.js +45 -0
- package/dist/lib/monitors/state.d.ts +69 -0
- package/dist/lib/monitors/state.js +144 -0
- package/dist/lib/platform/exec.d.ts +16 -0
- package/dist/lib/platform/exec.js +17 -0
- package/dist/lib/plugins.js +101 -2
- package/dist/lib/redact.d.ts +14 -1
- package/dist/lib/redact.js +47 -1
- package/dist/lib/remote-agents-json.js +7 -1
- package/dist/lib/rotate.d.ts +6 -3
- package/dist/lib/rotate.js +0 -1
- package/dist/lib/routines.d.ts +16 -0
- package/dist/lib/routines.js +19 -0
- package/dist/lib/runner.d.ts +1 -0
- package/dist/lib/runner.js +102 -9
- package/dist/lib/secrets/agent.d.ts +48 -10
- package/dist/lib/secrets/agent.js +123 -15
- package/dist/lib/secrets/bundles.d.ts +26 -0
- package/dist/lib/secrets/bundles.js +59 -8
- package/dist/lib/secrets/mcp.js +4 -2
- package/dist/lib/secrets/remote.d.ts +17 -0
- package/dist/lib/secrets/remote.js +40 -0
- package/dist/lib/self-update.d.ts +20 -0
- package/dist/lib/self-update.js +54 -1
- package/dist/lib/serve/control.d.ts +95 -0
- package/dist/lib/serve/control.js +260 -0
- package/dist/lib/serve/server.d.ts +35 -1
- package/dist/lib/serve/server.js +106 -76
- package/dist/lib/serve/stream.d.ts +43 -0
- package/dist/lib/serve/stream.js +116 -0
- package/dist/lib/serve/token.d.ts +35 -0
- package/dist/lib/serve/token.js +85 -0
- package/dist/lib/session/bundle.d.ts +14 -0
- package/dist/lib/session/bundle.js +12 -1
- package/dist/lib/session/remote-list.js +5 -1
- package/dist/lib/session/state.d.ts +7 -25
- package/dist/lib/session/state.js +16 -6
- package/dist/lib/session/sync/config.js +8 -2
- package/dist/lib/session/types.d.ts +30 -0
- package/dist/lib/ssh-tunnel.d.ts +19 -1
- package/dist/lib/ssh-tunnel.js +86 -7
- package/dist/lib/startup/command-registry.d.ts +2 -0
- package/dist/lib/startup/command-registry.js +4 -0
- package/dist/lib/state.d.ts +5 -0
- package/dist/lib/state.js +12 -0
- package/dist/lib/tmux/session.d.ts +7 -0
- package/dist/lib/tmux/session.js +3 -1
- package/dist/lib/triggers/webhook.d.ts +18 -0
- package/dist/lib/triggers/webhook.js +105 -0
- package/dist/lib/types.d.ts +26 -1
- package/dist/lib/usage.js +7 -5
- package/dist/lib/versions.js +14 -11
- package/dist/lib/workflows.d.ts +20 -0
- package/dist/lib/workflows.js +24 -0
- package/package.json +2 -1
package/dist/commands/cloud.js
CHANGED
|
@@ -8,6 +8,7 @@ import { insertTask, updateTaskStatus, getTaskById, listTasks as listStoredTasks
|
|
|
8
8
|
import { renderStream } from '../lib/cloud/stream.js';
|
|
9
9
|
import { MissingTargetError, MAX_IMAGES_PER_DISPATCH } from '../lib/cloud/types.js';
|
|
10
10
|
import { normalizeTriggerEvent, validateTrigger, writeJob, jobExists, GITHUB_TRIGGER_EVENTS } from '../lib/routines.js';
|
|
11
|
+
import { machineId } from '../lib/machine-id.js';
|
|
11
12
|
import { emit } from '../lib/events.js';
|
|
12
13
|
/** Map a supported image file extension to its wire mimeType. Rejects anything else. */
|
|
13
14
|
function imageMimeFromPath(file) {
|
|
@@ -145,7 +146,7 @@ Examples:
|
|
|
145
146
|
cloud
|
|
146
147
|
.command('run [prompt]')
|
|
147
148
|
.description('Dispatch a task to a cloud agent.')
|
|
148
|
-
.option('--provider <id>', 'Cloud backend: rush, codex, factory, antigravity (overrides agent auto-routing)')
|
|
149
|
+
.option('--provider <id>', 'Cloud backend: rush, codex, factory, antigravity, host (overrides agent auto-routing)')
|
|
149
150
|
.option('--agent <name>', 'Agent to run: claude, codex, droid, antigravity (auto-routes to its native cloud)')
|
|
150
151
|
.option('--repo <owner/repo>', 'GitHub repository. Repeatable for multi-repo dispatch (Rush Cloud only).', (value, previous) => {
|
|
151
152
|
const acc = Array.isArray(previous) ? previous : [];
|
|
@@ -160,6 +161,9 @@ Examples:
|
|
|
160
161
|
.option('--model <model>', 'Model override')
|
|
161
162
|
.option('--env <id>', 'Codex Cloud environment ID')
|
|
162
163
|
.option('--computer <name>', 'Factory/Droid computer target')
|
|
164
|
+
.option('--host <name>', 'One of your machines as the target (a registered host, device, capability tag, or user@host). Implies --provider host.')
|
|
165
|
+
.option('--remote-cwd <dir>', 'Working directory on the host (--provider host only)')
|
|
166
|
+
.option('--any', 'With --host <cap> (a capability tag), pick any matching host instead of erroring when several match')
|
|
163
167
|
.option('--autonomy <level>', 'Factory/Droid autonomy: low, medium, high (default high)')
|
|
164
168
|
.option('--mode <mode>', 'Execution mode (e.g., plan, edit, full)')
|
|
165
169
|
.option('--image <path>', `Attach an image (.png/.jpg/.webp) for vision dispatch. Repeatable, up to ${MAX_IMAGES_PER_DISPATCH} (Rush Cloud only).`, (value, previous) => {
|
|
@@ -189,6 +193,10 @@ Examples:
|
|
|
189
193
|
# Codex Cloud
|
|
190
194
|
agents cloud run "add auth tests" --provider codex --env env_abc123
|
|
191
195
|
|
|
196
|
+
# One of your own machines (agents hosts / agents devices), over SSH
|
|
197
|
+
agents cloud run "run the nightly benchmark" --host gpu-box --agent claude
|
|
198
|
+
agents cloud run "rebuild the index" --host gpu --any --remote-cwd ~/proj
|
|
199
|
+
|
|
192
200
|
# Default provider (set in ~/.agents/agents.yaml)
|
|
193
201
|
agents cloud run "refactor auth module" --repo user/repo
|
|
194
202
|
`)
|
|
@@ -208,9 +216,16 @@ Examples:
|
|
|
208
216
|
process.stderr.write(chalk.dim(`Reading prompt from ${filePath} (${sizeKB} KB)\n`));
|
|
209
217
|
}
|
|
210
218
|
}
|
|
219
|
+
// --host names one of YOUR machines as the target — that only means
|
|
220
|
+
// something to the host provider, so it implies --provider host rather
|
|
221
|
+
// than silently riding along to a cloud backend that would ignore it.
|
|
222
|
+
if (options.host && options.provider && options.provider !== 'host') {
|
|
223
|
+
die(`--host targets your own machines (--provider host), not ${options.provider}. Drop --host, or use --provider host.`);
|
|
224
|
+
}
|
|
225
|
+
const explicitProvider = options.provider ?? (options.host ? 'host' : undefined);
|
|
211
226
|
// Agent-aware: with no --provider, the agent routes to its native cloud
|
|
212
227
|
// (claude→rush, codex→codex, droid→factory, antigravity→antigravity).
|
|
213
|
-
const provider = resolveProvider(
|
|
228
|
+
const provider = resolveProvider(explicitProvider, options.agent);
|
|
214
229
|
// --repo is repeatable: commander gives us an array via our collector.
|
|
215
230
|
// A single --repo value arrives as a one-element array; keep the legacy
|
|
216
231
|
// singular `repo` field in sync so providers that only know that field
|
|
@@ -234,6 +249,12 @@ Examples:
|
|
|
234
249
|
dispatchOptions.providerOptions.env = options.env;
|
|
235
250
|
if (options.computer)
|
|
236
251
|
dispatchOptions.providerOptions.computer = options.computer;
|
|
252
|
+
if (options.host)
|
|
253
|
+
dispatchOptions.providerOptions.host = options.host;
|
|
254
|
+
if (options.remoteCwd)
|
|
255
|
+
dispatchOptions.providerOptions.remoteCwd = options.remoteCwd;
|
|
256
|
+
if (options.any)
|
|
257
|
+
dispatchOptions.providerOptions.any = true;
|
|
237
258
|
if (options.autonomy)
|
|
238
259
|
dispatchOptions.providerOptions.autonomy = options.autonomy;
|
|
239
260
|
if (options.mode)
|
|
@@ -279,6 +300,15 @@ Examples:
|
|
|
279
300
|
};
|
|
280
301
|
if (repoValues[0])
|
|
281
302
|
routine.repo = repoValues[0];
|
|
303
|
+
// --host with --on: the webhook-fired run places on that machine (the
|
|
304
|
+
// routine carries the placement, and firing pins to THIS device so a
|
|
305
|
+
// fleet of receivers can't each dispatch a duplicate).
|
|
306
|
+
if (options.host) {
|
|
307
|
+
routine.host = options.host;
|
|
308
|
+
if (options.remoteCwd)
|
|
309
|
+
routine.remoteCwd = options.remoteCwd;
|
|
310
|
+
routine.devices = [machineId()];
|
|
311
|
+
}
|
|
282
312
|
writeJob(routine);
|
|
283
313
|
if (json) {
|
|
284
314
|
console.log(JSON.stringify({ ok: true, registered: routineName, trigger }, null, 2));
|
package/dist/commands/doctor.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { addHostOption } from '../lib/hosts/option.js';
|
|
2
2
|
import { buildRemoteAgentsInvocation } from '../lib/hosts/remote-cmd.js';
|
|
3
|
-
import { loadDevices } from '../lib/devices/registry.js';
|
|
3
|
+
import { loadDevices, isControlDevice } from '../lib/devices/registry.js';
|
|
4
4
|
import { resolveHost } from '../lib/hosts/registry.js';
|
|
5
5
|
import { sshExec } from '../lib/ssh-exec.js';
|
|
6
6
|
import { sshTargetFor } from '../lib/hosts/types.js';
|
|
@@ -249,6 +249,9 @@ async function resolveFleetTargets(opts) {
|
|
|
249
249
|
const localName = machineId();
|
|
250
250
|
return Object.values(registry)
|
|
251
251
|
.filter((d) => d.name.toLowerCase() !== localName)
|
|
252
|
+
// Control devices (a cockpit) never run agents — skip them in the fleet
|
|
253
|
+
// fan-out (an explicit --device <name> still resolves above).
|
|
254
|
+
.filter((d) => !isControlDevice(d))
|
|
252
255
|
.map((d) => ({
|
|
253
256
|
name: d.name,
|
|
254
257
|
sshTarget: d.name,
|
package/dist/commands/exec.js
CHANGED
|
@@ -507,33 +507,45 @@ export function registerRunCommand(program) {
|
|
|
507
507
|
process.exit(1);
|
|
508
508
|
}
|
|
509
509
|
const hostName = hostGiven[0];
|
|
510
|
-
const {
|
|
511
|
-
const {
|
|
512
|
-
const {
|
|
510
|
+
const { resolveHostRunTarget, dispatchPromptToHost, HostResolutionError } = await import('../lib/hosts/run-target.js');
|
|
511
|
+
const { runInteractiveOnHost } = await import('../lib/hosts/dispatch.js');
|
|
512
|
+
const { registerInteractiveHostSession } = await import('../lib/hosts/session-index.js');
|
|
513
|
+
const { RUN_OPTION_REJECT_MESSAGES } = await import('../lib/hosts/remote-cmd.js');
|
|
513
514
|
const { normalizeRunStrategy, RUN_STRATEGIES } = await import('../lib/rotate.js');
|
|
514
|
-
//
|
|
515
|
-
//
|
|
516
|
-
//
|
|
517
|
-
|
|
518
|
-
if (
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
if (!host) {
|
|
534
|
-
console.error(chalk.red(`Unknown host "${hostName}". List hosts: agents hosts list`));
|
|
515
|
+
// The forwarding contract (RUN_OPTION_FORWARDING): options that cannot
|
|
516
|
+
// cross the SSH boundary fail loud BEFORE dispatch — never a silent
|
|
517
|
+
// drop. Value-aware: only reject what was actually passed.
|
|
518
|
+
const hostRejects = [];
|
|
519
|
+
if (options.secrets.length > 0)
|
|
520
|
+
hostRejects.push(RUN_OPTION_REJECT_MESSAGES.secrets);
|
|
521
|
+
if (options.secretsKeys)
|
|
522
|
+
hostRejects.push(RUN_OPTION_REJECT_MESSAGES.secretsKeys);
|
|
523
|
+
if (options.allowExpired)
|
|
524
|
+
hostRejects.push(RUN_OPTION_REJECT_MESSAGES.allowExpired);
|
|
525
|
+
if (options.resumeCheckpoint)
|
|
526
|
+
hostRejects.push(RUN_OPTION_REJECT_MESSAGES.resumeCheckpoint);
|
|
527
|
+
if (options.resume === true)
|
|
528
|
+
hostRejects.push(RUN_OPTION_REJECT_MESSAGES.resumeBare);
|
|
529
|
+
if (hostRejects.length > 0) {
|
|
530
|
+
for (const msg of hostRejects)
|
|
531
|
+
console.error(chalk.red(msg));
|
|
535
532
|
process.exit(1);
|
|
536
533
|
}
|
|
534
|
+
// Shared resolution (name → capability tag → error). A password-auth
|
|
535
|
+
// device throws DeviceOffloadUnsupportedError inside the helper and
|
|
536
|
+
// propagates untouched — it's printed cleanly by the top-level catch in
|
|
537
|
+
// index.ts (covers every resolveHost caller).
|
|
538
|
+
let host;
|
|
539
|
+
try {
|
|
540
|
+
host = await resolveHostRunTarget(hostName, { any: options.any });
|
|
541
|
+
}
|
|
542
|
+
catch (e) {
|
|
543
|
+
if (e instanceof HostResolutionError) {
|
|
544
|
+
console.error(chalk.red(e.message));
|
|
545
|
+
process.exit(1);
|
|
546
|
+
}
|
|
547
|
+
throw e;
|
|
548
|
+
}
|
|
537
549
|
try {
|
|
538
550
|
const [runAgent, rawRunVersion] = agentSpec.split('@');
|
|
539
551
|
// Forward the explicit @version pin verbatim. Resolving aliases like
|
|
@@ -636,10 +648,12 @@ export function registerRunCommand(program) {
|
|
|
636
648
|
agent: runAgent,
|
|
637
649
|
version: resumeId ? undefined : runVersion,
|
|
638
650
|
strategy: resumeId ? undefined : runStrategy,
|
|
651
|
+
fallback: options.fallback,
|
|
639
652
|
prompt,
|
|
640
653
|
mode: options.mode,
|
|
641
654
|
model: options.model,
|
|
642
655
|
effort: options.effort,
|
|
656
|
+
env: options.env,
|
|
643
657
|
addDir: hostAddDirs,
|
|
644
658
|
json: options.json,
|
|
645
659
|
verbose: options.verbose,
|
|
@@ -663,32 +677,37 @@ export function registerRunCommand(program) {
|
|
|
663
677
|
console.error(chalk.red('A prompt is required for headless host runs: agents run <agent> "<task>" --host <name>'));
|
|
664
678
|
process.exit(1);
|
|
665
679
|
}
|
|
666
|
-
|
|
667
|
-
|
|
680
|
+
// Session-id mint, detached dispatch, and local session-index
|
|
681
|
+
// registration all live in the shared helper (lib/hosts/run-target.ts).
|
|
682
|
+
const { task, exitCode } = await dispatchPromptToHost(host, {
|
|
668
683
|
agent: runAgent,
|
|
669
684
|
version: resumeId ? undefined : runVersion,
|
|
670
685
|
strategy: resumeId ? undefined : runStrategy,
|
|
686
|
+
fallback: options.fallback,
|
|
671
687
|
prompt,
|
|
672
688
|
mode: options.mode,
|
|
673
689
|
model: options.model,
|
|
674
690
|
effort: options.effort,
|
|
691
|
+
env: options.env,
|
|
675
692
|
addDir: hostAddDirs,
|
|
693
|
+
timeout: options.timeout,
|
|
694
|
+
loop: options.loop,
|
|
695
|
+
maxIterations: options.maxIterations,
|
|
696
|
+
budget: options.budget,
|
|
697
|
+
until: options.until,
|
|
698
|
+
interval: options.interval,
|
|
676
699
|
json: options.json,
|
|
677
700
|
verbose: options.verbose,
|
|
678
|
-
timeout: options.timeout,
|
|
679
701
|
yes: options.yes,
|
|
680
702
|
acp: options.acp,
|
|
703
|
+
autoSecrets: options.autoSecrets,
|
|
681
704
|
remoteCwd: hostCwd,
|
|
682
|
-
sessionId: hostSessionId,
|
|
683
705
|
name: options.name,
|
|
684
706
|
resume: resumeId,
|
|
685
707
|
follow: options.follow !== false,
|
|
708
|
+
passthroughArgs,
|
|
686
709
|
copyCreds: hostCopyCreds,
|
|
687
710
|
});
|
|
688
|
-
// Register the dispatched run in the LOCAL session index so it shows
|
|
689
|
-
// up in `agents sessions` and resolves by id/name, even though its
|
|
690
|
-
// transcript lives on the host. No-op when no session id was captured.
|
|
691
|
-
registerHostSession(task, { cwd: process.cwd(), prompt });
|
|
692
711
|
if (options.follow === false) {
|
|
693
712
|
// The handle the caller uses to check on the run: the name if given,
|
|
694
713
|
// else the real host-task id (never the old literal `<id>`). Steer
|
|
@@ -803,7 +822,7 @@ export function registerRunCommand(program) {
|
|
|
803
822
|
await warnUnpushedWork(resumeExec.cwd ?? process.cwd());
|
|
804
823
|
process.exit(resumeExit);
|
|
805
824
|
}
|
|
806
|
-
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([
|
|
825
|
+
const [{ buildExecCommand, parseExecEnv, execAgent, runWithFallback, normalizeMode, resolveMode, headlessPlanStallCommand, nativeResume, resolveInteractive }, { ALL_AGENT_IDS }, { profileExists, resolveProfileForRun }, { readAndResolveBundleEnv, describeBundle, assertRemoteBundleFlagsUnsupported, isHeadlessSecretsContext }, { splitBundleRef, resolveSshTarget, remoteResolveEnv }, { getConfiguredRunStrategy, normalizeRunStrategy, resolveRunVersion, rotationFailoverChain, shouldArmRotationFailover, RUN_STRATEGIES }, { getGlobalDefault, getVersionHomePath, resolveVersion, resolveVersionAlias, ensureAgentRunnable }, { buildDiscoveredPlugin, loadPluginManifest, syncPluginToVersion }, { parseWorkflowFrontmatter, resolveWorkflowRef, resolveAllowedSubagents, pruneStaleWorkflowSubagents, ensureSubagentDispatchTool }, { resolveRunDefaults }, { getMcpServersByName, buildWorkflowMcpConfig }, { supports },] = await Promise.all([
|
|
807
826
|
import('../lib/exec.js'),
|
|
808
827
|
import('../lib/agents.js'),
|
|
809
828
|
import('../lib/profiles.js'),
|
|
@@ -841,6 +860,11 @@ export function registerRunCommand(program) {
|
|
|
841
860
|
// produced item, and drives the teams supervisor to drain — no `teams`
|
|
842
861
|
// subcommands needed.
|
|
843
862
|
let workflowForEach;
|
|
863
|
+
// True once this run copies ≥1 dispatchable subagent into the shared agents
|
|
864
|
+
// dir. Used below to keep the `Task` tool in a `tools:`-restricted workflow —
|
|
865
|
+
// an orchestrator handed subagents but denied `Task` cannot reach them and
|
|
866
|
+
// degenerates to a no-op ("I'll wait for the completion notification").
|
|
867
|
+
let workflowHasSubagents = false;
|
|
844
868
|
const cwd = options.cwd ?? process.cwd();
|
|
845
869
|
if (isValidAgent(rawAgent)) {
|
|
846
870
|
agent = rawAgent;
|
|
@@ -922,6 +946,8 @@ export function registerRunCommand(program) {
|
|
|
922
946
|
workflowSubagentTargets.push(dest);
|
|
923
947
|
copied++;
|
|
924
948
|
}
|
|
949
|
+
if (copied > 0)
|
|
950
|
+
workflowHasSubagents = true;
|
|
925
951
|
if (allowedAgents !== undefined) {
|
|
926
952
|
// Surface any allowedAgents entry with no matching subagent file, and
|
|
927
953
|
// report how many were filtered out, so the scope is auditable.
|
|
@@ -965,7 +991,9 @@ export function registerRunCommand(program) {
|
|
|
965
991
|
}
|
|
966
992
|
// Auto-inject secrets bundles declared in the workflow's frontmatter `secrets:` field.
|
|
967
993
|
// Union with any --secrets flags the user passed; dedupe. Skip when --no-auto-secrets is set.
|
|
968
|
-
|
|
994
|
+
// (Commander stores the negated flag as `autoSecrets: false` — the old
|
|
995
|
+
// `noAutoSecrets` read was never populated, making the flag a no-op.)
|
|
996
|
+
if (options.autoSecrets !== false) {
|
|
969
997
|
const declared = workflowFrontmatter?.secrets ?? [];
|
|
970
998
|
if (declared.length > 0) {
|
|
971
999
|
const existing = new Set(options.secrets);
|
|
@@ -1002,8 +1030,14 @@ export function registerRunCommand(program) {
|
|
|
1002
1030
|
}
|
|
1003
1031
|
else if (hasScoping) {
|
|
1004
1032
|
if (tools && tools.length > 0) {
|
|
1005
|
-
|
|
1006
|
-
|
|
1033
|
+
// An orchestrator with dispatchable subagents MUST retain the `Task`
|
|
1034
|
+
// tool, or it can't reach the subagents this run just installed for it
|
|
1035
|
+
// — the run silently no-ops ("I'll wait for the completion notification").
|
|
1036
|
+
workflowToolsRestrict = ensureSubagentDispatchTool(tools, workflowHasSubagents);
|
|
1037
|
+
process.stderr.write(chalk.gray(`[workflow] restricting available tools to: ${workflowToolsRestrict.join(', ')} (Write/Bash/Edit unavailable unless listed)\n`));
|
|
1038
|
+
if (workflowToolsRestrict.length !== tools.length) {
|
|
1039
|
+
process.stderr.write(chalk.gray(`[workflow] kept Task tool: workflow ships subagents to dispatch\n`));
|
|
1040
|
+
}
|
|
1007
1041
|
}
|
|
1008
1042
|
if (mcpServerNames && mcpServerNames.length > 0) {
|
|
1009
1043
|
const servers = getMcpServersByName(mcpServerNames, { cwd });
|
|
@@ -1186,8 +1220,12 @@ export function registerRunCommand(program) {
|
|
|
1186
1220
|
process.exit(1);
|
|
1187
1221
|
}
|
|
1188
1222
|
const strategy = options.balanced ? 'balanced' : explicitStrategy ?? configuredStrategy;
|
|
1189
|
-
// Strategy only applies to bare agent invocations. Explicit @version
|
|
1190
|
-
// profiles
|
|
1223
|
+
// Strategy only applies to bare agent invocations. Explicit @version and
|
|
1224
|
+
// profiles already define their execution target. A --fallback chain does
|
|
1225
|
+
// NOT pin the primary: it only names where to cascade on a rate limit, so
|
|
1226
|
+
// the bare primary still resolves through the strategy — otherwise every
|
|
1227
|
+
// `agents run claude --fallback codex` run lands on the pinned default
|
|
1228
|
+
// account and account rotation silently stops (the gh-monitor heal bug).
|
|
1191
1229
|
if (strategy !== 'pinned' || options.balanced || explicitStrategy) {
|
|
1192
1230
|
if (version) {
|
|
1193
1231
|
process.stderr.write(chalk.yellow(`[agents] strategy ${strategy} ignored: version ${version} is pinned\n`));
|
|
@@ -1195,9 +1233,6 @@ export function registerRunCommand(program) {
|
|
|
1195
1233
|
else if (fromProfile) {
|
|
1196
1234
|
process.stderr.write(chalk.yellow(`[agents] strategy ${strategy} ignored: profile pins its own version/auth\n`));
|
|
1197
1235
|
}
|
|
1198
|
-
else if (options.fallback) {
|
|
1199
|
-
process.stderr.write(chalk.yellow(`[agents] strategy ${strategy} ignored: --fallback pins versions directly\n`));
|
|
1200
|
-
}
|
|
1201
1236
|
else {
|
|
1202
1237
|
try {
|
|
1203
1238
|
const resolved = await resolveRunVersion(agent, strategy, cwd);
|
|
@@ -1350,6 +1385,10 @@ export function registerRunCommand(program) {
|
|
|
1350
1385
|
caller: `agent ${agent}`,
|
|
1351
1386
|
keys: secretsKeysSubset,
|
|
1352
1387
|
allowExpired: options.allowExpired,
|
|
1388
|
+
// A headless/background run (routine, teammate, detached) must not
|
|
1389
|
+
// pop a Touch ID sheet nobody can answer — resolve broker-only and
|
|
1390
|
+
// fail fast with an actionable error. Interactive runs still prompt.
|
|
1391
|
+
agentOnly: isHeadlessSecretsContext(),
|
|
1353
1392
|
});
|
|
1354
1393
|
const entries = describeBundle(bundle);
|
|
1355
1394
|
const counts = {};
|
|
@@ -1470,17 +1509,23 @@ export function registerRunCommand(program) {
|
|
|
1470
1509
|
// path (continuing the session via /continue). Because this injects into
|
|
1471
1510
|
// the same `fallback` array `--fallback` uses, it must only arm for run
|
|
1472
1511
|
// shapes that accept a fallback chain — shouldArmRotationFailover excludes
|
|
1473
|
-
// acp/loop/resume-checkpoint (which reject a non-empty fallback below)
|
|
1474
|
-
// interactive/no-prompt runs
|
|
1475
|
-
//
|
|
1476
|
-
//
|
|
1477
|
-
//
|
|
1478
|
-
//
|
|
1512
|
+
// acp/loop/resume-checkpoint (which reject a non-empty fallback below)
|
|
1513
|
+
// and interactive/no-prompt runs. Pinned/single-account runs stay
|
|
1514
|
+
// unchanged because rotationResult is null or rotationFailoverChain
|
|
1515
|
+
// returns []. version is set here because rotationResult is only
|
|
1516
|
+
// populated when resolveRunVersion picked one.
|
|
1517
|
+
//
|
|
1518
|
+
// Composes with an explicit --fallback chain: the same-agent accounts are
|
|
1519
|
+
// UNSHIFTED ahead of the user's cross-agent entries, so a rate limit
|
|
1520
|
+
// first tries the other accounts of the same agent (cheapest recovery —
|
|
1521
|
+
// same CLI, session continues) and only then cascades to codex/gemini/etc.
|
|
1522
|
+
// Profiles never compose: strategy is skipped for them, rotationResult
|
|
1523
|
+
// stays null. (fromProfile's model-swap unshift above is therefore never
|
|
1524
|
+
// displaced by this one.)
|
|
1479
1525
|
if (shouldArmRotationFailover({
|
|
1480
1526
|
hasRotation: !!rotationResult,
|
|
1481
1527
|
hasVersion: !!version,
|
|
1482
1528
|
hasPrompt: prompt !== undefined,
|
|
1483
|
-
explicitFallback: fallback.length > 0,
|
|
1484
1529
|
interactive: !!options.interactive,
|
|
1485
1530
|
acp: !!options.acp,
|
|
1486
1531
|
loop: !!options.loop,
|
|
@@ -1488,7 +1533,7 @@ export function registerRunCommand(program) {
|
|
|
1488
1533
|
})) {
|
|
1489
1534
|
const failover = rotationFailoverChain(rotationResult, version);
|
|
1490
1535
|
if (failover.length > 0) {
|
|
1491
|
-
fallback.
|
|
1536
|
+
fallback.unshift(...failover);
|
|
1492
1537
|
if (!options.quiet) {
|
|
1493
1538
|
const accounts = failover.map(f => `${f.agent}@${f.version}`).join(', ');
|
|
1494
1539
|
process.stderr.write(chalk.gray(`[agents] rate-limit failover armed: ${accounts}\n`));
|
package/dist/commands/feed.js
CHANGED
|
@@ -8,6 +8,7 @@ import { gatherRemoteAgentsJson } from '../lib/remote-agents-json.js';
|
|
|
8
8
|
import { loadPolicy, applyPolicyToBlock, isPhoneUrgent } from '../lib/feed-policy.js';
|
|
9
9
|
import { notifyUrgentBlock } from '../lib/notify.js';
|
|
10
10
|
import { gcMailbox } from '../lib/mailbox-gc.js';
|
|
11
|
+
import { isValidMailboxId } from '../lib/mailbox.js';
|
|
11
12
|
import { getActiveSessions } from '../lib/session/active.js';
|
|
12
13
|
import { mailboxIdForActiveSession } from '../lib/mailbox-target.js';
|
|
13
14
|
import { GLYPH, masthead } from '../lib/comms-render.js';
|
|
@@ -38,6 +39,11 @@ export function parseRemoteFeed(stdout, machine) {
|
|
|
38
39
|
const block = item;
|
|
39
40
|
if (!block.blockId || !block.sessionId || !block.mailboxId || !block.questions?.length)
|
|
40
41
|
continue;
|
|
42
|
+
// A crafted mailboxId (path separators, `.`/`..`) would throw inside
|
|
43
|
+
// mailboxDir() when policy runs against the block, aborting the whole
|
|
44
|
+
// dispatch loop — drop it here so a malicious peer can't smuggle one in.
|
|
45
|
+
if (!isValidMailboxId(block.mailboxId))
|
|
46
|
+
continue;
|
|
41
47
|
blocks.push({ ...block, host: machine });
|
|
42
48
|
}
|
|
43
49
|
return blocks;
|
|
@@ -240,20 +246,28 @@ export function registerFeedCommand(program) {
|
|
|
240
246
|
const policy = loadPolicy();
|
|
241
247
|
const now = new Date();
|
|
242
248
|
for (const b of blocks) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
recordNotified(b.blockId);
|
|
251
|
-
console.log(`${chalk.green('notified')} ${b.blockId}`);
|
|
249
|
+
// Wrap per-block policy so one malformed block (e.g. a crafted
|
|
250
|
+
// mailboxId that throws in mailboxDir) can't abort the whole loop and
|
|
251
|
+
// strand every remaining block's dispatch.
|
|
252
|
+
try {
|
|
253
|
+
const result = applyPolicyToBlock(b, policy, now);
|
|
254
|
+
if (result.action !== 'none') {
|
|
255
|
+
console.log(`${chalk.yellow('policy')} ${b.blockId}: ${result.action}`);
|
|
252
256
|
}
|
|
253
|
-
|
|
254
|
-
|
|
257
|
+
if (isPhoneUrgent(b, policy)) {
|
|
258
|
+
const notifyResult = await notifyUrgentBlock(b, { dryRun: opts.json });
|
|
259
|
+
if (notifyResult.ok && !notifyResult.skipped) {
|
|
260
|
+
recordNotified(b.blockId);
|
|
261
|
+
console.log(`${chalk.green('notified')} ${b.blockId}`);
|
|
262
|
+
}
|
|
263
|
+
else if (notifyResult.error) {
|
|
264
|
+
console.error(chalk.yellow(`Notification failed for ${b.blockId}: ${notifyResult.error}`));
|
|
265
|
+
}
|
|
255
266
|
}
|
|
256
267
|
}
|
|
268
|
+
catch (err) {
|
|
269
|
+
console.error(chalk.yellow(`Skipped block ${b.blockId}: ${err.message}`));
|
|
270
|
+
}
|
|
257
271
|
}
|
|
258
272
|
}
|
|
259
273
|
for (const warning of setupWarnings) {
|
package/dist/commands/hosts.js
CHANGED
|
@@ -11,6 +11,7 @@ import { terminalWidth, truncateToWidth, stringWidth } from '../lib/session/widt
|
|
|
11
11
|
import { checkbox, confirm } from '@inquirer/prompts';
|
|
12
12
|
import { assertValidSshTarget } from '../lib/ssh-exec.js';
|
|
13
13
|
import { getProvider, listAllHosts, resolveHost } from '../lib/hosts/registry.js';
|
|
14
|
+
import { getDevice } from '../lib/devices/registry.js';
|
|
14
15
|
import { sshTargetFor } from '../lib/hosts/types.js';
|
|
15
16
|
import { listSshConfigHosts, listKnownHosts, isSshConfigHost } from '../lib/hosts/ssh-config.js';
|
|
16
17
|
import { probeHost, remoteAgentsVersion, bootstrapAgentsCli, localCliVersion, } from '../lib/hosts/ready.js';
|
|
@@ -93,7 +94,7 @@ async function doAdd(name, target, opts) {
|
|
|
93
94
|
return;
|
|
94
95
|
}
|
|
95
96
|
let spec;
|
|
96
|
-
let sshTarget;
|
|
97
|
+
let sshTarget = '';
|
|
97
98
|
if (target) {
|
|
98
99
|
assertValidSshTarget(target);
|
|
99
100
|
const { address, user } = parseTarget(target);
|
|
@@ -105,9 +106,36 @@ async function doAdd(name, target, opts) {
|
|
|
105
106
|
sshTarget = name;
|
|
106
107
|
}
|
|
107
108
|
else {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
// A registered device enrolls with no target — connection details come from
|
|
110
|
+
// the device profile. The main reason to enroll one at all is the overlay
|
|
111
|
+
// metadata (capability tags); plain dispatch already works via the devices
|
|
112
|
+
// provider.
|
|
113
|
+
const device = await getDevice(name);
|
|
114
|
+
if (device && device.auth.method !== 'password') {
|
|
115
|
+
const address = device.address.dnsName ?? device.address.ip;
|
|
116
|
+
if (address) {
|
|
117
|
+
spec = {
|
|
118
|
+
name,
|
|
119
|
+
provider: 'local',
|
|
120
|
+
source: 'inline',
|
|
121
|
+
address,
|
|
122
|
+
user: device.user,
|
|
123
|
+
caps: opts.cap,
|
|
124
|
+
os: opts.os ?? (device.platform !== 'unknown' ? device.platform : undefined),
|
|
125
|
+
};
|
|
126
|
+
sshTarget = device.user ? `${device.user}@${address}` : address;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (!spec) {
|
|
130
|
+
if (device?.auth.method === 'password') {
|
|
131
|
+
console.log(chalk.red(`Device "${name}" uses password auth — dispatch needs key auth. Switch it first: agents devices set ${name} --auth key`));
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
console.log(chalk.red(`"${name}" is not in ~/.ssh/config or the devices registry. Pass a target: agents hosts add ${name} <user@host>`));
|
|
135
|
+
}
|
|
136
|
+
process.exitCode = 1;
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
111
139
|
}
|
|
112
140
|
const probe = probeHost(sshTarget);
|
|
113
141
|
if (!spec.os && probe.os)
|
|
@@ -133,8 +161,12 @@ async function doList(json) {
|
|
|
133
161
|
// Cap TARGET at its column so a long user@host can't shove CAPS out of alignment.
|
|
134
162
|
const tgtW = stringWidth(tgt);
|
|
135
163
|
const tgtCol = tgtW > 28 ? truncateToWidth(tgt, 28) : tgt + ' '.repeat(28 - tgtW);
|
|
136
|
-
|
|
137
|
-
|
|
164
|
+
// Devices surface their registry as SOURCE (they're synced, not enrolled here).
|
|
165
|
+
const source = h.provider === 'devices' ? 'devices' : h.source;
|
|
166
|
+
const mark = h.dispatchable === false
|
|
167
|
+
? chalk.yellow(' ·password-auth (no dispatch)')
|
|
168
|
+
: h.enrolled ? '' : chalk.gray(' ·available');
|
|
169
|
+
console.log(h.name.padEnd(20) + source.padEnd(13) + tgtCol + (h.caps?.join(',') ?? '') + mark);
|
|
138
170
|
}
|
|
139
171
|
}
|
|
140
172
|
async function doCheck(name) {
|
|
@@ -163,6 +195,12 @@ async function doRemove(name) {
|
|
|
163
195
|
console.log(chalk.yellow(`"${name}" is not enrolled (nothing to remove).`));
|
|
164
196
|
return;
|
|
165
197
|
}
|
|
198
|
+
if (host.provider === 'devices') {
|
|
199
|
+
// The devices registry owns this entry — hosts remove would silently no-op.
|
|
200
|
+
console.log(chalk.yellow(`"${name}" comes from the devices registry. Remove it there: agents devices rm ${name}`));
|
|
201
|
+
process.exitCode = 1;
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
166
204
|
await getProvider('local').remove(name);
|
|
167
205
|
console.log(chalk.green(`Removed ${name}`));
|
|
168
206
|
}
|
package/dist/commands/mcp.js
CHANGED
|
@@ -6,9 +6,9 @@ import { capableAgents, isCapable } from '../lib/capabilities.js';
|
|
|
6
6
|
import { emit } from '../lib/events.js';
|
|
7
7
|
import { AGENTS, getAllCliStates, resolveAgentName, formatAgentError, registerMcpToTargets, unregisterMcpFromTargets, listInstalledMcpsWithScope, parseMcpConfig, getMcpConfigPathForHome, agentLabel, } from '../lib/agents.js';
|
|
8
8
|
import { readManifest, writeManifest, createDefaultManifest } from '../lib/manifest.js';
|
|
9
|
-
import { listMcpServerConfigs, discoverMcpConfigsFromRepo, installMcpConfigCentrally, } from '../lib/mcp.js';
|
|
9
|
+
import { listMcpServerConfigs, discoverMcpConfigsFromRepo, installMcpConfigCentrally, isProjectMcpTrusted, trustProjectMcp, untrustProjectMcp, getMcpTrustStorePath, } from '../lib/mcp.js';
|
|
10
10
|
import { cloneRepo } from '../lib/git.js';
|
|
11
|
-
import { getMcpDir } from '../lib/state.js';
|
|
11
|
+
import { getMcpDir, getProjectAgentsDir } from '../lib/state.js';
|
|
12
12
|
import { getEffectiveHome, getGlobalDefault, listInstalledVersions, getVersionHomePath, resolveInstalledAgentTargets, resolveConfiguredAgentTargets, syncResourcesToVersion, } from '../lib/versions.js';
|
|
13
13
|
import { getUserAgentsDir } from '../lib/state.js';
|
|
14
14
|
import { isPromptCancelled, isInteractiveTerminal, requireInteractiveSelection, promptRemovalTargets, parseCommaSeparatedList, ensureAgentVersionsInstalled, resolveAgentTargetsAutoInstalling, resolveInstalledAgentTargetsAutoInstalling, VersionNotInstalledError, resolveListFilterOrExit, } from './utils.js';
|
|
@@ -178,6 +178,43 @@ When to use:
|
|
|
178
178
|
filterVersion,
|
|
179
179
|
});
|
|
180
180
|
});
|
|
181
|
+
mcpCmd
|
|
182
|
+
.command('trust')
|
|
183
|
+
.description('Trust this project so its .agents/mcp/ servers may be registered and spawned')
|
|
184
|
+
.action(() => {
|
|
185
|
+
const projectAgentsDir = getProjectAgentsDir();
|
|
186
|
+
if (!projectAgentsDir) {
|
|
187
|
+
console.log(chalk.red('Not inside a project (no .agents/ directory found) — nothing to trust.'));
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
const servers = listMcpServerConfigs().filter((s) => s.scope === 'project');
|
|
191
|
+
if (servers.length > 0) {
|
|
192
|
+
console.log(chalk.bold('Project-scoped MCP servers that will be trusted to run:'));
|
|
193
|
+
for (const s of servers) {
|
|
194
|
+
console.log(' ' + chalk.cyan(s.name) + ' ' + chalk.white(formatCentralCommand(s)));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
const trusted = trustProjectMcp();
|
|
198
|
+
console.log(chalk.green(`Trusted project MCP servers for: ${trusted}`));
|
|
199
|
+
console.log(chalk.gray(`Recorded in ${getMcpTrustStorePath()} — revoke with: agents mcp untrust`));
|
|
200
|
+
});
|
|
201
|
+
mcpCmd
|
|
202
|
+
.command('untrust')
|
|
203
|
+
.description('Revoke MCP trust for this project (its .agents/mcp/ servers stop auto-applying)')
|
|
204
|
+
.action(() => {
|
|
205
|
+
const projectAgentsDir = getProjectAgentsDir();
|
|
206
|
+
if (!projectAgentsDir) {
|
|
207
|
+
console.log(chalk.red('Not inside a project (no .agents/ directory found).'));
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
const removed = untrustProjectMcp();
|
|
211
|
+
if (removed) {
|
|
212
|
+
console.log(chalk.green('Revoked MCP trust for this project.'));
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
console.log(chalk.gray('This project was not trusted — nothing to revoke.'));
|
|
216
|
+
}
|
|
217
|
+
});
|
|
181
218
|
mcpCmd
|
|
182
219
|
.command('add <name> [command_or_url...]')
|
|
183
220
|
.description('Add an MCP server to the manifest (run "agents mcp register" afterward to apply)')
|
|
@@ -797,9 +834,15 @@ function formatCentralCommand(server) {
|
|
|
797
834
|
function formatMcpDetail(name, source, centralConfig, manifestConfig, command, targets) {
|
|
798
835
|
const lines = [];
|
|
799
836
|
lines.push(chalk.bold.cyan(name));
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
|
|
837
|
+
const isUntrustedProject = centralConfig?.scope === 'project'
|
|
838
|
+
&& !isProjectMcpTrusted(getProjectAgentsDir() ?? '');
|
|
839
|
+
const tag = centralConfig?.scope === 'project'
|
|
840
|
+
? (isUntrustedProject
|
|
841
|
+
? chalk.yellow('declared in <repo>/.agents/mcp/ (untrusted project — will NOT auto-apply)')
|
|
842
|
+
: chalk.green('declared in <repo>/.agents/mcp/ (trusted project)')) :
|
|
843
|
+
source === 'central' ? chalk.green('declared in ~/.agents/mcp/') :
|
|
844
|
+
source === 'manifest' ? chalk.gray('declared in agents.yaml') :
|
|
845
|
+
chalk.yellow('unmanaged (not in central or manifest)');
|
|
803
846
|
lines.push(' ' + tag);
|
|
804
847
|
lines.push('');
|
|
805
848
|
if (centralConfig) {
|
|
@@ -814,6 +857,13 @@ function formatMcpDetail(name, source, centralConfig, manifestConfig, command, t
|
|
|
814
857
|
if (command) {
|
|
815
858
|
lines.push(` ${chalk.gray('command:')} ${chalk.white(command)}`);
|
|
816
859
|
}
|
|
860
|
+
// A project-scoped server is an arbitrary command from a (possibly cloned)
|
|
861
|
+
// repo. Show exactly what would run and how to opt in (RUSH-1776).
|
|
862
|
+
if (isUntrustedProject) {
|
|
863
|
+
lines.push('');
|
|
864
|
+
lines.push(' ' + chalk.yellow('This project is untrusted — its MCP servers are skipped on sync and workflow runs.'));
|
|
865
|
+
lines.push(' ' + chalk.gray('Review the command above, then opt in with: ') + chalk.white('agents mcp trust'));
|
|
866
|
+
}
|
|
817
867
|
lines.push('');
|
|
818
868
|
lines.push(chalk.bold(' Synced to'));
|
|
819
869
|
lines.push(buildTargetsSection(targets));
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Monitors — durable event-triggered watchers.
|
|
3
|
+
*
|
|
4
|
+
* Registers the `agents monitors` command tree: create, list, view, dry-run test,
|
|
5
|
+
* edit, logs, fire history, pause/resume, (re)pin the owner device, and remove.
|
|
6
|
+
* Mirrors `agents routines` (commands/routines.ts) and shares the same daemon +
|
|
7
|
+
* dispatch engine underneath — a monitor is a routine whose trigger is a watched
|
|
8
|
+
* source instead of a clock.
|
|
9
|
+
*/
|
|
10
|
+
import type { Command } from 'commander';
|
|
11
|
+
/** Register the `agents monitors` command tree. */
|
|
12
|
+
export declare function registerMonitorsCommands(program: Command): void;
|