@phnx-labs/agents-cli 1.22.24 → 1.22.26
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 +350 -1
- package/README.md +31 -6
- package/dist/bin/agents +0 -0
- package/dist/browser.js +14 -4
- package/dist/commands/apply.js +52 -8
- package/dist/commands/browser.js +35 -0
- package/dist/commands/cloud.js +9 -5
- package/dist/commands/doctor.d.ts +24 -0
- package/dist/commands/doctor.js +108 -9
- package/dist/commands/exec.js +19 -16
- package/dist/commands/feed.d.ts +5 -0
- package/dist/commands/feed.js +21 -1
- package/dist/commands/focus.js +2 -2
- package/dist/commands/insights.d.ts +25 -19
- package/dist/commands/insights.js +107 -33
- package/dist/commands/menubar.js +8 -0
- package/dist/commands/reconnect.d.ts +46 -0
- package/dist/commands/reconnect.js +109 -0
- package/dist/commands/routines.d.ts +3 -0
- package/dist/commands/routines.js +72 -49
- package/dist/commands/run-cloud.js +1 -1
- package/dist/commands/secrets.d.ts +2 -8
- package/dist/commands/secrets.js +29 -105
- package/dist/commands/sessions-browser.d.ts +1 -1
- package/dist/commands/sessions-browser.js +27 -7
- package/dist/commands/sessions-resume.js +3 -2
- package/dist/commands/sessions.d.ts +13 -1
- package/dist/commands/sessions.js +28 -2
- package/dist/commands/setup-secrets.d.ts +1 -0
- package/dist/commands/setup-secrets.js +1 -1
- package/dist/commands/setup-watchdog.js +5 -10
- package/dist/commands/setup.d.ts +26 -3
- package/dist/commands/setup.js +106 -47
- package/dist/commands/teams.d.ts +6 -0
- package/dist/commands/teams.js +46 -2
- package/dist/commands/trends.d.ts +8 -0
- package/dist/commands/trends.js +10 -156
- package/dist/commands/watchdog.d.ts +3 -4
- package/dist/commands/watchdog.js +26 -66
- package/dist/index.js +37 -2
- package/dist/lib/agents.d.ts +11 -0
- package/dist/lib/agents.js +155 -23
- package/dist/lib/analytics/dashboard.d.ts +10 -6
- package/dist/lib/analytics/dashboard.js +6 -4
- package/dist/lib/analytics/mix-commands.d.ts +53 -0
- package/dist/lib/analytics/mix-commands.js +229 -0
- package/dist/lib/analytics/recipes.d.ts +19 -14
- package/dist/lib/analytics/recipes.js +4 -2
- package/dist/lib/browser/ipc.d.ts +26 -0
- package/dist/lib/browser/ipc.js +139 -24
- package/dist/lib/browser/profiles.d.ts +11 -0
- package/dist/lib/browser/profiles.js +1 -1
- package/dist/lib/browser/stream.d.ts +14 -0
- package/dist/lib/browser/stream.js +71 -0
- package/dist/lib/channels/owner-sink.d.ts +27 -0
- package/dist/lib/channels/owner-sink.js +93 -0
- package/dist/lib/cloud/cursor.d.ts +79 -0
- package/dist/lib/cloud/cursor.js +228 -0
- package/dist/lib/cloud/registry.js +2 -0
- package/dist/lib/cloud/types.d.ts +7 -2
- package/dist/lib/cloud/types.js +14 -0
- package/dist/lib/crabbox/cli.d.ts +2 -2
- package/dist/lib/crabbox/config.d.ts +7 -8
- package/dist/lib/crabbox/config.js +14 -14
- package/dist/lib/crabbox/lease.d.ts +11 -4
- package/dist/lib/crabbox/lease.js +40 -8
- package/dist/lib/crabbox/setup-copy.d.ts +5 -0
- package/dist/lib/crabbox/setup-copy.js +17 -1
- package/dist/lib/daemon.js +27 -1
- package/dist/lib/device-config.js +7 -0
- package/dist/lib/devices/doctor-findings.d.ts +13 -1
- package/dist/lib/devices/doctor-findings.js +73 -2
- package/dist/lib/events.d.ts +9 -0
- package/dist/lib/events.js +58 -0
- package/dist/lib/exec.d.ts +3 -3
- package/dist/lib/exec.js +24 -10
- package/dist/lib/feed-outcome.d.ts +3 -0
- package/dist/lib/feed-outcome.js +18 -10
- package/dist/lib/feed.d.ts +4 -0
- package/dist/lib/fleet/apply.d.ts +59 -3
- package/dist/lib/fleet/apply.js +183 -6
- package/dist/lib/fleet/types.d.ts +21 -2
- package/dist/lib/hooks/cache.js +15 -0
- package/dist/lib/hosts/passthrough.d.ts +44 -0
- package/dist/lib/hosts/passthrough.js +84 -12
- package/dist/lib/hosts/ready.d.ts +2 -0
- package/dist/lib/hosts/ready.js +10 -1
- package/dist/lib/hosts/reconnect.d.ts +14 -12
- package/dist/lib/hosts/reconnect.js +41 -40
- package/dist/lib/mcp.js +5 -1
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/snapshot.d.ts +15 -0
- package/dist/lib/menubar/snapshot.js +40 -0
- package/dist/lib/plugins.js +13 -1
- package/dist/lib/resources/mcp.js +3 -0
- package/dist/lib/routine-process-cleanup.d.ts +9 -0
- package/dist/lib/routine-process-cleanup.js +73 -0
- package/dist/lib/routines.js +14 -2
- package/dist/lib/runner.d.ts +0 -3
- package/dist/lib/runner.js +6 -19
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/bundles.js +1 -20
- package/dist/lib/secrets/filestore.d.ts +2 -0
- package/dist/lib/secrets/filestore.js +13 -0
- package/dist/lib/secrets/push.d.ts +94 -0
- package/dist/lib/secrets/push.js +145 -0
- package/dist/lib/secrets/rc-hygiene.d.ts +14 -0
- package/dist/lib/secrets/rc-hygiene.js +14 -1
- package/dist/lib/secrets/reaper.d.ts +15 -1
- package/dist/lib/secrets/reaper.js +30 -3
- package/dist/lib/session/active.d.ts +4 -0
- package/dist/lib/session/db.d.ts +21 -3
- package/dist/lib/session/db.js +229 -15
- package/dist/lib/session/discover.d.ts +1 -0
- package/dist/lib/session/discover.js +115 -19
- package/dist/lib/session/insights.d.ts +18 -0
- package/dist/lib/session/insights.js +143 -1
- package/dist/lib/session/remote-list.d.ts +2 -0
- package/dist/lib/session/remote-list.js +1 -0
- package/dist/lib/session/session-cache.d.ts +4 -4
- package/dist/lib/session/session-cache.js +4 -4
- package/dist/lib/session/tool-index.js +133 -22
- package/dist/lib/session/tool-store.d.ts +26 -2
- package/dist/lib/session/tool-store.js +36 -17
- package/dist/lib/shims.js +21 -1
- package/dist/lib/signin-badge.js +2 -0
- package/dist/lib/ssh-exec.js +8 -2
- package/dist/lib/startup/command-registry.d.ts +16 -0
- package/dist/lib/startup/command-registry.js +46 -0
- package/dist/lib/teams/agents.d.ts +13 -0
- package/dist/lib/teams/agents.js +76 -8
- package/dist/lib/teams/parsers.d.ts +1 -1
- package/dist/lib/teams/placement-probe.d.ts +21 -0
- package/dist/lib/teams/placement-probe.js +135 -0
- package/dist/lib/teams/scheduler.d.ts +74 -1
- package/dist/lib/teams/scheduler.js +187 -10
- package/dist/lib/types.d.ts +1 -1
- package/dist/lib/versions.js +16 -1
- package/dist/lib/watchdog/service.d.ts +17 -0
- package/dist/lib/watchdog/service.js +39 -0
- package/package.json +1 -1
package/dist/commands/apply.js
CHANGED
|
@@ -15,6 +15,7 @@ import chalk from 'chalk';
|
|
|
15
15
|
import { setHelpSections } from '../lib/help.js';
|
|
16
16
|
import { machineId } from '../lib/session/sync/config.js';
|
|
17
17
|
import { loadDevices, isControlDevice } from '../lib/devices/registry.js';
|
|
18
|
+
import { isHostPinned, managedKnownHostsPath } from '../lib/devices/known-hosts.js';
|
|
18
19
|
import { ensureDevicesRegistered } from '../lib/devices/sync.js';
|
|
19
20
|
import { readFleetFile, resolveDesired } from '../lib/fleet/manifest.js';
|
|
20
21
|
import { snapshotAuth, materializeAuth, parseAuthBundle, KEYCHAIN_BOUND_ON_MAC, isCredentialSafeToPropagate } from '../lib/fleet/auth-sync.js';
|
|
@@ -82,7 +83,10 @@ function renderPlan(plan) {
|
|
|
82
83
|
}
|
|
83
84
|
return chalk.cyan('↑ ' + acts.map((a) => a.agent ?? a.kind.replace('-cli', '')).join(','));
|
|
84
85
|
};
|
|
85
|
-
|
|
86
|
+
// Only show the secrets column when the manifest declares any — an all-`-`
|
|
87
|
+
// column on every fleet that uses no bundles is noise.
|
|
88
|
+
const anySecrets = rows.some((r) => r.actions.some((a) => a.kind === 'push-secret' || a.kind === 'needs-secret'));
|
|
89
|
+
const header = ` ${'device'.padEnd(nameWidth)} ${'agents-cli'.padEnd(12)}${'agents'.padEnd(20)}${'config'.padEnd(10)}${anySecrets ? 'login'.padEnd(18) + 'secrets' : 'login'}`;
|
|
86
90
|
console.log(chalk.gray(header));
|
|
87
91
|
for (const row of rows) {
|
|
88
92
|
const cli = row.probe.reachable
|
|
@@ -100,10 +104,33 @@ function renderPlan(plan) {
|
|
|
100
104
|
? (row.actions.some((a) => a.kind === 'sync-config') ? chalk.cyan('↑ sync') : chalk.green('ok'))
|
|
101
105
|
: chalk.gray('-');
|
|
102
106
|
const loginCell = cell(row, ['push-login', 'needs-login'], `${row.desired.agents.length}/${row.desired.agents.length}`);
|
|
103
|
-
|
|
107
|
+
const secretsCell = (() => {
|
|
108
|
+
if (!anySecrets)
|
|
109
|
+
return '';
|
|
110
|
+
if (!row.probe.reachable)
|
|
111
|
+
return chalk.gray('- offline');
|
|
112
|
+
const push = row.actions.filter((a) => a.kind === 'push-secret').length;
|
|
113
|
+
const blocked = row.actions.filter((a) => a.kind === 'needs-secret').length;
|
|
114
|
+
if (push === 0 && blocked === 0)
|
|
115
|
+
return chalk.green('ok');
|
|
116
|
+
if (push > 0 && blocked === 0)
|
|
117
|
+
return chalk.cyan(`↑ push ${push}`);
|
|
118
|
+
if (push === 0)
|
|
119
|
+
return chalk.yellow(`blocked ${blocked}`);
|
|
120
|
+
return chalk.yellow(`↑ push ${push} · blocked ${blocked}`);
|
|
121
|
+
})();
|
|
122
|
+
const loginPart = anySecrets ? stripPad(loginCell, 18) + secretsCell : loginCell;
|
|
123
|
+
console.log(` ${row.device.padEnd(nameWidth)} ${stripPad(cli, 12)}${stripPad(agentsCell, 20)}${stripPad(configCell, 10)}${loginPart}`);
|
|
104
124
|
}
|
|
105
125
|
console.log();
|
|
106
126
|
console.log(chalk.gray(` ${plan.actions.length} action(s) across ${rows.filter((r) => r.probe.reachable).length} reachable device(s)`));
|
|
127
|
+
// The capability is opt-in, so when it is OFF and the manifest declares
|
|
128
|
+
// bundles, say that it exists. Otherwise an operator reads "manual recreate"
|
|
129
|
+
// and concludes there is no supported path — which is exactly the conclusion
|
|
130
|
+
// that led to a master key being hand-exported across the fleet (RUSH-1968).
|
|
131
|
+
if (anySecrets && !rows.some((r) => r.actions.some((a) => a.kind === 'push-secret'))) {
|
|
132
|
+
console.log(chalk.gray(' secrets: not pushed. `--provision-secrets` pushes declared bundles to devices whose host key is pinned.'));
|
|
133
|
+
}
|
|
107
134
|
// Distinguish *why* a login can't be propagated: macOS keychain-bound,
|
|
108
135
|
// single-use rotating refresh token (never copied), or the source simply not
|
|
109
136
|
// being signed in to that agent (no portable file).
|
|
@@ -130,14 +157,15 @@ function renderPlan(plan) {
|
|
|
130
157
|
if (noToken.length > 0) {
|
|
131
158
|
console.log(chalk.yellow(` manual login needed (no portable token on source): ${noToken.join(', ')}`));
|
|
132
159
|
}
|
|
133
|
-
// Secrets bundles are declared once for the fleet; surface the distinct set
|
|
134
|
-
//
|
|
160
|
+
// Secrets bundles are declared once for the fleet; surface the distinct set the
|
|
161
|
+
// gate did NOT push, so a refusal is never silent. "never pushed" used to be
|
|
162
|
+
// literally true here and no longer is — `--provision-secrets` pushes them.
|
|
135
163
|
const bundles = [...new Set(rows.flatMap((r) => r.secretsNeeded))];
|
|
136
164
|
if (bundles.length > 0) {
|
|
137
165
|
const shown = bundles.slice(0, 12);
|
|
138
166
|
const more = bundles.length - shown.length;
|
|
139
167
|
const list = shown.join(', ') + (more > 0 ? `, +${more} more` : '');
|
|
140
|
-
console.log(chalk.yellow(` ${bundles.length} secrets bundle(s)
|
|
168
|
+
console.log(chalk.yellow(` ${bundles.length} secrets bundle(s) not pushed — recreate where missing: ${list}`));
|
|
141
169
|
}
|
|
142
170
|
}
|
|
143
171
|
/** padEnd on the visible width, ignoring chalk color codes. Exported for tests. */
|
|
@@ -218,11 +246,24 @@ async function runApply(opts) {
|
|
|
218
246
|
// Probe every target device in parallel.
|
|
219
247
|
const nameToProfile = new Map(desired.map((d) => [d.device, registry[d.device]]));
|
|
220
248
|
const withVersions = rosterNeedsVersions(desired);
|
|
249
|
+
// One extra `secrets list --json` per device, and only when it can change the
|
|
250
|
+
// plan: the manifest declares bundles AND provisioning is on. Same cost
|
|
251
|
+
// discipline as `withVersions` — a fleet that uses no bundles never pays it.
|
|
252
|
+
const withSecrets = opts.provisionSecrets === true && (manifest.secrets?.bundles?.length ?? 0) > 0;
|
|
221
253
|
console.log(chalk.gray(`Probing ${desired.length} device(s)…`));
|
|
222
|
-
const probeList = await pool(desired, 6, async (d) => probeDevice(nameToProfile.get(d.device), { withVersions }));
|
|
254
|
+
const probeList = await pool(desired, 6, async (d) => probeDevice(nameToProfile.get(d.device), { withVersions, withSecrets }));
|
|
223
255
|
const probes = new Map(probeList.map((p) => [p.device, p]));
|
|
224
256
|
const targetCliVersion = localCliVersion();
|
|
225
|
-
let plan = diffFleet(desired, probes, {
|
|
257
|
+
let plan = diffFleet(desired, probes, {
|
|
258
|
+
targetCliVersion,
|
|
259
|
+
sourceAuth,
|
|
260
|
+
secretsBundles: manifest.secrets?.bundles,
|
|
261
|
+
provisionSecrets: opts.provisionSecrets === true,
|
|
262
|
+
forceSecrets: opts.force === true,
|
|
263
|
+
// Same bar as `exec --copy-creds` (EXEC-34): credential values only ever go
|
|
264
|
+
// to a host whose key we already pinned.
|
|
265
|
+
isHostPinned: (device) => isHostPinned(device, managedKnownHostsPath()),
|
|
266
|
+
});
|
|
226
267
|
// --only filter.
|
|
227
268
|
if (opts.only) {
|
|
228
269
|
const keep = new Set();
|
|
@@ -242,7 +283,8 @@ async function runApply(opts) {
|
|
|
242
283
|
return;
|
|
243
284
|
// `needs-login`/`needs-secret` are surfaced manual reminders, not executable
|
|
244
285
|
// mutations — exclude them so an otherwise-converged fleet still says "nothing
|
|
245
|
-
// to do" instead of looping forever on un-actionable surfacing.
|
|
286
|
+
// to do" instead of looping forever on un-actionable surfacing. `push-secret`
|
|
287
|
+
// IS executable and deliberately stays counted.
|
|
246
288
|
if (plan.actions.filter((a) => a.kind !== 'needs-login' && a.kind !== 'needs-secret').length === 0) {
|
|
247
289
|
console.log(chalk.green('\nNothing to do — fleet already matches the profile.'));
|
|
248
290
|
return;
|
|
@@ -301,6 +343,8 @@ export function configureApplyCommand(cmd) {
|
|
|
301
343
|
.option('--agent <specs...>', 'Override the roster for targeted device(s): install these specs instead of the manifest\'s. Use `claude@all` to replicate every version installed on this machine.')
|
|
302
344
|
.option('--only <dims>', 'Limit to dimensions: comma list of agents,config,login')
|
|
303
345
|
.option('--no-login', 'Do not propagate logins')
|
|
346
|
+
.option('--provision-secrets', "Push the manifest's declared secrets bundles to each device (OFF by default; moves credential values over SSH, and only to a device whose host key is already pinned)")
|
|
347
|
+
.option('--force', 'With --provision-secrets: re-push a bundle the device already has')
|
|
304
348
|
.addOption(new Option('--recv-auth', 'internal: receive an auth bundle on stdin').hideHelp())
|
|
305
349
|
.action(async (opts) => {
|
|
306
350
|
try {
|
package/dist/commands/browser.js
CHANGED
|
@@ -20,6 +20,7 @@ import { isInteractiveTerminal } from './utils.js';
|
|
|
20
20
|
import { registerCommandGroups, setHelpSections } from '../lib/help.js';
|
|
21
21
|
import { buildHar } from '../lib/browser/har.js';
|
|
22
22
|
import { getCliVersion } from '../lib/version.js';
|
|
23
|
+
import { runBrowserIPCStream } from '../lib/browser/stream.js';
|
|
23
24
|
/**
|
|
24
25
|
* Resolve which browser task a command targets. Order:
|
|
25
26
|
* 1. `--task <name>` flag (explicit per-command override)
|
|
@@ -48,6 +49,7 @@ const TASK_OPTION_DESC = 'Task name (defaults to $AGENTS_BROWSER_TASK)';
|
|
|
48
49
|
// trailing "Other commands" section automatically.
|
|
49
50
|
const BROWSER_HELP_GROUPS = [
|
|
50
51
|
{ title: 'Session lifecycle', names: ['start', 'done', 'status'] },
|
|
52
|
+
{ title: 'Fast action loop', names: ['stream'] },
|
|
51
53
|
{
|
|
52
54
|
title: 'Drive the page',
|
|
53
55
|
names: ['navigate', 'tabs', 'screenshot', 'evaluate', 'click', 'type', 'press', 'wait'],
|
|
@@ -79,6 +81,9 @@ export function registerBrowserCommand(program) {
|
|
|
79
81
|
agents browser navigate https://example.com
|
|
80
82
|
agents browser screenshot
|
|
81
83
|
|
|
84
|
+
# Keep one process and daemon socket warm for repeated actions
|
|
85
|
+
agents browser stream --task "$AGENTS_BROWSER_TASK"
|
|
86
|
+
|
|
82
87
|
# Drive another machine's browser (needs its consent — see remote-control)
|
|
83
88
|
agents browser start --host zion
|
|
84
89
|
|
|
@@ -654,6 +659,36 @@ function registerTaskCommands(browser) {
|
|
|
654
659
|
? 'Other fleet machines can now drive this browser via `browser --host <this-device>`.'
|
|
655
660
|
: 'Cross-machine `browser --host` drives to this machine are refused.');
|
|
656
661
|
});
|
|
662
|
+
const stream = browser
|
|
663
|
+
.command('stream')
|
|
664
|
+
.description('Keep one process and daemon IPC socket open; read NDJSON requests from stdin and write NDJSON responses')
|
|
665
|
+
.option(TASK_OPTION_FLAG, 'Default task for requests that omit `task` (defaults to $AGENTS_BROWSER_TASK)')
|
|
666
|
+
.action(async (opts) => {
|
|
667
|
+
await runBrowserIPCStream({
|
|
668
|
+
input: process.stdin,
|
|
669
|
+
output: process.stdout,
|
|
670
|
+
task: opts.task ?? process.env.AGENTS_BROWSER_TASK,
|
|
671
|
+
actor: resolveActor().id,
|
|
672
|
+
launchId: process.env.AGENT_LAUNCH_ID,
|
|
673
|
+
});
|
|
674
|
+
});
|
|
675
|
+
setHelpSections(stream, {
|
|
676
|
+
examples: `
|
|
677
|
+
# Batch two warm actions through one process and one daemon connection
|
|
678
|
+
printf '%s\\n' \\
|
|
679
|
+
'{"action":"screenshot","path":"/tmp/page.jpg"}' \\
|
|
680
|
+
'{"action":"click","atX":320,"atY":540}' \\
|
|
681
|
+
| agents browser stream --task "$AGENTS_BROWSER_TASK"
|
|
682
|
+
|
|
683
|
+
# Keep the command open and send one JSON object per line from a long-lived shell
|
|
684
|
+
agents browser stream --task "$AGENTS_BROWSER_TASK"
|
|
685
|
+
`,
|
|
686
|
+
notes: `
|
|
687
|
+
stdout is protocol-only: one compact JSON response for each non-empty input line.
|
|
688
|
+
Malformed JSON returns an error response without closing the stream.
|
|
689
|
+
The first start response becomes the default task for later lines in the same stream.
|
|
690
|
+
`,
|
|
691
|
+
});
|
|
657
692
|
browser
|
|
658
693
|
.command('start')
|
|
659
694
|
.description('Start a browser task. Pass --profile <name>; omit to use your configured default (`agents browser profiles set-default`), else auto-pick an installed Chromium-family browser.')
|
package/dist/commands/cloud.js
CHANGED
|
@@ -25,14 +25,15 @@ export function statusColor(status) {
|
|
|
25
25
|
export function registerCloudCommands(program) {
|
|
26
26
|
const cloud = program
|
|
27
27
|
.command('cloud', { hidden: true })
|
|
28
|
-
.description('Dispatch and manage cloud agent tasks across providers (Rush, Codex, Factory, Antigravity).')
|
|
28
|
+
.description('Dispatch and manage cloud agent tasks across providers (Rush, Codex, Cursor, Factory, Antigravity, Host).')
|
|
29
29
|
.addHelpText('after', `
|
|
30
30
|
Each agent runs in its own cloud. Pass --agent and the provider is auto-selected
|
|
31
|
-
(claude→rush, codex→codex, droid→factory, antigravity→antigravity); --provider overrides.
|
|
31
|
+
(claude→rush, codex→codex, cursor→cursor, droid→factory, antigravity→antigravity); --provider overrides.
|
|
32
32
|
|
|
33
33
|
Providers:
|
|
34
34
|
rush Rush Cloud — Claude against a GitHub repo + branch → PR
|
|
35
35
|
codex Codex Cloud — runs in a pre-built Codex environment (--env)
|
|
36
|
+
cursor Cursor Cloud Agents — v1 REST API, with optional GitHub repos
|
|
36
37
|
factory Factory Droid Computer — droid exec on a cloud VM (--computer)
|
|
37
38
|
antigravity Gemini Managed Agents — Antigravity harness in a remote sandbox
|
|
38
39
|
|
|
@@ -49,6 +50,9 @@ Examples:
|
|
|
49
50
|
# Codex Cloud against a saved environment
|
|
50
51
|
agents cloud run "add pytest fixtures for the new billing module" --provider codex --env env_a1b2c3 --agent codex --timeout 30m
|
|
51
52
|
|
|
53
|
+
# Cursor Cloud Agents against a GitHub repository
|
|
54
|
+
agents cloud run "fix the flaky parser test" --agent cursor --repo acme/example
|
|
55
|
+
|
|
52
56
|
# Factory pod targeting a specific computer (Droid)
|
|
53
57
|
agents cloud run "QA the new onboarding flow end-to-end" --provider factory --computer linux-vm-1 --agent droid
|
|
54
58
|
|
|
@@ -74,9 +78,9 @@ Examples:
|
|
|
74
78
|
cloud
|
|
75
79
|
.command('run [prompt]')
|
|
76
80
|
.description('Dispatch a task to a cloud agent.')
|
|
77
|
-
.option('--provider <id>', 'Cloud backend: rush, codex, factory, antigravity, host (overrides agent auto-routing)')
|
|
78
|
-
.option('--agent <name>', 'Agent to run: claude, codex, droid, antigravity (auto-routes to its native cloud)')
|
|
79
|
-
.option('--repo <owner/repo>', 'GitHub repository. Repeatable for multi-repo dispatch (Rush Cloud
|
|
81
|
+
.option('--provider <id>', 'Cloud backend: rush, codex, cursor, factory, antigravity, host (overrides agent auto-routing)')
|
|
82
|
+
.option('--agent <name>', 'Agent to run: claude, codex, cursor, droid, antigravity (auto-routes to its native cloud)')
|
|
83
|
+
.option('--repo <owner/repo>', 'GitHub repository. Repeatable for multi-repo dispatch (Rush or Cursor Cloud).', (value, previous) => {
|
|
80
84
|
const acc = Array.isArray(previous) ? previous : [];
|
|
81
85
|
acc.push(value);
|
|
82
86
|
return acc;
|
|
@@ -22,11 +22,35 @@
|
|
|
22
22
|
* apply pending sync.
|
|
23
23
|
*/
|
|
24
24
|
import type { Command } from 'commander';
|
|
25
|
+
import { type DoctorFinding } from '../lib/devices/doctor-findings.js';
|
|
25
26
|
import { type VersionResourceReport } from '../lib/doctor-diff.js';
|
|
26
27
|
import { type DuplicateVersionHook } from '../lib/hooks.js';
|
|
27
28
|
import { type SyncStatusRow, type OrphanRow } from '../lib/drift.js';
|
|
28
29
|
import { type FetchStatusMarker } from '../lib/auto-pull.js';
|
|
29
30
|
export declare function wrapLine(prefix: string, text: string, width?: number): string[];
|
|
31
|
+
export declare function asRemoteSecretFindings(raw: unknown, device: string): DoctorFinding[];
|
|
32
|
+
/**
|
|
33
|
+
* Fetch a device's harness inventory by running its top-level `agents doctor
|
|
34
|
+
* --json` (which carries the `fleet` field, RUSH-2027). Separate from
|
|
35
|
+
* {@link probeFleetTarget} — that forwards `teams doctor --json` for per-agent
|
|
36
|
+
* readiness, a different payload. Returns the parsed {@link FleetInventory} or
|
|
37
|
+
* null (unreachable, non-zero exit, unparseable, or an older CLI with no
|
|
38
|
+
* `fleet` field); a null is skipped by the comparator, never a false gap.
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* How long one remote `doctor --json` probe may take.
|
|
42
|
+
*
|
|
43
|
+
* 180s, matching `ChildProcess.doctorTimeout`, which was set to 180 for this
|
|
44
|
+
* same command for this same reason. The previous 30s sat BELOW the command's
|
|
45
|
+
* real cost — 56s measured on `yosemite-m0`, 136s on an idle box — so every slow
|
|
46
|
+
* device silently contributed nothing at all: no inventory, no sign-in, no
|
|
47
|
+
* divergence, no secret findings. A ceiling under the true cost does not make a
|
|
48
|
+
* probe cheap, it makes it always fail.
|
|
49
|
+
*
|
|
50
|
+
* This does not multiply by device count: `fanOutDevices` is `Promise.all`, so
|
|
51
|
+
* whole-fleet wall time is bounded near the slowest box, not 180s x N.
|
|
52
|
+
*/
|
|
53
|
+
export declare const FLEET_INVENTORY_TIMEOUT_MS = 180000;
|
|
30
54
|
export type IssueSeverity = 'critical' | 'warning' | 'info';
|
|
31
55
|
/**
|
|
32
56
|
* One triaged health finding. `severity`/`category`/`subject`/`impact`/`fix` are
|
package/dist/commands/doctor.js
CHANGED
|
@@ -8,7 +8,7 @@ import { enterDoctorOverviewGate, writeDoctorOverviewCache } from '../lib/device
|
|
|
8
8
|
import { fleetDialTarget } from '../lib/devices/connect.js';
|
|
9
9
|
import { compareFleetInventories } from '../lib/devices/fleet-divergence.js';
|
|
10
10
|
import { collectLocalFleetInventory } from '../lib/devices/fleet-inventory.js';
|
|
11
|
-
import { buildLocalFindings, fleetDivergenceToFindings, signInToFindings, renderFindings, } from '../lib/devices/doctor-findings.js';
|
|
11
|
+
import { buildLocalFindings, fleetDivergenceToFindings, signInToFindings, renderFindings, remediationFor, FINDING_SEVERITY, } from '../lib/devices/doctor-findings.js';
|
|
12
12
|
import { getCliVersion } from '../lib/version.js';
|
|
13
13
|
import { resolveHost } from '../lib/hosts/registry.js';
|
|
14
14
|
import { sshExecAsync } from '../lib/ssh-exec.js';
|
|
@@ -26,12 +26,14 @@ import { checkVersionHookWiring, inspectDuplicateVersionHooks, registerHooksToSe
|
|
|
26
26
|
import { isVersionIsolated } from '../lib/versions.js';
|
|
27
27
|
import { computeDrift, checkSyncStatus, countOrphans, computeSourceBehind } from '../lib/drift.js';
|
|
28
28
|
import { readAuthHealthCache, summarizeHostAuth } from '../lib/auth-health.js';
|
|
29
|
+
import { readMeta } from '../lib/state.js';
|
|
30
|
+
import { probeOwnerSink } from '../lib/channels/owner-sink.js';
|
|
29
31
|
import { unifiedDiff, colorizeUnifiedDiff } from '../lib/diff-text.js';
|
|
30
32
|
import { listCliStatus, listCliStatusAsync } from '../lib/cli-resources.js';
|
|
31
33
|
import { setHelpSections } from '../lib/help.js';
|
|
32
34
|
import { heal, healChangedAnything } from '../lib/heal.js';
|
|
33
35
|
import { getEffectiveExecutionPolicy } from '../lib/platform/winpath.js';
|
|
34
|
-
import { scanUserRcFiles } from '../lib/secrets/rc-hygiene.js';
|
|
36
|
+
import { scanUserRcFiles, masterPassphraseInEnv } from '../lib/secrets/rc-hygiene.js';
|
|
35
37
|
import { terminalWidth, truncateToWidth, stringWidth, padToWidth } from '../lib/session/width.js';
|
|
36
38
|
import { readRepoBehindMarkers } from '../lib/auto-pull.js';
|
|
37
39
|
import * as fs from 'fs';
|
|
@@ -80,6 +82,70 @@ function printWrappedLine(prefix, text) {
|
|
|
80
82
|
for (const line of wrapLine(prefix, text))
|
|
81
83
|
console.log(chalk.gray(line));
|
|
82
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Narrow a remote `findings` array to the secret-hygiene rows, or `[]`.
|
|
87
|
+
*
|
|
88
|
+
* Deliberately NOT "forward every remote finding". The aggregator already
|
|
89
|
+
* rebuilds a remote's sign-in rows from its inventory and its divergence rows
|
|
90
|
+
* from the comparator, so forwarding wholesale would double them; and pulling a
|
|
91
|
+
* remote's orphan/drift rows into a fleet readout is a much larger UX change
|
|
92
|
+
* than this fix. The two secret kinds are the ones that are BOTH unrecomputable
|
|
93
|
+
* centrally and security-relevant, which is exactly why they were being lost.
|
|
94
|
+
*
|
|
95
|
+
* **The remote contributes exactly one thing: the KIND.** Severity, message and
|
|
96
|
+
* remediation are all generated HERE. That is not defensiveness for its own
|
|
97
|
+
* sake — the remote runs its own agents-cli, whose version and integrity we do
|
|
98
|
+
* not control, and each forwarded string is load-bearing in a different way:
|
|
99
|
+
*
|
|
100
|
+
* - `remediation` is a command a human copies and runs. Accepting it from the
|
|
101
|
+
* wire is an injection channel, full stop.
|
|
102
|
+
* - `message` is the one place a secret VALUE could re-enter a readout that
|
|
103
|
+
* otherwise guarantees it never prints one. A local builder cannot leak what
|
|
104
|
+
* it never receives.
|
|
105
|
+
* - `severity` decides the CRITICAL section, so a remote could otherwise
|
|
106
|
+
* promote its own warning and bury real criticals under it.
|
|
107
|
+
* - `device` is overwritten with the name we dialled, so no box can pin a
|
|
108
|
+
* finding on another.
|
|
109
|
+
*
|
|
110
|
+
* The cost is detail: the fleet row says which box and which kind, not which
|
|
111
|
+
* file and line. Run `agents doctor` on that box for the specifics — the
|
|
112
|
+
* message says so.
|
|
113
|
+
*/
|
|
114
|
+
const REMOTE_FORWARDED_KINDS = ['rc-secret-export', 'env-secret-export'];
|
|
115
|
+
/** Canonical, locally-authored text for a forwarded kind. Never the remote's. */
|
|
116
|
+
const REMOTE_SECRET_MESSAGE = {
|
|
117
|
+
'rc-secret-export': 'a credential-shaped export was found in this box\'s shell rc files'
|
|
118
|
+
+ ' — run `agents doctor` there for the file and line',
|
|
119
|
+
'env-secret-export': 'AGENTS_SECRETS_PASSPHRASE is set in this box\'s process environment'
|
|
120
|
+
+ ' — run `agents doctor` there for detail',
|
|
121
|
+
};
|
|
122
|
+
export function asRemoteSecretFindings(raw, device) {
|
|
123
|
+
if (!Array.isArray(raw))
|
|
124
|
+
return [];
|
|
125
|
+
const seen = new Set();
|
|
126
|
+
const out = [];
|
|
127
|
+
for (const item of raw) {
|
|
128
|
+
if (!item || typeof item !== 'object' || Array.isArray(item))
|
|
129
|
+
continue;
|
|
130
|
+
const kind = item.kind;
|
|
131
|
+
if (typeof kind !== 'string')
|
|
132
|
+
continue;
|
|
133
|
+
if (!REMOTE_FORWARDED_KINDS.includes(kind))
|
|
134
|
+
continue;
|
|
135
|
+
if (seen.has(kind))
|
|
136
|
+
continue; // one row per kind per box
|
|
137
|
+
seen.add(kind);
|
|
138
|
+
const k = kind;
|
|
139
|
+
const base = {
|
|
140
|
+
severity: FINDING_SEVERITY[k],
|
|
141
|
+
kind: k,
|
|
142
|
+
device,
|
|
143
|
+
message: REMOTE_SECRET_MESSAGE[k],
|
|
144
|
+
};
|
|
145
|
+
out.push({ ...base, remediation: remediationFor({ ...base, remediation: '' }) });
|
|
146
|
+
}
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
83
149
|
async function resolveFleetTargets(opts) {
|
|
84
150
|
const singleName = opts.host || opts.device;
|
|
85
151
|
if (singleName) {
|
|
@@ -155,15 +221,32 @@ async function probeFleetTarget(target) {
|
|
|
155
221
|
* null (unreachable, non-zero exit, unparseable, or an older CLI with no
|
|
156
222
|
* `fleet` field); a null is skipped by the comparator, never a false gap.
|
|
157
223
|
*/
|
|
224
|
+
/**
|
|
225
|
+
* How long one remote `doctor --json` probe may take.
|
|
226
|
+
*
|
|
227
|
+
* 180s, matching `ChildProcess.doctorTimeout`, which was set to 180 for this
|
|
228
|
+
* same command for this same reason. The previous 30s sat BELOW the command's
|
|
229
|
+
* real cost — 56s measured on `yosemite-m0`, 136s on an idle box — so every slow
|
|
230
|
+
* device silently contributed nothing at all: no inventory, no sign-in, no
|
|
231
|
+
* divergence, no secret findings. A ceiling under the true cost does not make a
|
|
232
|
+
* probe cheap, it makes it always fail.
|
|
233
|
+
*
|
|
234
|
+
* This does not multiply by device count: `fanOutDevices` is `Promise.all`, so
|
|
235
|
+
* whole-fleet wall time is bounded near the slowest box, not 180s x N.
|
|
236
|
+
*/
|
|
237
|
+
export const FLEET_INVENTORY_TIMEOUT_MS = 180_000;
|
|
158
238
|
async function probeFleetInventory(target) {
|
|
159
239
|
const isWin = /^win/i.test((target.os ?? '').trim());
|
|
160
240
|
const remoteCmd = buildRemoteAgentsInvocation(['doctor', '--json'], undefined, isWin ? 'windows' : undefined, isWin ? undefined : { PATH: '$HOME/.agents/.cache/shims:$HOME/.local/bin:$PATH' });
|
|
161
|
-
const res = await sshExecAsync(target.sshTarget, remoteCmd, { timeoutMs:
|
|
241
|
+
const res = await sshExecAsync(target.sshTarget, remoteCmd, { timeoutMs: FLEET_INVENTORY_TIMEOUT_MS, multiplex: true });
|
|
162
242
|
if (res.code !== 0)
|
|
163
243
|
return null;
|
|
164
244
|
try {
|
|
165
245
|
const parsed = JSON.parse(res.stdout);
|
|
166
|
-
return
|
|
246
|
+
return {
|
|
247
|
+
inventory: asFleetInventory(parsed.fleet),
|
|
248
|
+
secretFindings: asRemoteSecretFindings(parsed.findings, target.name),
|
|
249
|
+
};
|
|
167
250
|
}
|
|
168
251
|
catch {
|
|
169
252
|
return null;
|
|
@@ -252,19 +335,22 @@ async function runDevicesDoctor(opts) {
|
|
|
252
335
|
fanOutDevices(targets, probeFleetTarget),
|
|
253
336
|
fanOutDevices(targets, probeFleetInventory),
|
|
254
337
|
]);
|
|
255
|
-
const
|
|
338
|
+
const payloadByName = new Map();
|
|
256
339
|
for (const r of inventoryResults)
|
|
257
|
-
|
|
340
|
+
payloadByName.set(r.name, r.status === 'ok' ? (r.value ?? null) : null);
|
|
258
341
|
results.push(...remoteResults.map((r) => {
|
|
259
|
-
const
|
|
342
|
+
const payload = payloadByName.get(r.name) ?? null;
|
|
343
|
+
const inventory = payload?.inventory ?? undefined;
|
|
344
|
+
const secretFindings = payload?.secretFindings;
|
|
260
345
|
if (r.status === 'ok' && r.value)
|
|
261
|
-
return { ...r.value, inventory
|
|
346
|
+
return { ...r.value, inventory, secretFindings };
|
|
262
347
|
return {
|
|
263
348
|
name: r.name,
|
|
264
349
|
online: false,
|
|
265
350
|
error: r.error ?? String(r.reason ?? 'skipped'),
|
|
266
351
|
agents: {},
|
|
267
|
-
inventory
|
|
352
|
+
inventory,
|
|
353
|
+
secretFindings,
|
|
268
354
|
};
|
|
269
355
|
}));
|
|
270
356
|
// Cross-device divergence: compare every device's inventory against the local
|
|
@@ -314,12 +400,16 @@ async function runDevicesDoctor(opts) {
|
|
|
314
400
|
duplicateHooks: inspectDuplicateVersionHooks(cwd),
|
|
315
401
|
hostClis: toHostCliInput(listCliStatus(cwd)),
|
|
316
402
|
rcSecrets: scanUserRcFiles(),
|
|
403
|
+
masterPassphraseInEnv: masterPassphraseInEnv(),
|
|
317
404
|
execPolicy: process.platform === 'win32'
|
|
318
405
|
? { platform: process.platform, policy: getEffectiveExecutionPolicy() }
|
|
319
406
|
: undefined,
|
|
320
407
|
isolatedVersions: localReports
|
|
321
408
|
.filter((rep) => isVersionIsolated(rep.agent, rep.version))
|
|
322
409
|
.map((rep) => `${rep.agent}@${rep.version}`),
|
|
410
|
+
// Can the owner-delivery lane escalate a block from THIS box? Local only —
|
|
411
|
+
// remote boxes self-report it in their own `agents doctor --json`.
|
|
412
|
+
ownerSink: await probeOwnerSink(readMeta()),
|
|
323
413
|
}));
|
|
324
414
|
accounts[localName] = r.inventory?.signIn ?? {};
|
|
325
415
|
continue;
|
|
@@ -335,6 +425,11 @@ async function runDevicesDoctor(opts) {
|
|
|
335
425
|
});
|
|
336
426
|
continue;
|
|
337
427
|
}
|
|
428
|
+
// Secret hygiene the aggregator cannot see for itself — the remote's shell
|
|
429
|
+
// rc files and its process environment. Without this the fleet readout
|
|
430
|
+
// reports a leaking box as clean (RUSH-1968).
|
|
431
|
+
if (r.secretFindings?.length)
|
|
432
|
+
findings.push(...r.secretFindings);
|
|
338
433
|
if (r.inventory?.signIn) {
|
|
339
434
|
findings.push(...signInToFindings(r.name, r.inventory.signIn));
|
|
340
435
|
accounts[r.name] = r.inventory.signIn;
|
|
@@ -1389,12 +1484,16 @@ export function registerDoctorCommand(program) {
|
|
|
1389
1484
|
duplicateHooks,
|
|
1390
1485
|
hostClis: toHostCliInput(hostClis),
|
|
1391
1486
|
rcSecrets: scanUserRcFiles(),
|
|
1487
|
+
masterPassphraseInEnv: masterPassphraseInEnv(),
|
|
1392
1488
|
// getEffectiveExecutionPolicy spawns powershell — a doomed process on
|
|
1393
1489
|
// POSIX, where the advisory never applies. Probe only on Windows.
|
|
1394
1490
|
execPolicy: process.platform === 'win32'
|
|
1395
1491
|
? { platform: process.platform, policy: getEffectiveExecutionPolicy() }
|
|
1396
1492
|
: undefined,
|
|
1397
1493
|
isolatedVersions,
|
|
1494
|
+
// Can the owner-delivery lane (feed/notify) escalate a block from this
|
|
1495
|
+
// box? A factory that cannot escalate is not healthy (RUSH-2262).
|
|
1496
|
+
ownerSink: await probeOwnerSink(readMeta()),
|
|
1398
1497
|
});
|
|
1399
1498
|
if (opts.json) {
|
|
1400
1499
|
const overviewPayload = {
|
package/dist/commands/exec.js
CHANGED
|
@@ -568,8 +568,8 @@ export function registerRunCommand(program) {
|
|
|
568
568
|
.option('--bare', 'With --lease, skip copying your local ~/.agents setup (skills/hooks/commands/MCP) onto the box.')
|
|
569
569
|
.option('--tailscale', 'Lease the box onto your tailnet (reachable only over Tailscale) rather than a public IP.')
|
|
570
570
|
.option('--no-tailscale', 'Force a public-IP lease even when a reuse context would default to Tailscale.')
|
|
571
|
-
.option('--cloud', 'Vendor cloud placement: dispatch to the agent\'s native cloud (claude→rush, codex→codex, droid→factory, antigravity→antigravity) and stream the result. Same dispatch as `agents cloud run --agent <agent>`; tracked by `agents cloud list/status/logs`. Same as --where cloud. Mutually exclusive with --host/--lease and local-run flags.')
|
|
572
|
-
.option('--provider <id>', 'With --cloud: override the agent\'s native cloud provider (rush | codex | factory | antigravity | host).')
|
|
571
|
+
.option('--cloud', 'Vendor cloud placement: dispatch to the agent\'s native cloud (claude→rush, codex→codex, cursor→cursor, droid→factory, antigravity→antigravity) and stream the result. Same dispatch as `agents cloud run --agent <agent>`; tracked by `agents cloud list/status/logs`. Same as --where cloud. Mutually exclusive with --host/--lease and local-run flags.')
|
|
572
|
+
.option('--provider <id>', 'With --cloud: override the agent\'s native cloud provider (rush | codex | cursor | factory | antigravity | host).')
|
|
573
573
|
.option('--repo <owner/repo>', 'With --cloud: GitHub repository. Repeatable for multi-repo dispatch (Rush Cloud only).', (val, prev) => [...prev, val], [])
|
|
574
574
|
.option('--branch <name>', 'With --cloud: target git branch.')
|
|
575
575
|
.option('--cloud-env <id>', 'With --cloud: Codex Cloud environment ID (run\'s --env is the KEY=VAL passthrough, so the cloud env id gets its own flag).');
|
|
@@ -657,8 +657,8 @@ export function registerRunCommand(program) {
|
|
|
657
657
|
Models (cheap|default|best|ultra): agents models <agent[@version]>
|
|
658
658
|
|
|
659
659
|
Headless plan support (a prompt makes the run headless):
|
|
660
|
-
plan works headless on claude, codex, droid, opencode.
|
|
661
|
-
kimi, grok,
|
|
660
|
+
plan works headless on claude, codex, cursor, droid, opencode.
|
|
661
|
+
kimi, grok, antigravity have no headless plan mode — a headless
|
|
662
662
|
--mode plan auto-downgrades to --mode auto (with a stderr warning).
|
|
663
663
|
Interactive plan (omit the prompt) works everywhere it is listed.
|
|
664
664
|
|
|
@@ -1019,19 +1019,21 @@ export function registerRunCommand(program) {
|
|
|
1019
1019
|
const leaseSecretsBundle = process.env.AGENTS_LEASE_SECRETS_BUNDLE;
|
|
1020
1020
|
const nowSecs = Math.floor(Date.now() / 1000);
|
|
1021
1021
|
let reuseSlug = options.box;
|
|
1022
|
-
//
|
|
1023
|
-
//
|
|
1024
|
-
//
|
|
1025
|
-
// pool-reuse match interchangeable with a fresh warmup.
|
|
1022
|
+
// Lease runs share one default warm pool across repositories. The generic
|
|
1023
|
+
// `.crabbox.yaml profile:` remains available to repo sandbox/CI scripts;
|
|
1024
|
+
// `leaseProfile:` is the explicit opt-in for a dedicated lease hot box.
|
|
1026
1025
|
const repoRoot = gitToplevel(leaseCwd);
|
|
1027
|
-
const {
|
|
1028
|
-
const poolProfile = repoRoot ?
|
|
1026
|
+
const { readCrabboxLeaseProfile } = await import('../lib/crabbox/config.js');
|
|
1027
|
+
const poolProfile = repoRoot ? readCrabboxLeaseProfile(repoRoot) : 'default';
|
|
1029
1028
|
if (options.lease && !reuseSlug && !options.fresh) {
|
|
1030
|
-
const { crabboxList } = await import('../lib/crabbox/cli.js');
|
|
1031
|
-
const {
|
|
1029
|
+
const { crabboxList, poolReusableBoxes } = await import('../lib/crabbox/cli.js');
|
|
1030
|
+
const { formatBoxRow } = await import('./lease.js');
|
|
1032
1031
|
let warm = [];
|
|
1033
1032
|
try {
|
|
1034
|
-
warm =
|
|
1033
|
+
warm = poolReusableBoxes(crabboxList({ secretsBundle: leaseSecretsBundle }), {
|
|
1034
|
+
profile: poolProfile,
|
|
1035
|
+
nowSecs,
|
|
1036
|
+
});
|
|
1035
1037
|
}
|
|
1036
1038
|
catch {
|
|
1037
1039
|
warm = []; // crabbox unavailable / no creds → the pool check in leaseAndRun decides
|
|
@@ -1102,7 +1104,7 @@ export function registerRunCommand(program) {
|
|
|
1102
1104
|
}
|
|
1103
1105
|
}
|
|
1104
1106
|
const { detectSignedInRuntimes, resolveClaudeCredentialsBlob, inferLeaseRuntime, profileNeedsBaseRuntimeCredentials } = await import('../lib/crabbox/runtimes.js');
|
|
1105
|
-
const { leaseAndRun } = await import('../lib/crabbox/lease.js');
|
|
1107
|
+
const { leaseAndRun, leaseWorkspaceId } = await import('../lib/crabbox/lease.js');
|
|
1106
1108
|
const { boxAddress } = await import('./lease.js');
|
|
1107
1109
|
const { getConfiguredRunStrategy, resolveRunVersion } = await import('../lib/rotate.js');
|
|
1108
1110
|
const { profileExists, readProfile, resolveProfileEnv } = await import('../lib/profiles.js');
|
|
@@ -1197,7 +1199,7 @@ export function registerRunCommand(program) {
|
|
|
1197
1199
|
? 'the box is kept after the run'
|
|
1198
1200
|
: options.fresh
|
|
1199
1201
|
? 'the box is destroyed after the run'
|
|
1200
|
-
: '
|
|
1202
|
+
: 'the shared-pool box is kept after the run';
|
|
1201
1203
|
console.error(chalk.gray(`${boxLifecycle} · shipping ${whatShips}${credentialRuntimes.length > 0 && leaseEmail ? ` (${leaseEmail})` : ''}; ${boxAfterRun}.`));
|
|
1202
1204
|
// Read the Claude OAuth token from the local Keychain (silent) so it can be
|
|
1203
1205
|
// written to ~/.claude/.credentials.json on the box — otherwise Claude boots
|
|
@@ -1279,6 +1281,7 @@ export function registerRunCommand(program) {
|
|
|
1279
1281
|
reuseBox: reuseSlug,
|
|
1280
1282
|
fresh: options.fresh,
|
|
1281
1283
|
profile: poolProfile,
|
|
1284
|
+
workspaceId: leaseWorkspaceId(repoRoot ?? leaseCwd),
|
|
1282
1285
|
copySetup,
|
|
1283
1286
|
netMode,
|
|
1284
1287
|
onData: (chunk) => router.push(chunk),
|
|
@@ -2455,7 +2458,7 @@ export function registerRunCommand(program) {
|
|
|
2455
2458
|
process.exit(1);
|
|
2456
2459
|
}
|
|
2457
2460
|
// Default CLI mode is the generic 'plan'. Agents without a read-only
|
|
2458
|
-
// mode (antigravity,
|
|
2461
|
+
// mode (antigravity, kiro, …) degrade via resolveMode to their
|
|
2459
2462
|
// safest native mode (modes[0], typically edit). That covers both the
|
|
2460
2463
|
// implicit default and an explicit `--mode plan`, so multi-agent
|
|
2461
2464
|
// scripts can pass a uniform plan flag without per-agent branching.
|
package/dist/commands/feed.d.ts
CHANGED
|
@@ -42,6 +42,8 @@ export declare function prepareLocalFeedBlocks(localBlocks: OpenBlock[], opts: {
|
|
|
42
42
|
dispatch: OpenBlock[];
|
|
43
43
|
filter: ReturnType<typeof filterBlocksForFeed>;
|
|
44
44
|
};
|
|
45
|
+
/** Specific host app + routine provenance for one feed row. */
|
|
46
|
+
export declare function formatFeedRuntime(block: Pick<OpenBlock, 'runtime' | 'origin' | 'routineName'>): string;
|
|
45
47
|
/** Human summary line for one outcome rollup. */
|
|
46
48
|
export declare function formatOutcomeHeader(group: OutcomeGroup): string;
|
|
47
49
|
/** Map active sessions into the lightweight hints outcome enrichment needs. */
|
|
@@ -59,6 +61,9 @@ export declare function sessionHintsFromActive(sessions: Array<{
|
|
|
59
61
|
worktree?: {
|
|
60
62
|
slug?: string;
|
|
61
63
|
};
|
|
64
|
+
host?: string;
|
|
65
|
+
origin?: 'cli' | 'routine';
|
|
66
|
+
routineName?: string;
|
|
62
67
|
}>): SessionOutcomeHint[];
|
|
63
68
|
/**
|
|
64
69
|
* True when a SQLite open/query failed because another writer holds the lock.
|
package/dist/commands/feed.js
CHANGED
|
@@ -16,6 +16,7 @@ import { notifyUrgentBlock } from '../lib/notify.js';
|
|
|
16
16
|
import { gcMailbox } from '../lib/mailbox-gc.js';
|
|
17
17
|
import { isValidMailboxId } from '../lib/mailbox.js';
|
|
18
18
|
import { getActiveSessions } from '../lib/session/active.js';
|
|
19
|
+
import { backfillActiveRowsFromMeta } from './sessions.js';
|
|
19
20
|
import { mailboxIdForActiveSession } from '../lib/mailbox-target.js';
|
|
20
21
|
import { GLYPH, masthead } from '../lib/comms-render.js';
|
|
21
22
|
import { discoverSessions } from '../lib/session/discover.js';
|
|
@@ -154,7 +155,7 @@ export function prepareLocalFeedBlocks(localBlocks, opts) {
|
|
|
154
155
|
}
|
|
155
156
|
function renderBlock(b, localHost, indent = '') {
|
|
156
157
|
const host = b.host !== localHost ? chalk.yellow(` [${b.host}]`) : '';
|
|
157
|
-
const runtime = chalk.gray(b
|
|
158
|
+
const runtime = chalk.gray(formatFeedRuntime(b));
|
|
158
159
|
const age = chalk.gray(relTime(b.ts));
|
|
159
160
|
const cls = b.blockClass ? chalk.gray(`(${b.blockClass})`) : '';
|
|
160
161
|
const consequence = b.consequence && b.consequence !== 'normal' ? chalk.red(`[${b.consequence}]`) : '';
|
|
@@ -218,6 +219,21 @@ function renderBlock(b, localHost, indent = '') {
|
|
|
218
219
|
}
|
|
219
220
|
console.log();
|
|
220
221
|
}
|
|
222
|
+
/** Specific host app + routine provenance for one feed row. */
|
|
223
|
+
export function formatFeedRuntime(block) {
|
|
224
|
+
const host = {
|
|
225
|
+
ghostty: 'Ghostty',
|
|
226
|
+
iterm: 'iTerm',
|
|
227
|
+
terminal: 'terminal',
|
|
228
|
+
kitty: 'Kitty',
|
|
229
|
+
codium: 'VSCodium',
|
|
230
|
+
code: 'VS Code',
|
|
231
|
+
cursor: 'Cursor',
|
|
232
|
+
}[block.runtime.toLowerCase()] ?? block.runtime;
|
|
233
|
+
if (block.origin !== 'routine')
|
|
234
|
+
return host;
|
|
235
|
+
return `${host} · routine${block.routineName ? `:${block.routineName}` : ''}`;
|
|
236
|
+
}
|
|
221
237
|
/** Human summary line for one outcome rollup. */
|
|
222
238
|
export function formatOutcomeHeader(group) {
|
|
223
239
|
const { agents, open, answered, parked } = group.counts;
|
|
@@ -251,6 +267,9 @@ export function sessionHintsFromActive(sessions) {
|
|
|
251
267
|
prUrl: s.pr?.url,
|
|
252
268
|
worktreeSlug: s.worktree?.slug,
|
|
253
269
|
project: s.cwd ? projectKeyFromCwd(s.cwd) : undefined,
|
|
270
|
+
host: s.host,
|
|
271
|
+
origin: s.origin,
|
|
272
|
+
routineName: s.routineName,
|
|
254
273
|
}));
|
|
255
274
|
}
|
|
256
275
|
/**
|
|
@@ -489,6 +508,7 @@ export function registerFeedCommand(program) {
|
|
|
489
508
|
console.error(chalk.yellow('Feed: session index is locked; skipping local outcome enrichment'));
|
|
490
509
|
}
|
|
491
510
|
sessionMetas = loaded.metas;
|
|
511
|
+
backfillActiveRowsFromMeta(sessions, new Map(sessionMetas.map((meta) => [meta.id, meta])));
|
|
492
512
|
}
|
|
493
513
|
const localSignals = buildSessionSignals(sessions, sessionMetas);
|
|
494
514
|
if (opts.pause || opts.kill) {
|