@phnx-labs/agents-cli 1.20.63 → 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 +56 -3
- package/README.md +46 -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 +149 -77
- package/dist/commands/feed.d.ts +4 -0
- package/dist/commands/feed.js +52 -19
- package/dist/commands/hosts.js +44 -6
- package/dist/commands/lease.d.ts +23 -0
- package/dist/commands/lease.js +201 -0
- package/dist/commands/mailboxes.d.ts +20 -0
- package/dist/commands/mailboxes.js +390 -0
- 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 +43 -16
- 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.d.ts +2 -0
- package/dist/commands/sessions-export.js +284 -0
- package/dist/commands/sessions-import.d.ts +2 -0
- package/dist/commands/sessions-import.js +230 -0
- package/dist/commands/sessions.d.ts +16 -0
- package/dist/commands/sessions.js +40 -6
- package/dist/commands/ssh.js +143 -5
- package/dist/commands/usage.d.ts +2 -0
- package/dist/commands/usage.js +7 -2
- package/dist/commands/versions.js +7 -3
- package/dist/commands/view.d.ts +27 -1
- package/dist/commands/view.js +32 -9
- package/dist/commands/webhook.js +10 -2
- package/dist/index.js +35 -14
- package/dist/lib/agents.d.ts +36 -0
- package/dist/lib/agents.js +80 -20
- 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/browser/drivers/ssh.js +19 -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/comms-render.d.ts +37 -0
- package/dist/lib/comms-render.js +89 -0
- package/dist/lib/computer-rpc.js +3 -1
- package/dist/lib/crabbox/cli.d.ts +72 -0
- package/dist/lib/crabbox/cli.js +162 -9
- package/dist/lib/crabbox/runtimes.d.ts +13 -0
- package/dist/lib/crabbox/runtimes.js +35 -2
- package/dist/lib/daemon.d.ts +20 -4
- package/dist/lib/daemon.js +68 -20
- package/dist/lib/devices/fleet.d.ts +3 -2
- package/dist/lib/devices/fleet.js +9 -0
- package/dist/lib/devices/health.d.ts +77 -0
- package/dist/lib/devices/health.js +186 -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/mailbox.d.ts +39 -0
- package/dist/lib/mailbox.js +112 -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/paths.d.ts +13 -0
- package/dist/lib/paths.js +26 -4
- 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 +37 -2
- package/dist/lib/routines.js +54 -12
- package/dist/lib/runner.d.ts +1 -0
- package/dist/lib/runner.js +348 -13
- package/dist/lib/sandbox.d.ts +9 -1
- package/dist/lib/sandbox.js +11 -2
- 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 +164 -0
- package/dist/lib/session/bundle.js +200 -0
- package/dist/lib/session/remote-bundle.d.ts +12 -0
- package/dist/lib/session/remote-bundle.js +61 -0
- 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/agents.d.ts +54 -6
- package/dist/lib/session/sync/agents.js +0 -0
- package/dist/lib/session/sync/config.js +8 -2
- package/dist/lib/session/sync/manifest.d.ts +14 -3
- package/dist/lib/session/sync/manifest.js +4 -0
- package/dist/lib/session/sync/sync.d.ts +23 -2
- package/dist/lib/session/sync/sync.js +177 -74
- package/dist/lib/session/types.d.ts +30 -0
- package/dist/lib/ssh-tunnel.d.ts +19 -1
- package/dist/lib/ssh-tunnel.js +99 -8
- package/dist/lib/staleness/detectors/subagents.d.ts +5 -0
- package/dist/lib/staleness/detectors/subagents.js +5 -192
- package/dist/lib/staleness/writers/subagents.d.ts +10 -0
- package/dist/lib/staleness/writers/subagents.js +11 -102
- package/dist/lib/startup/command-registry.d.ts +4 -0
- package/dist/lib/startup/command-registry.js +9 -0
- package/dist/lib/state.d.ts +5 -0
- package/dist/lib/state.js +12 -0
- package/dist/lib/subagents-registry.d.ts +85 -0
- package/dist/lib/subagents-registry.js +393 -0
- package/dist/lib/subagents.d.ts +8 -8
- package/dist/lib/subagents.js +32 -663
- package/dist/lib/sync-umbrella.d.ts +1 -0
- package/dist/lib/sync-umbrella.js +14 -3
- 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 +35 -1
- package/dist/lib/usage.d.ts +42 -3
- package/dist/lib/usage.js +163 -21
- 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/browser.js
CHANGED
|
@@ -4,7 +4,7 @@ import { listProfiles, getProfile, createProfile, deleteProfile, ensureDefaultBr
|
|
|
4
4
|
import { updateMeta } from '../lib/state.js';
|
|
5
5
|
import { loginsForProfile, profilesLoggedInto, serviceForUrl, loginsWithAccountsForProfile, accountsForProfile, credKeysForService, AUTH_SIGNATURES, } from '../lib/browser/login-detection.js';
|
|
6
6
|
import { parseSecretRef } from '../lib/browser/secret-ref.js';
|
|
7
|
-
import { readAndResolveBundleEnv, bundleExists, readBundle, describeBundle } from '../lib/secrets/bundles.js';
|
|
7
|
+
import { readAndResolveBundleEnv, isHeadlessSecretsContext, bundleExists, readBundle, describeBundle } from '../lib/secrets/bundles.js';
|
|
8
8
|
import { findBrowserPath, getPortOccupant, isLauncherScript } from '../lib/browser/chrome.js';
|
|
9
9
|
import { listProfileCacheDirs, removeProfileCache, listAllProfileSnapshots, } from '../lib/browser/runtime-state.js';
|
|
10
10
|
import { DEFAULT_VIEWPORT } from '../lib/browser/devices.js';
|
|
@@ -1420,7 +1420,7 @@ function registerTaskCommands(browser) {
|
|
|
1420
1420
|
process.exit(1);
|
|
1421
1421
|
}
|
|
1422
1422
|
try {
|
|
1423
|
-
const { env } = readAndResolveBundleEnv(parsed.bundle, { caller: 'browser type', keys: [parsed.key] });
|
|
1423
|
+
const { env } = readAndResolveBundleEnv(parsed.bundle, { caller: 'browser type', keys: [parsed.key], agentOnly: isHeadlessSecretsContext() });
|
|
1424
1424
|
if (!(parsed.key in env)) {
|
|
1425
1425
|
console.error(`Key "${parsed.key}" not in bundle "${parsed.bundle}".`);
|
|
1426
1426
|
process.exit(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
|
@@ -329,41 +329,69 @@ export function registerRunCommand(program) {
|
|
|
329
329
|
process.exit(1);
|
|
330
330
|
}
|
|
331
331
|
const backend = typeof options.lease === 'string' ? options.lease : undefined;
|
|
332
|
-
|
|
332
|
+
// First-run: no provider credential resolves (no env var, no config, no
|
|
333
|
+
// detectable bundle) → guide the user through one-time setup, then continue.
|
|
334
|
+
const { resolveLeaseBundle } = await import('../lib/crabbox/cli.js');
|
|
335
|
+
if (!resolveLeaseBundle()) {
|
|
336
|
+
const { runLeaseSetup } = await import('./lease.js');
|
|
337
|
+
const ok = await runLeaseSetup({ provider: backend ?? 'hetzner' });
|
|
338
|
+
if (!ok) {
|
|
339
|
+
console.error(chalk.yellow('Leasing needs a cloud provider set up. Run `agents lease setup` and retry.'));
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
const { detectSignedInRuntimes, resolveClaudeCredentialsBlob, inferLeaseRuntime } = await import('../lib/crabbox/runtimes.js');
|
|
333
344
|
const { leaseAndRun } = await import('../lib/crabbox/lease.js');
|
|
334
|
-
const {
|
|
345
|
+
const { getConfiguredRunStrategy, resolveRunVersion } = await import('../lib/rotate.js');
|
|
335
346
|
const detected = await detectSignedInRuntimes();
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
347
|
+
const agentName = agentSpec.split('@')[0];
|
|
348
|
+
const leaseCwd = options.cwd ?? process.cwd();
|
|
349
|
+
// `--lease` requires a prompt (guarded above), so it is headless by
|
|
350
|
+
// contract — never block on an interactive picker. Provision exactly the
|
|
351
|
+
// one runtime this run needs, inferred from the agent, not every
|
|
352
|
+
// signed-in CLI (which would ship unrelated tokens to a throwaway box).
|
|
353
|
+
const runtime = inferLeaseRuntime(agentName, detected);
|
|
354
|
+
if (!runtime) {
|
|
355
|
+
console.error(chalk.yellow('No signed-in runtime to provision on the box. Sign into one locally (e.g. run `claude` once) then retry.'));
|
|
339
356
|
process.exit(1);
|
|
340
357
|
}
|
|
341
|
-
|
|
342
|
-
//
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
//
|
|
350
|
-
//
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
358
|
+
const runtimes = [runtime];
|
|
359
|
+
// Copy the account the run's OWN strategy would pick (default `balanced`:
|
|
360
|
+
// a healthy, non-rate-limited account weighted by remaining headroom) —
|
|
361
|
+
// never the raw default signed-in one, which could be throttled or out of
|
|
362
|
+
// credits and would boot the box straight into a wall.
|
|
363
|
+
//
|
|
364
|
+
// Only claude's token is account-switched: resolveClaudeCredentialsBlob
|
|
365
|
+
// honors preferEmail across version-homes. The other runtimes'
|
|
366
|
+
// buildCredentialScript copies the single currently-active credential file
|
|
367
|
+
// with no switching, so run the balanced picker only for claude — otherwise
|
|
368
|
+
// the notice below would name a balanced-picked account that is NOT the one
|
|
369
|
+
// actually shipped. Best-effort: fall back to the default account on error.
|
|
370
|
+
let leaseEmail = detected.find((d) => d.id === runtime)?.email ?? null;
|
|
371
|
+
if (runtime === 'claude') {
|
|
372
|
+
try {
|
|
373
|
+
const strategy = getConfiguredRunStrategy(runtime, leaseCwd);
|
|
374
|
+
const { rotation } = await resolveRunVersion(runtime, strategy, leaseCwd);
|
|
375
|
+
if (rotation?.picked.email)
|
|
376
|
+
leaseEmail = rotation.picked.email;
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
/* strategy resolution is best-effort; keep the default signed-in account */
|
|
380
|
+
}
|
|
359
381
|
}
|
|
382
|
+
// Headless-by-contract: don't prompt, but print exactly what ships and
|
|
383
|
+
// where — copying an auth token to a cloud box is a credential transfer.
|
|
384
|
+
// The box is destroyed after the run, so the credential's lifetime is
|
|
385
|
+
// bounded by the run.
|
|
386
|
+
const whatShips = runtime === 'claude' ? 'credentials + Claude OAuth token' : 'credentials';
|
|
387
|
+
console.error(chalk.gray(`Leasing a ${backend ?? 'hetzner'} box · shipping ${runtime}${leaseEmail ? ` (${leaseEmail})` : ''} ${whatShips}; the box is destroyed after the run.`));
|
|
360
388
|
// Read the Claude OAuth token from the local Keychain (silent) so it can be
|
|
361
389
|
// written to ~/.claude/.credentials.json on the box — otherwise Claude boots
|
|
362
|
-
// "Not logged in".
|
|
390
|
+
// "Not logged in". `preferEmail` targets the strategy-picked account so the
|
|
391
|
+
// token matches the account this run resolved to.
|
|
363
392
|
let claudeCredentialsJson = null;
|
|
364
|
-
if (
|
|
365
|
-
|
|
366
|
-
claudeCredentialsJson = await resolveClaudeCredentialsBlob({ preferEmail: claudeEmail });
|
|
393
|
+
if (runtime === 'claude') {
|
|
394
|
+
claudeCredentialsJson = await resolveClaudeCredentialsBlob({ preferEmail: leaseEmail });
|
|
367
395
|
if (!claudeCredentialsJson) {
|
|
368
396
|
console.error(chalk.yellow('Warning: could not read the local Claude OAuth token — the box may come up "Not logged in".'));
|
|
369
397
|
}
|
|
@@ -374,7 +402,6 @@ export function registerRunCommand(program) {
|
|
|
374
402
|
// box-side marker. Rule: only ONE spinner phase is active at a time, and no
|
|
375
403
|
// other output is written to stderr while it spins — so it can never storm.
|
|
376
404
|
const { createLeaseOutputRouter, createSpinner } = await import('../lib/crabbox/progress.js');
|
|
377
|
-
const agentName = agentSpec.split('@')[0];
|
|
378
405
|
const spinner = createSpinner({ stream: process.stderr });
|
|
379
406
|
let warmupTimer;
|
|
380
407
|
const stopTimer = () => { if (warmupTimer) {
|
|
@@ -480,33 +507,45 @@ export function registerRunCommand(program) {
|
|
|
480
507
|
process.exit(1);
|
|
481
508
|
}
|
|
482
509
|
const hostName = hostGiven[0];
|
|
483
|
-
const {
|
|
484
|
-
const {
|
|
485
|
-
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');
|
|
486
514
|
const { normalizeRunStrategy, RUN_STRATEGIES } = await import('../lib/rotate.js');
|
|
487
|
-
//
|
|
488
|
-
//
|
|
489
|
-
//
|
|
490
|
-
|
|
491
|
-
if (
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
if (!host) {
|
|
507
|
-
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));
|
|
508
532
|
process.exit(1);
|
|
509
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
|
+
}
|
|
510
549
|
try {
|
|
511
550
|
const [runAgent, rawRunVersion] = agentSpec.split('@');
|
|
512
551
|
// Forward the explicit @version pin verbatim. Resolving aliases like
|
|
@@ -609,10 +648,12 @@ export function registerRunCommand(program) {
|
|
|
609
648
|
agent: runAgent,
|
|
610
649
|
version: resumeId ? undefined : runVersion,
|
|
611
650
|
strategy: resumeId ? undefined : runStrategy,
|
|
651
|
+
fallback: options.fallback,
|
|
612
652
|
prompt,
|
|
613
653
|
mode: options.mode,
|
|
614
654
|
model: options.model,
|
|
615
655
|
effort: options.effort,
|
|
656
|
+
env: options.env,
|
|
616
657
|
addDir: hostAddDirs,
|
|
617
658
|
json: options.json,
|
|
618
659
|
verbose: options.verbose,
|
|
@@ -636,32 +677,37 @@ export function registerRunCommand(program) {
|
|
|
636
677
|
console.error(chalk.red('A prompt is required for headless host runs: agents run <agent> "<task>" --host <name>'));
|
|
637
678
|
process.exit(1);
|
|
638
679
|
}
|
|
639
|
-
|
|
640
|
-
|
|
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, {
|
|
641
683
|
agent: runAgent,
|
|
642
684
|
version: resumeId ? undefined : runVersion,
|
|
643
685
|
strategy: resumeId ? undefined : runStrategy,
|
|
686
|
+
fallback: options.fallback,
|
|
644
687
|
prompt,
|
|
645
688
|
mode: options.mode,
|
|
646
689
|
model: options.model,
|
|
647
690
|
effort: options.effort,
|
|
691
|
+
env: options.env,
|
|
648
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,
|
|
649
699
|
json: options.json,
|
|
650
700
|
verbose: options.verbose,
|
|
651
|
-
timeout: options.timeout,
|
|
652
701
|
yes: options.yes,
|
|
653
702
|
acp: options.acp,
|
|
703
|
+
autoSecrets: options.autoSecrets,
|
|
654
704
|
remoteCwd: hostCwd,
|
|
655
|
-
sessionId: hostSessionId,
|
|
656
705
|
name: options.name,
|
|
657
706
|
resume: resumeId,
|
|
658
707
|
follow: options.follow !== false,
|
|
708
|
+
passthroughArgs,
|
|
659
709
|
copyCreds: hostCopyCreds,
|
|
660
710
|
});
|
|
661
|
-
// Register the dispatched run in the LOCAL session index so it shows
|
|
662
|
-
// up in `agents sessions` and resolves by id/name, even though its
|
|
663
|
-
// transcript lives on the host. No-op when no session id was captured.
|
|
664
|
-
registerHostSession(task, { cwd: process.cwd(), prompt });
|
|
665
711
|
if (options.follow === false) {
|
|
666
712
|
// The handle the caller uses to check on the run: the name if given,
|
|
667
713
|
// else the real host-task id (never the old literal `<id>`). Steer
|
|
@@ -776,7 +822,7 @@ export function registerRunCommand(program) {
|
|
|
776
822
|
await warnUnpushedWork(resumeExec.cwd ?? process.cwd());
|
|
777
823
|
process.exit(resumeExit);
|
|
778
824
|
}
|
|
779
|
-
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([
|
|
780
826
|
import('../lib/exec.js'),
|
|
781
827
|
import('../lib/agents.js'),
|
|
782
828
|
import('../lib/profiles.js'),
|
|
@@ -814,6 +860,11 @@ export function registerRunCommand(program) {
|
|
|
814
860
|
// produced item, and drives the teams supervisor to drain — no `teams`
|
|
815
861
|
// subcommands needed.
|
|
816
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;
|
|
817
868
|
const cwd = options.cwd ?? process.cwd();
|
|
818
869
|
if (isValidAgent(rawAgent)) {
|
|
819
870
|
agent = rawAgent;
|
|
@@ -895,6 +946,8 @@ export function registerRunCommand(program) {
|
|
|
895
946
|
workflowSubagentTargets.push(dest);
|
|
896
947
|
copied++;
|
|
897
948
|
}
|
|
949
|
+
if (copied > 0)
|
|
950
|
+
workflowHasSubagents = true;
|
|
898
951
|
if (allowedAgents !== undefined) {
|
|
899
952
|
// Surface any allowedAgents entry with no matching subagent file, and
|
|
900
953
|
// report how many were filtered out, so the scope is auditable.
|
|
@@ -938,7 +991,9 @@ export function registerRunCommand(program) {
|
|
|
938
991
|
}
|
|
939
992
|
// Auto-inject secrets bundles declared in the workflow's frontmatter `secrets:` field.
|
|
940
993
|
// Union with any --secrets flags the user passed; dedupe. Skip when --no-auto-secrets is set.
|
|
941
|
-
|
|
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) {
|
|
942
997
|
const declared = workflowFrontmatter?.secrets ?? [];
|
|
943
998
|
if (declared.length > 0) {
|
|
944
999
|
const existing = new Set(options.secrets);
|
|
@@ -975,8 +1030,14 @@ export function registerRunCommand(program) {
|
|
|
975
1030
|
}
|
|
976
1031
|
else if (hasScoping) {
|
|
977
1032
|
if (tools && tools.length > 0) {
|
|
978
|
-
|
|
979
|
-
|
|
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
|
+
}
|
|
980
1041
|
}
|
|
981
1042
|
if (mcpServerNames && mcpServerNames.length > 0) {
|
|
982
1043
|
const servers = getMcpServersByName(mcpServerNames, { cwd });
|
|
@@ -1159,8 +1220,12 @@ export function registerRunCommand(program) {
|
|
|
1159
1220
|
process.exit(1);
|
|
1160
1221
|
}
|
|
1161
1222
|
const strategy = options.balanced ? 'balanced' : explicitStrategy ?? configuredStrategy;
|
|
1162
|
-
// Strategy only applies to bare agent invocations. Explicit @version
|
|
1163
|
-
// 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).
|
|
1164
1229
|
if (strategy !== 'pinned' || options.balanced || explicitStrategy) {
|
|
1165
1230
|
if (version) {
|
|
1166
1231
|
process.stderr.write(chalk.yellow(`[agents] strategy ${strategy} ignored: version ${version} is pinned\n`));
|
|
@@ -1168,9 +1233,6 @@ export function registerRunCommand(program) {
|
|
|
1168
1233
|
else if (fromProfile) {
|
|
1169
1234
|
process.stderr.write(chalk.yellow(`[agents] strategy ${strategy} ignored: profile pins its own version/auth\n`));
|
|
1170
1235
|
}
|
|
1171
|
-
else if (options.fallback) {
|
|
1172
|
-
process.stderr.write(chalk.yellow(`[agents] strategy ${strategy} ignored: --fallback pins versions directly\n`));
|
|
1173
|
-
}
|
|
1174
1236
|
else {
|
|
1175
1237
|
try {
|
|
1176
1238
|
const resolved = await resolveRunVersion(agent, strategy, cwd);
|
|
@@ -1323,6 +1385,10 @@ export function registerRunCommand(program) {
|
|
|
1323
1385
|
caller: `agent ${agent}`,
|
|
1324
1386
|
keys: secretsKeysSubset,
|
|
1325
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(),
|
|
1326
1392
|
});
|
|
1327
1393
|
const entries = describeBundle(bundle);
|
|
1328
1394
|
const counts = {};
|
|
@@ -1443,17 +1509,23 @@ export function registerRunCommand(program) {
|
|
|
1443
1509
|
// path (continuing the session via /continue). Because this injects into
|
|
1444
1510
|
// the same `fallback` array `--fallback` uses, it must only arm for run
|
|
1445
1511
|
// shapes that accept a fallback chain — shouldArmRotationFailover excludes
|
|
1446
|
-
// acp/loop/resume-checkpoint (which reject a non-empty fallback below)
|
|
1447
|
-
// interactive/no-prompt runs
|
|
1448
|
-
//
|
|
1449
|
-
//
|
|
1450
|
-
//
|
|
1451
|
-
//
|
|
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.)
|
|
1452
1525
|
if (shouldArmRotationFailover({
|
|
1453
1526
|
hasRotation: !!rotationResult,
|
|
1454
1527
|
hasVersion: !!version,
|
|
1455
1528
|
hasPrompt: prompt !== undefined,
|
|
1456
|
-
explicitFallback: fallback.length > 0,
|
|
1457
1529
|
interactive: !!options.interactive,
|
|
1458
1530
|
acp: !!options.acp,
|
|
1459
1531
|
loop: !!options.loop,
|
|
@@ -1461,7 +1533,7 @@ export function registerRunCommand(program) {
|
|
|
1461
1533
|
})) {
|
|
1462
1534
|
const failover = rotationFailoverChain(rotationResult, version);
|
|
1463
1535
|
if (failover.length > 0) {
|
|
1464
|
-
fallback.
|
|
1536
|
+
fallback.unshift(...failover);
|
|
1465
1537
|
if (!options.quiet) {
|
|
1466
1538
|
const accounts = failover.map(f => `${f.agent}@${f.version}`).join(', ');
|
|
1467
1539
|
process.stderr.write(chalk.gray(`[agents] rate-limit failover armed: ${accounts}\n`));
|
package/dist/commands/feed.d.ts
CHANGED
|
@@ -13,6 +13,10 @@ import type { Command } from 'commander';
|
|
|
13
13
|
import { type OpenBlock } from '../lib/feed.js';
|
|
14
14
|
import { type OutcomeGroup, type SessionOutcomeHint } from '../lib/feed-outcome.js';
|
|
15
15
|
export declare const FEED_NO_FANOUT_ENV = "AGENTS_FEED_LOCAL";
|
|
16
|
+
/** Right-hand masthead summary: `N blocks · M agents`. */
|
|
17
|
+
export declare function formatFeedMastheadRight(blocks: OpenBlock[]): string;
|
|
18
|
+
/** Reply hint matching the shared fleet-comms reply line. */
|
|
19
|
+
export declare function formatFeedReplyHint(mailboxId: string): string;
|
|
16
20
|
export declare function parseRemoteFeed(stdout: string, machine: string): OpenBlock[];
|
|
17
21
|
/** Merge local and remote rows, keeping the first copy of a host/session block. */
|
|
18
22
|
export declare function mergeFeedBlocks(...groups: OpenBlock[][]): OpenBlock[];
|