overlord-cli 0.2605290912.0 → 0.2605291450.0
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/bin/_cli/direct-launch.mjs +7 -3
- package/bin/_cli/launcher.mjs +93 -14
- package/bin/_cli/runner.mjs +176 -6
- package/package.json +1 -1
|
@@ -39,7 +39,9 @@ const VALUE_FLAGS = new Set([
|
|
|
39
39
|
'ssh-command',
|
|
40
40
|
'remote-working-directory',
|
|
41
41
|
'server-multiplexer',
|
|
42
|
-
'tmux-command'
|
|
42
|
+
'tmux-command',
|
|
43
|
+
'feed-post-id',
|
|
44
|
+
'initial-question'
|
|
43
45
|
]);
|
|
44
46
|
|
|
45
47
|
const BOOLEAN_FLAGS = new Set(['personal', 'for-human', 'allow-uninstalled']);
|
|
@@ -52,7 +54,9 @@ const FORWARDED_VALUE_FLAGS = [
|
|
|
52
54
|
'ssh-command',
|
|
53
55
|
'remote-working-directory',
|
|
54
56
|
'server-multiplexer',
|
|
55
|
-
'tmux-command'
|
|
57
|
+
'tmux-command',
|
|
58
|
+
'feed-post-id',
|
|
59
|
+
'initial-question'
|
|
56
60
|
];
|
|
57
61
|
|
|
58
62
|
export function isBuiltinLaunchAgent(name) {
|
|
@@ -212,7 +216,7 @@ Overlord flags:
|
|
|
212
216
|
--model <identifier> Preferred model (sets the ticket delegate and is forwarded to the agent)
|
|
213
217
|
--thinking <level> Agent reasoning/effort level
|
|
214
218
|
--flag <value> Extra agent flag (repeatable)
|
|
215
|
-
--pre-command <command>
|
|
219
|
+
--pre-command <command> Shell command to run before the agent binary (e.g. ollama, agent-pod)
|
|
216
220
|
--project-id <uuid> Target project (skips working-directory resolution)
|
|
217
221
|
--personal Create the ticket in your personal project
|
|
218
222
|
--for-human Create a human-review ticket
|
package/bin/_cli/launcher.mjs
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { execFileSync } from 'node:child_process';
|
|
9
|
+
import crypto from 'node:crypto';
|
|
9
10
|
import fs from 'node:fs';
|
|
10
11
|
import os from 'node:os';
|
|
11
12
|
import path from 'node:path';
|
|
@@ -90,12 +91,21 @@ function buildAgyLaunchArgv({ contextFile, scratchDir, mode, sessionId, extraArg
|
|
|
90
91
|
return argv;
|
|
91
92
|
}
|
|
92
93
|
|
|
93
|
-
async function fetchContext(platformUrl, bearerToken, localSecret, organizationId, ticketId, agent) {
|
|
94
|
+
async function fetchContext(platformUrl, bearerToken, localSecret, organizationId, ticketId, agent, options = {}) {
|
|
94
95
|
const params = new URLSearchParams({
|
|
95
96
|
context: 'cli',
|
|
96
97
|
agent,
|
|
97
98
|
instructionMode: getInstructionMode(agent)
|
|
98
99
|
});
|
|
100
|
+
if (options.launchMode === 'ask') params.set('mode', 'ask');
|
|
101
|
+
if (options.sessionId) params.set('sessionId', options.sessionId);
|
|
102
|
+
if (options.workspace) params.set('workspace', options.workspace);
|
|
103
|
+
if (options.feedPostId) {
|
|
104
|
+
params.set('feedPostId', options.feedPostId);
|
|
105
|
+
if (typeof options.initialQuestion === 'string') {
|
|
106
|
+
params.set('initialQuestion', options.initialQuestion);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
99
109
|
const url = `${platformUrl}/api/protocol/context/${ticketId}?${params.toString()}`;
|
|
100
110
|
const response = await fetch(url, {
|
|
101
111
|
headers: buildAuthHeaders(bearerToken, localSecret, organizationId)
|
|
@@ -107,7 +117,10 @@ async function fetchContext(platformUrl, bearerToken, localSecret, organizationI
|
|
|
107
117
|
);
|
|
108
118
|
}
|
|
109
119
|
|
|
110
|
-
|
|
120
|
+
const context = await response.text();
|
|
121
|
+
const workingDirectory = response.headers.get('X-Working-Directory') ?? '';
|
|
122
|
+
const humanTicketId = response.headers.get('X-Ticket-Id') ?? '';
|
|
123
|
+
return { context, workingDirectory, humanTicketId };
|
|
111
124
|
}
|
|
112
125
|
|
|
113
126
|
const agentIdentifierMap = {
|
|
@@ -124,24 +137,53 @@ const supportedAgents = ['claude', 'codex', 'cursor', 'antigravity', 'opencode',
|
|
|
124
137
|
// Re-exported under clearer names for the direct-launch dispatcher in index.mjs.
|
|
125
138
|
export { supportedAgents as BUILTIN_LAUNCH_AGENTS, agentIdentifierMap };
|
|
126
139
|
|
|
140
|
+
/** @internal Test-only overrides for launcher subprocess calls. */
|
|
141
|
+
export const launcherTestHooks = {
|
|
142
|
+
execFileSync: null,
|
|
143
|
+
shell: null,
|
|
144
|
+
platform: null
|
|
145
|
+
};
|
|
146
|
+
|
|
127
147
|
function shellQuote(value) {
|
|
128
148
|
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
129
149
|
}
|
|
130
150
|
|
|
151
|
+
function resolveLauncherShell() {
|
|
152
|
+
const platform = launcherTestHooks.platform ?? process.platform;
|
|
153
|
+
if (platform === 'win32') {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
return launcherTestHooks.shell ?? process.env.SHELL ?? 'sh';
|
|
157
|
+
}
|
|
158
|
+
|
|
131
159
|
/**
|
|
132
160
|
* Run an agent binary, optionally routed through a user-defined pre-command
|
|
133
|
-
* (e.g. `ollama` to run claude-code through Ollama).
|
|
134
|
-
*
|
|
135
|
-
*
|
|
161
|
+
* (e.g. `ollama` to run claude-code through Ollama). On POSIX shells, execute
|
|
162
|
+
* the wrapper through the user's interactive login shell so aliases, functions,
|
|
163
|
+
* and PATH customizations are available:
|
|
164
|
+
* preCommand="agent-pod" → $SHELL -ilc 'agent-pod codex <args...>'
|
|
165
|
+
*
|
|
166
|
+
* The shell must be interactive (`-i`), not just a login shell (`-l`): zsh only
|
|
167
|
+
* sources ~/.zshrc — and bash only sources ~/.bashrc — for interactive shells.
|
|
168
|
+
* Wrappers like agent-pod are commonly installed as a shell alias in ~/.zshrc
|
|
169
|
+
* (its documented default), so a plain `-lc` login shell never sees them and
|
|
170
|
+
* fails with `command not found`.
|
|
136
171
|
*/
|
|
137
172
|
function execAgentBinary(binary, args, opts, preCommand) {
|
|
173
|
+
const exec = launcherTestHooks.execFileSync ?? execFileSync;
|
|
138
174
|
const pre = typeof preCommand === 'string' ? preCommand.trim() : '';
|
|
139
175
|
if (pre) {
|
|
176
|
+
const shell = resolveLauncherShell();
|
|
177
|
+
if (shell) {
|
|
178
|
+
const command = [pre, shellQuote(binary), ...args.map(arg => shellQuote(arg))].join(' ');
|
|
179
|
+
exec(shell, ['-ilc', command], opts);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
140
182
|
const preTokens = pre.split(/\s+/).filter(Boolean);
|
|
141
|
-
|
|
183
|
+
exec(preTokens[0], [...preTokens.slice(1), binary, ...args], opts);
|
|
142
184
|
return;
|
|
143
185
|
}
|
|
144
|
-
|
|
186
|
+
exec(binary, args, opts);
|
|
145
187
|
}
|
|
146
188
|
|
|
147
189
|
function toTomlString(value) {
|
|
@@ -279,11 +321,13 @@ Options:
|
|
|
279
321
|
--model <identifier> Preferred model identifier
|
|
280
322
|
--thinking <level> Agent reasoning/effort level
|
|
281
323
|
--flag <value> Extra agent flag (repeatable)
|
|
282
|
-
--pre-command <command>
|
|
324
|
+
--pre-command <command> Shell command to run before the agent binary (e.g. ollama, agent-pod)
|
|
283
325
|
--ssh-command <command> Launch remotely over SSH by running ovld on the target host
|
|
284
326
|
--remote-working-directory <path> Change to this path on the remote host before launch
|
|
285
327
|
--server-multiplexer <none|tmux> Wrap remote launches in tmux
|
|
286
328
|
--tmux-command <template> Remote tmux template; use {script} as the placeholder
|
|
329
|
+
--feed-post-id <uuid> Include a feed post in the context (desktop feed discuss)
|
|
330
|
+
--initial-question <text> First user question for feed discuss (paired with --feed-post-id)
|
|
287
331
|
|
|
288
332
|
Notes:
|
|
289
333
|
- ovld launch is the primary user-facing launcher.
|
|
@@ -370,7 +414,10 @@ async function runAgent(agent, mode = 'run', options = {}) {
|
|
|
370
414
|
options.organizationId,
|
|
371
415
|
organizationId
|
|
372
416
|
);
|
|
373
|
-
|
|
417
|
+
const launchSessionId = crypto.randomUUID();
|
|
418
|
+
const isRemote = Boolean(options.sshCommand?.trim());
|
|
419
|
+
|
|
420
|
+
if (isRemote) {
|
|
374
421
|
const remoteCommand = buildRemoteLaunchCommand(agent, {
|
|
375
422
|
...options,
|
|
376
423
|
organizationId: resolvedLaunchOrganizationId
|
|
@@ -385,18 +432,40 @@ async function runAgent(agent, mode = 'run', options = {}) {
|
|
|
385
432
|
}
|
|
386
433
|
}
|
|
387
434
|
|
|
388
|
-
const context = await fetchContext(
|
|
435
|
+
const { context, workingDirectory: apiWorkingDirectory, humanTicketId } = await fetchContext(
|
|
389
436
|
platformUrl,
|
|
390
437
|
bearerToken,
|
|
391
438
|
localSecret,
|
|
392
439
|
resolvedLaunchOrganizationId,
|
|
393
440
|
ticketId,
|
|
394
|
-
agent
|
|
441
|
+
agent,
|
|
442
|
+
{
|
|
443
|
+
launchMode: options.launchMode,
|
|
444
|
+
sessionId: launchSessionId,
|
|
445
|
+
workspace: isRemote ? 'ssh' : undefined,
|
|
446
|
+
feedPostId: options.feedPostId,
|
|
447
|
+
initialQuestion: options.initialQuestion
|
|
448
|
+
}
|
|
395
449
|
);
|
|
396
450
|
|
|
451
|
+
if (humanTicketId) {
|
|
452
|
+
process.env.TICKET_ID = humanTicketId;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (!options.workingDirectory && apiWorkingDirectory) {
|
|
456
|
+
try {
|
|
457
|
+
process.chdir(apiWorkingDirectory);
|
|
458
|
+
} catch {
|
|
459
|
+
// Best-effort; the agent will run in the current directory.
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
397
463
|
const childEnv = withLaunchTempEnv({
|
|
398
464
|
...process.env,
|
|
399
465
|
AGENT_IDENTIFIER: agentIdentifierMap[agent],
|
|
466
|
+
OVERLORD_LAUNCH_SESSION_ID: launchSessionId,
|
|
467
|
+
OVERLORD_MODEL_IDENTIFIER: options.model ?? '',
|
|
468
|
+
MODEL_IDENTIFIER: options.model ?? '',
|
|
400
469
|
...(resolvedLaunchOrganizationId
|
|
401
470
|
? { OVERLORD_ORGANIZATION_ID: String(resolvedLaunchOrganizationId) }
|
|
402
471
|
: {})
|
|
@@ -568,19 +637,27 @@ async function runCustomAgent(args) {
|
|
|
568
637
|
flags['organization-id'],
|
|
569
638
|
organizationId
|
|
570
639
|
);
|
|
640
|
+
const launchSessionId = crypto.randomUUID();
|
|
641
|
+
const feedPostId = typeof flags['feed-post-id'] === 'string' ? flags['feed-post-id'].trim() : '';
|
|
642
|
+
const initialQuestion = typeof flags['initial-question'] === 'string' ? flags['initial-question'].trim() : '';
|
|
571
643
|
// Custom agents have no Overlord plugin/bundle; use the generic "claude" context.
|
|
572
|
-
const context = await fetchContext(
|
|
644
|
+
const { context } = await fetchContext(
|
|
573
645
|
platformUrl,
|
|
574
646
|
bearerToken,
|
|
575
647
|
localSecret,
|
|
576
648
|
resolvedLaunchOrganizationId,
|
|
577
649
|
resolvedTicketId,
|
|
578
|
-
'claude'
|
|
650
|
+
'claude',
|
|
651
|
+
{
|
|
652
|
+
sessionId: launchSessionId,
|
|
653
|
+
...(feedPostId ? { feedPostId, initialQuestion } : {})
|
|
654
|
+
}
|
|
579
655
|
);
|
|
580
656
|
|
|
581
657
|
const childEnv = withLaunchTempEnv({
|
|
582
658
|
...process.env,
|
|
583
659
|
AGENT_IDENTIFIER: 'custom',
|
|
660
|
+
OVERLORD_LAUNCH_SESSION_ID: launchSessionId,
|
|
584
661
|
...(resolvedLaunchOrganizationId
|
|
585
662
|
? { OVERLORD_ORGANIZATION_ID: String(resolvedLaunchOrganizationId) }
|
|
586
663
|
: {})
|
|
@@ -639,7 +716,9 @@ export async function runLauncherCommand(command, args) {
|
|
|
639
716
|
: '',
|
|
640
717
|
serverMultiplexer:
|
|
641
718
|
flags['server-multiplexer'] === 'tmux' ? 'tmux' : 'none',
|
|
642
|
-
tmuxCommand: typeof flags['tmux-command'] === 'string' ? flags['tmux-command'].trim() : ''
|
|
719
|
+
tmuxCommand: typeof flags['tmux-command'] === 'string' ? flags['tmux-command'].trim() : '',
|
|
720
|
+
feedPostId: typeof flags['feed-post-id'] === 'string' ? flags['feed-post-id'].trim() : '',
|
|
721
|
+
initialQuestion: typeof flags['initial-question'] === 'string' ? flags['initial-question'].trim() : ''
|
|
643
722
|
};
|
|
644
723
|
|
|
645
724
|
if (normalizedCommand === 'run') {
|
package/bin/_cli/runner.mjs
CHANGED
|
@@ -8,11 +8,13 @@ import path from 'node:path';
|
|
|
8
8
|
|
|
9
9
|
const OVLD_ENTRY = process.argv[1];
|
|
10
10
|
const DEVICE_FILE = path.join(os.homedir(), '.ovld', 'device.json');
|
|
11
|
+
const DEFAULT_TMUX_COMMAND = 'tmux new-session bash {script}';
|
|
11
12
|
|
|
12
13
|
/** @internal Test-only overrides for protocol/launch subprocess calls. */
|
|
13
14
|
export const runnerTestHooks = {
|
|
14
15
|
execFileSync: null,
|
|
15
|
-
spawn: null
|
|
16
|
+
spawn: null,
|
|
17
|
+
platform: null
|
|
16
18
|
};
|
|
17
19
|
|
|
18
20
|
function printRunnerHelp(primaryCommand = 'ovld') {
|
|
@@ -131,9 +133,167 @@ export function buildLaunchArgs(launch) {
|
|
|
131
133
|
}
|
|
132
134
|
if (launch.serverMultiplexer === 'tmux') args.push('--server-multiplexer', 'tmux');
|
|
133
135
|
if (launch.tmuxCommand) args.push('--tmux-command', launch.tmuxCommand);
|
|
136
|
+
if (launch.feedPostId) args.push('--feed-post-id', launch.feedPostId);
|
|
137
|
+
if (launch.initialQuestion) args.push('--initial-question', launch.initialQuestion);
|
|
134
138
|
return args;
|
|
135
139
|
}
|
|
136
140
|
|
|
141
|
+
function shellQuote(value) {
|
|
142
|
+
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function appleScriptString(value) {
|
|
146
|
+
return `"${String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function normalizeRunnerTerminalProfile(value) {
|
|
150
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
151
|
+
return {
|
|
152
|
+
terminalApp: typeof value.terminalApp === 'string' ? value.terminalApp : 'default',
|
|
153
|
+
terminalLaunchMode:
|
|
154
|
+
typeof value.terminalLaunchMode === 'string' ? value.terminalLaunchMode : 'tab',
|
|
155
|
+
terminalCustomHotkey:
|
|
156
|
+
typeof value.terminalCustomHotkey === 'string' ? value.terminalCustomHotkey : '',
|
|
157
|
+
customTerminalApp:
|
|
158
|
+
typeof value.customTerminalApp === 'string' ? value.customTerminalApp : '',
|
|
159
|
+
terminalTmuxHostApp:
|
|
160
|
+
typeof value.terminalTmuxHostApp === 'string' ? value.terminalTmuxHostApp : 'terminal',
|
|
161
|
+
customTerminalTmuxHostApp:
|
|
162
|
+
typeof value.customTerminalTmuxHostApp === 'string'
|
|
163
|
+
? value.customTerminalTmuxHostApp
|
|
164
|
+
: '',
|
|
165
|
+
terminalTmuxCommand:
|
|
166
|
+
typeof value.terminalTmuxCommand === 'string' && value.terminalTmuxCommand.trim()
|
|
167
|
+
? value.terminalTmuxCommand
|
|
168
|
+
: DEFAULT_TMUX_COMMAND
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function buildRunnerLaunchShellCommand(args, deviceFingerprint) {
|
|
173
|
+
return [
|
|
174
|
+
`export OVERLORD_DEVICE_FINGERPRINT=${shellQuote(deviceFingerprint)}`,
|
|
175
|
+
`exec ${shellQuote(process.execPath)} ${[OVLD_ENTRY, ...args].map(shellQuote).join(' ')}`
|
|
176
|
+
].join('\n');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function writeRunnerLaunchScript(launchCommand) {
|
|
180
|
+
const dir = path.join(os.tmpdir(), 'ovld-runner');
|
|
181
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
182
|
+
const scriptPath = path.join(
|
|
183
|
+
dir,
|
|
184
|
+
`launch-${process.pid}-${Date.now()}-${crypto.randomUUID()}.sh`
|
|
185
|
+
);
|
|
186
|
+
fs.writeFileSync(scriptPath, `#!/usr/bin/env bash\nset -e\n${launchCommand}\n`, {
|
|
187
|
+
mode: 0o700
|
|
188
|
+
});
|
|
189
|
+
return scriptPath;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function buildTmuxCommand(template, scriptPath) {
|
|
193
|
+
const resolvedTemplate =
|
|
194
|
+
typeof template === 'string' && template.trim().includes('{script}')
|
|
195
|
+
? template.trim()
|
|
196
|
+
: DEFAULT_TMUX_COMMAND;
|
|
197
|
+
return resolvedTemplate.replaceAll('{script}', shellQuote(scriptPath));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function terminalAppName(profile) {
|
|
201
|
+
if (profile.terminalApp === 'custom') return profile.customTerminalApp.trim();
|
|
202
|
+
const appMap = {
|
|
203
|
+
default: 'Terminal',
|
|
204
|
+
terminal: 'Terminal',
|
|
205
|
+
iterm: 'iTerm',
|
|
206
|
+
warp: 'Warp',
|
|
207
|
+
ghostty: 'Ghostty',
|
|
208
|
+
alacritty: 'Alacritty',
|
|
209
|
+
kitty: 'Kitty',
|
|
210
|
+
hyper: 'Hyper'
|
|
211
|
+
};
|
|
212
|
+
return appMap[profile.terminalApp] ?? '';
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function buildRunnerTerminalOpenCommand(
|
|
216
|
+
profileValue,
|
|
217
|
+
launchCommand,
|
|
218
|
+
platform = process.platform
|
|
219
|
+
) {
|
|
220
|
+
const profile = normalizeRunnerTerminalProfile(profileValue);
|
|
221
|
+
if (!profile) return null;
|
|
222
|
+
|
|
223
|
+
const command =
|
|
224
|
+
profile.terminalApp === 'tmux'
|
|
225
|
+
? buildTmuxCommand(profile.terminalTmuxCommand, writeRunnerLaunchScript(launchCommand))
|
|
226
|
+
: launchCommand;
|
|
227
|
+
|
|
228
|
+
if (profile.terminalApp === 'tmux' && platform !== 'darwin') {
|
|
229
|
+
return command;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (platform !== 'darwin') return null;
|
|
233
|
+
|
|
234
|
+
const appName =
|
|
235
|
+
profile.terminalApp === 'tmux'
|
|
236
|
+
? terminalAppName({
|
|
237
|
+
...profile,
|
|
238
|
+
terminalApp: profile.terminalTmuxHostApp,
|
|
239
|
+
customTerminalApp: profile.customTerminalTmuxHostApp
|
|
240
|
+
})
|
|
241
|
+
: terminalAppName(profile);
|
|
242
|
+
if (!appName) return null;
|
|
243
|
+
|
|
244
|
+
if (appName === 'iTerm') {
|
|
245
|
+
return [
|
|
246
|
+
'osascript',
|
|
247
|
+
'-e',
|
|
248
|
+
shellQuote('tell application "iTerm"'),
|
|
249
|
+
'-e',
|
|
250
|
+
shellQuote(`create window with default profile command ${appleScriptString(command)}`),
|
|
251
|
+
'-e',
|
|
252
|
+
shellQuote('activate'),
|
|
253
|
+
'-e',
|
|
254
|
+
shellQuote('end tell')
|
|
255
|
+
].join(' ');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return [
|
|
259
|
+
'osascript',
|
|
260
|
+
'-e',
|
|
261
|
+
shellQuote(
|
|
262
|
+
`tell application ${appleScriptString(appName)} to do script ${appleScriptString(command)}`
|
|
263
|
+
),
|
|
264
|
+
'-e',
|
|
265
|
+
shellQuote(`tell application ${appleScriptString(appName)} to activate`)
|
|
266
|
+
].join(' ');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function spawnLaunchProcess(args, claim, deviceFingerprint) {
|
|
270
|
+
const spawnImpl = runnerTestHooks.spawn ?? spawn;
|
|
271
|
+
const launchCommand = buildRunnerLaunchShellCommand(args, deviceFingerprint);
|
|
272
|
+
const terminalCommand = buildRunnerTerminalOpenCommand(
|
|
273
|
+
claim.launch?.runnerTerminalProfile,
|
|
274
|
+
launchCommand,
|
|
275
|
+
runnerTestHooks.platform ?? process.platform
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
if (!terminalCommand) {
|
|
279
|
+
return {
|
|
280
|
+
child: spawnImpl(process.execPath, [OVLD_ENTRY, ...args], {
|
|
281
|
+
stdio: 'inherit',
|
|
282
|
+
env: { ...process.env, OVERLORD_DEVICE_FINGERPRINT: deviceFingerprint }
|
|
283
|
+
}),
|
|
284
|
+
completeOnClose: false
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
child: spawnImpl('sh', ['-lc', terminalCommand], {
|
|
290
|
+
stdio: 'inherit',
|
|
291
|
+
env: { ...process.env, OVERLORD_DEVICE_FINGERPRINT: deviceFingerprint }
|
|
292
|
+
}),
|
|
293
|
+
completeOnClose: true
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
137
297
|
export async function launchClaimedRequest(claim, deviceFingerprint) {
|
|
138
298
|
const requestId = claim.request?.id;
|
|
139
299
|
if (!requestId || !claim.launch?.agent || !claim.launch?.ticketId) return false;
|
|
@@ -144,13 +304,10 @@ export async function launchClaimedRequest(claim, deviceFingerprint) {
|
|
|
144
304
|
);
|
|
145
305
|
|
|
146
306
|
await new Promise((resolve, reject) => {
|
|
147
|
-
const
|
|
148
|
-
const child = spawnImpl(process.execPath, [OVLD_ENTRY, ...args], {
|
|
149
|
-
stdio: 'inherit',
|
|
150
|
-
env: { ...process.env, OVERLORD_DEVICE_FINGERPRINT: deviceFingerprint }
|
|
151
|
-
});
|
|
307
|
+
const { child, completeOnClose } = spawnLaunchProcess(args, claim, deviceFingerprint);
|
|
152
308
|
|
|
153
309
|
child.once('spawn', () => {
|
|
310
|
+
if (completeOnClose) return;
|
|
154
311
|
try {
|
|
155
312
|
completeLaunch(requestId, deviceFingerprint);
|
|
156
313
|
} catch (error) {
|
|
@@ -168,6 +325,19 @@ export async function launchClaimedRequest(claim, deviceFingerprint) {
|
|
|
168
325
|
});
|
|
169
326
|
|
|
170
327
|
child.once('close', code => {
|
|
328
|
+
if (completeOnClose && (!code || code === 0)) {
|
|
329
|
+
try {
|
|
330
|
+
completeLaunch(requestId, deviceFingerprint);
|
|
331
|
+
} catch (error) {
|
|
332
|
+
process.stderr.write(`[runner] failed to mark request launched: ${error}\n`);
|
|
333
|
+
}
|
|
334
|
+
} else if (completeOnClose && code) {
|
|
335
|
+
try {
|
|
336
|
+
failLaunch(requestId, deviceFingerprint, `terminal launcher exited with code ${code}`);
|
|
337
|
+
} catch {
|
|
338
|
+
// Preserve the launcher exit below.
|
|
339
|
+
}
|
|
340
|
+
}
|
|
171
341
|
if (code && code !== 0) {
|
|
172
342
|
process.stderr.write(`[runner] launch process exited with code ${code}\n`);
|
|
173
343
|
}
|