@phnx-labs/agents-cli 1.22.26 → 1.22.27
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 +244 -0
- package/README.md +18 -1
- package/dist/bin/agents +0 -0
- package/dist/commands/bench.d.ts +2 -0
- package/dist/commands/bench.js +101 -0
- package/dist/commands/doctor.js +24 -10
- package/dist/commands/exec.js +17 -16
- package/dist/commands/insights.js +39 -5
- package/dist/commands/output.js +100 -25
- package/dist/commands/resume.d.ts +11 -0
- package/dist/commands/resume.js +51 -0
- package/dist/commands/sessions-picker.js +12 -7
- package/dist/commands/sessions.d.ts +47 -1
- package/dist/commands/sessions.js +345 -37
- package/dist/commands/ssh.js +184 -11
- package/dist/commands/teams.js +3 -3
- package/dist/index.js +16 -20
- package/dist/lib/auth-health.d.ts +47 -3
- package/dist/lib/auth-health.js +74 -15
- package/dist/lib/bench/index.d.ts +4 -0
- package/dist/lib/bench/index.js +4 -0
- package/dist/lib/bench/runner.d.ts +16 -0
- package/dist/lib/bench/runner.js +111 -0
- package/dist/lib/bench/schema.d.ts +5 -0
- package/dist/lib/bench/schema.js +91 -0
- package/dist/lib/bench/storage.d.ts +5 -0
- package/dist/lib/bench/storage.js +32 -0
- package/dist/lib/bench/types.d.ts +40 -0
- package/dist/lib/bench/types.js +1 -0
- package/dist/lib/claude-account-token.d.ts +2 -0
- package/dist/lib/claude-account-token.js +27 -1
- package/dist/lib/crabbox/cli.d.ts +2 -0
- package/dist/lib/crabbox/cli.js +2 -0
- package/dist/lib/crabbox/lease.js +7 -1
- package/dist/lib/daemon.js +20 -0
- package/dist/lib/devices/connect.d.ts +2 -0
- package/dist/lib/devices/connect.js +7 -0
- package/dist/lib/devices/doctor-findings.d.ts +4 -1
- package/dist/lib/devices/doctor-findings.js +15 -1
- package/dist/lib/devices/harness-inventory.d.ts +97 -0
- package/dist/lib/devices/harness-inventory.js +0 -0
- package/dist/lib/devices/registry.d.ts +2 -0
- package/dist/lib/devices/ssh-config.js +3 -0
- package/dist/lib/devices/windows-ssh-enrollment.d.ts +20 -0
- package/dist/lib/devices/windows-ssh-enrollment.js +98 -0
- package/dist/lib/exec.bench.d.ts +1 -0
- package/dist/lib/exec.bench.js +186 -0
- package/dist/lib/exec.js +18 -8
- package/dist/lib/fleet/apply.js +9 -7
- package/dist/lib/fleet/remote-login.d.ts +4 -3
- package/dist/lib/fleet/remote-login.js +11 -9
- package/dist/lib/gemini-settings.d.ts +0 -1
- package/dist/lib/gemini-settings.js +12 -7
- package/dist/lib/hooks.d.ts +8 -5
- package/dist/lib/hooks.js +10 -6
- package/dist/lib/hosts/dispatch.js +7 -3
- package/dist/lib/hosts/passthrough.d.ts +22 -0
- package/dist/lib/hosts/passthrough.js +14 -8
- package/dist/lib/hosts/progress.d.ts +2 -1
- package/dist/lib/hosts/progress.js +4 -3
- package/dist/lib/hosts/providers/devices.js +1 -0
- package/dist/lib/hosts/ready.d.ts +4 -2
- package/dist/lib/hosts/ready.js +24 -8
- package/dist/lib/hosts/reconcile.d.ts +1 -1
- package/dist/lib/hosts/reconcile.js +16 -7
- package/dist/lib/hosts/reconnect.d.ts +45 -12
- package/dist/lib/hosts/reconnect.js +90 -34
- package/dist/lib/hosts/registry.d.ts +2 -2
- package/dist/lib/hosts/registry.js +3 -5
- package/dist/lib/hosts/remote-cmd.d.ts +17 -0
- package/dist/lib/hosts/remote-cmd.js +29 -0
- package/dist/lib/hosts/tasks.d.ts +2 -0
- package/dist/lib/hosts/types.d.ts +1 -0
- package/dist/lib/hosts/types.js +3 -0
- 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/install-menubar.d.ts +9 -6
- package/dist/lib/menubar/install-menubar.js +20 -9
- package/dist/lib/models.d.ts +30 -1
- package/dist/lib/models.js +42 -144
- package/dist/lib/pricing/cost.d.ts +9 -0
- package/dist/lib/pricing/cost.js +24 -0
- package/dist/lib/pricing/index.d.ts +1 -1
- package/dist/lib/pricing/index.js +1 -1
- package/dist/lib/redact.js +8 -3
- package/dist/lib/remote-agents-json.d.ts +32 -0
- package/dist/lib/remote-agents-json.js +47 -16
- package/dist/lib/resource-profiles.js +1 -2
- package/dist/lib/routine-notify-owner.d.ts +102 -0
- package/dist/lib/routine-notify-owner.js +232 -0
- package/dist/lib/routines.d.ts +10 -0
- package/dist/lib/runner.d.ts +9 -1
- package/dist/lib/runner.js +182 -17
- package/dist/lib/sandbox.d.ts +0 -2
- package/dist/lib/sandbox.js +2 -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/agent.js +3 -2
- package/dist/lib/secrets/lease.d.ts +25 -0
- package/dist/lib/secrets/lease.js +44 -0
- package/dist/lib/self-update.d.ts +20 -5
- package/dist/lib/self-update.js +93 -16
- package/dist/lib/session/db.d.ts +19 -2
- package/dist/lib/session/db.js +54 -5
- package/dist/lib/session/discover.d.ts +21 -1
- package/dist/lib/session/discover.js +124 -32
- package/dist/lib/session/insights.d.ts +19 -0
- package/dist/lib/session/insights.js +78 -10
- package/dist/lib/session/recovery.d.ts +17 -1
- package/dist/lib/session/recovery.js +111 -4
- package/dist/lib/session/remote-list.d.ts +10 -9
- package/dist/lib/session/remote-list.js +25 -23
- package/dist/lib/session/resume-owner.d.ts +55 -0
- package/dist/lib/session/resume-owner.js +69 -0
- package/dist/lib/session/team-filter.d.ts +65 -0
- package/dist/lib/session/team-filter.js +98 -3
- package/dist/lib/session/types.d.ts +33 -0
- package/dist/lib/smart-launch.d.ts +31 -5
- package/dist/lib/smart-launch.js +43 -7
- package/dist/lib/ssh-exec.d.ts +27 -0
- package/dist/lib/ssh-exec.js +34 -1
- package/dist/lib/ssh-tunnel.d.ts +3 -2
- package/dist/lib/ssh-tunnel.js +25 -16
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +2 -0
- package/dist/lib/teams/placement-probe.d.ts +1 -1
- package/dist/lib/teams/placement-probe.js +24 -24
- package/dist/lib/tmux/session.d.ts +8 -0
- package/dist/lib/tmux/session.js +22 -0
- package/dist/lib/types.d.ts +2 -0
- package/package.json +1 -1
package/dist/commands/ssh.js
CHANGED
|
@@ -31,7 +31,7 @@ import { resolveDeviceTarget, splitUserHost } from '../lib/devices/resolve-targe
|
|
|
31
31
|
import { clearPendingSentinel } from '../lib/devices/pending.js';
|
|
32
32
|
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
33
33
|
import { hostNameFor, renderSshConfig } from '../lib/devices/ssh-config.js';
|
|
34
|
-
import { ASKPASS_BUNDLE_ENV, ASKPASS_KEY_ENV, buildSshInvocation, fleetDialTarget, writeAskpassShim, } from '../lib/devices/connect.js';
|
|
34
|
+
import { ASKPASS_BUNDLE_ENV, ASKPASS_KEY_ENV, buildSshInvocation, deviceIdentityArgs, fleetDialTarget, writeAskpassShim, } from '../lib/devices/connect.js';
|
|
35
35
|
import { ensureManagedKnownHostsDir, isHostPinned } from '../lib/devices/known-hosts.js';
|
|
36
36
|
import { shouldSyncTerminfo, syncTerminfoToDevice, terminfoHostKey } from '../lib/devices/terminfo.js';
|
|
37
37
|
import { fanOutDevices, fleetHealthSkip, planFleetTargets, remoteFleetTargets, runFleet, skipLabel, upgradeCommand, } from '../lib/devices/fleet.js';
|
|
@@ -44,6 +44,7 @@ import { checkAllClis } from '../lib/teams/agents.js';
|
|
|
44
44
|
import { buildRemoteAgentsInvocation } from '../lib/hosts/remote-cmd.js';
|
|
45
45
|
import { sshExecAsync } from '../lib/ssh-exec.js';
|
|
46
46
|
import { ALL_AGENT_IDS } from '../lib/agents.js';
|
|
47
|
+
import { collectLocalHarnessInventory, groupByAccount, renderAccountsMatrix, renderHarnessMatrix, } from '../lib/devices/harness-inventory.js';
|
|
47
48
|
import { crabboxList, crabboxFind, crabboxSshArgv } from '../lib/crabbox/cli.js';
|
|
48
49
|
import { boxAddress, boxStatus, fmtIdleShort, fmtExpiresShort } from './lease.js';
|
|
49
50
|
import { authCellColor, formatCheckedAge, isDeadVerdict, probeLocalFleetAuth, readAuthHealthCache, summarizeHostAuth, summarizeVerdicts, verdictColor, verdictLabel, writeFleetAuthRows, } from '../lib/auth-health.js';
|
|
@@ -351,7 +352,7 @@ async function probeRemoteFleetStatus(target) {
|
|
|
351
352
|
const isWin = /^win/i.test((target.platform ?? '').trim());
|
|
352
353
|
const env = isWin ? undefined : { PATH: '$HOME/.agents/.cache/shims:$HOME/.local/bin:$PATH' };
|
|
353
354
|
const cmd = buildRemoteAgentsInvocation(['devices', 'status', '--local', '--json'], undefined, isWin ? 'windows' : undefined, env);
|
|
354
|
-
const res = await sshExecAsync(target.dialTarget, cmd, { timeoutMs: 15000, multiplex: true });
|
|
355
|
+
const res = await sshExecAsync(target.dialTarget, cmd, { timeoutMs: 15000, multiplex: true, extraSshArgs: target.extraSshArgs });
|
|
355
356
|
if (res.code !== 0) {
|
|
356
357
|
throw new Error(res.timedOut ? 'timed out' : (res.stderr.trim() || `exit ${res.code ?? 'unknown'}`));
|
|
357
358
|
}
|
|
@@ -361,10 +362,10 @@ async function probeRemoteHealth(target) {
|
|
|
361
362
|
const isWin = /^win/i.test((target.platform ?? '').trim());
|
|
362
363
|
const env = isWin ? undefined : { PATH: '$HOME/.agents/.cache/shims:$HOME/.local/bin:$PATH' };
|
|
363
364
|
const versionCmd = buildRemoteAgentsInvocation(['--version'], undefined, isWin ? 'windows' : undefined, env);
|
|
364
|
-
const versionRes = await sshExecAsync(target.dialTarget, versionCmd, { timeoutMs: 15000, multiplex: true });
|
|
365
|
+
const versionRes = await sshExecAsync(target.dialTarget, versionCmd, { timeoutMs: 15000, multiplex: true, extraSshArgs: target.extraSshArgs });
|
|
365
366
|
const version = versionRes.code === 0 ? versionRes.stdout.trim().split(/\s+/)[0] || null : null;
|
|
366
367
|
const doctorCmd = buildRemoteAgentsInvocation(['doctor', '--json'], undefined, isWin ? 'windows' : undefined, env);
|
|
367
|
-
const doctorRes = await sshExecAsync(target.dialTarget, doctorCmd, { timeoutMs: 30000, multiplex: true });
|
|
368
|
+
const doctorRes = await sshExecAsync(target.dialTarget, doctorCmd, { timeoutMs: 30000, multiplex: true, extraSshArgs: target.extraSshArgs });
|
|
368
369
|
if (doctorRes.code !== 0) {
|
|
369
370
|
throw new Error(doctorRes.timedOut ? 'timed out' : (doctorRes.stderr.trim() || `exit ${doctorRes.code ?? 'unknown'}`));
|
|
370
371
|
}
|
|
@@ -425,6 +426,7 @@ async function runFleetStatus(opts) {
|
|
|
425
426
|
// this is trusted on the default path, not just under `--refresh`.
|
|
426
427
|
skip: fleetHealthSkip(t.skip, statsMap.get(t.device.name)),
|
|
427
428
|
dialTarget: fleetDialTarget(t.device),
|
|
429
|
+
extraSshArgs: deviceIdentityArgs(t.device),
|
|
428
430
|
}));
|
|
429
431
|
const remote = await fanOutDevices(remoteTargets, probeRemoteHealth);
|
|
430
432
|
for (const result of remote) {
|
|
@@ -533,7 +535,7 @@ async function probeRemoteAuth(target) {
|
|
|
533
535
|
const isWin = /^win/i.test((target.platform ?? '').trim());
|
|
534
536
|
const env = isWin ? undefined : { PATH: '$HOME/.agents/.cache/shims:$HOME/.local/bin:$PATH' };
|
|
535
537
|
const cmd = buildRemoteAgentsInvocation(['devices', 'ping', '--local', '--json'], undefined, isWin ? 'windows' : undefined, env);
|
|
536
|
-
const res = await sshExecAsync(target.dialTarget, cmd, { timeoutMs: 15000, multiplex: true });
|
|
538
|
+
const res = await sshExecAsync(target.dialTarget, cmd, { timeoutMs: 15000, multiplex: true, extraSshArgs: target.extraSshArgs });
|
|
537
539
|
if (res.code !== 0) {
|
|
538
540
|
throw new Error(res.timedOut ? 'timed out' : (res.stderr.trim() || `exit ${res.code ?? 'unknown'}`));
|
|
539
541
|
}
|
|
@@ -601,6 +603,7 @@ async function runFleetPing(opts) {
|
|
|
601
603
|
platform: t.device.platform,
|
|
602
604
|
skip: t.skip,
|
|
603
605
|
dialTarget: fleetDialTarget(t.device),
|
|
606
|
+
extraSshArgs: deviceIdentityArgs(t.device),
|
|
604
607
|
}));
|
|
605
608
|
const probeable = remoteTargets.filter((t) => !t.skip).length;
|
|
606
609
|
const spinner = isInteractiveTerminal() && !opts.json
|
|
@@ -645,6 +648,118 @@ async function runFleetPing(opts) {
|
|
|
645
648
|
if (opts.strict && anyBad)
|
|
646
649
|
process.exitCode = 1;
|
|
647
650
|
}
|
|
651
|
+
/** SSH into a host and read its raw harness rows (the `--local --json` worker). */
|
|
652
|
+
async function probeRemoteHarnesses(target, refresh) {
|
|
653
|
+
const isWin = /^win/i.test((target.platform ?? '').trim());
|
|
654
|
+
const env = isWin ? undefined : { PATH: '$HOME/.agents/.cache/shims:$HOME/.local/bin:$PATH' };
|
|
655
|
+
const args = ['devices', 'harnesses', '--local', '--json'];
|
|
656
|
+
if (refresh)
|
|
657
|
+
args.push('--refresh');
|
|
658
|
+
const cmd = buildRemoteAgentsInvocation(args, undefined, isWin ? 'windows' : undefined, env);
|
|
659
|
+
const res = await sshExecAsync(target.dialTarget, cmd, { timeoutMs: 15000, multiplex: true, extraSshArgs: target.extraSshArgs });
|
|
660
|
+
if (res.code !== 0) {
|
|
661
|
+
throw new Error(res.timedOut ? 'timed out' : (res.stderr.trim() || `exit ${res.code ?? 'unknown'}`));
|
|
662
|
+
}
|
|
663
|
+
const parsed = JSON.parse(res.stdout);
|
|
664
|
+
return parsed.rows ?? [];
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Gather harness rows across the fleet: THIS host in process, every reachable
|
|
668
|
+
* peer over SSH. Shared by both `harnesses` and `accounts` (they differ only in
|
|
669
|
+
* how the rows are rendered). Honors an optional `--device` allowlist on both
|
|
670
|
+
* the local and remote rows. Bounded by the same per-device + overall deadlines
|
|
671
|
+
* as `fleet ping`, so one unreachable box can never stall the glance.
|
|
672
|
+
*/
|
|
673
|
+
async function collectFleetHarnesses(opts) {
|
|
674
|
+
const self = machineId();
|
|
675
|
+
const want = opts.devices?.length ? new Set(opts.devices) : null;
|
|
676
|
+
const results = [];
|
|
677
|
+
if (!want || want.has(self)) {
|
|
678
|
+
const localRows = await collectLocalHarnessInventory({ agents: opts.agents, refresh: opts.refresh });
|
|
679
|
+
results.push({ host: self, rows: localRows });
|
|
680
|
+
}
|
|
681
|
+
const reg = await loadDevices();
|
|
682
|
+
const planned = planFleetTargets(reg);
|
|
683
|
+
let remoteTargets = remoteFleetTargets(planned, self).map((t) => ({
|
|
684
|
+
name: t.device.name,
|
|
685
|
+
platform: t.device.platform,
|
|
686
|
+
skip: t.skip,
|
|
687
|
+
dialTarget: fleetDialTarget(t.device),
|
|
688
|
+
extraSshArgs: deviceIdentityArgs(t.device),
|
|
689
|
+
}));
|
|
690
|
+
if (want)
|
|
691
|
+
remoteTargets = remoteTargets.filter((t) => want.has(t.name));
|
|
692
|
+
if (remoteTargets.length > 0) {
|
|
693
|
+
const probeable = remoteTargets.filter((t) => !t.skip).length;
|
|
694
|
+
const spinner = isInteractiveTerminal() && !opts.json
|
|
695
|
+
? ora(`Probing ${probeable} device${probeable === 1 ? '' : 's'}…`).start()
|
|
696
|
+
: undefined;
|
|
697
|
+
let remote;
|
|
698
|
+
try {
|
|
699
|
+
const fanOut = fanOutDevices(remoteTargets, (t) => probeRemoteHarnesses(t, !!opts.refresh), { perDeviceTimeoutMs: 15_000 });
|
|
700
|
+
remote = await raceFleetPingDeadline(fanOut, remoteTargets, 30_000);
|
|
701
|
+
}
|
|
702
|
+
finally {
|
|
703
|
+
spinner?.stop();
|
|
704
|
+
}
|
|
705
|
+
for (const r of remote) {
|
|
706
|
+
if (r.status === 'ok' && r.value) {
|
|
707
|
+
results.push({ host: r.name, rows: r.value });
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
results.push({
|
|
711
|
+
host: r.name,
|
|
712
|
+
rows: [],
|
|
713
|
+
error: r.error,
|
|
714
|
+
skipped: r.reason ? String(r.reason) : undefined,
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
return results;
|
|
720
|
+
}
|
|
721
|
+
async function runDevicesHarnesses(opts) {
|
|
722
|
+
if (opts.local) {
|
|
723
|
+
const rows = await collectLocalHarnessInventory({ agents: opts.agents, refresh: opts.refresh });
|
|
724
|
+
if (opts.json)
|
|
725
|
+
console.log(JSON.stringify({ host: machineId(), rows }));
|
|
726
|
+
else
|
|
727
|
+
for (const line of renderHarnessMatrix([{ host: machineId(), rows }]))
|
|
728
|
+
console.log(line);
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
const results = await collectFleetHarnesses(opts);
|
|
732
|
+
if (opts.json)
|
|
733
|
+
console.log(JSON.stringify(results, null, 2));
|
|
734
|
+
else
|
|
735
|
+
for (const line of renderHarnessMatrix(results))
|
|
736
|
+
console.log(line);
|
|
737
|
+
}
|
|
738
|
+
async function runDevicesAccounts(opts) {
|
|
739
|
+
if (opts.local) {
|
|
740
|
+
const rows = await collectLocalHarnessInventory({ agents: opts.agents, refresh: opts.refresh });
|
|
741
|
+
if (opts.json)
|
|
742
|
+
console.log(JSON.stringify({ host: machineId(), accounts: groupByAccount(rows) }));
|
|
743
|
+
else
|
|
744
|
+
for (const line of renderAccountsMatrix([{ host: machineId(), rows }]))
|
|
745
|
+
console.log(line);
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
const results = await collectFleetHarnesses(opts);
|
|
749
|
+
if (opts.json) {
|
|
750
|
+
const grouped = results.map((r) => ({
|
|
751
|
+
host: r.host,
|
|
752
|
+
error: r.error,
|
|
753
|
+
skipped: r.skipped,
|
|
754
|
+
accounts: groupByAccount(r.rows),
|
|
755
|
+
}));
|
|
756
|
+
console.log(JSON.stringify(grouped, null, 2));
|
|
757
|
+
}
|
|
758
|
+
else {
|
|
759
|
+
for (const line of renderAccountsMatrix(results))
|
|
760
|
+
console.log(line);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
648
763
|
/** Resolve an {@link AuthCellColor} to a chalk painter. Single map for cells + labels. */
|
|
649
764
|
const CELL_PAINT = {
|
|
650
765
|
green: chalk.green,
|
|
@@ -777,6 +892,7 @@ Typical workflow:
|
|
|
777
892
|
agents devices configure mac-mini --max-agents 4 --scheduler off
|
|
778
893
|
agents devices note mac-mini "runs the releases — don't reboot"
|
|
779
894
|
agents devices set win-mini --auth password --bundle muqsit
|
|
895
|
+
agents devices set worker --auth key --identity-file ~/.ssh/worker_ed25519
|
|
780
896
|
agents devices render --write # write ~/.ssh/config.d/agents include
|
|
781
897
|
agents fleet update # roll out latest agents-cli to every online device
|
|
782
898
|
agents fleet run uname -a # run a command on every online device
|
|
@@ -1219,6 +1335,59 @@ Typical workflow:
|
|
|
1219
1335
|
const verbose = opts.verbose ?? Boolean(cmd.optsWithGlobals().verbose);
|
|
1220
1336
|
await runFleetPing({ ...opts, verbose });
|
|
1221
1337
|
});
|
|
1338
|
+
const csvList = (s) => s ? s.split(',').map((x) => x.trim()).filter(Boolean) : undefined;
|
|
1339
|
+
const harnessInvOpts = (opts) => ({
|
|
1340
|
+
agents: csvList(opts.agents),
|
|
1341
|
+
devices: csvList(opts.device ?? opts.devices),
|
|
1342
|
+
refresh: opts.refresh || opts.live,
|
|
1343
|
+
json: opts.json,
|
|
1344
|
+
local: opts.local,
|
|
1345
|
+
});
|
|
1346
|
+
const harnessesCmd = devicesCmd
|
|
1347
|
+
.command('harnesses')
|
|
1348
|
+
.description('Per device, one row per installed agent@version: account, signed-in, quota, and a single ready verdict. SSH-probes each online box.')
|
|
1349
|
+
.option('--json', 'output machine-readable JSON (per-host rows)')
|
|
1350
|
+
.option('--agents <csv>', 'only these agents (comma-separated)')
|
|
1351
|
+
.option('--device <csv>', 'only these devices (comma-separated); default: every online box')
|
|
1352
|
+
.option('--refresh', 'fetch live quota instead of the cached snapshot (slower)')
|
|
1353
|
+
.option('--live', 'alias of --refresh')
|
|
1354
|
+
.option('--local', "this host only: emit THIS box's rows (the per-host worker the fan-out reads over ssh)")
|
|
1355
|
+
.action(async (opts) => {
|
|
1356
|
+
await runDevicesHarnesses(harnessInvOpts(opts));
|
|
1357
|
+
});
|
|
1358
|
+
setHelpSections(harnessesCmd, {
|
|
1359
|
+
examples: `
|
|
1360
|
+
agents devices harnesses # every box: agent@version · account · signed · quota · ready
|
|
1361
|
+
agents devices harnesses --agents claude,codex # just these harnesses
|
|
1362
|
+
agents devices harnesses --device zion # one box
|
|
1363
|
+
agents devices harnesses --refresh # live quota (bypass the cached snapshot)
|
|
1364
|
+
agents devices harnesses --json # machine-readable, per-host rows`,
|
|
1365
|
+
notes: `
|
|
1366
|
+
"ready" = signed in AND not rate-limited — usable for a run right now.
|
|
1367
|
+
Quota is the cached usage snapshot (the daemon warms it); --refresh fetches live.
|
|
1368
|
+
Use \`agents devices accounts\` for the same data grouped by account.`,
|
|
1369
|
+
});
|
|
1370
|
+
const accountsCmd = devicesCmd
|
|
1371
|
+
.command('accounts')
|
|
1372
|
+
.description('Per device, one row per account: which harnesses share it, signed-in, quota, and ready. The identity lens on `agents devices harnesses`.')
|
|
1373
|
+
.option('--json', 'output machine-readable JSON (per-host account groups)')
|
|
1374
|
+
.option('--agents <csv>', 'only these agents (comma-separated)')
|
|
1375
|
+
.option('--device <csv>', 'only these devices (comma-separated); default: every online box')
|
|
1376
|
+
.option('--refresh', 'fetch live quota instead of the cached snapshot (slower)')
|
|
1377
|
+
.option('--live', 'alias of --refresh')
|
|
1378
|
+
.option('--local', "this host only: emit THIS box's account groups")
|
|
1379
|
+
.action(async (opts) => {
|
|
1380
|
+
await runDevicesAccounts(harnessInvOpts(opts));
|
|
1381
|
+
});
|
|
1382
|
+
setHelpSections(accountsCmd, {
|
|
1383
|
+
examples: `
|
|
1384
|
+
agents devices accounts # every box: account · agents · signed · quota · ready
|
|
1385
|
+
agents devices accounts --device mac-mini
|
|
1386
|
+
agents devices accounts --json # machine-readable, per-host account groups`,
|
|
1387
|
+
notes: `
|
|
1388
|
+
Collapses the installs that share one account (e.g. five claude versions on one
|
|
1389
|
+
email) into a single row. Use \`agents devices harnesses\` for the per-install view.`,
|
|
1390
|
+
});
|
|
1222
1391
|
devicesCmd
|
|
1223
1392
|
.command('login')
|
|
1224
1393
|
.description('Log agent CLIs into fleet boxes over SSH: drive each box\'s device-code OAuth, scrape the URL + code, and surface every pending login in one local browser page. Default drives all codes at once; --interactive walks one box at a time (codes requested just-in-time so they don\'t expire).')
|
|
@@ -1281,15 +1450,19 @@ Typical workflow:
|
|
|
1281
1450
|
.option('--auth <method>', 'key | password')
|
|
1282
1451
|
.option('--bundle <bundle>', 'secrets bundle holding the password (for --auth password)')
|
|
1283
1452
|
.option('--bundle-key <key>', "key within the bundle (default 'password')")
|
|
1453
|
+
.option('--identity-file <path>', 'private-key path for --auth key')
|
|
1454
|
+
.option('--clear-identity-file', 'return key auth to ssh-agent/default-key discovery')
|
|
1284
1455
|
.action(async (name, opts) => {
|
|
1285
1456
|
try {
|
|
1286
1457
|
const existing = await mustGetDevice(name);
|
|
1287
|
-
const
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1458
|
+
const nextMethod = opts.auth ?? existing.auth.method;
|
|
1459
|
+
if (opts.identityFile && nextMethod !== 'key') {
|
|
1460
|
+
throw new Error('--identity-file requires key auth; pass --auth key in the same command.');
|
|
1461
|
+
}
|
|
1462
|
+
const auth = opts.auth || opts.bundle || opts.bundleKey || opts.identityFile || opts.clearIdentityFile
|
|
1463
|
+
? nextMethod === 'key'
|
|
1464
|
+
? { method: nextMethod, identityFile: opts.clearIdentityFile ? undefined : (opts.identityFile ?? existing.auth.identityFile) }
|
|
1465
|
+
: { method: nextMethod, bundle: opts.bundle ?? existing.auth.bundle, bundleKey: opts.bundleKey ?? existing.auth.bundleKey }
|
|
1293
1466
|
: undefined;
|
|
1294
1467
|
const d = await upsertDevice(name, {
|
|
1295
1468
|
platform: opts.platform ?? undefined,
|
package/dist/commands/teams.js
CHANGED
|
@@ -17,7 +17,7 @@ import { createTeam, ensureTeam, getTeam, loadTeams, removeTeam, teamExists, } f
|
|
|
17
17
|
import { setHelpSections } from '../lib/help.js';
|
|
18
18
|
import { createWorktree, isGitRepo, hasUncommittedChanges, removeWorktree, } from '../lib/teams/worktree.js';
|
|
19
19
|
import { resolveHost } from '../lib/hosts/registry.js';
|
|
20
|
-
import { isDeviceAuto,
|
|
20
|
+
import { isDeviceAuto, resolveDeviceAuto } from '../lib/smart-launch.js';
|
|
21
21
|
import { sshTargetFor } from '../lib/hosts/types.js';
|
|
22
22
|
import { ensureHostReady } from '../lib/hosts/ready.js';
|
|
23
23
|
import { remoteShellFor } from '../lib/hosts/remote-cmd.js';
|
|
@@ -1354,12 +1354,12 @@ export function registerTeamsCommands(program) {
|
|
|
1354
1354
|
}
|
|
1355
1355
|
return h ?? d ?? null;
|
|
1356
1356
|
})();
|
|
1357
|
-
// `auto` is the same
|
|
1357
|
+
// `auto` is the same live fleet sentinel `agents run --device auto` resolves
|
|
1358
1358
|
// (RUSH-2185) — pick the concrete device name up front so the local-machine
|
|
1359
1359
|
// check right below (and every dieFriction message further down) sees the
|
|
1360
1360
|
// real target instead of the literal string "auto".
|
|
1361
1361
|
if (explicitDevice && isDeviceAuto(explicitDevice)) {
|
|
1362
|
-
const plan =
|
|
1362
|
+
const plan = await resolveDeviceAuto(parseTeammate(teammate).agent);
|
|
1363
1363
|
const picked = plan.host ?? machineId();
|
|
1364
1364
|
process.stderr.write(chalk.gray(`[teams] device=auto → ${picked === machineId() ? 'local' : picked}\n`));
|
|
1365
1365
|
explicitDevice = picked;
|
package/dist/index.js
CHANGED
|
@@ -94,7 +94,7 @@ if (IS_DEV_BUILD) {
|
|
|
94
94
|
// module on each invocation (which loaded the whole ~50-module tree before the
|
|
95
95
|
// first byte of output), the registry maps a command name to a thunk that
|
|
96
96
|
// imports only what that command needs. See src/lib/startup/command-registry.ts.
|
|
97
|
-
import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadMemory, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadExport, loadPackages, loadRoutines, loadMonitors, loadProjects, loadRun, loadFork, loadDefaults, loadSet, loadModels, loadModes, loadPrune, loadTrash, loadRestore, loadDoctor, loadApply, loadStatus, loadSnapshot, loadProfiles, loadHarness, loadSecrets, loadLogin, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadLock, loadRefreshRules, loadFactory, loadUsage, loadCost, loadInsights, loadPerf, loadTrends, loadOutput, loadBudget, loadAlias, loadMine, loadPty, loadTmux, loadWatchdog, loadBrowser, loadComputer, loadHosts, loadLogs, loadEvents, loadAudit, loadWebhook, loadFunnel, loadHumans, loadSsh, loadPull, loadPush, loadRepo, loadSetup, loadUninstall, loadShare, loadSend, loadFeed, loadMailboxes, } from './lib/startup/command-registry.js';
|
|
97
|
+
import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadMemory, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadExport, loadPackages, loadRoutines, loadMonitors, loadProjects, loadRun, loadFork, loadDefaults, loadSet, loadModels, loadModes, loadPrune, loadTrash, loadRestore, loadDoctor, loadApply, loadStatus, loadSnapshot, loadProfiles, loadHarness, loadSecrets, loadLogin, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadLock, loadRefreshRules, loadFactory, loadUsage, loadCost, loadInsights, loadPerf, loadTrends, loadOutput, loadBudget, loadAlias, loadMine, loadPty, loadTmux, loadWatchdog, loadBrowser, loadComputer, loadHosts, loadLogs, loadEvents, loadAudit, loadWebhook, loadFunnel, loadHumans, loadSsh, loadPull, loadPush, loadRepo, loadSetup, loadUninstall, loadBench, loadShare, loadSend, loadFeed, loadMailboxes, } from './lib/startup/command-registry.js';
|
|
98
98
|
import { applyGlobalHelpConventions } from './lib/help.js';
|
|
99
99
|
import { renderWhatsNew } from './lib/whats-new.js';
|
|
100
100
|
import { getCliLaunch } from './lib/cli-entry.js';
|
|
@@ -392,15 +392,15 @@ async function showWhatsNew(fromVersion, toVersion) {
|
|
|
392
392
|
const UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
393
393
|
import { getUpdateCheckPath, getMigratedSentinelPath, getUserAgentsDir, getRuntimeStateDir } from './lib/state.js';
|
|
394
394
|
import { resolveBrandName, disabledCommandsForActiveBrand } from './lib/brand.js';
|
|
395
|
-
import { readUpdateCache, saveUpdateCheck, dismissUpdateVersion, shouldPromptUpgrade, findAgentsCliInstalls, resolveRunningPackageRoot, } from './lib/self-update.js';
|
|
395
|
+
import { readUpdateCache, saveUpdateCheck, dismissUpdateVersion, shouldPromptUpgrade, buildMultiInstallInventory, findAgentsCliInstalls, resolveRunningPackageRoot, } from './lib/self-update.js';
|
|
396
396
|
const UPDATE_CHECK_FILE = getUpdateCheckPath();
|
|
397
397
|
/**
|
|
398
|
-
* Warn once when
|
|
399
|
-
*
|
|
398
|
+
* Warn once when this machine contains a different agents-cli install than the
|
|
399
|
+
* copy that is currently running (or several). Divergent installs
|
|
400
400
|
* are how self-updates "succeed" without changing the command the user types.
|
|
401
|
-
* The warning re-fires only when the set of install roots
|
|
402
|
-
*
|
|
403
|
-
*
|
|
401
|
+
* The warning re-fires only when the set of install roots or their helper-copy
|
|
402
|
+
* safety changes. Dev builds are included because old dev copies can still
|
|
403
|
+
* overwrite the shared macOS helper bundle non-atomically.
|
|
404
404
|
*/
|
|
405
405
|
function maybeWarnMultiInstall() {
|
|
406
406
|
const sentinel = path.join(getRuntimeStateDir(), 'multi-install-warned');
|
|
@@ -414,31 +414,26 @@ function maybeWarnMultiInstall() {
|
|
|
414
414
|
// "/$bunfs" install. This warning is advisory — stay silent instead.
|
|
415
415
|
return;
|
|
416
416
|
}
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
for (const install of findAgentsCliInstalls(process.env.PATH || '')) {
|
|
420
|
-
if (install.version.startsWith('0.0.0-dev'))
|
|
421
|
-
continue;
|
|
422
|
-
if (!byRoot.has(install.packageRoot)) {
|
|
423
|
-
byRoot.set(install.packageRoot, { version: install.version, note: `agents on PATH: ${install.binPath}` });
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
if (byRoot.size < 2) {
|
|
417
|
+
const inventory = buildMultiInstallInventory(runningRoot, VERSION, findAgentsCliInstalls(process.env.PATH || ''));
|
|
418
|
+
if (inventory.length < 2) {
|
|
427
419
|
try {
|
|
428
420
|
fs.unlinkSync(sentinel);
|
|
429
421
|
}
|
|
430
422
|
catch { /* nothing recorded */ }
|
|
431
423
|
return;
|
|
432
424
|
}
|
|
433
|
-
const key =
|
|
425
|
+
const key = inventory
|
|
426
|
+
.map((info) => `${info.packageRoot}\t${info.version}\t${info.note}`)
|
|
427
|
+
.sort()
|
|
428
|
+
.join('\n');
|
|
434
429
|
try {
|
|
435
430
|
if (fs.readFileSync(sentinel, 'utf-8') === key)
|
|
436
431
|
return;
|
|
437
432
|
}
|
|
438
433
|
catch { /* not warned for this set yet */ }
|
|
439
434
|
console.error(chalk.yellow('Multiple agents-cli installs detected:'));
|
|
440
|
-
for (const
|
|
441
|
-
console.error(chalk.gray(` ${
|
|
435
|
+
for (const info of inventory) {
|
|
436
|
+
console.error(chalk.gray(` ${info.packageRoot} ${info.version} (${info.note})`));
|
|
442
437
|
}
|
|
443
438
|
console.error(chalk.gray('Upgrades apply to the running copy. Remove a stale copy with: npm uninstall -g --prefix <prefix> @phnx-labs/agents-cli'));
|
|
444
439
|
try {
|
|
@@ -967,6 +962,7 @@ async function registerAllEagerCommands() {
|
|
|
967
962
|
await reg(loadCost);
|
|
968
963
|
await reg(loadInsights);
|
|
969
964
|
await reg(loadPerf);
|
|
965
|
+
await reg(loadBench);
|
|
970
966
|
await reg(loadTrends);
|
|
971
967
|
await reg(loadOutput);
|
|
972
968
|
await reg(loadBudget);
|
|
@@ -156,10 +156,54 @@ export interface AuthProbeRow {
|
|
|
156
156
|
account?: string;
|
|
157
157
|
health: AuthHealth;
|
|
158
158
|
}
|
|
159
|
+
/** One installed (agent, version) home, tagged with its resolved account label. */
|
|
160
|
+
export interface FleetAuthInstall {
|
|
161
|
+
agent: AgentId;
|
|
162
|
+
version: string;
|
|
163
|
+
/** Human account label from {@link authAccountLabel}, or undefined when none resolves. */
|
|
164
|
+
account: string | undefined;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* A set of installs that share one provider account and MUST be probed once.
|
|
168
|
+
* The live probe runs against `probe` (the representative home); every entry in
|
|
169
|
+
* `members` — the representative included — then receives that one verdict.
|
|
170
|
+
*/
|
|
171
|
+
export interface FleetAuthProbeGroup<T extends FleetAuthInstall> {
|
|
172
|
+
probe: T;
|
|
173
|
+
members: T[];
|
|
174
|
+
}
|
|
159
175
|
/**
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
176
|
+
* Collapse installs so a live auth probe fires ONCE per (agent, account) rather
|
|
177
|
+
* than once per version home.
|
|
178
|
+
*
|
|
179
|
+
* Several version homes signed into the same provider account share one OAuth
|
|
180
|
+
* rate limit, so probing each of them concurrently — which the daemon did every
|
|
181
|
+
* three minutes across every installed home (RUSH-2111) — raced that limit into
|
|
182
|
+
* a 429 storm that then parked the whole box behind a `Retry-After` penalty (the
|
|
183
|
+
* incident {@link file:./usage-backoff.ts} was written to survive; this removes
|
|
184
|
+
* its cause). Grouping by account means N homes on one account issue ONE request.
|
|
185
|
+
*
|
|
186
|
+
* `isMergeable` scopes the dedup to the installs it actually helps: only agents
|
|
187
|
+
* that make a network probe ({@link LIVE_PROBE_AGENTS}) can 429, so only they are
|
|
188
|
+
* merged. A best-effort agent (its verdict is a cheap local file read, no rate
|
|
189
|
+
* limit) is left per-install — collapsing it would gain nothing and could
|
|
190
|
+
* silently override one home's local `signedIn` verdict with another's. Installs
|
|
191
|
+
* with no resolvable account label, or that `isMergeable` rejects, are never
|
|
192
|
+
* merged: each becomes its own group keyed by version, preserving the old
|
|
193
|
+
* per-install probe (also the `unconfigured` case dropped downstream). Pure: no
|
|
194
|
+
* fs, no network, so the dedup decision is unit-tested directly.
|
|
195
|
+
*/
|
|
196
|
+
export declare function groupFleetAuthInstalls<T extends FleetAuthInstall>(installs: readonly T[], isMergeable?: (install: T) => boolean): FleetAuthProbeGroup<T>[];
|
|
197
|
+
/**
|
|
198
|
+
* Enumerate every installed (agent, version) on THIS host and return one row per
|
|
199
|
+
* install (installs with no credential at all are dropped). Shared by
|
|
200
|
+
* `agents fleet ping --local` and the daemon refresh.
|
|
201
|
+
*
|
|
202
|
+
* The live network probe is deduped by account: homes sharing one provider
|
|
203
|
+
* account are probed ONCE and the verdict is fanned out to each home's row, so
|
|
204
|
+
* the daemon's every-3-minute refresh can no longer fire concurrent same-account
|
|
205
|
+
* requests and self-inflict a 429 (RUSH-2111). Each home still gets its own cache
|
|
206
|
+
* row (the cache key is per-version by design — see {@link authCacheKey}).
|
|
163
207
|
*/
|
|
164
208
|
export declare function probeLocalFleetAuth(opts?: {
|
|
165
209
|
cliVersion?: string | null;
|
package/dist/lib/auth-health.js
CHANGED
|
@@ -283,28 +283,87 @@ export async function probeAuthHealth(agent, home, opts) {
|
|
|
283
283
|
return { verdict: info?.signedIn ? 'unverified' : 'unconfigured', checkedAt };
|
|
284
284
|
}
|
|
285
285
|
/**
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
286
|
+
* Collapse installs so a live auth probe fires ONCE per (agent, account) rather
|
|
287
|
+
* than once per version home.
|
|
288
|
+
*
|
|
289
|
+
* Several version homes signed into the same provider account share one OAuth
|
|
290
|
+
* rate limit, so probing each of them concurrently — which the daemon did every
|
|
291
|
+
* three minutes across every installed home (RUSH-2111) — raced that limit into
|
|
292
|
+
* a 429 storm that then parked the whole box behind a `Retry-After` penalty (the
|
|
293
|
+
* incident {@link file:./usage-backoff.ts} was written to survive; this removes
|
|
294
|
+
* its cause). Grouping by account means N homes on one account issue ONE request.
|
|
295
|
+
*
|
|
296
|
+
* `isMergeable` scopes the dedup to the installs it actually helps: only agents
|
|
297
|
+
* that make a network probe ({@link LIVE_PROBE_AGENTS}) can 429, so only they are
|
|
298
|
+
* merged. A best-effort agent (its verdict is a cheap local file read, no rate
|
|
299
|
+
* limit) is left per-install — collapsing it would gain nothing and could
|
|
300
|
+
* silently override one home's local `signedIn` verdict with another's. Installs
|
|
301
|
+
* with no resolvable account label, or that `isMergeable` rejects, are never
|
|
302
|
+
* merged: each becomes its own group keyed by version, preserving the old
|
|
303
|
+
* per-install probe (also the `unconfigured` case dropped downstream). Pure: no
|
|
304
|
+
* fs, no network, so the dedup decision is unit-tested directly.
|
|
305
|
+
*/
|
|
306
|
+
export function groupFleetAuthInstalls(installs, isMergeable = () => true) {
|
|
307
|
+
const groups = new Map();
|
|
308
|
+
for (const inst of installs) {
|
|
309
|
+
// The `acct:` / `ver:` tokens make the two branches disjoint, so an account
|
|
310
|
+
// label can never collide with a version fallback key no matter its content.
|
|
311
|
+
const key = inst.account && isMergeable(inst)
|
|
312
|
+
? `${inst.agent} acct:${inst.account}`
|
|
313
|
+
: `${inst.agent} ver:${inst.version}`;
|
|
314
|
+
const existing = groups.get(key);
|
|
315
|
+
if (existing)
|
|
316
|
+
existing.members.push(inst);
|
|
317
|
+
else
|
|
318
|
+
groups.set(key, { probe: inst, members: [inst] });
|
|
319
|
+
}
|
|
320
|
+
return [...groups.values()];
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Enumerate every installed (agent, version) on THIS host and return one row per
|
|
324
|
+
* install (installs with no credential at all are dropped). Shared by
|
|
325
|
+
* `agents fleet ping --local` and the daemon refresh.
|
|
326
|
+
*
|
|
327
|
+
* The live network probe is deduped by account: homes sharing one provider
|
|
328
|
+
* account are probed ONCE and the verdict is fanned out to each home's row, so
|
|
329
|
+
* the daemon's every-3-minute refresh can no longer fire concurrent same-account
|
|
330
|
+
* requests and self-inflict a 429 (RUSH-2111). Each home still gets its own cache
|
|
331
|
+
* row (the cache key is per-version by design — see {@link authCacheKey}).
|
|
289
332
|
*/
|
|
290
333
|
export async function probeLocalFleetAuth(opts) {
|
|
291
334
|
const agentIds = opts?.agents ?? ALL_AGENT_IDS;
|
|
292
|
-
|
|
335
|
+
// Enumerate every install, then resolve its account label. getAccountInfo is a
|
|
336
|
+
// local credential-file read (no network), so this fan-out is cheap and cannot
|
|
337
|
+
// contribute to the rate limit the probe grouping below exists to avoid.
|
|
338
|
+
const installs = [];
|
|
293
339
|
for (const agent of agentIds) {
|
|
294
340
|
for (const version of listInstalledVersions(agent)) {
|
|
295
|
-
|
|
296
|
-
tasks.push((async () => {
|
|
297
|
-
const info = await getAccountInfo(agent, home).catch(() => null);
|
|
298
|
-
const health = await probeAuthHealth(agent, home, { cliVersion: opts?.cliVersion, info });
|
|
299
|
-
health.account = authAccountLabel(info);
|
|
300
|
-
if (health.verdict === 'unconfigured')
|
|
301
|
-
return null;
|
|
302
|
-
return { agent, version, account: health.account, health };
|
|
303
|
-
})());
|
|
341
|
+
installs.push({ agent, version, home: getVersionHomePath(agent, version), info: null, account: undefined });
|
|
304
342
|
}
|
|
305
343
|
}
|
|
306
|
-
|
|
307
|
-
|
|
344
|
+
await Promise.all(installs.map(async (inst) => {
|
|
345
|
+
inst.info = await getAccountInfo(inst.agent, inst.home).catch(() => null);
|
|
346
|
+
inst.account = authAccountLabel(inst.info);
|
|
347
|
+
}));
|
|
348
|
+
// Probe once per (agent, account) — but only for the network-probing agents
|
|
349
|
+
// that can actually 429; best-effort agents stay per-install (see
|
|
350
|
+
// groupFleetAuthInstalls). Groups run in parallel: they target distinct
|
|
351
|
+
// accounts, so no same-account concurrency is left to trip the throttle.
|
|
352
|
+
const perGroup = await Promise.all(groupFleetAuthInstalls(installs, (inst) => LIVE_PROBE_AGENTS.has(inst.agent)).map(async (group) => {
|
|
353
|
+
const rep = group.probe;
|
|
354
|
+
const health = await probeAuthHealth(rep.agent, rep.home, { cliVersion: opts?.cliVersion, info: rep.info });
|
|
355
|
+
health.account = authAccountLabel(rep.info);
|
|
356
|
+
if (health.verdict === 'unconfigured')
|
|
357
|
+
return [];
|
|
358
|
+
return group.members.map((inst) => ({
|
|
359
|
+
agent: inst.agent,
|
|
360
|
+
version: inst.version,
|
|
361
|
+
account: health.account,
|
|
362
|
+
// A distinct object per row so a later mutation of one can't bleed across.
|
|
363
|
+
health: { ...health },
|
|
364
|
+
}));
|
|
365
|
+
}));
|
|
366
|
+
return perGroup.flat();
|
|
308
367
|
}
|
|
309
368
|
/** Persist a host's probed rows into the cache (keyed by host+agent+version). */
|
|
310
369
|
export function writeFleetAuthRows(host, rows) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { BenchCell, BenchCellResult, BenchRunResult, BenchTask } from "./types.js";
|
|
2
|
+
export interface CellExecutionRequest extends BenchCell {
|
|
3
|
+
prompt: string;
|
|
4
|
+
cwd: string;
|
|
5
|
+
}
|
|
6
|
+
export type CellExecutor = (request: CellExecutionRequest) => Promise<Omit<BenchCellResult, keyof BenchCell | "wall_ms" | "status">>;
|
|
7
|
+
export declare function extractTokenCount(stdout: string, stderr: string): number | undefined;
|
|
8
|
+
export declare function executeCellViaAgentsRun(request: CellExecutionRequest): ReturnType<CellExecutor>;
|
|
9
|
+
export declare function runCells(options: {
|
|
10
|
+
task?: BenchTask;
|
|
11
|
+
prompt: string;
|
|
12
|
+
cells: BenchCell[];
|
|
13
|
+
concurrency: number;
|
|
14
|
+
execute?: CellExecutor;
|
|
15
|
+
tempRoot?: string;
|
|
16
|
+
}): Promise<BenchRunResult>;
|