@phnx-labs/agents-cli 1.22.105 → 1.22.107
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 +32 -0
- package/README.md +28 -6
- package/dist/browser.js +0 -0
- package/dist/commands/exec.d.ts +78 -8
- package/dist/commands/exec.js +407 -284
- package/dist/commands/resume.d.ts +6 -21
- package/dist/commands/resume.js +18 -55
- package/dist/commands/run-account-picker.d.ts +11 -0
- package/dist/commands/run-account-picker.js +11 -1
- package/dist/commands/sessions-resume.d.ts +4 -0
- package/dist/commands/sessions-resume.js +132 -49
- package/dist/commands/sessions.js +32 -5
- package/dist/index.js +0 -0
- package/dist/lib/accounting/account-launch.d.ts +54 -0
- package/dist/lib/accounting/account-launch.js +117 -0
- package/dist/lib/accounting/account-pool-collect.js +2 -1
- package/dist/lib/accounting/account-pool.d.ts +2 -0
- package/dist/lib/accounting/account-pool.js +1 -0
- package/dist/lib/accounting/rotate.d.ts +32 -6
- package/dist/lib/accounting/rotate.js +70 -42
- package/dist/lib/accounting/usage.d.ts +71 -0
- package/dist/lib/accounting/usage.js +160 -11
- package/dist/lib/exec-account-home.d.ts +3 -1
- package/dist/lib/exec-account-home.js +2 -2
- package/dist/lib/exec.d.ts +27 -1
- package/dist/lib/exec.js +150 -27
- package/dist/lib/hosts/dispatch.d.ts +1 -1
- package/dist/lib/hosts/dispatch.js +1 -1
- package/dist/lib/models.d.ts +1 -1
- package/dist/lib/models.js +4 -4
- package/dist/lib/session/actor-sidecar.d.ts +3 -11
- package/dist/lib/session/actor-sidecar.js +3 -0
- package/dist/lib/session/claude-accounts.d.ts +12 -73
- package/dist/lib/session/claude-accounts.js +32 -70
- package/dist/lib/session/db.d.ts +1 -1
- package/dist/lib/session/db.js +18 -5
- package/dist/lib/session/discover.d.ts +4 -0
- package/dist/lib/session/discover.js +116 -15
- package/dist/lib/session/recovery.d.ts +30 -34
- package/dist/lib/session/recovery.js +212 -76
- package/dist/lib/session/types.d.ts +2 -0
- package/dist/lib/teams/placement-probe.js +1 -1
- package/dist/session-tracker/dist/adapters/claude.d.ts +10 -0
- package/dist/session-tracker/dist/adapters/claude.js +45 -0
- package/dist/session-tracker/dist/hook.sh +191 -0
- package/dist/session-tracker/dist/index.d.ts +19 -0
- package/dist/session-tracker/dist/index.js +67 -0
- package/dist/session-tracker/dist/install-hook.d.ts +19 -0
- package/dist/session-tracker/dist/install-hook.js +245 -0
- package/dist/session-tracker/dist/prune-state.d.ts +2 -0
- package/dist/session-tracker/dist/prune-state.js +7 -0
- package/dist/session-tracker/dist/reader.d.ts +7 -0
- package/dist/session-tracker/dist/reader.js +151 -0
- package/dist/session-tracker/dist/state-file.d.ts +10 -0
- package/dist/session-tracker/dist/state-file.js +119 -0
- package/dist/session-tracker/dist/types.d.ts +32 -0
- package/dist/session-tracker/dist/types.js +1 -0
- package/dist/session-tracker/dist/writer.d.ts +12 -0
- package/dist/session-tracker/dist/writer.js +27 -0
- package/package.json +1 -1
package/dist/commands/exec.js
CHANGED
|
@@ -11,6 +11,7 @@ import { isTierToken } from '../lib/model-tiers.js';
|
|
|
11
11
|
import { RUN_AUTO_KEYWORD } from '../lib/types.js';
|
|
12
12
|
import { setHelpSections } from '../lib/help.js';
|
|
13
13
|
import { isInteractiveTerminal, isPromptCancelled, requireInteractiveSelection } from './utils.js';
|
|
14
|
+
import { isHumanFacingRun } from './run-account-picker.js';
|
|
14
15
|
import { getUserAgentsDir, readMeta } from '../lib/state.js';
|
|
15
16
|
import { parseLoopInterval } from '../lib/loop.js';
|
|
16
17
|
import { AGENTS, resolveAgentName, isAgentHardDeprecated, hardDeprecationError } from '../lib/agents.js';
|
|
@@ -36,15 +37,47 @@ export function parseExplicitSessionId(value) {
|
|
|
36
37
|
}
|
|
37
38
|
return value;
|
|
38
39
|
}
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Parse the trailing picker markers on an `agents run` agent spec. A terminal
|
|
42
|
+
* run of `#`/`@` characters requests the account picker (`#`) and/or the
|
|
43
|
+
* device picker (`@`) — each at most once, in either order (`claude#@`,
|
|
44
|
+
* `claude@#`). Stripping them yields the normalized spec (`claude#work@` →
|
|
45
|
+
* `claude#work`, version pins and `#label` account pins intact). A picker
|
|
46
|
+
* cannot combine with an explicit pin of what it picks: `claude@2.1.218#`
|
|
47
|
+
* (the account picker already chooses the version — the old `claude@2.1.218@`
|
|
48
|
+
* rule), `claude#work#`, `claude@@`.
|
|
49
|
+
*/
|
|
50
|
+
export function parseRunPickerMarkers(agentSpec) {
|
|
51
|
+
let rest = agentSpec;
|
|
52
|
+
let accountPicker = false;
|
|
53
|
+
let devicePicker = false;
|
|
54
|
+
let reason;
|
|
55
|
+
while (rest.endsWith('#') || rest.endsWith('@')) {
|
|
56
|
+
const marker = rest.endsWith('#') ? '#' : '@';
|
|
57
|
+
if (marker === '#') {
|
|
58
|
+
if (accountPicker && reason === undefined)
|
|
59
|
+
reason = `the # picker marker may appear at most once in '${agentSpec}'`;
|
|
60
|
+
accountPicker = true;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
if (devicePicker && reason === undefined)
|
|
64
|
+
reason = `the @ picker marker may appear at most once in '${agentSpec}'`;
|
|
65
|
+
devicePicker = true;
|
|
66
|
+
}
|
|
67
|
+
rest = rest.slice(0, -1);
|
|
68
|
+
}
|
|
69
|
+
if (reason === undefined) {
|
|
70
|
+
if (!rest) {
|
|
71
|
+
reason = `'${agentSpec}' names no agent before the picker markers`;
|
|
72
|
+
}
|
|
73
|
+
else if (accountPicker && (rest.includes('@') || rest.includes('#'))) {
|
|
74
|
+
reason = `an explicit pin in '${rest}' already selects what the # account picker chooses`;
|
|
75
|
+
}
|
|
76
|
+
else if (devicePicker && rest.includes('@')) {
|
|
77
|
+
reason = `an explicit pin in '${rest}' cannot combine with the @ device picker`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return { accountPicker, devicePicker, normalizedAgentSpec: rest, valid: reason === undefined, reason };
|
|
48
81
|
}
|
|
49
82
|
/**
|
|
50
83
|
* The `--device` alias family — the flags that mean "dispatch this run to another
|
|
@@ -57,7 +90,11 @@ export function parseRunAccountPickerRequest(agentSpec) {
|
|
|
57
90
|
export function hostTargetGiven(options) {
|
|
58
91
|
return [options.host, options.device, options.on, options.computer].filter((v) => !!v);
|
|
59
92
|
}
|
|
60
|
-
/**
|
|
93
|
+
/**
|
|
94
|
+
* Return every option whose selection semantics conflict with an account
|
|
95
|
+
* choice (`agent#`). Device routing is deliberately absent: the marker rides
|
|
96
|
+
* the hop and the peer picks from ITS slots.
|
|
97
|
+
*/
|
|
61
98
|
export function runAccountPickerConflicts(options) {
|
|
62
99
|
const conflicts = [];
|
|
63
100
|
if (options.resume !== undefined)
|
|
@@ -66,6 +103,21 @@ export function runAccountPickerConflicts(options) {
|
|
|
66
103
|
conflicts.push('--strategy');
|
|
67
104
|
if (options.balanced)
|
|
68
105
|
conflicts.push('--balanced');
|
|
106
|
+
if (options.lease)
|
|
107
|
+
conflicts.push('--lease');
|
|
108
|
+
if (options.box)
|
|
109
|
+
conflicts.push('--box');
|
|
110
|
+
if (options.account)
|
|
111
|
+
conflicts.push(`--account ${options.account}`);
|
|
112
|
+
return conflicts;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Return every option that already decides where the run lands, so a device
|
|
116
|
+
* choice (`agent@`) would be silently ignored: any explicit host flag, and the
|
|
117
|
+
* lease/box paths, which own placement outright.
|
|
118
|
+
*/
|
|
119
|
+
export function runDevicePickerConflicts(options) {
|
|
120
|
+
const conflicts = hostTargetGiven(options).map((h) => `--device ${h}`);
|
|
69
121
|
if (options.lease)
|
|
70
122
|
conflicts.push('--lease');
|
|
71
123
|
if (options.box)
|
|
@@ -82,16 +134,54 @@ export { RUN_AUTO_KEYWORD };
|
|
|
82
134
|
/**
|
|
83
135
|
* Whether `run auto` should default its host layer to the affinity pick (the
|
|
84
136
|
* same machinery as `--device auto`). False when the caller pinned any host
|
|
85
|
-
* flag, and false when this process was itself dispatched by a host run —
|
|
86
|
-
* dispatcher exports AGENTS_RUN_AUTO_HOST_RESOLVED=1 into the remote SHELL
|
|
137
|
+
* flag, and false when this process was itself dispatched by a host run —
|
|
138
|
+
* the dispatcher exports AGENTS_RUN_AUTO_HOST_RESOLVED=1 into the remote SHELL
|
|
87
139
|
* (hosts/dispatch.ts remoteRunShellPrelude) because it already resolved the
|
|
88
140
|
* host layer, and re-picking here would chain-hop the run across the fleet.
|
|
89
|
-
*
|
|
141
|
+
* An INTERACTIVE dispatch of a named harness also reaches the remote as a bare
|
|
142
|
+
* `agents run <harness>` (its argv forwards without the routing flag), so the
|
|
143
|
+
* interactive prelude's AGENTS_REMOTE_INTERACTIVE=1 counts as "already placed"
|
|
144
|
+
* too — without it the remote would re-place the run and ping-pong across the
|
|
145
|
+
* fleet. Pure so the pinning matrix is unit-testable.
|
|
90
146
|
*/
|
|
91
147
|
export function runAutoDefaultsToAffinity(options, env = process.env) {
|
|
92
148
|
if (hostTargetGiven(options).length > 0)
|
|
93
149
|
return false;
|
|
94
|
-
|
|
150
|
+
if (env.AGENTS_RUN_AUTO_HOST_RESOLVED === '1')
|
|
151
|
+
return false;
|
|
152
|
+
return env.AGENTS_REMOTE_INTERACTIVE !== '1';
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Whether a bare human-facing `agents run <harness>` — no prompt, so an
|
|
156
|
+
* interactive TUI run — defaults its placement to `--device auto` (PHNX-4083).
|
|
157
|
+
* A marker left off is decided for you: no `#` means balanced rotation, and no
|
|
158
|
+
* `@` (and no other placement flag) now means automatic device placement — the
|
|
159
|
+
* same engine as `--device auto`, whose pool never contains a box marked
|
|
160
|
+
* `personal`. ALL of these must hold:
|
|
161
|
+
*
|
|
162
|
+
* - no prompt (headless runs — teams, routines, hooks, `run <agent> "…"` —
|
|
163
|
+
* keep running in place, unchanged);
|
|
164
|
+
* - a human-facing surface: a real TTY and no `--json`. This is the same
|
|
165
|
+
* two-condition gate `signInLaunchDecision` uses in run-account-picker.ts —
|
|
166
|
+
* reused here through isHumanFacingRun, not re-derived;
|
|
167
|
+
* - no device-picker marker (`agent@` already chose the device; picking this
|
|
168
|
+
* machine there is a plain local run);
|
|
169
|
+
* - no `--resume` / `--lease` / `--box` / `--cloud` (those own placement);
|
|
170
|
+
* - the host layer is unpinned and this process is not itself a dispatched
|
|
171
|
+
* hop — delegated to runAutoDefaultsToAffinity, which encodes both.
|
|
172
|
+
*
|
|
173
|
+
* Pure so the default-placement matrix is unit-testable.
|
|
174
|
+
*/
|
|
175
|
+
export function bareInteractiveRunDefaultsToDeviceAuto(options, run, surface, env = process.env) {
|
|
176
|
+
if (run.prompt !== undefined)
|
|
177
|
+
return false;
|
|
178
|
+
if (!isHumanFacingRun({ tty: surface.tty, json: surface.json === true }))
|
|
179
|
+
return false;
|
|
180
|
+
if (run.devicePickerRequested)
|
|
181
|
+
return false;
|
|
182
|
+
if (options.resume !== undefined || options.lease || options.box || options.cloud)
|
|
183
|
+
return false;
|
|
184
|
+
return runAutoDefaultsToAffinity(options, env);
|
|
95
185
|
}
|
|
96
186
|
/**
|
|
97
187
|
* Whether an interactive host dispatch must mint a correlation launch id and
|
|
@@ -426,7 +516,7 @@ async function handleTerminalHandoff(agentSpec, options, prompt) {
|
|
|
426
516
|
// isValidAgent / profileExists / resolveWorkflowRef chain below), so this must
|
|
427
517
|
// accept all three. Gating on the agent table alone rejected every profile —
|
|
428
518
|
// the whole Kimi/DeepSeek/Qwen/GLM path — for `--terminal` runs only.
|
|
429
|
-
const rawTarget =
|
|
519
|
+
const rawTarget = parseRunPickerMarkers(agentSpec).normalizedAgentSpec.split('#')[0].split('@')[0];
|
|
430
520
|
const knownAgent = resolveAgentName(rawTarget);
|
|
431
521
|
const [{ profileExists }, { resolveWorkflowRef }] = await Promise.all([
|
|
432
522
|
import('../lib/profiles.js'),
|
|
@@ -521,7 +611,7 @@ export function registerRunCommand(program) {
|
|
|
521
611
|
.option('--list-tasks', 'With --broadcast: list available broadcast task ids')
|
|
522
612
|
.option('--results [run-id]', 'With --broadcast: show one saved matrix run, or list saved runs newest first')
|
|
523
613
|
.option('--concurrency <n>', 'With --broadcast: maximum cells running at once', '3')
|
|
524
|
-
.option('--resume [id]', '
|
|
614
|
+
.option('--resume [id]', 'Resume a conversation with its account on the origin device. Omit the id for the shared session picker; #account filters the history. Pair an id with a prompt to continue headlessly.')
|
|
525
615
|
.option('--session-id <id>', 'Force a NEW conversation to use this exact session UUID (Claude only). This CREATES a session — to resume an existing one, use --resume.', parseExplicitSessionId)
|
|
526
616
|
.option('--name <slug>', 'Name the run — seeds the session label so it shows up as `<name>` in `agents sessions` and resolves by it (and `agents hosts logs <name>` for --device runs) instead of an opaque id. An agent-generated title later refines the label; your name shows until then. Optional.')
|
|
527
617
|
.option('--notify', 'Post a desktop notification when a headless run finishes. Fired by this process on exit, so it survives whatever launched the run (the menu bar dispatching it, a terminal you closed).')
|
|
@@ -587,12 +677,22 @@ export function registerRunCommand(program) {
|
|
|
587
677
|
# Headless, can edit: have the agent make changes
|
|
588
678
|
agents run claude "fix lint errors in src/" --mode edit
|
|
589
679
|
|
|
590
|
-
# Interactive (TUI)
|
|
680
|
+
# Interactive (TUI): a bare run places itself like --device auto (a fleet
|
|
681
|
+
# worker, TUI forwarded over SSH); pin the device to stay on this machine
|
|
591
682
|
agents run claude
|
|
683
|
+
agents run claude --device zion # stay local (or pick this machine in claude@)
|
|
592
684
|
|
|
593
|
-
# Pick a signed-in account/version for only this run
|
|
685
|
+
# Pick a signed-in account/version for only this run (# = account picker)
|
|
686
|
+
agents run claude#
|
|
687
|
+
|
|
688
|
+
# Pick the device this run lands on (@ = device picker); this machine
|
|
689
|
+
# first, offline rows disabled, fleet state aged in the prompt
|
|
594
690
|
agents run claude@
|
|
595
691
|
|
|
692
|
+
# Ask both, account first, then device — the run dispatches with the
|
|
693
|
+
# picked account to the picked device
|
|
694
|
+
agents run claude#@
|
|
695
|
+
|
|
596
696
|
# Full-auto: affinity-pick the host, then the harness with the most
|
|
597
697
|
# account headroom, then a balanced account on it
|
|
598
698
|
agents run auto "fix the flaky test" --mode edit
|
|
@@ -677,9 +777,21 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
677
777
|
best-account headroom), and the account (the strategy above). Zero
|
|
678
778
|
healthy accounts on any harness exits nonzero with the earliest reset.
|
|
679
779
|
|
|
680
|
-
|
|
681
|
-
installed account for this run
|
|
682
|
-
and available limits; unsafe accounts stay visible but
|
|
780
|
+
Pickers: a trailing # opens the account picker (agents run claude#) to
|
|
781
|
+
choose one installed account for this run — rows show identity, login
|
|
782
|
+
state, plan, and available limits; unsafe accounts stay visible but
|
|
783
|
+
disabled. A trailing @ opens the device picker (agents run claude@);
|
|
784
|
+
#@ asks both, account first, then device. The pickers cannot combine
|
|
785
|
+
with an explicit pin of the same thing (--account, --device/--on/
|
|
786
|
+
--computer/--host) or with --strategy/--balanced/--resume/--lease/--box.
|
|
787
|
+
|
|
788
|
+
Interactive placement: a bare 'agents run <harness>' (no prompt, real TTY)
|
|
789
|
+
places itself like --device auto — a fleet worker runs it, with the TUI
|
|
790
|
+
forwarded over SSH. Headless runs (any prompt, --json, no TTY) are
|
|
791
|
+
unchanged: they run in place. To stay on this machine, pass
|
|
792
|
+
--device <this machine>, or pick this machine (listed first) in the
|
|
793
|
+
'<harness>@' device picker. When placement finds no healthy device the
|
|
794
|
+
run fails loud and names the local spellings.
|
|
683
795
|
|
|
684
796
|
Fallback: --fallback codex,antigravity retries on rate-limit failure via /continue handoff. Each entry accepts @version.
|
|
685
797
|
|
|
@@ -692,7 +804,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
692
804
|
given. --cloud is mutually exclusive with --device/--lease and with
|
|
693
805
|
local-run flags (--loop, --resume, --secrets, --terminal, …).
|
|
694
806
|
|
|
695
|
-
Resume: --resume <id> resolves full IDs locally first, then fleet-wide, and recovers on the source device with its cwd/mode.
|
|
807
|
+
Resume: --resume <id> resolves full IDs locally first, then fleet-wide, and recovers on the source device with its cwd/mode. Resume preserves the conversation account and uses the installed binary; starting a new conversation from archived context requires an explicit choice. agents sessions resume <id> infers the harness too.
|
|
696
808
|
|
|
697
809
|
Passthrough: everything after -- is forwarded verbatim to the underlying agent CLI.
|
|
698
810
|
agents run kimi -- --plan --some-native-flag value
|
|
@@ -834,19 +946,25 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
834
946
|
host: options.host,
|
|
835
947
|
});
|
|
836
948
|
}
|
|
837
|
-
//
|
|
838
|
-
//
|
|
839
|
-
//
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
949
|
+
// Trailing picker markers request an interactive choice: `#` picks the
|
|
950
|
+
// account (and the version it signs in with), `@` picks the device, and
|
|
951
|
+
// `#@`/`@#` ask both. Strip only those terminal markers; concrete
|
|
952
|
+
// agent@version pins and `#label` account pins retain their meaning in
|
|
953
|
+
// every dispatch path below.
|
|
954
|
+
const pickerMarkers = parseRunPickerMarkers(agentSpec);
|
|
955
|
+
const accountPickerRequested = pickerMarkers.accountPicker;
|
|
956
|
+
const devicePickerRequested = pickerMarkers.devicePicker;
|
|
957
|
+
let normalizedAgentSpec = pickerMarkers.normalizedAgentSpec;
|
|
958
|
+
if (!pickerMarkers.valid) {
|
|
959
|
+
console.error(chalk.red(`Invalid run picker target: ${agentSpec}. ` +
|
|
960
|
+
`${pickerMarkers.reason ?? 'unrecognized picker markers'}. ` +
|
|
961
|
+
'Use agents run <agent># to pick an account, <agent>@ to pick a device, <agent>#@ for both.'));
|
|
845
962
|
process.exit(1);
|
|
846
963
|
}
|
|
847
964
|
// Peel `#name` off before --device dispatch so the selector rides the hop
|
|
848
965
|
// unchanged and the peer resolves ITS slot (PHNX-3940 T5). The later local
|
|
849
|
-
// parse is idempotent when the values match.
|
|
966
|
+
// parse is idempotent when the values match. A bare trailing `#` was the
|
|
967
|
+
// picker marker above, so it can never land here as an empty label.
|
|
850
968
|
{
|
|
851
969
|
const labelParts = normalizedAgentSpec.split('#');
|
|
852
970
|
if (labelParts.length > 2 || labelParts[1] === '') {
|
|
@@ -861,6 +979,27 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
861
979
|
if (specAccountLabel)
|
|
862
980
|
options.account = specAccountLabel;
|
|
863
981
|
}
|
|
982
|
+
// Bare resume is a session selection request, before fresh-run defaults or placement.
|
|
983
|
+
// Concrete IDs re-enter the executor below, so this cannot recurse.
|
|
984
|
+
if (options.resume === true || options.resume === '') {
|
|
985
|
+
if (options.sessionId || options.loop || options.fallback || options.resumeCheckpoint || options.lease) {
|
|
986
|
+
throw new Error('--resume cannot be combined with --session-id, --loop, --fallback, --resume-checkpoint, or --lease.');
|
|
987
|
+
}
|
|
988
|
+
const { sessionsResumeAction } = await import('./sessions-resume.js');
|
|
989
|
+
await sessionsResumeAction(undefined, prompt, {
|
|
990
|
+
agent: normalizedAgentSpec.split('#')[0] === RUN_AUTO_KEYWORD ? undefined : normalizedAgentSpec.split('#')[0],
|
|
991
|
+
account: options.account,
|
|
992
|
+
model: options.model,
|
|
993
|
+
mode: command.getOptionValueSource('mode') === 'default' ? undefined : options.mode,
|
|
994
|
+
interactive: options.interactive,
|
|
995
|
+
headless: options.headless,
|
|
996
|
+
cwd: options.cwd,
|
|
997
|
+
quiet: options.quiet,
|
|
998
|
+
device: options.host || options.device || options.on || options.computer,
|
|
999
|
+
runArgs: rawArgs.slice(2),
|
|
1000
|
+
});
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
864
1003
|
// Hard-deprecated harnesses cannot be run — point the user at the successor.
|
|
865
1004
|
const runBaseAgentName = normalizedAgentSpec.split('#')[0].split('@')[0];
|
|
866
1005
|
const runBaseAgentId = resolveAgentName(runBaseAgentName);
|
|
@@ -869,14 +1008,22 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
869
1008
|
console.error(chalk.red(hardDeprecationError(runBaseAgentId)));
|
|
870
1009
|
process.exit(1);
|
|
871
1010
|
}
|
|
872
|
-
//
|
|
873
|
-
// so an
|
|
874
|
-
// "cannot be combined with --
|
|
1011
|
+
// Picker conflict checks run BEFORE --resume/--device auto may place the
|
|
1012
|
+
// run implicitly, so an implied placement never surfaces as a fake
|
|
1013
|
+
// "cannot be combined with --device" when the user only typed a marker.
|
|
875
1014
|
if (accountPickerRequested) {
|
|
876
1015
|
const conflicts = runAccountPickerConflicts(options);
|
|
877
1016
|
if (conflicts.length > 0) {
|
|
878
1017
|
console.error(chalk.red(`Account selection with ${agentSpec} cannot be combined with ${conflicts.join(', ')}. ` +
|
|
879
|
-
'Remove the conflicting selector, or
|
|
1018
|
+
'Remove the conflicting selector, or pin the target explicitly (agent@version or agent#label).'));
|
|
1019
|
+
process.exit(1);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
if (devicePickerRequested) {
|
|
1023
|
+
const conflicts = runDevicePickerConflicts(options);
|
|
1024
|
+
if (conflicts.length > 0) {
|
|
1025
|
+
console.error(chalk.red(`Device selection with ${agentSpec} cannot be combined with ${conflicts.join(', ')}. ` +
|
|
1026
|
+
'Remove one — the picker already chooses where the run lands.'));
|
|
880
1027
|
process.exit(1);
|
|
881
1028
|
}
|
|
882
1029
|
}
|
|
@@ -908,7 +1055,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
908
1055
|
delete process.env.AGENTS_RESUME_SOURCE_JSON;
|
|
909
1056
|
const outcome = injectedSource
|
|
910
1057
|
? { kind: 'resolved', session: injectedSource }
|
|
911
|
-
: await (await import('./sessions.js')).resolveSessionMetadataValue(selector);
|
|
1058
|
+
: await (await import('./sessions.js')).resolveSessionMetadataValue(selector, { agent: runBaseAgentId ?? undefined });
|
|
912
1059
|
if (outcome.kind === 'partial') {
|
|
913
1060
|
// RUSH-2492: an unreachable peer is a warning, not a hard failure. The
|
|
914
1061
|
// resolver already resolves an id found on the reachable fleet (SES-9a),
|
|
@@ -929,17 +1076,12 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
929
1076
|
process.exit(1);
|
|
930
1077
|
}
|
|
931
1078
|
resolvedResumeSource = outcome.session;
|
|
932
|
-
const [requestedAgent
|
|
1079
|
+
const [requestedAgent] = normalizedAgentSpec.split('#')[0].split('@');
|
|
933
1080
|
if (!autoHarnessRequested && requestedAgent !== resolvedResumeSource.agent) {
|
|
934
1081
|
console.error(chalk.red(`Session ${resolvedResumeSource.shortId} belongs to ${resolvedResumeSource.agent}, not ${requestedAgent}. ` +
|
|
935
1082
|
`Use: agents sessions resume ${resolvedResumeSource.id}`));
|
|
936
1083
|
process.exit(1);
|
|
937
1084
|
}
|
|
938
|
-
if (!autoHarnessRequested && requestedVersion && requestedVersion !== resolvedResumeSource.version) {
|
|
939
|
-
console.error(chalk.red(`Session ${resolvedResumeSource.shortId} started with ${resolvedResumeSource.agent}@${resolvedResumeSource.version ?? 'unknown'}, ` +
|
|
940
|
-
`not @${requestedVersion}.`));
|
|
941
|
-
process.exit(1);
|
|
942
|
-
}
|
|
943
1085
|
// Omitted --mode inherits the effective source mode. Commander keeps
|
|
944
1086
|
// the normal new-run default as a real value, so consult its provenance
|
|
945
1087
|
// rather than mistaking the default for an explicit override.
|
|
@@ -960,7 +1102,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
960
1102
|
options.host = sourcePeer;
|
|
961
1103
|
}
|
|
962
1104
|
else if (sourcePeer && explicitPlacement && !hostTargetGiven(options).some((host) => sessionRecoveryDestinationMatches(resolvedResumeSource, host))) {
|
|
963
|
-
console.error(chalk.red(`Session ${resolvedResumeSource.shortId} must recover on ${sourcePeer}, where its
|
|
1105
|
+
console.error(chalk.red(`Session ${resolvedResumeSource.shortId} must recover on ${sourcePeer}, where its conversation state is stored; ` +
|
|
964
1106
|
`the requested device was ${hostTargetGiven(options).join(', ')}.`));
|
|
965
1107
|
process.exit(1);
|
|
966
1108
|
}
|
|
@@ -972,7 +1114,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
972
1114
|
if (!sourcePeer) {
|
|
973
1115
|
try {
|
|
974
1116
|
const { resolveSessionRecovery } = await import('../lib/session/recovery.js');
|
|
975
|
-
resolvedRecoveryTarget = await resolveSessionRecovery(resolvedResumeSource);
|
|
1117
|
+
resolvedRecoveryTarget = await resolveSessionRecovery(resolvedResumeSource, undefined, { account: options.account, model: options.model ?? resolvedResumeSource.model });
|
|
976
1118
|
}
|
|
977
1119
|
catch (err) {
|
|
978
1120
|
console.error(chalk.red(err.message));
|
|
@@ -981,7 +1123,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
981
1123
|
normalizedAgentSpec = `${resolvedRecoveryTarget.agent}@${resolvedRecoveryTarget.version}`;
|
|
982
1124
|
autoHarnessRequested = false;
|
|
983
1125
|
if (!options.quiet)
|
|
984
|
-
process.stderr.write(chalk.gray(`[agents] session recovery → ${resolvedRecoveryTarget.mode} ${
|
|
1126
|
+
process.stderr.write(chalk.gray(`[agents] session recovery → ${resolvedRecoveryTarget.mode} ${resolvedRecoveryTarget.agent} on ${sourceMachine ?? machineId()} · ${resolvedRecoveryTarget.reason}\n`));
|
|
985
1127
|
}
|
|
986
1128
|
else if (autoHarnessRequested) {
|
|
987
1129
|
normalizedAgentSpec = sourceAgent;
|
|
@@ -997,7 +1139,11 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
997
1139
|
process.exit(1);
|
|
998
1140
|
}
|
|
999
1141
|
if (accountPickerRequested) {
|
|
1000
|
-
console.error(chalk.red(`agents run auto picks the harness and account itself — the trailing
|
|
1142
|
+
console.error(chalk.red(`agents run auto picks the harness and account itself — the trailing-# account picker needs a concrete harness (agents run <harness>#).`));
|
|
1143
|
+
process.exit(1);
|
|
1144
|
+
}
|
|
1145
|
+
if (devicePickerRequested) {
|
|
1146
|
+
console.error(chalk.red(`agents run auto picks the harness and its placement itself — the trailing-@ device picker needs a concrete harness (agents run <harness>@).`));
|
|
1001
1147
|
process.exit(1);
|
|
1002
1148
|
}
|
|
1003
1149
|
// Host layer: with no explicit --device, default to the
|
|
@@ -1006,19 +1152,100 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
1006
1152
|
if (!resolvedResumeSource && runAutoDefaultsToAffinity(options))
|
|
1007
1153
|
options.device = 'auto';
|
|
1008
1154
|
}
|
|
1155
|
+
// The picker menus run HERE — after every conflict check, before
|
|
1156
|
+
// placement resolves — so the picked account labels the device rows and
|
|
1157
|
+
// the picked device is concrete for every host branch below.
|
|
1158
|
+
// `#@`/`@#`: account first, against THIS machine's slots; the picked
|
|
1159
|
+
// account becomes options.account and the device menu shows its
|
|
1160
|
+
// ✓/– per-device mark. A remote device pick dispatches with
|
|
1161
|
+
// `<agent>#<label>` so the peer resolves its own slot.
|
|
1162
|
+
// `@`: just the device menu; picking this machine is a plain local run.
|
|
1163
|
+
// Either menu cancelled (Esc/Ctrl-C) launches nothing and exits 0.
|
|
1164
|
+
let upFrontAccountPick = null;
|
|
1165
|
+
if (accountPickerRequested && devicePickerRequested) {
|
|
1166
|
+
const baseName = normalizedAgentSpec.split('#')[0].split('@')[0];
|
|
1167
|
+
const baseAgentId = resolveAgentName(baseName);
|
|
1168
|
+
const { profileExists: baseProfileExists } = await import('../lib/profiles.js');
|
|
1169
|
+
if (!baseAgentId || baseProfileExists(baseName)) {
|
|
1170
|
+
console.error(chalk.red(baseProfileExists(baseName)
|
|
1171
|
+
? `Account selection is not available for custom harness '${baseName}'. Run its concrete host agent with # instead.`
|
|
1172
|
+
: `Account selection is not available for '${baseName}'. Run a concrete agent with # instead.`));
|
|
1173
|
+
process.exit(1);
|
|
1174
|
+
}
|
|
1175
|
+
const { supportsAccountInspection: baseSupportsInspection, agentLabel: baseAgentLabel, ACCOUNT_INSPECTION_AGENT_IDS: inspectionAgentIds } = await import('../lib/agents.js');
|
|
1176
|
+
if (!baseSupportsInspection(baseAgentId)) {
|
|
1177
|
+
console.error(chalk.red(`${baseAgentLabel(baseAgentId)} does not expose local account state, so agents-cli cannot safely select an account.`));
|
|
1178
|
+
console.error(chalk.gray(`Supported account pickers: ${inspectionAgentIds.join(', ')}`));
|
|
1179
|
+
process.exit(1);
|
|
1180
|
+
}
|
|
1181
|
+
const { pickRunAccountCandidate } = await import('./run-account-picker.js');
|
|
1182
|
+
const selected = await pickRunAccountCandidate(baseAgentId);
|
|
1183
|
+
if (!selected)
|
|
1184
|
+
return; // Esc/Ctrl-C: launch nothing.
|
|
1185
|
+
if (selected.nativeAccount)
|
|
1186
|
+
options.account = selected.nativeAccount;
|
|
1187
|
+
upFrontAccountPick = selected;
|
|
1188
|
+
if (!options.quiet) {
|
|
1189
|
+
const identity = selected.accountLabel || 'signed-in account';
|
|
1190
|
+
process.stderr.write(chalk.gray(`[agents] selected ${identity} · ${baseAgentId}@${selected.version} for this run\n`));
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
if (devicePickerRequested) {
|
|
1194
|
+
// --resume above may have pinned the host implicitly (recovery to the
|
|
1195
|
+
// source peer); a device menu against it would be a silent no-op.
|
|
1196
|
+
const impliedHost = hostTargetGiven(options);
|
|
1197
|
+
if (impliedHost.length > 0) {
|
|
1198
|
+
console.error(chalk.red(`Device selection with ${agentSpec} cannot be combined with the placement --resume already chose (${impliedHost.join(', ')}). ` +
|
|
1199
|
+
'Remove one — both pick where the run lands.'));
|
|
1200
|
+
process.exit(1);
|
|
1201
|
+
}
|
|
1202
|
+
const { pickRunDevice } = await import('./run-device-picker.js');
|
|
1203
|
+
const baseName = normalizedAgentSpec.split('#')[0].split('@')[0];
|
|
1204
|
+
const pickedDevice = await pickRunDevice({
|
|
1205
|
+
agent: (resolveAgentName(baseName) ?? baseName),
|
|
1206
|
+
accountLabel: options.account,
|
|
1207
|
+
});
|
|
1208
|
+
if (pickedDevice === null)
|
|
1209
|
+
return; // Esc/Ctrl-C: launch nothing.
|
|
1210
|
+
const { isSelfHost } = await import('../lib/devices/self-host.js');
|
|
1211
|
+
// Picking this machine is a plain local run — leave options.device unset.
|
|
1212
|
+
if (!isSelfHost(pickedDevice))
|
|
1213
|
+
options.device = pickedDevice;
|
|
1214
|
+
}
|
|
1215
|
+
// Default placement (PHNX-4083): a bare human-facing `agents run
|
|
1216
|
+
// <harness>` (no prompt, real TTY, no placement flags, not a dispatched
|
|
1217
|
+
// hop) places itself like `--device auto` — a marker left off is decided
|
|
1218
|
+
// for you. Headless runs (a prompt, --json, teams/routines/hooks) keep
|
|
1219
|
+
// running in place, unchanged.
|
|
1220
|
+
const defaultPlacement = bareInteractiveRunDefaultsToDeviceAuto(options, { prompt, devicePickerRequested }, { tty: isInteractiveTerminal(), json: options.json });
|
|
1221
|
+
if (defaultPlacement)
|
|
1222
|
+
options.device = 'auto';
|
|
1009
1223
|
// --device auto (and deprecated --smart): live fleet pick.
|
|
1010
1224
|
// Harness is always the agent the user typed — never auto-picked.
|
|
1011
1225
|
// Placement failure propagates; an automatic request never becomes local.
|
|
1012
1226
|
{
|
|
1013
1227
|
const { applyDeviceAutoToOptions } = await import('../lib/smart-launch.js');
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
:
|
|
1021
|
-
|
|
1228
|
+
let result;
|
|
1229
|
+
try {
|
|
1230
|
+
result = await applyDeviceAutoToOptions(options, {
|
|
1231
|
+
accountPickerRequested,
|
|
1232
|
+
// `run auto` selects its harness after placement, so do not filter
|
|
1233
|
+
// candidates against an arbitrary proxy harness at this stage.
|
|
1234
|
+
agent: normalizedAgentSpec.split('#')[0].split('@')[0] === RUN_AUTO_KEYWORD
|
|
1235
|
+
? undefined
|
|
1236
|
+
: (resolveAgentName(normalizedAgentSpec.split('#')[0].split('@')[0]) ?? undefined),
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
catch (err) {
|
|
1240
|
+
// Placement the DEFAULT chose must fail loud AND name the local
|
|
1241
|
+
// escape hatch — never silently fall back to a local launch.
|
|
1242
|
+
if (!defaultPlacement)
|
|
1243
|
+
throw err;
|
|
1244
|
+
console.error(chalk.red(err.message));
|
|
1245
|
+
const { machineId } = await import('../lib/machine-id.js');
|
|
1246
|
+
console.error(chalk.gray(`Run here instead: agents run ${runBaseAgentName} --device ${machineId()}`));
|
|
1247
|
+
process.exit(1);
|
|
1248
|
+
}
|
|
1022
1249
|
if (!options.quiet && result.deprecationSmart) {
|
|
1023
1250
|
process.stderr.write(chalk.yellow('[agents] --smart is deprecated; use --device auto\n'));
|
|
1024
1251
|
}
|
|
@@ -1419,8 +1646,6 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
1419
1646
|
hostRejects.push(RUN_OPTION_REJECT_MESSAGES.allowExpired);
|
|
1420
1647
|
if (options.resumeCheckpoint)
|
|
1421
1648
|
hostRejects.push(RUN_OPTION_REJECT_MESSAGES.resumeCheckpoint);
|
|
1422
|
-
if (options.resume === true)
|
|
1423
|
-
hostRejects.push(RUN_OPTION_REJECT_MESSAGES.resumeBare);
|
|
1424
1649
|
if (hostRejects.length > 0) {
|
|
1425
1650
|
for (const msg of hostRejects)
|
|
1426
1651
|
console.error(chalk.red(msg));
|
|
@@ -1498,9 +1723,11 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
1498
1723
|
process.exit(1);
|
|
1499
1724
|
}
|
|
1500
1725
|
const interactiveHost = options.interactive === true || (prompt === undefined && options.headless !== true);
|
|
1501
|
-
|
|
1726
|
+
// When the account was already picked up front (`#@`), the dispatch
|
|
1727
|
+
// forwards `#label` and the peer needs no interactive picker.
|
|
1728
|
+
if (accountPickerRequested && !upFrontAccountPick && !interactiveHost) {
|
|
1502
1729
|
console.error(chalk.red(`Account selection with ${agentSpec} requires an interactive host run. ` +
|
|
1503
|
-
`Use agents run ${runAgent}
|
|
1730
|
+
`Use agents run ${runAgent}# --device ${host.name} --interactive.`));
|
|
1504
1731
|
process.exit(1);
|
|
1505
1732
|
}
|
|
1506
1733
|
if (interactiveHost) {
|
|
@@ -1562,9 +1789,9 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
1562
1789
|
const exitCode = await runInteractiveOnHost(host, {
|
|
1563
1790
|
agent: runAgent,
|
|
1564
1791
|
version: resumeId ? undefined : runVersion,
|
|
1565
|
-
accountPicker: accountPickerRequested,
|
|
1792
|
+
accountPicker: accountPickerRequested && !upFrontAccountPick,
|
|
1566
1793
|
strategy: resumeId ? undefined : runStrategy,
|
|
1567
|
-
account:
|
|
1794
|
+
account: options.account,
|
|
1568
1795
|
fallback: options.fallback,
|
|
1569
1796
|
prompt,
|
|
1570
1797
|
mode: forwardedMode,
|
|
@@ -1672,7 +1899,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
1672
1899
|
agent: runAgent,
|
|
1673
1900
|
version: resumeId ? undefined : runVersion,
|
|
1674
1901
|
strategy: resumeId ? undefined : runStrategy,
|
|
1675
|
-
account:
|
|
1902
|
+
account: options.account,
|
|
1676
1903
|
fallback: options.fallback,
|
|
1677
1904
|
prompt,
|
|
1678
1905
|
mode: forwardedMode,
|
|
@@ -1894,12 +2121,12 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
1894
2121
|
let workflowHasSubagents = false;
|
|
1895
2122
|
const cwd = options.cwd ?? process.cwd();
|
|
1896
2123
|
if (accountPickerRequested && profileExists(rawAgent)) {
|
|
1897
|
-
console.error(chalk.red(`Account selection is not available for custom harness '${rawAgent}'. Run its concrete host agent with
|
|
2124
|
+
console.error(chalk.red(`Account selection is not available for custom harness '${rawAgent}'. Run its concrete host agent with # instead.`));
|
|
1898
2125
|
process.exit(1);
|
|
1899
2126
|
}
|
|
1900
2127
|
if (accountPickerRequested && !isValidAgent(rawAgent)) {
|
|
1901
2128
|
if (resolveWorkflowRef(rawAgent, cwd)) {
|
|
1902
|
-
console.error(chalk.red(`Account selection is not available for workflow '${rawAgent}'. Run a concrete agent with
|
|
2129
|
+
console.error(chalk.red(`Account selection is not available for workflow '${rawAgent}'. Run a concrete agent with # instead.`));
|
|
1903
2130
|
process.exit(1);
|
|
1904
2131
|
}
|
|
1905
2132
|
}
|
|
@@ -2180,7 +2407,10 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
2180
2407
|
process.exit(1);
|
|
2181
2408
|
}
|
|
2182
2409
|
}
|
|
2183
|
-
|
|
2410
|
+
// `#@` already asked the account up front (before the device menu) — a
|
|
2411
|
+
// remote device pick never reaches here, so apply the local pick's
|
|
2412
|
+
// version/home and skip the on-the-spot menu.
|
|
2413
|
+
if (accountPickerRequested && !upFrontAccountPick) {
|
|
2184
2414
|
if (!supportsAccountInspection(agent)) {
|
|
2185
2415
|
console.error(chalk.red(`${agentLabel(agent)} does not expose local account state, so agents-cli cannot safely select an account.`));
|
|
2186
2416
|
console.error(chalk.gray(`Supported account pickers: ${ACCOUNT_INSPECTION_AGENT_IDS.join(', ')}`));
|
|
@@ -2206,88 +2436,101 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
2206
2436
|
process.exit(1);
|
|
2207
2437
|
}
|
|
2208
2438
|
}
|
|
2439
|
+
if (upFrontAccountPick) {
|
|
2440
|
+
version = upFrontAccountPick.version;
|
|
2441
|
+
if (upFrontAccountPick.slotDir)
|
|
2442
|
+
execHome = upFrontAccountPick.slotDir;
|
|
2443
|
+
}
|
|
2209
2444
|
version = resolveVersionAlias(agent, version);
|
|
2210
|
-
// Account selection follows the binding order: explicit --account → exact
|
|
2211
|
-
// `agent@version` binding → device-scoped `agent` binding → per-harness
|
|
2212
|
-
// default. The exact-installation binding needs the concrete launch
|
|
2213
|
-
// version, so resolve it (default when the run named no version).
|
|
2214
2445
|
const { resolveSpawnAccount } = await import('../lib/account-registry.js');
|
|
2215
|
-
// Binding key: a custom harness keys on its own profile name; a native /
|
|
2216
|
-
// global run keys on the exact `agent@version` (default when unpinned).
|
|
2217
|
-
const bindingTarget = fromProfile ? rawAgent : (version ? `${agent}@${version}` : `${agent}@${getGlobalDefault(agent) ?? ''}`);
|
|
2218
2446
|
let spawnAccount = null;
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2447
|
+
let launchAccount;
|
|
2448
|
+
if (resolvedRecoveryTarget) {
|
|
2449
|
+
const { resolveLocalAccountLaunch } = await import('../lib/accounting/account-launch.js');
|
|
2450
|
+
launchAccount = await resolveLocalAccountLaunch({
|
|
2451
|
+
agent,
|
|
2452
|
+
executableVersion: resolvedRecoveryTarget.version,
|
|
2453
|
+
candidate: resolvedRecoveryTarget.candidate,
|
|
2454
|
+
useDefault: false,
|
|
2455
|
+
});
|
|
2456
|
+
execHome = resolvedRecoveryTarget.mode === 'native' ? (resolvedRecoveryTarget.execHome ?? launchAccount.execHome) : launchAccount.execHome;
|
|
2457
|
+
accountConfigVersion = resolvedRecoveryTarget.mode === 'native' ? (resolvedRecoveryTarget.configVersion ?? launchAccount.configVersion) : launchAccount.configVersion;
|
|
2458
|
+
accountEnv = { ...accountEnv, ...launchAccount.env };
|
|
2225
2459
|
}
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2460
|
+
else {
|
|
2461
|
+
// Binding key: a custom harness keys on its own profile name; a native /
|
|
2462
|
+
// global run keys on the exact `agent@version` (default when unpinned).
|
|
2463
|
+
const bindingTarget = fromProfile ? rawAgent : (version ? `${agent}@${version}` : `${agent}@${getGlobalDefault(agent) ?? ''}`);
|
|
2464
|
+
try {
|
|
2465
|
+
spawnAccount = resolveSpawnAccount(options.account, agent, version, readMeta(), { useDefault: !fromProfile, provider: profileProvider, target: bindingTarget });
|
|
2466
|
+
}
|
|
2467
|
+
catch (err) {
|
|
2468
|
+
console.error(chalk.red(err.message));
|
|
2231
2469
|
process.exit(1);
|
|
2232
2470
|
}
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2471
|
+
// Downstream rotation gating asks only "was an account selected?".
|
|
2472
|
+
if (spawnAccount) {
|
|
2473
|
+
if (options.cloud || options.provider || options.lease) {
|
|
2474
|
+
console.error(chalk.red('--account selects a device-local credential and cannot be combined with cloud or lease placement.'));
|
|
2475
|
+
process.exit(1);
|
|
2476
|
+
}
|
|
2477
|
+
if (spawnAccount.kind === 'native') {
|
|
2478
|
+
// `#name` / `--account` on a --device run is forwarded unchanged at
|
|
2479
|
+
// dispatch time; the peer resolves ITS slot. This block is local-only.
|
|
2480
|
+
const remoteTarget = options.host || options.device;
|
|
2481
|
+
if (!remoteTarget) {
|
|
2482
|
+
const { adoptedConfigPointsAtHome, adoptedSymlinkMismatchError, durableSlotEnv, isSymlinkAdoptedHarness, resolveNativeSpawnHome, symlinkAdoptedAccountError, } = await import('../lib/exec-account-home.js');
|
|
2483
|
+
const { readMeta } = await import('../lib/state.js');
|
|
2484
|
+
const meta = readMeta();
|
|
2485
|
+
if (isSymlinkAdoptedHarness(agent)) {
|
|
2486
|
+
const defaultName = meta.accounts?.defaults?.[agent];
|
|
2487
|
+
if (spawnAccount.name !== defaultName) {
|
|
2488
|
+
console.error(chalk.red(symlinkAdoptedAccountError(agent, spawnAccount.name, defaultName)));
|
|
2489
|
+
process.exit(1);
|
|
2490
|
+
}
|
|
2246
2491
|
}
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2492
|
+
try {
|
|
2493
|
+
const resolved = await resolveNativeSpawnHome(agent, spawnAccount, meta);
|
|
2494
|
+
if (isSymlinkAdoptedHarness(agent) && !adoptedConfigPointsAtHome(agent, resolved.execHome)) {
|
|
2495
|
+
console.error(chalk.red(adoptedSymlinkMismatchError(agent, spawnAccount.name, resolved.execHome)));
|
|
2496
|
+
process.exit(1);
|
|
2497
|
+
}
|
|
2498
|
+
execHome = resolved.execHome;
|
|
2499
|
+
// A worker's durable api-key slot holds no file: the pushed key
|
|
2500
|
+
// rides the harness env var (CURSOR_API_KEY, …) on this launch.
|
|
2501
|
+
const durable = durableSlotEnv(agent, spawnAccount, resolved, meta);
|
|
2502
|
+
if (Object.keys(durable).length > 0)
|
|
2503
|
+
accountEnv = { ...accountEnv, ...durable };
|
|
2504
|
+
if (resolved.source === 'legacy-home') {
|
|
2505
|
+
accountConfigVersion = resolved.label;
|
|
2506
|
+
// A leftover version-labeled home is both the spawn HOME and
|
|
2507
|
+
// the account's installation — the pre-T5 `resolveAccountVersion`
|
|
2508
|
+
// behavior. An explicit `@<label>` still controls the binary.
|
|
2509
|
+
if (!version && !fromProfile && resolved.label)
|
|
2510
|
+
version = resolved.label;
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
catch (err) {
|
|
2514
|
+
console.error(chalk.red(err.message));
|
|
2252
2515
|
process.exit(1);
|
|
2253
2516
|
}
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
accountEnv = { ...accountEnv, ...durable };
|
|
2260
|
-
if (resolved.source === 'legacy-home') {
|
|
2261
|
-
accountConfigVersion = resolved.label;
|
|
2262
|
-
// A leftover version-labeled home is both the spawn HOME and
|
|
2263
|
-
// the account's installation — the pre-T5 `resolveAccountVersion`
|
|
2264
|
-
// behavior. An explicit `@<label>` still controls the binary.
|
|
2265
|
-
if (!version && !fromProfile && resolved.label)
|
|
2266
|
-
version = resolved.label;
|
|
2517
|
+
// Slot / provisioned: binary comes from the one managed install unless `@<label>` pins it.
|
|
2518
|
+
if (!version && !fromProfile) {
|
|
2519
|
+
const { ensureHarnessInstallation } = await import('../lib/installations/store.js');
|
|
2520
|
+
const { installation } = await ensureHarnessInstallation(agent);
|
|
2521
|
+
version = installation.label;
|
|
2267
2522
|
}
|
|
2523
|
+
if (!options.quiet)
|
|
2524
|
+
process.stderr.write(chalk.gray(`[agents] account '${spawnAccount.name}' · ${agent}\n`));
|
|
2268
2525
|
}
|
|
2269
|
-
catch (err) {
|
|
2270
|
-
console.error(chalk.red(err.message));
|
|
2271
|
-
process.exit(1);
|
|
2272
|
-
}
|
|
2273
|
-
// Slot / provisioned: binary comes from the one managed install unless `@<label>` pins it.
|
|
2274
|
-
if (!version && !fromProfile) {
|
|
2275
|
-
const { ensureHarnessInstallation } = await import('../lib/installations/store.js');
|
|
2276
|
-
const { installation } = await ensureHarnessInstallation(agent);
|
|
2277
|
-
version = installation.label;
|
|
2278
|
-
}
|
|
2279
|
-
if (!options.quiet)
|
|
2280
|
-
process.stderr.write(chalk.gray(`[agents] account '${spawnAccount.name}' · ${agent}\n`));
|
|
2281
2526
|
}
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2527
|
+
else {
|
|
2528
|
+
accountEnv = spawnAccount.env;
|
|
2529
|
+
}
|
|
2285
2530
|
}
|
|
2286
2531
|
}
|
|
2287
|
-
|
|
2288
|
-
//
|
|
2289
|
-
// FILTER (undefined for default/any, concrete for @latest/@oldest/@x.y.z);
|
|
2290
|
-
// it is replaced below by the chosen session's OWN version (isolation).
|
|
2532
|
+
const configuredAccount = launchAccount?.account?.name ?? spawnAccount?.name;
|
|
2533
|
+
// The account and native context were resolved before launch defaults.
|
|
2291
2534
|
let resumeNative = false;
|
|
2292
2535
|
let resumeSessionId;
|
|
2293
2536
|
let forceInteractive = false;
|
|
@@ -2300,136 +2543,23 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
2300
2543
|
console.error(chalk.red('--resume cannot be combined with --loop, --fallback, or --resume-checkpoint (those are separate continuation mechanisms).'));
|
|
2301
2544
|
process.exit(1);
|
|
2302
2545
|
}
|
|
2303
|
-
const { findSessionsById } = await import('../lib/session/db.js');
|
|
2304
|
-
const { discoverSessions } = await import('../lib/session/discover.js');
|
|
2305
|
-
const { pickSessionInteractive } = await import('./sessions.js');
|
|
2306
2546
|
const { buildContinuePrompt } = await import('../lib/loop.js');
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
if (!
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
let scopeCwd;
|
|
2317
|
-
try {
|
|
2318
|
-
scopeCwd = fs.realpathSync(cwd);
|
|
2319
|
-
}
|
|
2320
|
-
catch {
|
|
2321
|
-
scopeCwd = cwd;
|
|
2322
|
-
}
|
|
2323
|
-
let session = resolvedResumeSource;
|
|
2324
|
-
if (idArg) {
|
|
2325
|
-
let matches = session ? [session] : findSessionsById(idArg, { agent: sessionAgent, version, cwd: scopeCwd });
|
|
2326
|
-
if (matches.length === 0) {
|
|
2327
|
-
const wide = findSessionsById(idArg, { agent: sessionAgent, version });
|
|
2328
|
-
if (wide.length > 0) {
|
|
2329
|
-
if (!options.quiet)
|
|
2330
|
-
process.stderr.write(chalk.gray(`No match for "${idArg}" in this project; widened to all projects.\n`));
|
|
2331
|
-
matches = wide;
|
|
2332
|
-
}
|
|
2333
|
-
}
|
|
2334
|
-
if (matches.length === 0) {
|
|
2335
|
-
console.error(chalk.red(`No ${agent} session matching "${idArg}".`));
|
|
2336
|
-
console.error(chalk.gray(`Browse sessions: agents sessions ${idArg}`));
|
|
2337
|
-
process.exit(1);
|
|
2338
|
-
}
|
|
2339
|
-
else if (matches.length === 1) {
|
|
2340
|
-
session = matches[0];
|
|
2341
|
-
}
|
|
2342
|
-
else if (wantsInteractive) {
|
|
2343
|
-
const picked = await pickSessionInteractive(matches, `Multiple sessions match "${idArg}":`);
|
|
2344
|
-
if (!picked)
|
|
2345
|
-
process.exit(0);
|
|
2346
|
-
session = picked.session;
|
|
2347
|
-
}
|
|
2348
|
-
else {
|
|
2349
|
-
console.error(chalk.red(`"${idArg}" is ambiguous — ${matches.length} sessions match:`));
|
|
2350
|
-
for (const m of matches.slice(0, 10)) {
|
|
2351
|
-
console.error(chalk.gray(` ${m.shortId} ${m.timestamp.slice(0, 16).replace('T', ' ')} ${m.topic ?? m.label ?? ''}`));
|
|
2352
|
-
}
|
|
2353
|
-
console.error(chalk.gray('Pass more of the id, or resume interactively (drop the prompt).'));
|
|
2354
|
-
process.exit(1);
|
|
2355
|
-
}
|
|
2356
|
-
}
|
|
2357
|
-
else {
|
|
2358
|
-
// Bare --resume: pick from recent sessions in scope. Needs a TTY.
|
|
2359
|
-
if (!wantsInteractive) {
|
|
2360
|
-
console.error(chalk.red('--resume with no id needs an interactive terminal. Pass a session id (full or prefix), or run without --headless.'));
|
|
2361
|
-
process.exit(1);
|
|
2362
|
-
}
|
|
2363
|
-
const recent = await discoverSessions({ agent: sessionAgent, version, limit: 200 });
|
|
2364
|
-
if (recent.length === 0) {
|
|
2365
|
-
console.error(chalk.red(`No ${agent} sessions found to resume in this project.`));
|
|
2366
|
-
console.error(chalk.gray('Browse all: agents sessions'));
|
|
2367
|
-
process.exit(1);
|
|
2368
|
-
}
|
|
2369
|
-
const picked = await pickSessionInteractive(recent, `Resume which ${agent} session?`);
|
|
2370
|
-
if (!picked)
|
|
2371
|
-
process.exit(0);
|
|
2372
|
-
session = picked.session;
|
|
2373
|
-
forceInteractive = true; // bare resume always lands in the agent's TUI
|
|
2374
|
-
}
|
|
2375
|
-
// Bare `run <harness> --resume` learns the chosen SessionMeta only after
|
|
2376
|
-
// the host-placement phase above. If the picker chose a synced session
|
|
2377
|
-
// from another device, route the recovery command now; resolving local
|
|
2378
|
-
// candidates would otherwise native-resume through this device's
|
|
2379
|
-
// unrelated isolated home.
|
|
2380
|
-
if (!resolvedResumeSource) {
|
|
2381
|
-
const { sessionRecoveryPeer, sessionRecoveryRunArgs, } = await import('../lib/session/recovery.js');
|
|
2382
|
-
const peer = sessionRecoveryPeer(session);
|
|
2383
|
-
if (peer) {
|
|
2384
|
-
const { runOnPeer } = await import('../lib/session/remote-list.js');
|
|
2385
|
-
const routed = await runOnPeer(sessionRecoveryRunArgs(session), peer, { tty: true, sessionId: session.id });
|
|
2386
|
-
if (routed === 'no-target') {
|
|
2387
|
-
console.error(chalk.red(`Cannot recover session ${session.shortId}: origin device ${peer} is not a registered reachable peer.`));
|
|
2388
|
-
process.exitCode = 1;
|
|
2389
|
-
}
|
|
2390
|
-
return;
|
|
2391
|
-
}
|
|
2392
|
-
}
|
|
2393
|
-
// Bare interactive --resume selects the SessionMeta only down here, so
|
|
2394
|
-
// it has not gone through the early concrete-id resolver. Resolve it now;
|
|
2395
|
-
// concrete ids reuse the exact same target chosen above.
|
|
2396
|
-
if (!resolvedRecoveryTarget) {
|
|
2397
|
-
try {
|
|
2398
|
-
const { resolveSessionRecovery } = await import('../lib/session/recovery.js');
|
|
2399
|
-
resolvedRecoveryTarget = await resolveSessionRecovery(session);
|
|
2400
|
-
}
|
|
2401
|
-
catch (err) {
|
|
2402
|
-
console.error(chalk.red(err.message));
|
|
2403
|
-
process.exit(1);
|
|
2404
|
-
}
|
|
2405
|
-
}
|
|
2547
|
+
const session = resolvedResumeSource;
|
|
2548
|
+
if (!session || !resolvedRecoveryTarget) {
|
|
2549
|
+
throw new Error('Session recovery did not resolve a conversation on this device.');
|
|
2550
|
+
}
|
|
2551
|
+
if (!options.cwd)
|
|
2552
|
+
options.cwd = resolvedRecoveryTarget.mode === 'native'
|
|
2553
|
+
? resolvedRecoveryTarget.cwd ?? session.cwd
|
|
2554
|
+
: session.cwd;
|
|
2555
|
+
forceInteractive = resolveInteractive({ interactive: options.interactive, headless: options.headless, prompt });
|
|
2406
2556
|
if (resolvedRecoveryTarget.agent !== agent) {
|
|
2407
2557
|
console.error(chalk.red(`Session ${session.shortId} belongs to ${resolvedRecoveryTarget.agent}, not ${agent}. ` +
|
|
2408
2558
|
`Use: agents run auto --resume ${session.id}`));
|
|
2409
2559
|
process.exit(1);
|
|
2410
2560
|
}
|
|
2411
2561
|
version = resolvedRecoveryTarget.version;
|
|
2412
|
-
// Account rotation (PHNX-3626 / PHNX-3674): recovery picked a healthy
|
|
2413
|
-
// provider of the SAME harness. Inject that credential through the
|
|
2414
|
-
// `--account` path so spawn does not authenticate as the version home's
|
|
2415
|
-
// native login (the exhausted origin when the continue pick is a
|
|
2416
|
-
// provider). An explicit --account (configuredAccount) always wins.
|
|
2417
|
-
const rotatedAccount = resolvedRecoveryTarget.account;
|
|
2418
|
-
if (rotatedAccount && !configuredAccount) {
|
|
2419
|
-
try {
|
|
2420
|
-
const picked = resolveSpawnAccount(rotatedAccount.providerAccount, agent, version, readMeta(), { useDefault: false });
|
|
2421
|
-
if (picked?.kind === 'provider')
|
|
2422
|
-
accountEnv = picked.env;
|
|
2423
|
-
}
|
|
2424
|
-
catch {
|
|
2425
|
-
// Account-registry errors can carry provider credential material;
|
|
2426
|
-
// never relay them onto the terminal from this automatic path.
|
|
2427
|
-
console.error(chalk.red('Could not prepare the rotated provider account for session recovery.'));
|
|
2428
|
-
process.exit(1);
|
|
2429
|
-
}
|
|
2430
|
-
}
|
|
2431
2562
|
if (resolvedRecoveryTarget.mode === 'native') {
|
|
2432
|
-
version = session.version;
|
|
2433
2563
|
resumeNative = true;
|
|
2434
2564
|
resumeSessionId = session.id;
|
|
2435
2565
|
// The centralized recovery decision proves the transcript belongs to
|
|
@@ -2438,23 +2568,23 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
2438
2568
|
// differ from the earlier cwd that selected projects/<cwd-key>.
|
|
2439
2569
|
if (!options.cwd && resolvedRecoveryTarget.cwd)
|
|
2440
2570
|
options.cwd = resolvedRecoveryTarget.cwd;
|
|
2441
|
-
if (rotatedAccount && !configuredAccount && !options.quiet) {
|
|
2442
|
-
process.stderr.write(chalk.gray(`[agents] origin ${agent} account limited → rotated to ${rotatedAccount.label} (native resume)\n`));
|
|
2443
|
-
}
|
|
2444
2571
|
if (!options.quiet)
|
|
2445
|
-
process.stderr.write(chalk.gray(`Resuming ${agent} ${session.shortId} (native)
|
|
2572
|
+
process.stderr.write(chalk.gray(`Resuming ${agent} ${session.shortId} (native) in ${options.cwd ?? cwd}\n`));
|
|
2446
2573
|
}
|
|
2447
2574
|
else {
|
|
2448
|
-
|
|
2449
|
-
|
|
2575
|
+
if (!isInteractiveTerminal() || options.headless) {
|
|
2576
|
+
console.error(chalk.red(`Native resume is unavailable: ${resolvedRecoveryTarget.reason}. To start a new conversation with this context, explicitly run: agents run ${agent} "/continue ${session.id}"`));
|
|
2577
|
+
process.exit(1);
|
|
2578
|
+
}
|
|
2579
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
2580
|
+
const replay = await confirm({ message: `${resolvedRecoveryTarget.reason}. Start a new conversation using this transcript?`, default: false }).catch(() => false);
|
|
2581
|
+
if (!replay)
|
|
2582
|
+
return;
|
|
2450
2583
|
prompt = buildContinuePrompt(session.id, prompt);
|
|
2451
2584
|
if (prompt.trim() === `/continue ${session.id}`)
|
|
2452
2585
|
forceInteractive = true;
|
|
2453
|
-
if (rotatedAccount && !configuredAccount && !options.quiet) {
|
|
2454
|
-
process.stderr.write(chalk.gray(`[agents] origin ${agent} account limited → rotated to ${rotatedAccount.label} (/continue)\n`));
|
|
2455
|
-
}
|
|
2456
2586
|
if (!options.quiet)
|
|
2457
|
-
process.stderr.write(chalk.gray(`Resuming ${agent} ${session.shortId} (/continue replay)
|
|
2587
|
+
process.stderr.write(chalk.gray(`Resuming ${agent} ${session.shortId} (/continue replay)\n`));
|
|
2458
2588
|
}
|
|
2459
2589
|
}
|
|
2460
2590
|
const configuredStrategy = getConfiguredRunStrategy(agent, cwd);
|
|
@@ -2511,7 +2641,9 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
2511
2641
|
// picker's other callers keep the native-only collector.
|
|
2512
2642
|
const { collectRunCandidatesForRun } = await import('../lib/accounting/account-pool-collect.js');
|
|
2513
2643
|
bootMark('resolve-version:start');
|
|
2514
|
-
const
|
|
2644
|
+
const routingModel = options.model ?? resolvedResumeSource?.model ?? workflowModel
|
|
2645
|
+
?? (options.fallback ? undefined : resolveRunDefaults(agent, resolveVersion(agent, cwd), cwd).model);
|
|
2646
|
+
const resolved = await resolveRunVersion(agent, strategy, cwd, collectRunCandidatesForRun, routingModel);
|
|
2515
2647
|
bootMark('resolve-version:done');
|
|
2516
2648
|
if (resolved.exhausted) {
|
|
2517
2649
|
// Zero healthy accounts splits two ways, and conflating them is what
|
|
@@ -2526,7 +2658,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
2526
2658
|
// - NEEDS A SIGN-IN (signed_out / revoked) -> launching IS the fix,
|
|
2527
2659
|
// because the harness's own TUI is the login surface. So on a TTY we
|
|
2528
2660
|
// carry the user into that login instead of erroring. Exiting here
|
|
2529
|
-
// made `agents run <agent>`, `agents run <agent
|
|
2661
|
+
// made `agents run <agent>`, `agents run <agent>#`, and `agents use`
|
|
2530
2662
|
// all dead-end with no reachable way to authenticate.
|
|
2531
2663
|
const recoverable = signInRecoverableCandidates(resolved.exhausted);
|
|
2532
2664
|
const { signInLaunchDecision } = await import('./run-account-picker.js');
|
|
@@ -2539,7 +2671,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
2539
2671
|
const { pickSignInLaunchVersion } = await import('./run-account-picker.js');
|
|
2540
2672
|
const signInVersion = await pickSignInLaunchVersion(agent, recoverable, !!options.quiet);
|
|
2541
2673
|
// A cancelled prompt launches nothing — same contract as the
|
|
2542
|
-
// trailing
|
|
2674
|
+
// trailing-# account picker above.
|
|
2543
2675
|
if (!signInVersion)
|
|
2544
2676
|
return;
|
|
2545
2677
|
version = signInVersion;
|
|
@@ -2577,7 +2709,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
2577
2709
|
if (decision === 'picker') {
|
|
2578
2710
|
const selected = await pickRunAccountCandidate(agent);
|
|
2579
2711
|
// A cancelled picker launches nothing — same contract as the
|
|
2580
|
-
// trailing
|
|
2712
|
+
// trailing-# account picker and the sign-in launch above.
|
|
2581
2713
|
if (!selected)
|
|
2582
2714
|
return;
|
|
2583
2715
|
version = selected.version;
|
|
@@ -2665,17 +2797,6 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
2665
2797
|
console.error(chalk.red(`agents: ${agent}@${launchTarget} is not runnable and could not be repaired. Try: ${hint}`));
|
|
2666
2798
|
process.exit(1);
|
|
2667
2799
|
}
|
|
2668
|
-
if (resolvedRecoveryTarget && healed !== launchTarget) {
|
|
2669
|
-
console.error(chalk.red(`agents: session recovery target ${agent}@${launchTarget} became unavailable on ` +
|
|
2670
|
-
`${resolvedResumeSource?.machine ?? 'this device'}; refusing to resume through another version home. ` +
|
|
2671
|
-
`Retry the command so recovery can select a healthy ${agent} version.`));
|
|
2672
|
-
process.exit(1);
|
|
2673
|
-
}
|
|
2674
|
-
// Always adopt the healed version explicitly. In the version-undefined
|
|
2675
|
-
// path a fallback re-pins the GLOBAL default, but `resolveVersion`
|
|
2676
|
-
// prefers a PROJECT pin — so leaving `version` undefined would let the
|
|
2677
|
-
// shim re-resolve the still-broken project pin and crash anyway. Pinning
|
|
2678
|
-
// the runnable version here is a no-op when nothing changed.
|
|
2679
2800
|
version = healed;
|
|
2680
2801
|
}
|
|
2681
2802
|
}
|
|
@@ -2741,13 +2862,13 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
2741
2862
|
// `signInLaunch` means the zero-healthy path already reported this exact
|
|
2742
2863
|
// account as logged out and named the login command, so re-probing here
|
|
2743
2864
|
// only prints a second, near-identical warning.
|
|
2744
|
-
rotated: !!rotationResult || accountPickerRequested || signInLaunch || spawnAccount?.kind === 'provider',
|
|
2865
|
+
rotated: !!rotationResult || accountPickerRequested || signInLaunch || (launchAccount?.account?.kind ?? spawnAccount?.kind) === 'provider',
|
|
2745
2866
|
});
|
|
2746
2867
|
if (preflight) {
|
|
2747
2868
|
try {
|
|
2748
2869
|
const { getAccountInfo } = await import('../lib/agents.js');
|
|
2749
2870
|
const authVersion = accountConfigVersion ?? version;
|
|
2750
|
-
const info = await getAccountInfo(agent, authVersion ? getVersionHomePath(agent, authVersion) : undefined);
|
|
2871
|
+
const info = await getAccountInfo(agent, execHome ?? (authVersion ? getVersionHomePath(agent, authVersion) : undefined));
|
|
2751
2872
|
// Claude authenticates interactively from a per-version setup-token on a
|
|
2752
2873
|
// keychain-less worker (the shim's .oauth_token fallback), which the
|
|
2753
2874
|
// native-credential probe above can't see — so don't warn "logged out" when
|
|
@@ -2761,8 +2882,8 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
2761
2882
|
}
|
|
2762
2883
|
if (!info.signedIn && !authedViaSetupToken) {
|
|
2763
2884
|
const { addSupported } = await import('../lib/accounts/add.js');
|
|
2764
|
-
const hint = spawnAccount?.kind === 'native'
|
|
2765
|
-
? `agents accounts login ${agent}#${
|
|
2885
|
+
const hint = (launchAccount?.account?.kind ?? spawnAccount?.kind) === 'native'
|
|
2886
|
+
? `agents accounts login ${agent}#${configuredAccount}`
|
|
2766
2887
|
: addSupported(agent)
|
|
2767
2888
|
? `agents accounts add ${agent} <name>`
|
|
2768
2889
|
: loginHint(agent);
|
|
@@ -2823,7 +2944,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
2823
2944
|
// `--mode plan` is respected for genuine read-only command runs.
|
|
2824
2945
|
const stallCmd = headlessPlanStallCommand({
|
|
2825
2946
|
prompt,
|
|
2826
|
-
interactive: options.interactive,
|
|
2947
|
+
interactive: resolveInteractive({ prompt, headless: options.headless, interactive: options.interactive || forceInteractive }),
|
|
2827
2948
|
mode: resolvedMode,
|
|
2828
2949
|
modeIsDefault,
|
|
2829
2950
|
});
|
|
@@ -2918,6 +3039,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
2918
3039
|
: undefined;
|
|
2919
3040
|
const modelSource = runCmd.getOptionValueSource('model');
|
|
2920
3041
|
let model = options.model
|
|
3042
|
+
?? resolvedResumeSource?.model
|
|
2921
3043
|
?? (!fromProfile && modelSource === undefined
|
|
2922
3044
|
? (workflowModel ?? (options.fallback ? undefined : runDefaults.model))
|
|
2923
3045
|
: undefined);
|
|
@@ -2933,6 +3055,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
2933
3055
|
const execOptions = {
|
|
2934
3056
|
agent,
|
|
2935
3057
|
harnessName: profileName,
|
|
3058
|
+
accountId: launchAccount?.account?.kind === 'legacy-native' ? undefined : (launchAccount?.account?.id ?? spawnAccount?.id),
|
|
2936
3059
|
version,
|
|
2937
3060
|
configVersion: accountConfigVersion,
|
|
2938
3061
|
execHome,
|