overlord-cli 0.2608141252.0 → 0.2608141841.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/dist/index.js +297 -245
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3073,16 +3073,21 @@ function normalizeBaseUrl4(value) {
|
|
|
3073
3073
|
function resolveAuthHeaders({ baseUrl }) {
|
|
3074
3074
|
const fromEnv = process.env.OVERLORD_USER_TOKEN?.trim() || process.env.OVLD_USER_TOKEN?.trim() || process.env.USER_TOKEN?.trim();
|
|
3075
3075
|
if (fromEnv) {
|
|
3076
|
-
return {
|
|
3076
|
+
return {
|
|
3077
|
+
headers: { Authorization: `Bearer ${fromEnv}` },
|
|
3078
|
+
fromStored: false,
|
|
3079
|
+
storedCredentialBackendUrl: null
|
|
3080
|
+
};
|
|
3077
3081
|
}
|
|
3078
3082
|
const stored = readStoredAuthCredentials();
|
|
3079
|
-
if (!stored) return { headers: {}, fromStored: false };
|
|
3083
|
+
if (!stored) return { headers: {}, fromStored: false, storedCredentialBackendUrl: null };
|
|
3080
3084
|
if (normalizeBaseUrl4(stored.backendUrl) !== normalizeBaseUrl4(baseUrl)) {
|
|
3081
|
-
return { headers: {}, fromStored: false };
|
|
3085
|
+
return { headers: {}, fromStored: false, storedCredentialBackendUrl: stored.backendUrl };
|
|
3082
3086
|
}
|
|
3083
3087
|
return {
|
|
3084
3088
|
headers: { Authorization: `Bearer ${stored.token}` },
|
|
3085
|
-
fromStored: true
|
|
3089
|
+
fromStored: true,
|
|
3090
|
+
storedCredentialBackendUrl: null
|
|
3086
3091
|
};
|
|
3087
3092
|
}
|
|
3088
3093
|
async function readResponseJson2(response) {
|
|
@@ -3146,6 +3151,12 @@ function createBackendClient() {
|
|
|
3146
3151
|
}) {
|
|
3147
3152
|
const url = `${baseUrl}${path34.startsWith("/") ? path34 : `/${path34}`}`;
|
|
3148
3153
|
const auth = resolveAuthHeaders({ baseUrl });
|
|
3154
|
+
if (auth.storedCredentialBackendUrl) {
|
|
3155
|
+
throw new CliError({
|
|
3156
|
+
message: `Saved credentials are for ${normalizeBaseUrl4(auth.storedCredentialBackendUrl)}, but this CLI is configured for ${baseUrl}.
|
|
3157
|
+
The backend URL may have changed. Run \`ovld auth login\` to sign in to the current backend.`
|
|
3158
|
+
});
|
|
3159
|
+
}
|
|
3149
3160
|
let response;
|
|
3150
3161
|
try {
|
|
3151
3162
|
response = await fetch(url, {
|
|
@@ -21804,7 +21815,15 @@ var init_runner_identity = __esm({
|
|
|
21804
21815
|
|
|
21805
21816
|
// src/runner-service.ts
|
|
21806
21817
|
import { execFile } from "node:child_process";
|
|
21807
|
-
import {
|
|
21818
|
+
import {
|
|
21819
|
+
chmodSync as chmodSync5,
|
|
21820
|
+
existsSync as existsSync26,
|
|
21821
|
+
mkdirSync as mkdirSync14,
|
|
21822
|
+
readFileSync as readFileSync22,
|
|
21823
|
+
rmSync as rmSync6,
|
|
21824
|
+
statSync as statSync7,
|
|
21825
|
+
writeFileSync as writeFileSync13
|
|
21826
|
+
} from "node:fs";
|
|
21808
21827
|
import os5 from "node:os";
|
|
21809
21828
|
import path31 from "node:path";
|
|
21810
21829
|
import { promisify } from "node:util";
|
|
@@ -21812,6 +21831,38 @@ function writeServiceFile(filePath, contents) {
|
|
|
21812
21831
|
writeFileSync13(filePath, contents, { mode: 384 });
|
|
21813
21832
|
chmodSync5(filePath, 384);
|
|
21814
21833
|
}
|
|
21834
|
+
function resolveRunnerEntryScript(argv = process.argv) {
|
|
21835
|
+
return argv[1] ? path31.resolve(argv[1]) : null;
|
|
21836
|
+
}
|
|
21837
|
+
function readRunnerExecutableFileIdentity(filePath, stat = statSync7) {
|
|
21838
|
+
try {
|
|
21839
|
+
const metadata = stat(filePath);
|
|
21840
|
+
return { dev: metadata.dev, ino: metadata.ino, mtimeMs: metadata.mtimeMs };
|
|
21841
|
+
} catch (error) {
|
|
21842
|
+
return error.code === "ENOENT" ? null : void 0;
|
|
21843
|
+
}
|
|
21844
|
+
}
|
|
21845
|
+
function captureRunnerSupervisorIdentity({
|
|
21846
|
+
execPath = process.execPath,
|
|
21847
|
+
entryScript = resolveRunnerEntryScript(),
|
|
21848
|
+
stat = statSync7
|
|
21849
|
+
} = {}) {
|
|
21850
|
+
return {
|
|
21851
|
+
program: readRunnerExecutableFileIdentity(execPath, stat),
|
|
21852
|
+
entryScript: entryScript ? readRunnerExecutableFileIdentity(entryScript, stat) : void 0
|
|
21853
|
+
};
|
|
21854
|
+
}
|
|
21855
|
+
function executableIdentityChanged(initial, current) {
|
|
21856
|
+
if (current === null) return true;
|
|
21857
|
+
if (!initial || !current) return false;
|
|
21858
|
+
return initial.dev !== current.dev || initial.ino !== current.ino || initial.mtimeMs !== current.mtimeMs;
|
|
21859
|
+
}
|
|
21860
|
+
function shouldRestartRunnerSupervisor({
|
|
21861
|
+
initial,
|
|
21862
|
+
current
|
|
21863
|
+
}) {
|
|
21864
|
+
return executableIdentityChanged(initial.program, current.program) || executableIdentityChanged(initial.entryScript, current.entryScript);
|
|
21865
|
+
}
|
|
21815
21866
|
function emptyRunnerServiceState() {
|
|
21816
21867
|
return {
|
|
21817
21868
|
serviceKind: null,
|
|
@@ -21850,41 +21901,10 @@ function patchRunnerServiceState(patch, dataDir = resolveGlobalDataDir()) {
|
|
|
21850
21901
|
writeRunnerServiceState(next, dataDir);
|
|
21851
21902
|
return next;
|
|
21852
21903
|
}
|
|
21853
|
-
function runnerFocusStatePath(dataDir = resolveGlobalDataDir()) {
|
|
21854
|
-
return path31.join(dataDir, RUNNER_FOCUS_STATE_FILENAME);
|
|
21855
|
-
}
|
|
21856
|
-
function readDesktopFocusState(dataDir = resolveGlobalDataDir()) {
|
|
21857
|
-
const filePath = runnerFocusStatePath(dataDir);
|
|
21858
|
-
if (!existsSync26(filePath)) return { lastFocusedAt: null };
|
|
21859
|
-
try {
|
|
21860
|
-
const parsed = JSON.parse(readFileSync22(filePath, "utf8"));
|
|
21861
|
-
return { lastFocusedAt: parsed.lastFocusedAt ?? null };
|
|
21862
|
-
} catch {
|
|
21863
|
-
return { lastFocusedAt: null };
|
|
21864
|
-
}
|
|
21865
|
-
}
|
|
21866
|
-
function withinWindow(timestamp, now, windowMs) {
|
|
21867
|
-
if (!timestamp) return false;
|
|
21868
|
-
const ms = Date.parse(timestamp);
|
|
21869
|
-
if (Number.isNaN(ms)) return false;
|
|
21870
|
-
return now - ms <= windowMs;
|
|
21871
|
-
}
|
|
21872
|
-
function selectBasePollIntervalMs({
|
|
21873
|
-
lastLaunchedAt,
|
|
21874
|
-
lastDesktopFocusAt = null,
|
|
21875
|
-
now
|
|
21876
|
-
}) {
|
|
21877
|
-
const jobActive = withinWindow(lastLaunchedAt, now, IDLE_BACKOFF_MS);
|
|
21878
|
-
const focusActive = withinWindow(lastDesktopFocusAt, now, DESKTOP_FOCUS_WINDOW_MS);
|
|
21879
|
-
return jobActive || focusActive ? FAST_POLL_INTERVAL_MS : SLOW_POLL_INTERVAL_MS;
|
|
21880
|
-
}
|
|
21881
21904
|
function applyPollJitter(intervalMs, random = Math.random) {
|
|
21882
21905
|
const spread = intervalMs * 0.1;
|
|
21883
21906
|
return Math.round(intervalMs + (random() * 2 - 1) * spread);
|
|
21884
21907
|
}
|
|
21885
|
-
function nextRunnerLastError(pollError) {
|
|
21886
|
-
return pollError;
|
|
21887
|
-
}
|
|
21888
21908
|
function resolveOverlordAppInvocation({
|
|
21889
21909
|
platform: platform2 = process.platform,
|
|
21890
21910
|
homedir: homedir2 = os5.homedir(),
|
|
@@ -21920,27 +21940,15 @@ function resolveOvldInvocation(argv = process.argv, execPath = process.execPath)
|
|
|
21920
21940
|
}
|
|
21921
21941
|
return { program: execPath, args: ["runner", "supervise"] };
|
|
21922
21942
|
}
|
|
21923
|
-
function describeServicePublisher({
|
|
21924
|
-
execProgram,
|
|
21925
|
-
platform: platform2 = process.platform
|
|
21926
|
-
}) {
|
|
21927
|
-
if (platform2 !== "darwin" || !execProgram) {
|
|
21928
|
-
return { publisher: "unknown", needsReinstallForOverlord: false };
|
|
21929
|
-
}
|
|
21930
|
-
if (execProgram.includes(".app/Contents/MacOS/")) {
|
|
21931
|
-
return { publisher: "overlord", needsReinstallForOverlord: false };
|
|
21932
|
-
}
|
|
21933
|
-
return { publisher: "node", needsReinstallForOverlord: true };
|
|
21934
|
-
}
|
|
21935
21943
|
function buildRunnerServiceEnv({
|
|
21936
21944
|
backendUrl,
|
|
21937
21945
|
overlordHome,
|
|
21938
21946
|
runAsElectronNode
|
|
21939
21947
|
}) {
|
|
21940
21948
|
const env = {
|
|
21941
|
-
OVERLORD_BACKEND_URL: backendUrl,
|
|
21942
21949
|
PATH: process.env.PATH?.trim() || "/usr/local/bin:/usr/bin:/bin"
|
|
21943
21950
|
};
|
|
21951
|
+
if (!isLoopbackBackendUrl(backendUrl)) env.OVERLORD_BACKEND_URL = backendUrl;
|
|
21944
21952
|
const home = overlordHome?.trim() || process.env.OVLD_HOME?.trim();
|
|
21945
21953
|
if (home) env.OVLD_HOME = path31.resolve(home);
|
|
21946
21954
|
const token = process.env.OVERLORD_USER_TOKEN?.trim() || process.env.OVLD_USER_TOKEN?.trim();
|
|
@@ -22025,31 +22033,72 @@ async function runCommand(program, args) {
|
|
|
22025
22033
|
const { stdout } = await execFileAsync(program, args);
|
|
22026
22034
|
return { stdout: stdout.toString() };
|
|
22027
22035
|
}
|
|
22036
|
+
function launchdDomainTarget(uid) {
|
|
22037
|
+
return `gui/${uid}`;
|
|
22038
|
+
}
|
|
22039
|
+
function launchdServiceTarget(uid) {
|
|
22040
|
+
return `${launchdDomainTarget(uid)}/${LAUNCHD_LABEL}`;
|
|
22041
|
+
}
|
|
22042
|
+
function planLaunchdUnloadCommands(opts) {
|
|
22043
|
+
return [{ args: ["bootout", launchdServiceTarget(opts.uid)], tolerateFailure: true }];
|
|
22044
|
+
}
|
|
22045
|
+
function planLaunchdLoadCommands(opts) {
|
|
22046
|
+
const service = launchdServiceTarget(opts.uid);
|
|
22047
|
+
return [
|
|
22048
|
+
{ args: ["enable", service], tolerateFailure: true },
|
|
22049
|
+
{
|
|
22050
|
+
args: ["bootstrap", launchdDomainTarget(opts.uid), opts.plistPath],
|
|
22051
|
+
tolerateFailure: true
|
|
22052
|
+
},
|
|
22053
|
+
{ args: ["kickstart", service], tolerateFailure: false }
|
|
22054
|
+
];
|
|
22055
|
+
}
|
|
22028
22056
|
function resolveServiceManager(platform2 = process.platform) {
|
|
22029
22057
|
if (platform2 === "darwin") return new LaunchdManager();
|
|
22030
22058
|
if (platform2 === "linux") return new SystemdUserManager();
|
|
22031
22059
|
return null;
|
|
22032
22060
|
}
|
|
22033
|
-
var execFileAsync, RUNNER_SERVICE_STATE_FILENAME,
|
|
22061
|
+
var execFileAsync, RUNNER_SERVICE_STATE_FILENAME, LAUNCHD_LABEL, SYSTEMD_UNIT_NAME, FALLBACK_POLL_INTERVAL_MS, LaunchdManager, SystemdUserManager;
|
|
22034
22062
|
var init_runner_service = __esm({
|
|
22035
22063
|
"src/runner-service.ts"() {
|
|
22036
22064
|
"use strict";
|
|
22037
22065
|
init_config();
|
|
22038
22066
|
execFileAsync = promisify(execFile);
|
|
22039
22067
|
RUNNER_SERVICE_STATE_FILENAME = "runner-service.json";
|
|
22040
|
-
RUNNER_FOCUS_STATE_FILENAME = "runner-focus.json";
|
|
22041
22068
|
LAUNCHD_LABEL = "io.overlord.runner";
|
|
22042
22069
|
SYSTEMD_UNIT_NAME = "overlord-runner";
|
|
22043
|
-
|
|
22044
|
-
SLOW_POLL_INTERVAL_MS = 5e3;
|
|
22045
|
-
IDLE_BACKOFF_MS = 2 * 60 * 60 * 1e3;
|
|
22046
|
-
DESKTOP_FOCUS_WINDOW_MS = 30 * 60 * 1e3;
|
|
22070
|
+
FALLBACK_POLL_INTERVAL_MS = 5e3;
|
|
22047
22071
|
LaunchdManager = class {
|
|
22048
22072
|
kind = "launchd";
|
|
22049
22073
|
identifier = LAUNCHD_LABEL;
|
|
22074
|
+
exec;
|
|
22075
|
+
constructor(exec) {
|
|
22076
|
+
this.exec = exec ?? ((args) => runCommand("launchctl", args));
|
|
22077
|
+
}
|
|
22050
22078
|
unitPath() {
|
|
22051
22079
|
return path31.join(launchAgentsDir(), `${LAUNCHD_LABEL}.plist`);
|
|
22052
22080
|
}
|
|
22081
|
+
uid() {
|
|
22082
|
+
return process.getuid?.() ?? "";
|
|
22083
|
+
}
|
|
22084
|
+
async runSteps(steps) {
|
|
22085
|
+
for (const step of steps) {
|
|
22086
|
+
try {
|
|
22087
|
+
await this.exec(step.args);
|
|
22088
|
+
} catch (error) {
|
|
22089
|
+
if (!step.tolerateFailure) throw error;
|
|
22090
|
+
}
|
|
22091
|
+
}
|
|
22092
|
+
}
|
|
22093
|
+
/** True when launchd still has the label registered, whatever its run state. */
|
|
22094
|
+
async isLoaded() {
|
|
22095
|
+
try {
|
|
22096
|
+
await this.exec(["list", LAUNCHD_LABEL]);
|
|
22097
|
+
return true;
|
|
22098
|
+
} catch {
|
|
22099
|
+
return false;
|
|
22100
|
+
}
|
|
22101
|
+
}
|
|
22053
22102
|
render(opts) {
|
|
22054
22103
|
return renderLaunchdPlist({
|
|
22055
22104
|
label: LAUNCHD_LABEL,
|
|
@@ -22061,41 +22110,33 @@ var init_runner_service = __esm({
|
|
|
22061
22110
|
async install(opts) {
|
|
22062
22111
|
mkdirSync14(launchAgentsDir(), { recursive: true });
|
|
22063
22112
|
mkdirSync14(path31.join(resolveGlobalDataDir(), "logs"), { recursive: true });
|
|
22113
|
+
await this.runSteps(planLaunchdUnloadCommands({ uid: this.uid() }));
|
|
22064
22114
|
writeServiceFile(this.unitPath(), this.render(opts));
|
|
22065
22115
|
if (opts.autoStart) await this.start();
|
|
22066
22116
|
}
|
|
22067
22117
|
async start() {
|
|
22068
|
-
|
|
22069
|
-
await runCommand("launchctl", ["load", "-w", this.unitPath()]);
|
|
22070
|
-
} catch {
|
|
22071
|
-
await runCommand("launchctl", [
|
|
22072
|
-
"kickstart",
|
|
22073
|
-
"-k",
|
|
22074
|
-
`gui/${process.getuid?.() ?? ""}/${LAUNCHD_LABEL}`
|
|
22075
|
-
]);
|
|
22076
|
-
}
|
|
22118
|
+
await this.runSteps(planLaunchdLoadCommands({ uid: this.uid(), plistPath: this.unitPath() }));
|
|
22077
22119
|
}
|
|
22078
22120
|
async stop() {
|
|
22079
|
-
await
|
|
22121
|
+
await this.runSteps(planLaunchdUnloadCommands({ uid: this.uid() }));
|
|
22080
22122
|
}
|
|
22081
22123
|
async restart() {
|
|
22082
|
-
|
|
22083
|
-
await this.stop();
|
|
22084
|
-
} catch {
|
|
22085
|
-
}
|
|
22124
|
+
await this.stop();
|
|
22086
22125
|
await this.start();
|
|
22087
22126
|
}
|
|
22088
22127
|
async uninstall() {
|
|
22089
|
-
|
|
22090
|
-
|
|
22091
|
-
|
|
22128
|
+
await this.runSteps(planLaunchdUnloadCommands({ uid: this.uid() }));
|
|
22129
|
+
if (await this.isLoaded()) {
|
|
22130
|
+
throw new Error(
|
|
22131
|
+
`launchctl still reports ${LAUNCHD_LABEL} as loaded after bootout; left ${this.unitPath()} in place so the service can be repaired with \`ovld runner service restart\`.`
|
|
22132
|
+
);
|
|
22092
22133
|
}
|
|
22093
22134
|
if (existsSync26(this.unitPath())) rmSync6(this.unitPath());
|
|
22094
22135
|
}
|
|
22095
22136
|
async status() {
|
|
22096
22137
|
const installed = existsSync26(this.unitPath());
|
|
22097
22138
|
try {
|
|
22098
|
-
const { stdout } = await
|
|
22139
|
+
const { stdout } = await this.exec(["list"]);
|
|
22099
22140
|
const line = stdout.split("\n").find((row) => row.includes(LAUNCHD_LABEL));
|
|
22100
22141
|
if (!line) return { installed, running: installed ? "stopped" : "unknown" };
|
|
22101
22142
|
const pid = line.trim().split(/\s+/)[0];
|
|
@@ -22272,7 +22313,8 @@ var commands_exports = {};
|
|
|
22272
22313
|
__export(commands_exports, {
|
|
22273
22314
|
resolveProtocolFileInputs: () => resolveProtocolFileInputs,
|
|
22274
22315
|
runManagementCommand: () => runManagementCommand,
|
|
22275
|
-
runProtocolCommand: () => runProtocolCommand
|
|
22316
|
+
runProtocolCommand: () => runProtocolCommand,
|
|
22317
|
+
runRunnerOnce: () => runRunnerOnce
|
|
22276
22318
|
});
|
|
22277
22319
|
import { existsSync as existsSync28, readFileSync as readFileSync24 } from "node:fs";
|
|
22278
22320
|
import os6 from "node:os";
|
|
@@ -23435,6 +23477,165 @@ function isNoExecutionTargetRegisteredError(error) {
|
|
|
23435
23477
|
const message = error instanceof Error ? error.message : String(error);
|
|
23436
23478
|
return message.includes("(no_execution_target_registered)");
|
|
23437
23479
|
}
|
|
23480
|
+
async function runRunnerOnce({
|
|
23481
|
+
runtime,
|
|
23482
|
+
parsed,
|
|
23483
|
+
json
|
|
23484
|
+
}) {
|
|
23485
|
+
let claim;
|
|
23486
|
+
try {
|
|
23487
|
+
claim = await runtime.backend.post({
|
|
23488
|
+
path: "/api/runner/claim",
|
|
23489
|
+
body: {
|
|
23490
|
+
projectId: flagValue(parsed.flags, "--project-id"),
|
|
23491
|
+
...clientDeviceIdentity(),
|
|
23492
|
+
// Contract v40: the claim poll doubles as this runner instance's
|
|
23493
|
+
// heartbeat, so local target liveness reflects a runner that can
|
|
23494
|
+
// actually take work rather than any CLI traffic from the machine.
|
|
23495
|
+
...runnerRegistrationPayload()
|
|
23496
|
+
}
|
|
23497
|
+
});
|
|
23498
|
+
} catch (error) {
|
|
23499
|
+
if (isNoExecutionTargetRegisteredError(error)) {
|
|
23500
|
+
throw new CliError({
|
|
23501
|
+
message: `${error instanceof Error ? error.message : String(error)}
|
|
23502
|
+
The runner will not register a target for you \u2014 declare this machine first, then start it again.`
|
|
23503
|
+
});
|
|
23504
|
+
}
|
|
23505
|
+
throw error;
|
|
23506
|
+
}
|
|
23507
|
+
const request = asRecord5(claim).request;
|
|
23508
|
+
const longPoll = asRecord5(claim).longPoll === true;
|
|
23509
|
+
if (!request) return { launched: false, longPoll };
|
|
23510
|
+
const requestRecord = asRecord5(request);
|
|
23511
|
+
const requestId = String(requestRecord.id);
|
|
23512
|
+
const projectId = String(requestRecord.projectId ?? "");
|
|
23513
|
+
const executionTargetId = typeof requestRecord.executionTargetId === "string" ? requestRecord.executionTargetId.trim() : "";
|
|
23514
|
+
if (projectId && executionTargetId) {
|
|
23515
|
+
try {
|
|
23516
|
+
await reportRunnerResourceObservations({
|
|
23517
|
+
backend: runtime.backend,
|
|
23518
|
+
projectId,
|
|
23519
|
+
executionTargetId
|
|
23520
|
+
});
|
|
23521
|
+
} catch {
|
|
23522
|
+
}
|
|
23523
|
+
}
|
|
23524
|
+
try {
|
|
23525
|
+
await runtime.backend.post({ path: `/api/runner/requests/${requestId}/launching` });
|
|
23526
|
+
const mutation = parseMutationFromMetadata(requestRecord.metadata);
|
|
23527
|
+
if (mutation) {
|
|
23528
|
+
const mutationResult = await executeLocalTargetMutation({ mutation });
|
|
23529
|
+
if (!mutationResult.ok) {
|
|
23530
|
+
await runtime.backend.post({
|
|
23531
|
+
path: `/api/runner/requests/${requestId}/failed`,
|
|
23532
|
+
body: { error: mutationResult.message }
|
|
23533
|
+
});
|
|
23534
|
+
throw new CliError({ message: mutationResult.message });
|
|
23535
|
+
}
|
|
23536
|
+
await runtime.backend.post({
|
|
23537
|
+
path: `/api/runner/requests/${requestId}/completed`,
|
|
23538
|
+
body: { mutationResult }
|
|
23539
|
+
});
|
|
23540
|
+
if (json) printJson({ request, mutationResult });
|
|
23541
|
+
else console.log(`Completed ${mutation.kind} for ${requestRecord.missionId}`);
|
|
23542
|
+
return { launched: true, longPoll };
|
|
23543
|
+
}
|
|
23544
|
+
const requestedAgent = typeof requestRecord.requestedAgent === "string" ? requestRecord.requestedAgent.trim() : "";
|
|
23545
|
+
if (!requestedAgent) {
|
|
23546
|
+
throw new CliError({
|
|
23547
|
+
message: `Execution request ${requestId} has no agent; the objective must specify one before it can be launched.`
|
|
23548
|
+
});
|
|
23549
|
+
}
|
|
23550
|
+
const launchConfig = asRecord5(requestRecord.launchConfig);
|
|
23551
|
+
const terminal = await resolveTerminalLaunchSettings({ runtime, flags: parsed.flags });
|
|
23552
|
+
const missionId = String(requestRecord.missionId);
|
|
23553
|
+
const dryRun = flagBoolean(parsed.flags, "--dry-run");
|
|
23554
|
+
const prepared = await prepareMissionBranch({
|
|
23555
|
+
runtime,
|
|
23556
|
+
options: {
|
|
23557
|
+
missionId,
|
|
23558
|
+
workingDirectory: String(requestRecord.workingDirectory ?? process.cwd()),
|
|
23559
|
+
objectiveId: String(requestRecord.objectiveId ?? ""),
|
|
23560
|
+
workspaceAutomationEnabled: await readWorktreeBranchAutomationEnabled(runtime),
|
|
23561
|
+
dryRun,
|
|
23562
|
+
overrideBranch: flagValue(parsed.flags, "--branch"),
|
|
23563
|
+
noWorktree: flagBoolean(parsed.flags, "--no-worktree")
|
|
23564
|
+
}
|
|
23565
|
+
});
|
|
23566
|
+
await recordBranchPrepared({
|
|
23567
|
+
runtime,
|
|
23568
|
+
missionId,
|
|
23569
|
+
requestId,
|
|
23570
|
+
branchAutomation: prepared.branchAutomation
|
|
23571
|
+
});
|
|
23572
|
+
const launchSession = launchSessionSnapshotFromMetadata(asRecord5(requestRecord.metadata));
|
|
23573
|
+
const result = await launchAgent({
|
|
23574
|
+
runtime,
|
|
23575
|
+
options: {
|
|
23576
|
+
agent: requestedAgent,
|
|
23577
|
+
missionId,
|
|
23578
|
+
workingDirectory: prepared.workingDirectory,
|
|
23579
|
+
model: typeof requestRecord.requestedModel === "string" ? requestRecord.requestedModel : void 0,
|
|
23580
|
+
thinking: typeof requestRecord.requestedReasoningEffort === "string" ? requestRecord.requestedReasoningEffort : void 0,
|
|
23581
|
+
flags: normalizeAgentLaunchFlags(launchConfig.flags),
|
|
23582
|
+
preCommand: typeof launchConfig.preCommand === "string" ? launchConfig.preCommand : void 0,
|
|
23583
|
+
preLaunchCommands: Array.isArray(requestRecord.preLaunchCommands) ? requestRecord.preLaunchCommands.filter(
|
|
23584
|
+
(value) => typeof value === "string"
|
|
23585
|
+
) : void 0,
|
|
23586
|
+
launchEnvVars: parseLaunchEnvVarsValue(requestRecord.launchEnvVars),
|
|
23587
|
+
executionRequestId: requestId,
|
|
23588
|
+
executionTargetId: executionTargetId || void 0,
|
|
23589
|
+
launchSession,
|
|
23590
|
+
...terminal,
|
|
23591
|
+
dryRun
|
|
23592
|
+
}
|
|
23593
|
+
});
|
|
23594
|
+
if (result.providerFallbackWarning) {
|
|
23595
|
+
console.error(`[overlord] ${result.providerFallbackWarning}`);
|
|
23596
|
+
}
|
|
23597
|
+
if (result.viewerOpen && result.viewerOpen.ok === false) {
|
|
23598
|
+
console.error(`[overlord] ${result.viewerOpen.warning}`);
|
|
23599
|
+
}
|
|
23600
|
+
if (result.status && result.status !== 0) {
|
|
23601
|
+
throw new CliError({ message: `Launch command exited with status ${result.status}` });
|
|
23602
|
+
}
|
|
23603
|
+
await runtime.backend.post({
|
|
23604
|
+
path: `/api/runner/requests/${requestId}/launched`,
|
|
23605
|
+
body: result.providerSession ? { providerSession: result.providerSession } : {}
|
|
23606
|
+
});
|
|
23607
|
+
if (result.providerSession && !dryRun) {
|
|
23608
|
+
void postLatchHarnessEventsAfterLaunch({
|
|
23609
|
+
backend: runtime.backend,
|
|
23610
|
+
requestId,
|
|
23611
|
+
providerSessionId: result.providerSession.providerSessionId,
|
|
23612
|
+
executable: launchSession?.executionProvider.kind === "latch" ? launchSession.executionProvider.executable : void 0
|
|
23613
|
+
});
|
|
23614
|
+
}
|
|
23615
|
+
if (json || dryRun) {
|
|
23616
|
+
printJson({
|
|
23617
|
+
request,
|
|
23618
|
+
plan: result.plan,
|
|
23619
|
+
status: result.status,
|
|
23620
|
+
providerSession: result.providerSession ?? null,
|
|
23621
|
+
viewerOpen: result.viewerOpen ?? null,
|
|
23622
|
+
providerFallbackWarning: result.providerFallbackWarning ?? null
|
|
23623
|
+
});
|
|
23624
|
+
} else {
|
|
23625
|
+
console.log(`Launched ${requestedAgent} for ${requestRecord.missionId}`);
|
|
23626
|
+
}
|
|
23627
|
+
return { launched: true, longPoll };
|
|
23628
|
+
} catch (error) {
|
|
23629
|
+
try {
|
|
23630
|
+
await runtime.backend.post({
|
|
23631
|
+
path: `/api/runner/requests/${requestId}/failed`,
|
|
23632
|
+
body: { error: error instanceof Error ? error.message : String(error) }
|
|
23633
|
+
});
|
|
23634
|
+
} catch {
|
|
23635
|
+
}
|
|
23636
|
+
throw error;
|
|
23637
|
+
}
|
|
23638
|
+
}
|
|
23438
23639
|
async function runRunnerCommand({
|
|
23439
23640
|
runtime,
|
|
23440
23641
|
parsed,
|
|
@@ -23468,161 +23669,7 @@ async function runRunnerCommand({
|
|
|
23468
23669
|
message: "Usage: ovld runner once|start|supervise|status|clear <objective_id>|clear-all|service <install|start|stop|restart|status|uninstall>"
|
|
23469
23670
|
});
|
|
23470
23671
|
}
|
|
23471
|
-
const runOnce =
|
|
23472
|
-
let claim;
|
|
23473
|
-
try {
|
|
23474
|
-
claim = await runtime.backend.post({
|
|
23475
|
-
path: "/api/runner/claim",
|
|
23476
|
-
body: {
|
|
23477
|
-
projectId: flagValue(parsed.flags, "--project-id"),
|
|
23478
|
-
...clientDeviceIdentity(),
|
|
23479
|
-
// Contract v40: the claim poll doubles as this runner instance's
|
|
23480
|
-
// heartbeat, so local target liveness reflects a runner that can
|
|
23481
|
-
// actually take work rather than any CLI traffic from the machine.
|
|
23482
|
-
...runnerRegistrationPayload()
|
|
23483
|
-
}
|
|
23484
|
-
});
|
|
23485
|
-
} catch (error) {
|
|
23486
|
-
if (isNoExecutionTargetRegisteredError(error)) {
|
|
23487
|
-
throw new CliError({
|
|
23488
|
-
message: `${error instanceof Error ? error.message : String(error)}
|
|
23489
|
-
The runner will not register a target for you \u2014 declare this machine first, then start it again.`
|
|
23490
|
-
});
|
|
23491
|
-
}
|
|
23492
|
-
throw error;
|
|
23493
|
-
}
|
|
23494
|
-
const request = asRecord5(claim).request;
|
|
23495
|
-
const longPoll = asRecord5(claim).longPoll === true;
|
|
23496
|
-
if (!request) return { launched: false, longPoll };
|
|
23497
|
-
const requestRecord = asRecord5(request);
|
|
23498
|
-
const requestId = String(requestRecord.id);
|
|
23499
|
-
const projectId = String(requestRecord.projectId ?? "");
|
|
23500
|
-
const executionTargetId = typeof requestRecord.executionTargetId === "string" ? requestRecord.executionTargetId.trim() : "";
|
|
23501
|
-
if (projectId && executionTargetId) {
|
|
23502
|
-
try {
|
|
23503
|
-
await reportRunnerResourceObservations({
|
|
23504
|
-
backend: runtime.backend,
|
|
23505
|
-
projectId,
|
|
23506
|
-
executionTargetId
|
|
23507
|
-
});
|
|
23508
|
-
} catch {
|
|
23509
|
-
}
|
|
23510
|
-
}
|
|
23511
|
-
try {
|
|
23512
|
-
await runtime.backend.post({ path: `/api/runner/requests/${requestId}/launching` });
|
|
23513
|
-
const mutation = parseMutationFromMetadata(requestRecord.metadata);
|
|
23514
|
-
if (mutation) {
|
|
23515
|
-
const mutationResult = await executeLocalTargetMutation({ mutation });
|
|
23516
|
-
if (!mutationResult.ok) {
|
|
23517
|
-
await runtime.backend.post({
|
|
23518
|
-
path: `/api/runner/requests/${requestId}/failed`,
|
|
23519
|
-
body: { error: mutationResult.message }
|
|
23520
|
-
});
|
|
23521
|
-
throw new CliError({ message: mutationResult.message });
|
|
23522
|
-
}
|
|
23523
|
-
await runtime.backend.post({
|
|
23524
|
-
path: `/api/runner/requests/${requestId}/completed`,
|
|
23525
|
-
body: { mutationResult }
|
|
23526
|
-
});
|
|
23527
|
-
if (json) printJson({ request, mutationResult });
|
|
23528
|
-
else console.log(`Completed ${mutation.kind} for ${requestRecord.missionId}`);
|
|
23529
|
-
return { launched: true, longPoll };
|
|
23530
|
-
}
|
|
23531
|
-
const requestedAgent = typeof requestRecord.requestedAgent === "string" ? requestRecord.requestedAgent.trim() : "";
|
|
23532
|
-
if (!requestedAgent) {
|
|
23533
|
-
throw new CliError({
|
|
23534
|
-
message: `Execution request ${requestId} has no agent; the objective must specify one before it can be launched.`
|
|
23535
|
-
});
|
|
23536
|
-
}
|
|
23537
|
-
const launchConfig = asRecord5(requestRecord.launchConfig);
|
|
23538
|
-
const terminal = await resolveTerminalLaunchSettings({ runtime, flags: parsed.flags });
|
|
23539
|
-
const missionId = String(requestRecord.missionId);
|
|
23540
|
-
const dryRun = flagBoolean(parsed.flags, "--dry-run");
|
|
23541
|
-
const prepared = await prepareMissionBranch({
|
|
23542
|
-
runtime,
|
|
23543
|
-
options: {
|
|
23544
|
-
missionId,
|
|
23545
|
-
workingDirectory: String(requestRecord.workingDirectory ?? process.cwd()),
|
|
23546
|
-
objectiveId: String(requestRecord.objectiveId ?? ""),
|
|
23547
|
-
workspaceAutomationEnabled: await readWorktreeBranchAutomationEnabled(runtime),
|
|
23548
|
-
dryRun,
|
|
23549
|
-
overrideBranch: flagValue(parsed.flags, "--branch"),
|
|
23550
|
-
noWorktree: flagBoolean(parsed.flags, "--no-worktree")
|
|
23551
|
-
}
|
|
23552
|
-
});
|
|
23553
|
-
await recordBranchPrepared({
|
|
23554
|
-
runtime,
|
|
23555
|
-
missionId,
|
|
23556
|
-
requestId,
|
|
23557
|
-
branchAutomation: prepared.branchAutomation
|
|
23558
|
-
});
|
|
23559
|
-
const launchSession = launchSessionSnapshotFromMetadata(asRecord5(requestRecord.metadata));
|
|
23560
|
-
const result = await launchAgent({
|
|
23561
|
-
runtime,
|
|
23562
|
-
options: {
|
|
23563
|
-
agent: requestedAgent,
|
|
23564
|
-
missionId,
|
|
23565
|
-
workingDirectory: prepared.workingDirectory,
|
|
23566
|
-
model: typeof requestRecord.requestedModel === "string" ? requestRecord.requestedModel : void 0,
|
|
23567
|
-
thinking: typeof requestRecord.requestedReasoningEffort === "string" ? requestRecord.requestedReasoningEffort : void 0,
|
|
23568
|
-
flags: normalizeAgentLaunchFlags(launchConfig.flags),
|
|
23569
|
-
preCommand: typeof launchConfig.preCommand === "string" ? launchConfig.preCommand : void 0,
|
|
23570
|
-
preLaunchCommands: Array.isArray(requestRecord.preLaunchCommands) ? requestRecord.preLaunchCommands.filter(
|
|
23571
|
-
(value) => typeof value === "string"
|
|
23572
|
-
) : void 0,
|
|
23573
|
-
launchEnvVars: parseLaunchEnvVarsValue(requestRecord.launchEnvVars),
|
|
23574
|
-
executionRequestId: requestId,
|
|
23575
|
-
executionTargetId: executionTargetId || void 0,
|
|
23576
|
-
launchSession,
|
|
23577
|
-
...terminal,
|
|
23578
|
-
dryRun
|
|
23579
|
-
}
|
|
23580
|
-
});
|
|
23581
|
-
if (result.providerFallbackWarning) {
|
|
23582
|
-
console.error(`[overlord] ${result.providerFallbackWarning}`);
|
|
23583
|
-
}
|
|
23584
|
-
if (result.viewerOpen && result.viewerOpen.ok === false) {
|
|
23585
|
-
console.error(`[overlord] ${result.viewerOpen.warning}`);
|
|
23586
|
-
}
|
|
23587
|
-
if (result.status && result.status !== 0) {
|
|
23588
|
-
throw new CliError({ message: `Launch command exited with status ${result.status}` });
|
|
23589
|
-
}
|
|
23590
|
-
await runtime.backend.post({
|
|
23591
|
-
path: `/api/runner/requests/${requestId}/launched`,
|
|
23592
|
-
body: result.providerSession ? { providerSession: result.providerSession } : {}
|
|
23593
|
-
});
|
|
23594
|
-
if (result.providerSession && !dryRun) {
|
|
23595
|
-
void postLatchHarnessEventsAfterLaunch({
|
|
23596
|
-
backend: runtime.backend,
|
|
23597
|
-
requestId,
|
|
23598
|
-
providerSessionId: result.providerSession.providerSessionId,
|
|
23599
|
-
executable: launchSession?.executionProvider.kind === "latch" ? launchSession.executionProvider.executable : void 0
|
|
23600
|
-
});
|
|
23601
|
-
}
|
|
23602
|
-
if (json || dryRun) {
|
|
23603
|
-
printJson({
|
|
23604
|
-
request,
|
|
23605
|
-
plan: result.plan,
|
|
23606
|
-
status: result.status,
|
|
23607
|
-
providerSession: result.providerSession ?? null,
|
|
23608
|
-
viewerOpen: result.viewerOpen ?? null,
|
|
23609
|
-
providerFallbackWarning: result.providerFallbackWarning ?? null
|
|
23610
|
-
});
|
|
23611
|
-
} else {
|
|
23612
|
-
console.log(`Launched ${requestedAgent} for ${requestRecord.missionId}`);
|
|
23613
|
-
}
|
|
23614
|
-
return { launched: true, longPoll };
|
|
23615
|
-
} catch (error) {
|
|
23616
|
-
try {
|
|
23617
|
-
await runtime.backend.post({
|
|
23618
|
-
path: `/api/runner/requests/${requestId}/failed`,
|
|
23619
|
-
body: { error: error instanceof Error ? error.message : String(error) }
|
|
23620
|
-
});
|
|
23621
|
-
} catch {
|
|
23622
|
-
}
|
|
23623
|
-
throw error;
|
|
23624
|
-
}
|
|
23625
|
-
};
|
|
23672
|
+
const runOnce = () => runRunnerOnce({ runtime, parsed, json });
|
|
23626
23673
|
if (sub === "once") {
|
|
23627
23674
|
const result = await runOnce();
|
|
23628
23675
|
if (!result.launched) {
|
|
@@ -23646,8 +23693,18 @@ async function runRunnerSupervisor({
|
|
|
23646
23693
|
runOnce,
|
|
23647
23694
|
json
|
|
23648
23695
|
}) {
|
|
23649
|
-
if (!json) console.log("Runner supervisor started.
|
|
23696
|
+
if (!json) console.log("Runner supervisor started. Polling for execution requests.");
|
|
23697
|
+
const initialExecutableIdentity = captureRunnerSupervisorIdentity();
|
|
23650
23698
|
for (; ; ) {
|
|
23699
|
+
if (shouldRestartRunnerSupervisor({
|
|
23700
|
+
initial: initialExecutableIdentity,
|
|
23701
|
+
current: captureRunnerSupervisorIdentity()
|
|
23702
|
+
})) {
|
|
23703
|
+
console.error(
|
|
23704
|
+
"Runner supervisor executable was replaced or removed; exiting cleanly so the service manager restarts the installed build."
|
|
23705
|
+
);
|
|
23706
|
+
process.exit(0);
|
|
23707
|
+
}
|
|
23651
23708
|
const now = /* @__PURE__ */ new Date();
|
|
23652
23709
|
let result = { launched: false, longPoll: false };
|
|
23653
23710
|
let lastError = null;
|
|
@@ -23656,25 +23713,22 @@ async function runRunnerSupervisor({
|
|
|
23656
23713
|
} catch (error) {
|
|
23657
23714
|
if (isNoExecutionTargetRegisteredError(error)) throw error;
|
|
23658
23715
|
lastError = error instanceof Error ? error.message : String(error);
|
|
23716
|
+
console.error(`[overlord] Runner poll failed: ${lastError}`);
|
|
23659
23717
|
}
|
|
23660
23718
|
const state = readRunnerServiceState();
|
|
23661
|
-
const focus = readDesktopFocusState();
|
|
23662
|
-
const base = selectBasePollIntervalMs({
|
|
23663
|
-
lastLaunchedAt: result.launched ? now.toISOString() : state.lastLaunchedAt,
|
|
23664
|
-
lastDesktopFocusAt: focus.lastFocusedAt,
|
|
23665
|
-
now: now.getTime()
|
|
23666
|
-
});
|
|
23667
23719
|
patchRunnerServiceState({
|
|
23668
23720
|
lastHeartbeatAt: now.toISOString(),
|
|
23669
23721
|
lastClaimedAt: result.launched ? now.toISOString() : state.lastClaimedAt,
|
|
23670
23722
|
lastLaunchedAt: result.launched ? now.toISOString() : state.lastLaunchedAt,
|
|
23671
|
-
// Reflect the latest poll's outcome
|
|
23672
|
-
//
|
|
23673
|
-
|
|
23674
|
-
|
|
23723
|
+
// Reflect the latest poll's outcome rather than falling back to the stored
|
|
23724
|
+
// value. A successful poll must clear a resolved failure (for example, an
|
|
23725
|
+
// auth error from before login) instead of leaving the desktop status box
|
|
23726
|
+
// stuck on "Runner error"; persistent failures recur on every poll.
|
|
23727
|
+
lastError,
|
|
23728
|
+
currentPollIntervalMs: FALLBACK_POLL_INTERVAL_MS
|
|
23675
23729
|
});
|
|
23676
23730
|
if (!result.longPoll) {
|
|
23677
|
-
await new Promise((resolve) => setTimeout(resolve, applyPollJitter(
|
|
23731
|
+
await new Promise((resolve) => setTimeout(resolve, applyPollJitter(FALLBACK_POLL_INTERVAL_MS)));
|
|
23678
23732
|
}
|
|
23679
23733
|
}
|
|
23680
23734
|
}
|
|
@@ -23692,10 +23746,8 @@ async function runRunnerServiceCommand({
|
|
|
23692
23746
|
if (action === "status") {
|
|
23693
23747
|
const runState = await manager.status();
|
|
23694
23748
|
const state = readRunnerServiceState();
|
|
23695
|
-
const
|
|
23696
|
-
|
|
23697
|
-
});
|
|
23698
|
-
const reinstallHint = runState.installed && needsReinstallForOverlord ? 'This service is registered under "Node.js Foundation" because it runs the plain node binary. Reinstall it from the Overlord desktop app (or with the desktop app present) to re-register it under "Overlord".' : null;
|
|
23749
|
+
const publisher = process.platform !== "darwin" || !state.execProgram ? "unknown" : state.execProgram.includes(".app/Contents/MacOS/") ? "overlord" : "node";
|
|
23750
|
+
const reinstallHint = runState.installed && publisher === "node" ? 'This service is registered under "Node.js Foundation" because it runs the plain node binary. Reinstall it from the Overlord desktop app (or with the desktop app present) to re-register it under "Overlord".' : null;
|
|
23699
23751
|
const payload = {
|
|
23700
23752
|
supported: true,
|
|
23701
23753
|
kind: manager.kind,
|