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 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 { headers: { Authorization: `Bearer ${fromEnv}` }, fromStored: false };
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 { chmodSync as chmodSync5, existsSync as existsSync26, mkdirSync as mkdirSync14, readFileSync as readFileSync22, rmSync as rmSync6, writeFileSync as writeFileSync13 } from "node:fs";
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, RUNNER_FOCUS_STATE_FILENAME, LAUNCHD_LABEL, SYSTEMD_UNIT_NAME, FAST_POLL_INTERVAL_MS, SLOW_POLL_INTERVAL_MS, IDLE_BACKOFF_MS, DESKTOP_FOCUS_WINDOW_MS, LaunchdManager, SystemdUserManager;
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
- FAST_POLL_INTERVAL_MS = 3e3;
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
- try {
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 runCommand("launchctl", ["unload", "-w", this.unitPath()]);
22121
+ await this.runSteps(planLaunchdUnloadCommands({ uid: this.uid() }));
22080
22122
  }
22081
22123
  async restart() {
22082
- try {
22083
- await this.stop();
22084
- } catch {
22085
- }
22124
+ await this.stop();
22086
22125
  await this.start();
22087
22126
  }
22088
22127
  async uninstall() {
22089
- try {
22090
- await this.stop();
22091
- } catch {
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 runCommand("launchctl", ["list"]);
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 = async () => {
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. Adaptive polling for execution requests.");
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 so a resolved failure (e.g. an auth
23672
- // error from before the user logged in) clears instead of sticking.
23673
- lastError: nextRunnerLastError(lastError),
23674
- currentPollIntervalMs: base
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(base)));
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 { publisher, needsReinstallForOverlord } = describeServicePublisher({
23696
- execProgram: state.execProgram
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,