@phnx-labs/agents-cli 1.22.115 → 1.22.117
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 +161 -14
- package/README.md +1 -1
- package/dist/commands/browser.js +11 -0
- package/dist/commands/exec.d.ts +18 -0
- package/dist/commands/exec.js +263 -152
- package/dist/commands/feed.js +65 -14
- package/dist/commands/sessions-picker.d.ts +11 -0
- package/dist/commands/sessions-picker.js +88 -7
- package/dist/commands/sessions.d.ts +21 -2
- package/dist/commands/sessions.js +157 -3
- package/dist/commands/setup-secrets.d.ts +2 -2
- package/dist/commands/setup-term.d.ts +25 -0
- package/dist/commands/setup-term.js +71 -0
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +12 -4
- package/dist/commands/ssh.js +1 -69
- package/dist/lib/accounting/rotate.d.ts +63 -1
- package/dist/lib/accounting/rotate.js +56 -0
- package/dist/lib/accounts/add.js +2 -2
- package/dist/lib/accounts/slots.js +32 -2
- package/dist/lib/answer-router.d.ts +11 -2
- package/dist/lib/answer-router.js +26 -2
- package/dist/lib/auth-mint.d.ts +5 -4
- package/dist/lib/auth-mint.js +4 -3
- package/dist/lib/browser/drivers/arc.d.ts +1 -1
- package/dist/lib/browser/service.d.ts +10 -0
- package/dist/lib/browser/service.js +208 -36
- package/dist/lib/browser/types.d.ts +18 -0
- package/dist/lib/config-keys.d.ts +1 -1
- package/dist/lib/config-keys.js +5 -0
- package/dist/lib/device-config.js +61 -0
- package/dist/lib/devices/doctor-findings.js +2 -6
- package/dist/lib/feed/answer.d.ts +153 -4
- package/dist/lib/feed/answer.js +716 -105
- package/dist/lib/feed/feed.d.ts +61 -1
- package/dist/lib/feed/feed.js +226 -14
- package/dist/lib/feed/hub-server.d.ts +58 -3
- package/dist/lib/feed/hub-server.js +306 -54
- package/dist/lib/feed/pr-status.d.ts +8 -0
- package/dist/lib/feed/pr-status.js +9 -1
- package/dist/lib/feed-outcome.d.ts +1 -1
- package/dist/lib/feed-outcome.js +9 -2
- package/dist/lib/feed-policy.js +9 -3
- package/dist/lib/fleet/auth-sync.d.ts +2 -55
- package/dist/lib/fleet/auth-sync.js +2 -89
- package/dist/lib/harness-auth-capabilities.js +7 -2
- package/dist/lib/hosts/dispatch.d.ts +20 -1
- package/dist/lib/hosts/dispatch.js +52 -30
- package/dist/lib/hosts/remote-cmd.d.ts +21 -0
- package/dist/lib/hosts/remote-cmd.js +27 -2
- package/dist/lib/mailbox.d.ts +12 -0
- package/dist/lib/mailbox.js +16 -2
- package/dist/lib/menubar/snapshot.d.ts +51 -0
- package/dist/lib/menubar/snapshot.js +42 -3
- package/dist/lib/open-url.js +2 -2
- package/dist/lib/placement.d.ts +2 -0
- package/dist/lib/placement.js +5 -0
- package/dist/lib/projects.d.ts +23 -0
- package/dist/lib/projects.js +78 -0
- package/dist/lib/secrets-cli.d.ts +3 -3
- package/dist/lib/secrets-cli.js +1 -1
- package/dist/lib/session/active.d.ts +1 -0
- package/dist/lib/session/active.js +8 -0
- package/dist/lib/session/db.d.ts +67 -3
- package/dist/lib/session/db.js +381 -126
- package/dist/lib/session/prompt.d.ts +23 -7
- package/dist/lib/session/prompt.js +46 -8
- package/dist/lib/session/remote/remote-list.d.ts +20 -0
- package/dist/lib/session/remote/remote-list.js +22 -6
- package/dist/lib/session/remote/watch.d.ts +12 -0
- package/dist/lib/session/remote/watch.js +9 -0
- package/dist/lib/session/remote-preview-cache.d.ts +29 -0
- package/dist/lib/session/remote-preview-cache.js +373 -0
- package/dist/lib/session/tail.d.ts +50 -0
- package/dist/lib/session/tail.js +219 -0
- package/dist/lib/setup-tool-install.js +2 -1
- package/dist/lib/setup-tool-status.d.ts +1 -1
- package/dist/lib/setup-tool-status.js +7 -1
- package/dist/lib/signin-badge.d.ts +19 -4
- package/dist/lib/signin-badge.js +29 -11
- package/dist/lib/term-driver.d.ts +24 -0
- package/dist/lib/term-driver.js +36 -0
- package/dist/lib/terminal/index.d.ts +1 -1
- package/dist/lib/terminal/index.js +1 -1
- package/dist/lib/terminal/inject.d.ts +38 -0
- package/dist/lib/terminal/inject.js +55 -9
- package/dist/lib/terminal/transport.d.ts +15 -5
- package/dist/lib/terminal/transport.js +61 -11
- package/package.json +1 -1
- package/dist/lib/fleet/remote-login.d.ts +0 -170
- package/dist/lib/fleet/remote-login.js +0 -568
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agents setup term` — install the standalone `term` CLI if missing (PHNX-4092).
|
|
3
|
+
*
|
|
4
|
+
* The PTY engine lives in `@phnx-labs/term-cli` (extracted PHNX-4091);
|
|
5
|
+
* agents-cli never rebundles it. The setup-token mint behind `agents accounts
|
|
6
|
+
* add` / `agents accounts login` (auth-mint.ts → term-driver.ts) spawns `term`
|
|
7
|
+
* on demand and fails loud when it is absent, so onboarding installs it here
|
|
8
|
+
* like every other standalone tool.
|
|
9
|
+
*
|
|
10
|
+
* Unlike browser/computer/secrets there is nothing to configure — no profile,
|
|
11
|
+
* no OS permission, no migrate step. A missing binary is a routine install
|
|
12
|
+
* (`agents clis install term` via the system `clis/term.yaml`, then a pinned
|
|
13
|
+
* `npm i -g`, both handled by installSetupTool), and once it is on PATH the
|
|
14
|
+
* tool is ready.
|
|
15
|
+
*/
|
|
16
|
+
import chalk from 'chalk';
|
|
17
|
+
import { isPromptCancelled } from './utils.js';
|
|
18
|
+
import { openSetupTerminal } from './setup-terminal.js';
|
|
19
|
+
import { resolveTermBin } from '../lib/term-client.js';
|
|
20
|
+
import { installSetupTool } from '../lib/setup-tool-install.js';
|
|
21
|
+
import { refreshToolSetup } from '../lib/setup-tool-status.js';
|
|
22
|
+
const INSTALL_HINT = 'agents clis install term # or: npm i -g @phnx-labs/term-cli';
|
|
23
|
+
/** True when the standalone `term` executable resolves ($TERM_BIN or PATH). */
|
|
24
|
+
export function isTermCliInstalled() {
|
|
25
|
+
return resolveTermBin() !== null;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Install the standalone `term` CLI if missing. Returns whether `term` is on
|
|
29
|
+
* PATH afterwards. There is no further onboarding — presence is readiness.
|
|
30
|
+
*/
|
|
31
|
+
export async function runTermWizard() {
|
|
32
|
+
if (isTermCliInstalled()) {
|
|
33
|
+
console.log(chalk.green('The standalone `term` CLI is installed.'));
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
if (await installSetupTool('term')) {
|
|
37
|
+
console.log(chalk.green('Installed the standalone `term` CLI.'));
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
console.log(chalk.yellow('The standalone `term` CLI is not installed.'));
|
|
41
|
+
console.log(chalk.gray('Install it, then re-run `agents setup term`:'));
|
|
42
|
+
console.log(chalk.cyan(` ${INSTALL_HINT}`));
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
/** Register `agents setup term` under the parent `setup` command. */
|
|
46
|
+
export function registerSetupTermCommand(setupCmd) {
|
|
47
|
+
setupCmd
|
|
48
|
+
.command('term')
|
|
49
|
+
.description('Install the standalone `term` CLI (the PTY engine `agents accounts add`/`login` spawn) if missing.')
|
|
50
|
+
.option('--install-only', 'Install the standalone term CLI (identical to the wizard; term needs no further setup)')
|
|
51
|
+
.option('--terminal [backend]', 'Open interactive setup in a detected or selected terminal')
|
|
52
|
+
.action(async (options) => {
|
|
53
|
+
try {
|
|
54
|
+
if (options.terminal !== undefined) {
|
|
55
|
+
await openSetupTerminal('term', options.terminal, options.installOnly);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (!(await runTermWizard()))
|
|
59
|
+
process.exitCode = 1;
|
|
60
|
+
await refreshToolSetup('term');
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
if (isPromptCancelled(err)) {
|
|
64
|
+
console.log(chalk.yellow('\nCancelled'));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
console.error(chalk.red(err.message));
|
|
68
|
+
process.exitCode = 1;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
package/dist/commands/setup.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export declare function ensureInitialized(program: Command): Promise<void>;
|
|
|
31
31
|
* wizard. Never throws — a cancel or an optional wizard's error just skips the
|
|
32
32
|
* rest and lets core setup complete.
|
|
33
33
|
*/
|
|
34
|
-
type SetupPhase = 'browser' | 'computer' | 'secrets' | 'accounts' | 'fleet' | 'watchdog' | 'preferences';
|
|
34
|
+
type SetupPhase = 'browser' | 'computer' | 'secrets' | 'term' | 'accounts' | 'fleet' | 'watchdog' | 'preferences';
|
|
35
35
|
type SetupStatusState = 'ready' | 'missing' | 'n/a';
|
|
36
36
|
interface SetupStatusRow {
|
|
37
37
|
phase: 'core' | SetupPhase;
|
package/dist/commands/setup.js
CHANGED
|
@@ -22,6 +22,7 @@ import { registerSetupBrowserCommand, runBrowserWizard } from './setup-browser.j
|
|
|
22
22
|
import { registerSetupComputerCommand, runComputerWizard } from './setup-computer.js';
|
|
23
23
|
import { registerSetupMineCommand } from './setup-mine.js';
|
|
24
24
|
import { registerSetupSecretsCommand } from './setup-secrets.js';
|
|
25
|
+
import { registerSetupTermCommand, runTermWizard, isTermCliInstalled } from './setup-term.js';
|
|
25
26
|
import { registerSetupFleetCommand } from './setup-fleet.js';
|
|
26
27
|
import { registerSetupAccountsCommand, runAccountsSetupWizard } from './setup-accounts.js';
|
|
27
28
|
import { registerSetupWatchdogCommand, runWatchdogSetupWizard } from './setup-watchdog.js';
|
|
@@ -278,6 +279,7 @@ export async function getSetupStatus() {
|
|
|
278
279
|
const devices = await loadDevices();
|
|
279
280
|
const coreReady = isGitRepo(getAgentsDir());
|
|
280
281
|
const secretsReady = fs.existsSync(setupSecretsPrefsPath());
|
|
282
|
+
const termReady = isTermCliInstalled();
|
|
281
283
|
const minted = hasMintedSetupToken();
|
|
282
284
|
const watchdogEnabled = getConfigValue('watchdog.enabled').value === true;
|
|
283
285
|
const interactiveHost = getConfigValue('interactive.host').value;
|
|
@@ -287,6 +289,7 @@ export async function getSetupStatus() {
|
|
|
287
289
|
{ phase: 'browser', state: browserReady ? 'ready' : 'missing', detail: browserReady ? `profile ${browserProfile.name}` : browserProfile ? `profile ${browserProfile.name} cannot launch here` : installedBrowsers.length ? 'no default profile' : 'no supported browser found' },
|
|
288
290
|
{ phase: 'computer', state: computerState, detail: computerState === 'ready' ? 'helper trusted' : computerState === 'n/a' ? 'macOS local setup only' : 'helper not running or not trusted' },
|
|
289
291
|
{ phase: 'secrets', state: secretsReady ? 'ready' : 'missing', detail: secretsReady ? 'defaults chosen' : 'defaults not chosen' },
|
|
292
|
+
{ phase: 'term', state: termReady ? 'ready' : 'missing', detail: termReady ? 'installed' : 'not installed (accounts add/login spawn it)' },
|
|
290
293
|
{ phase: 'accounts', state: minted.ready ? 'ready' : 'missing', detail: minted.detail },
|
|
291
294
|
{ phase: 'fleet', state: Object.keys(devices).length ? 'ready' : 'missing', detail: Object.keys(devices).length ? `${Object.keys(devices).length} device${Object.keys(devices).length === 1 ? '' : 's'} registered` : 'no devices registered' },
|
|
292
295
|
{ phase: 'watchdog', state: watchdogEnabled ? 'ready' : 'missing', detail: watchdogEnabled ? 'enabled on this device' : 'disabled on this device' },
|
|
@@ -308,6 +311,8 @@ async function runSetupPhase(phase) {
|
|
|
308
311
|
await runComputerWizard();
|
|
309
312
|
else if (phase === 'secrets')
|
|
310
313
|
await import('./setup-secrets.js').then((m) => m.runSecretsSetupWizard());
|
|
314
|
+
else if (phase === 'term')
|
|
315
|
+
await runTermWizard();
|
|
311
316
|
else if (phase === 'accounts')
|
|
312
317
|
await runAccountsSetupWizard();
|
|
313
318
|
else if (phase === 'fleet')
|
|
@@ -366,11 +371,12 @@ export function registerSetupCommand(program) {
|
|
|
366
371
|
.description('Set up agents-cli, or re-open the capability onboarding hub.')
|
|
367
372
|
.option('-f, --force', 'Re-run setup even if ~/.agents/.system/ already exists (use with caution)')
|
|
368
373
|
.option('--no-system-repo', 'Skip cloning the system repo (you must populate ~/.agents/.system/ yourself)');
|
|
369
|
-
// Capability subcommands: `agents setup browser|computer|mine|secrets|accounts|fleet|alias|beta`.
|
|
374
|
+
// Capability subcommands: `agents setup browser|computer|term|mine|secrets|accounts|fleet|alias|beta`.
|
|
370
375
|
// Artifact publishing is no longer set up here — it lives in the standalone
|
|
371
376
|
// `artifacts` CLI (`artifacts share setup`/`join`, PHNX-3992).
|
|
372
377
|
registerSetupBrowserCommand(setupCmd);
|
|
373
378
|
registerSetupComputerCommand(setupCmd);
|
|
379
|
+
registerSetupTermCommand(setupCmd);
|
|
374
380
|
registerSetupMineCommand(setupCmd);
|
|
375
381
|
registerSetupSecretsCommand(setupCmd);
|
|
376
382
|
registerSetupAccountsCommand(setupCmd);
|
|
@@ -399,15 +405,15 @@ export function registerSetupCommand(program) {
|
|
|
399
405
|
`,
|
|
400
406
|
});
|
|
401
407
|
setupCmd.command('status')
|
|
402
|
-
.description('Show setup readiness for core, browser, computer, secrets, accounts, fleet, watchdog, and preferences.')
|
|
408
|
+
.description('Show setup readiness for core, browser, computer, secrets, term, accounts, fleet, watchdog, and preferences.')
|
|
403
409
|
.option('--json', 'print machine-readable JSON')
|
|
404
|
-
.option('--tool <name>', 'Read cached standalone tool status: browser, computer, secrets, or all')
|
|
410
|
+
.option('--tool <name>', 'Read cached standalone tool status: browser, computer, secrets, term, or all')
|
|
405
411
|
.option('--refresh', 'Explicitly refresh only the selected standalone tool health checks')
|
|
406
412
|
.action(async (options) => {
|
|
407
413
|
if (options.tool || options.refresh) {
|
|
408
414
|
const selected = options.tool ?? 'all';
|
|
409
415
|
if (selected !== 'all' && !SETUP_TOOLS.includes(selected))
|
|
410
|
-
throw new Error('Unknown tool. Choose browser, computer, secrets, or all.');
|
|
416
|
+
throw new Error('Unknown tool. Choose browser, computer, secrets, term, or all.');
|
|
411
417
|
const rows = options.refresh
|
|
412
418
|
? await refreshToolSetup(selected)
|
|
413
419
|
: getCachedToolSetup().filter((row) => selected === 'all' || row.tool === selected);
|
|
@@ -438,6 +444,7 @@ export function registerSetupCommand(program) {
|
|
|
438
444
|
agents setup browser
|
|
439
445
|
agents setup computer
|
|
440
446
|
agents setup secrets
|
|
447
|
+
agents setup term
|
|
441
448
|
agents setup accounts
|
|
442
449
|
agents setup fleet
|
|
443
450
|
agents setup watchdog
|
|
@@ -455,6 +462,7 @@ export function registerSetupCommand(program) {
|
|
|
455
462
|
agents setup browser # detect a browser + create the default profile
|
|
456
463
|
agents setup computer # install the signed macOS helper + grant permissions
|
|
457
464
|
agents setup secrets # choose secrets backend/policy defaults + import
|
|
465
|
+
agents setup term # install the standalone term CLI (PTY engine agents accounts add/login spawn)
|
|
458
466
|
agents setup accounts # mint a Claude setup-token for unattended usage/probe
|
|
459
467
|
agents setup fleet # discover Tailscale devices + configure SSH access
|
|
460
468
|
agents setup watchdog # choose which devices run the daemon watchdog pass
|
package/dist/commands/ssh.js
CHANGED
|
@@ -58,7 +58,6 @@ import { crabboxList, crabboxFind, crabboxSshArgv } from '../lib/crabbox/cli.js'
|
|
|
58
58
|
import { boxAddress, boxStatus, fmtIdleShort, fmtExpiresShort, registerLeaseCommand } from './lease.js';
|
|
59
59
|
import { registerSnapshotCommand } from './snapshot.js';
|
|
60
60
|
import { authCellColor, formatCheckedAge, isDeadVerdict, readAuthHealthCache, summarizeHostAuth, summarizeVerdicts, verdictColor, verdictLabel, writeFleetAuthRows, } from '../lib/auth-health.js';
|
|
61
|
-
import { runFleetLogin } from '../lib/fleet/remote-login.js';
|
|
62
61
|
import { getConfigValue, listConfig, setConfigValue, unsetConfigValue, configKeySpec, autoPoolMode, configuredDeviceRole, listConfiguredDeviceRoles, setConfiguredDeviceRole, } from '../lib/device-config.js';
|
|
63
62
|
import { filterAutoPool, listWorkerDevices } from '../lib/devices/pool.js';
|
|
64
63
|
import { registerCommandGroups, setHelpSections } from '../lib/help.js';
|
|
@@ -1055,46 +1054,6 @@ function renderAuthMatrix(results, opts) {
|
|
|
1055
1054
|
}
|
|
1056
1055
|
return lines;
|
|
1057
1056
|
}
|
|
1058
|
-
/** Render the final `fleet login` result summary in the house auth-matrix style. */
|
|
1059
|
-
function renderLoginMatrix(results) {
|
|
1060
|
-
const lines = [chalk.bold('Fleet login')];
|
|
1061
|
-
if (results.length === 0) {
|
|
1062
|
-
lines.push(chalk.gray(' Nothing pending — every requested account is already logged in.'));
|
|
1063
|
-
return lines;
|
|
1064
|
-
}
|
|
1065
|
-
const nameW = Math.max(6, ...results.map((r) => `${r.agent}@${r.device}`.length));
|
|
1066
|
-
for (const r of results) {
|
|
1067
|
-
const who = `${r.agent}@${r.device}`.padEnd(nameW);
|
|
1068
|
-
let badge;
|
|
1069
|
-
switch (r.state) {
|
|
1070
|
-
case 'authorized':
|
|
1071
|
-
badge = chalk.green('authorized');
|
|
1072
|
-
break;
|
|
1073
|
-
case 'ready':
|
|
1074
|
-
badge = chalk.cyan('code ready');
|
|
1075
|
-
break;
|
|
1076
|
-
case 'driving':
|
|
1077
|
-
badge = chalk.yellow('driving');
|
|
1078
|
-
break;
|
|
1079
|
-
case 'skipped':
|
|
1080
|
-
badge = chalk.gray('not remotable');
|
|
1081
|
-
break;
|
|
1082
|
-
case 'error':
|
|
1083
|
-
badge = chalk.red('error');
|
|
1084
|
-
break;
|
|
1085
|
-
default:
|
|
1086
|
-
badge = chalk.gray(r.state);
|
|
1087
|
-
break;
|
|
1088
|
-
}
|
|
1089
|
-
const note = r.detail ? chalk.dim(` ${r.detail}`) : (r.reason && !r.remotable ? chalk.dim(` ${r.reason}`) : '');
|
|
1090
|
-
lines.push(` ${who} ${badge}${note}`);
|
|
1091
|
-
}
|
|
1092
|
-
const auth = results.filter((r) => r.state === 'authorized').length;
|
|
1093
|
-
const remotable = results.filter((r) => r.remotable).length;
|
|
1094
|
-
lines.push('');
|
|
1095
|
-
lines.push(chalk.gray(` ${auth}/${remotable} remotable logins authorized`));
|
|
1096
|
-
return lines;
|
|
1097
|
-
}
|
|
1098
1057
|
/** Register the `agents devices` command tree (also aliased as `fleet`). */
|
|
1099
1058
|
function registerDevicesCommands(program) {
|
|
1100
1059
|
const devicesCmd = program
|
|
@@ -1141,7 +1100,7 @@ function registerDevicesCommands(program) {
|
|
|
1141
1100
|
{ title: 'Inspect', names: ['list', 'show', 'status', 'ping', 'harnesses', 'accounts', 'snapshot'] },
|
|
1142
1101
|
{ title: 'Disposable devices', names: ['lease'] },
|
|
1143
1102
|
{ title: 'Configure a device', names: ['config', 'describe', 'render'] },
|
|
1144
|
-
{ title: 'Fleet operations', names: ['update', 'run', '
|
|
1103
|
+
{ title: 'Fleet operations', names: ['update', 'run', 'capture', 'apply', 'worktrees'] },
|
|
1145
1104
|
]);
|
|
1146
1105
|
devicesCmd
|
|
1147
1106
|
.command('sync')
|
|
@@ -2008,33 +1967,6 @@ agents devices accounts --json # machine-readable, per-host account gr
|
|
|
2008
1967
|
Collapses the installs that share one account (e.g. five claude versions on one
|
|
2009
1968
|
email) into a single row. Use \`agents devices harnesses\` for the per-install view.`,
|
|
2010
1969
|
});
|
|
2011
|
-
devicesCmd
|
|
2012
|
-
.command('login')
|
|
2013
|
-
.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).')
|
|
2014
|
-
.option('--agents <csv>', 'only these agents (comma-separated); default: every agent with a device-code flow')
|
|
2015
|
-
.option('--devices <csv>', 'only these devices (comma-separated); default: every online box')
|
|
2016
|
-
.option('--all', 'target every device-code pair regardless of cached login state (cold cache / forced re-login)')
|
|
2017
|
-
.option('--interactive', 'guided one-box-at-a-time wizard (codes requested just-in-time)')
|
|
2018
|
-
.option('--json', 'output the final result matrix as JSON')
|
|
2019
|
-
.action(async (opts) => {
|
|
2020
|
-
const csv = (s) => (s ? s.split(',').map((x) => x.trim()).filter(Boolean) : undefined);
|
|
2021
|
-
const results = await runFleetLogin({
|
|
2022
|
-
agents: csv(opts.agents),
|
|
2023
|
-
devices: csv(opts.devices),
|
|
2024
|
-
all: opts.all,
|
|
2025
|
-
interactive: opts.interactive,
|
|
2026
|
-
json: opts.json,
|
|
2027
|
-
});
|
|
2028
|
-
if (opts.json) {
|
|
2029
|
-
console.log(JSON.stringify(results, null, 2));
|
|
2030
|
-
}
|
|
2031
|
-
else {
|
|
2032
|
-
for (const line of renderLoginMatrix(results))
|
|
2033
|
-
console.log(line);
|
|
2034
|
-
}
|
|
2035
|
-
if (results.some((r) => r.remotable && r.state !== 'authorized'))
|
|
2036
|
-
process.exitCode = 1;
|
|
2037
|
-
});
|
|
2038
1970
|
devicesCmd
|
|
2039
1971
|
.command('show <name>')
|
|
2040
1972
|
.description('Show the full profile for one device.')
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Detects which installed versions have expired credentials and rotates
|
|
5
5
|
* authentication tokens so users maintain active sessions across version switches.
|
|
6
6
|
*/
|
|
7
|
-
import type
|
|
7
|
+
import { type AgentId, type RunStrategy } from '../types.js';
|
|
8
8
|
import type { FallbackEntry } from '../exec.js';
|
|
9
9
|
import { PROJECTION_HORIZON_MIN, capacityWeight } from './capacity.js';
|
|
10
10
|
import { type AccountInfo, type CredentialPresence } from '../agents.js';
|
|
@@ -549,6 +549,68 @@ export declare const DEFAULT_ROTATION_FAILOVER_LIMIT = 3;
|
|
|
549
549
|
* completely unchanged.
|
|
550
550
|
*/
|
|
551
551
|
export declare function rotationFailoverChain(rotation: RotateResult | null, pickedVersion: string, limit?: number): FallbackEntry[];
|
|
552
|
+
/**
|
|
553
|
+
* Whether a run SHAPE may take the preflight alternate-harness handoff
|
|
554
|
+
* ({@link preflightFallbackHandoff}) — the sibling of
|
|
555
|
+
* {@link shouldArmRotationFailover}, and load-bearing for the same reason.
|
|
556
|
+
*
|
|
557
|
+
* The handoff consumes the entry it uses from the `--fallback` spec, and the
|
|
558
|
+
* canonical `--fallback` validation runs LATER in the run command: it rejects a
|
|
559
|
+
* chain on an interactive, `--acp`, `--loop`, or `--resume-checkpoint` run, and
|
|
560
|
+
* requires a prompt. Handing off first on one of those shapes would empty the
|
|
561
|
+
* spec, leave that guard nothing to reject, and let the run proceed as the
|
|
562
|
+
* alternate harness — an interactive Codex launch from
|
|
563
|
+
* `agents run claude --interactive --fallback codex`, which the CLI refuses
|
|
564
|
+
* today. Gating the shape here keeps the original validation intact: an
|
|
565
|
+
* ineligible shape never switches, so the spec survives and still fails there
|
|
566
|
+
* with its own message.
|
|
567
|
+
*
|
|
568
|
+
* `resume` and `workflowScoped` are excluded for their own reasons: a resume is
|
|
569
|
+
* bound to the session's OWN harness, and a workflow's tool/MCP scoping is
|
|
570
|
+
* claude-only, so switching harness would silently drop the declared sandbox —
|
|
571
|
+
* the fail-open `runWithFallback` only warns about.
|
|
572
|
+
*
|
|
573
|
+
* Pure so the matrix is unit-testable without invoking the run command.
|
|
574
|
+
*/
|
|
575
|
+
export interface PreflightHandoffContext {
|
|
576
|
+
hasPrompt: boolean;
|
|
577
|
+
interactive: boolean;
|
|
578
|
+
acp: boolean;
|
|
579
|
+
loop: boolean;
|
|
580
|
+
resumeCheckpoint: boolean;
|
|
581
|
+
resume: boolean;
|
|
582
|
+
workflowScoped: boolean;
|
|
583
|
+
}
|
|
584
|
+
export declare function preflightHandoffEligible(ctx: PreflightHandoffContext): boolean;
|
|
585
|
+
/**
|
|
586
|
+
* The permitted alternate harness to launch when EVERY account of the primary
|
|
587
|
+
* agent is already exhausted at PREFLIGHT (PHNX-3999 F19).
|
|
588
|
+
*
|
|
589
|
+
* `runWithFallback` only reaches a `--fallback` entry after the primary has
|
|
590
|
+
* actually RUN and hit a rate limit. When the primary's accounts are throttled
|
|
591
|
+
* before the run starts, version resolution reports `exhausted` and the run used
|
|
592
|
+
* to exit — so the alternate a user configured for exactly this case never fired.
|
|
593
|
+
* This resolves the first usable alternate from the same `--fallback` spec, and
|
|
594
|
+
* returns the remainder of the spec so the alternate becomes the primary and is
|
|
595
|
+
* not also listed as its own fallback.
|
|
596
|
+
*
|
|
597
|
+
* Deliberately NOT a second parser: an entry this cannot resolve exactly — a
|
|
598
|
+
* typo, an alias the canonical parse would fuzzy-match, the primary itself —
|
|
599
|
+
* makes the WHOLE spec yield `null`, so the run reaches the one canonical
|
|
600
|
+
* `--fallback` parse and fails (or corrects) there with the message it already
|
|
601
|
+
* has. `--fallback typoo,codex` must never silently launch codex while dropping
|
|
602
|
+
* `typoo`.
|
|
603
|
+
*
|
|
604
|
+
* Returns `null` when there is no spec, when any entry is not an exact agent id
|
|
605
|
+
* or names the primary, or when the primary is only sign-in-recoverable rather
|
|
606
|
+
* than throttled — a logged-out account is fixed by logging in, not by silently
|
|
607
|
+
* running a different harness.
|
|
608
|
+
*/
|
|
609
|
+
export declare function preflightFallbackHandoff(spec: string | undefined, primary: AgentId, exhausted: RotateCandidate[]): {
|
|
610
|
+
agent: AgentId;
|
|
611
|
+
version?: string;
|
|
612
|
+
remainingSpec?: string;
|
|
613
|
+
} | null;
|
|
552
614
|
/**
|
|
553
615
|
* Whether a run is eligible to have a mid-run rate-limit failover chain armed
|
|
554
616
|
* (issue #348). Failover injects synthesized `FallbackEntry`s into the same
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import * as fs from 'fs';
|
|
8
8
|
import * as path from 'path';
|
|
9
|
+
import { isAgentId } from '../types.js';
|
|
9
10
|
import { PROJECTION_HORIZON_MIN, capacityWeight } from './capacity.js';
|
|
10
11
|
import { accountDisplayLabel, getAccountInfo, credentialPresence, ALL_AGENT_IDS, } from '../agents.js';
|
|
11
12
|
import { readMeta, getHelpersDir } from '../state.js';
|
|
@@ -1206,6 +1207,61 @@ export function rotationFailoverChain(rotation, pickedVersion, limit = DEFAULT_R
|
|
|
1206
1207
|
}
|
|
1207
1208
|
return chain;
|
|
1208
1209
|
}
|
|
1210
|
+
export function preflightHandoffEligible(ctx) {
|
|
1211
|
+
return (ctx.hasPrompt &&
|
|
1212
|
+
!ctx.interactive &&
|
|
1213
|
+
!ctx.acp &&
|
|
1214
|
+
!ctx.loop &&
|
|
1215
|
+
!ctx.resumeCheckpoint &&
|
|
1216
|
+
!ctx.resume &&
|
|
1217
|
+
!ctx.workflowScoped);
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* The permitted alternate harness to launch when EVERY account of the primary
|
|
1221
|
+
* agent is already exhausted at PREFLIGHT (PHNX-3999 F19).
|
|
1222
|
+
*
|
|
1223
|
+
* `runWithFallback` only reaches a `--fallback` entry after the primary has
|
|
1224
|
+
* actually RUN and hit a rate limit. When the primary's accounts are throttled
|
|
1225
|
+
* before the run starts, version resolution reports `exhausted` and the run used
|
|
1226
|
+
* to exit — so the alternate a user configured for exactly this case never fired.
|
|
1227
|
+
* This resolves the first usable alternate from the same `--fallback` spec, and
|
|
1228
|
+
* returns the remainder of the spec so the alternate becomes the primary and is
|
|
1229
|
+
* not also listed as its own fallback.
|
|
1230
|
+
*
|
|
1231
|
+
* Deliberately NOT a second parser: an entry this cannot resolve exactly — a
|
|
1232
|
+
* typo, an alias the canonical parse would fuzzy-match, the primary itself —
|
|
1233
|
+
* makes the WHOLE spec yield `null`, so the run reaches the one canonical
|
|
1234
|
+
* `--fallback` parse and fails (or corrects) there with the message it already
|
|
1235
|
+
* has. `--fallback typoo,codex` must never silently launch codex while dropping
|
|
1236
|
+
* `typoo`.
|
|
1237
|
+
*
|
|
1238
|
+
* Returns `null` when there is no spec, when any entry is not an exact agent id
|
|
1239
|
+
* or names the primary, or when the primary is only sign-in-recoverable rather
|
|
1240
|
+
* than throttled — a logged-out account is fixed by logging in, not by silently
|
|
1241
|
+
* running a different harness.
|
|
1242
|
+
*/
|
|
1243
|
+
export function preflightFallbackHandoff(spec, primary, exhausted) {
|
|
1244
|
+
if (!spec)
|
|
1245
|
+
return null;
|
|
1246
|
+
if (exhausted.length === 0)
|
|
1247
|
+
return null;
|
|
1248
|
+
if (signInRecoverableCandidates(exhausted).length > 0)
|
|
1249
|
+
return null;
|
|
1250
|
+
const entries = spec.split(',').map((e) => e.trim()).filter(Boolean);
|
|
1251
|
+
if (entries.length === 0)
|
|
1252
|
+
return null;
|
|
1253
|
+
const parsed = entries.map((entry) => entry.split('@'));
|
|
1254
|
+
// Every entry has to be exactly resolvable, or this defers entirely.
|
|
1255
|
+
if (parsed.some(([name]) => !isAgentId(name) || name === primary))
|
|
1256
|
+
return null;
|
|
1257
|
+
const [name, version] = parsed[0];
|
|
1258
|
+
const remaining = entries.slice(1);
|
|
1259
|
+
return {
|
|
1260
|
+
agent: name,
|
|
1261
|
+
version: version || undefined,
|
|
1262
|
+
remainingSpec: remaining.length > 0 ? remaining.join(',') : undefined,
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1209
1265
|
export function shouldArmRotationFailover(ctx) {
|
|
1210
1266
|
return (ctx.hasRotation &&
|
|
1211
1267
|
ctx.hasVersion &&
|
package/dist/lib/accounts/add.js
CHANGED
|
@@ -89,11 +89,11 @@ export function workerProvisioningHint(agent) {
|
|
|
89
89
|
parts.push(env === 'provider' ? 'a provider API key collected by add (--api-key)' : `the ${env} collected by add (--api-key)`);
|
|
90
90
|
}
|
|
91
91
|
else if (kind.startsWith('per-device')) {
|
|
92
|
-
parts.push(`per-device login on each worker (--per-device
|
|
92
|
+
parts.push(`per-device login on each worker (--per-device; run \`agents run ${agent} --device <box>\` there and complete its native login)`);
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
if (parts.length === 0)
|
|
96
|
-
return `${agent} has no portable credential — it logs in per box (agents
|
|
96
|
+
return `${agent} has no portable credential — it logs in per box (run \`agents run ${agent} --device <box>\` on the worker and complete its native login)`;
|
|
97
97
|
return parts.join(', or ');
|
|
98
98
|
}
|
|
99
99
|
/**
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import * as fs from 'node:fs';
|
|
15
15
|
import * as path from 'node:path';
|
|
16
|
-
import { agentConfigDirName } from '../agents.js';
|
|
16
|
+
import { AGENTS, agentConfigDirName } from '../agents.js';
|
|
17
|
+
import { createLink } from '../platform/links.js';
|
|
17
18
|
import { harnessAuth, harnessWorkerIsPerDevice } from '../harness-auth-capabilities.js';
|
|
18
19
|
import { installSessionTrackerHookSync } from '../hooks/install.js';
|
|
19
20
|
import { getGlobalDefault, getVersionHomePath, listInstalledVersions } from '../installations/store.js';
|
|
@@ -64,7 +65,36 @@ function projectResources(harness, version, destHome, fromHome) {
|
|
|
64
65
|
if (!writer)
|
|
65
66
|
continue;
|
|
66
67
|
if (kind === 'rules') {
|
|
67
|
-
|
|
68
|
+
// Symlink to the version home's rules instead of composing a duplicate.
|
|
69
|
+
// Harnesses whose shim pins a separate config dir (e.g. CLAUDE_CONFIG_DIR
|
|
70
|
+
// → account slot) cause the harness to load rules from both the version
|
|
71
|
+
// home (~/.claude/) and the slot, doubling ~5 K tokens per session.
|
|
72
|
+
// A symlink gives both paths the same inode so the harness can dedup.
|
|
73
|
+
const cap = AGENTS[harness].capabilities.rules;
|
|
74
|
+
if (typeof cap !== 'object')
|
|
75
|
+
continue;
|
|
76
|
+
const srcFile = path.join(fromHome, agentConfigDirName(harness), cap.file);
|
|
77
|
+
if (!fs.existsSync(srcFile)) {
|
|
78
|
+
writer.write({ version, versionHome: destHome, selection: { preset: 'default' }, cwd });
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const destFile = path.join(destHome, agentConfigDirName(harness), cap.file);
|
|
82
|
+
fs.mkdirSync(path.dirname(destFile), { recursive: true });
|
|
83
|
+
try {
|
|
84
|
+
const st = fs.lstatSync(destFile);
|
|
85
|
+
if (st.isSymbolicLink() || st.isFile())
|
|
86
|
+
fs.unlinkSync(destFile);
|
|
87
|
+
}
|
|
88
|
+
catch { /* did not exist */ }
|
|
89
|
+
try {
|
|
90
|
+
createLink(srcFile, destFile);
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
if (err.code === 'EEXIST') {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
68
98
|
continue;
|
|
69
99
|
}
|
|
70
100
|
const names = getDetector(kind, harness)?.list({ version, versionHome: fromHome, cwd }) ?? [];
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* This module picks the delivery mechanism from (open feed block × session
|
|
11
11
|
* liveness × runtime rail). Pure — unit-testable without a live terminal.
|
|
12
12
|
*/
|
|
13
|
-
import type
|
|
13
|
+
import { type OpenBlock, type BlockOption } from './feed/feed.js';
|
|
14
14
|
import type { ActiveSession } from './session/active.js';
|
|
15
15
|
import type { InjectTarget } from './terminal/index.js';
|
|
16
16
|
export type AnswerRouteKind = 'mailbox' | 'tmux' | 'iterm' | 'resume' | 'refuse';
|
|
@@ -67,7 +67,16 @@ export declare function keystrokesForAnswer(answer: string, options?: Array<Pick
|
|
|
67
67
|
};
|
|
68
68
|
/** True when the session is waiting on user input (parked on a question/plan). */
|
|
69
69
|
export declare function isParkedOnInput(session: ActiveSession | null | undefined): boolean;
|
|
70
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* True when an open feed block still needs an answer.
|
|
72
|
+
*
|
|
73
|
+
* Openness comes from {@link deriveBlockState}, not from `block.answer` being
|
|
74
|
+
* truthy: a PENDING claim records the answer while deliberately leaving the
|
|
75
|
+
* block `open` (feed.ts `recordAnswer`'s two-phase mode), so reading the field
|
|
76
|
+
* directly would call a claimed-but-undelivered block "closed" — and a retry of
|
|
77
|
+
* a stranded claim would then re-route a parked headless agent to the mailbox it
|
|
78
|
+
* will never drain (PHNX-3999).
|
|
79
|
+
*/
|
|
71
80
|
export declare function isOpenQuestionBlock(block: OpenBlock | null | undefined): boolean;
|
|
72
81
|
/**
|
|
73
82
|
* Pick the delivery mechanism for one answer.
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parked-agent answer router (RUSH-1474).
|
|
3
|
+
*
|
|
4
|
+
* `agents message` historically only enqueued into the mailbox (delivered at the
|
|
5
|
+
* next PreToolUse). That steers a *running* agent between tool calls but never
|
|
6
|
+
* unblocks a *parked* agent:
|
|
7
|
+
* - interactive TUI open on AskUserQuestion (needs keystrokes, not context)
|
|
8
|
+
* - headless run waiting on input (no next tool call; needs resume)
|
|
9
|
+
*
|
|
10
|
+
* This module picks the delivery mechanism from (open feed block × session
|
|
11
|
+
* liveness × runtime rail). Pure — unit-testable without a live terminal.
|
|
12
|
+
*/
|
|
13
|
+
import { deriveBlockState } from './feed/feed.js';
|
|
1
14
|
import { addressabilityRecoveryHint } from './terminal/resolve.js';
|
|
2
15
|
import { injectTargetFromReplyRail } from './session/inject.js';
|
|
3
16
|
/** The Escape keystroke as its raw control byte — what a tmux/iterm rail reads as a real Escape. */
|
|
@@ -64,11 +77,22 @@ export function isParkedOnInput(session) {
|
|
|
64
77
|
}
|
|
65
78
|
return false;
|
|
66
79
|
}
|
|
67
|
-
/**
|
|
80
|
+
/**
|
|
81
|
+
* True when an open feed block still needs an answer.
|
|
82
|
+
*
|
|
83
|
+
* Openness comes from {@link deriveBlockState}, not from `block.answer` being
|
|
84
|
+
* truthy: a PENDING claim records the answer while deliberately leaving the
|
|
85
|
+
* block `open` (feed.ts `recordAnswer`'s two-phase mode), so reading the field
|
|
86
|
+
* directly would call a claimed-but-undelivered block "closed" — and a retry of
|
|
87
|
+
* a stranded claim would then re-route a parked headless agent to the mailbox it
|
|
88
|
+
* will never drain (PHNX-3999).
|
|
89
|
+
*/
|
|
68
90
|
export function isOpenQuestionBlock(block) {
|
|
69
91
|
if (!block)
|
|
70
92
|
return false;
|
|
71
|
-
if (block.
|
|
93
|
+
if (block.parkedAt || block.continuedAt || block.defaultedAt)
|
|
94
|
+
return false;
|
|
95
|
+
if (deriveBlockState(block) !== 'open')
|
|
72
96
|
return false;
|
|
73
97
|
return (block.questions?.length ?? 0) > 0;
|
|
74
98
|
}
|
package/dist/lib/auth-mint.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* First-class setup-token mint + seed (PHNX-2364).
|
|
3
3
|
*
|
|
4
|
-
* Closes the mint-auth manual recipe: drive `claude setup-token` through
|
|
5
|
-
*
|
|
4
|
+
* Closes the mint-auth manual recipe: drive `claude setup-token` through an
|
|
5
|
+
* injectable term driver (`lib/term-driver.ts`), capture a well-formed
|
|
6
6
|
* `sk-ant-oat01-…` token (the #1767 ANSI-banner guard), and seed BOTH:
|
|
7
7
|
*
|
|
8
8
|
* 1. a named provider account (`agents accounts add` shape, policy never)
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import type { AgentId, Meta } from './types.js';
|
|
17
17
|
import { type CredentialAccount } from './account-registry.js';
|
|
18
18
|
import { type StorableCredentialKind } from './secrets-policy.js';
|
|
19
|
-
import { type DriveOptions, type TermDriver } from './
|
|
19
|
+
import { type DriveOptions, type TermDriver } from './term-driver.js';
|
|
20
20
|
/** Well-formed Claude setup-token as it appears inside a TTY blob. */
|
|
21
21
|
export declare const CLAUDE_SETUP_TOKEN_CAPTURE_RE: RegExp;
|
|
22
22
|
export interface MintFlow {
|
|
@@ -37,7 +37,8 @@ export interface MintFlow {
|
|
|
37
37
|
* key, COLLECTED via `--api-key` or a prompt by `accounts add` (never derived
|
|
38
38
|
* from OAuth). The api-key entries describe collection only — they have no
|
|
39
39
|
* mint argv, and `mintAndSeed` refuses them with the `accounts add` pointer.
|
|
40
|
-
*
|
|
40
|
+
* A token-less harness (kimi/antigravity) has no portable credential and logs
|
|
41
|
+
* in per box (run it there and complete its native login).
|
|
41
42
|
* The apiKeyEnv values are pinned to HARNESS_AUTH's `api-key:<ENV>` worker
|
|
42
43
|
* kinds by auth-mint.test.ts — keep them in lockstep.
|
|
43
44
|
*/
|
package/dist/lib/auth-mint.js
CHANGED
|
@@ -5,7 +5,7 @@ import { bundleBackendSync, isSecretsClientError, bundleExists, bundleExistsSync
|
|
|
5
5
|
import { assertStorableCredentialKind, reservedStoreName, } from './secrets-policy.js';
|
|
6
6
|
import { getBinaryPath, getGlobalDefault, getVersionHomePath, listInstalledVersions } from './installations/versions.js';
|
|
7
7
|
import { shellQuote } from './ssh-exec.js';
|
|
8
|
-
import { defaultTermDriver, } from './
|
|
8
|
+
import { defaultTermDriver, } from './term-driver.js';
|
|
9
9
|
import { showUrl } from './open-url.js';
|
|
10
10
|
import { loadDevices } from './devices/registry.js';
|
|
11
11
|
import { isSelfHost } from './devices/self-host.js';
|
|
@@ -21,7 +21,8 @@ const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
|
21
21
|
* key, COLLECTED via `--api-key` or a prompt by `accounts add` (never derived
|
|
22
22
|
* from OAuth). The api-key entries describe collection only — they have no
|
|
23
23
|
* mint argv, and `mintAndSeed` refuses them with the `accounts add` pointer.
|
|
24
|
-
*
|
|
24
|
+
* A token-less harness (kimi/antigravity) has no portable credential and logs
|
|
25
|
+
* in per box (run it there and complete its native login).
|
|
25
26
|
* The apiKeyEnv values are pinned to HARNESS_AUTH's `api-key:<ENV>` worker
|
|
26
27
|
* kinds by auth-mint.test.ts — keep them in lockstep.
|
|
27
28
|
*/
|
|
@@ -63,7 +64,7 @@ export function unmintableMessage(harness) {
|
|
|
63
64
|
return [
|
|
64
65
|
`Cannot mint a setup-token for '${harness}'.`,
|
|
65
66
|
`Interactive setup-token mint is implemented for: ${listMintableHarnesses().join(', ')}.`,
|
|
66
|
-
`
|
|
67
|
+
`A token-less harness like '${harness}' logs in per box — run it on that device and complete its native login.`,
|
|
67
68
|
`API-key / pasted-token accounts use \`agents accounts add <name> --provider <provider> --auth api-key\`.`,
|
|
68
69
|
].join(' ');
|
|
69
70
|
}
|
|
@@ -731,6 +731,16 @@ export declare class BrowserService {
|
|
|
731
731
|
* async/promise evaluation fails honestly with an AppleScript error.
|
|
732
732
|
*/
|
|
733
733
|
private evaluateArcNative;
|
|
734
|
+
/**
|
|
735
|
+
* Resolve a task's owned Arc tabs against one shared snapshot of every
|
|
736
|
+
* window/space/tab, rather than asking Arc per tab as `listArcTaskTabs` does.
|
|
737
|
+
*
|
|
738
|
+
* Same ownership rule as the live path — `requireArcTask`, then the recorded
|
|
739
|
+
* ref must be present in its original window and Space. The snapshot changes
|
|
740
|
+
* how often Arc is asked, never what counts as owned. Actions keep the live
|
|
741
|
+
* path, because a snapshot can go stale before it is acted on.
|
|
742
|
+
*/
|
|
743
|
+
private listArcTaskTabsFromSnapshot;
|
|
734
744
|
private listArcTaskTabs;
|
|
735
745
|
/**
|
|
736
746
|
* Close native Arc tabs owned by a task. Only closes tabs the task created
|