overlord-cli 0.2608221603.0 → 0.2608240740.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.
@@ -1 +1 @@
1
- 0.3.35
1
+ 0.3.36
@@ -1,4 +1,4 @@
1
- contractVersion: "116"
1
+ contractVersion: "117"
2
2
  componentType: connector
3
3
  componentKey: antigravity
4
4
  label: "Antigravity Connector"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://antigravity.google/schemas/v1/plugin.json",
3
3
  "name": "overlord",
4
- "version": "0.3.35",
4
+ "version": "0.3.36",
5
5
  "description": "Chat-driven access to Overlord mission protocol workflows through skills and MCP.",
6
6
  "author": {
7
7
  "name": "Cooperativ",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overlord",
3
- "version": "0.3.35",
3
+ "version": "0.3.36",
4
4
  "description": "Overlord mission protocol workflow for Claude Code — attach/update/ask/deliver, slash commands, and hook-driven activity capture.",
5
5
  "author": {
6
6
  "name": "Cooperativ",
@@ -1,4 +1,4 @@
1
- contractVersion: "116"
1
+ contractVersion: "117"
2
2
  componentType: connector
3
3
  componentKey: claude
4
4
  label: "Claude Code Connector"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overlord",
3
- "version": "0.3.35",
3
+ "version": "0.3.36",
4
4
  "description": "Chat-driven access to Overlord mission protocol workflows with hook-driven activity capture.",
5
5
  "author": {
6
6
  "name": "Cooperativ",
@@ -1,4 +1,4 @@
1
- contractVersion: "116"
1
+ contractVersion: "117"
2
2
  componentType: connector
3
3
  componentKey: codex
4
4
  label: "Codex Connector"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overlord",
3
- "version": "0.3.35",
3
+ "version": "0.3.36",
4
4
  "description": "Overlord mission protocol workflow for Cursor — attach/update/ask/deliver, slash commands, MCP bridge, and follow-up capture.",
5
5
  "author": {
6
6
  "name": "Cooperativ",
@@ -69,5 +69,9 @@ events:
69
69
  inputPath: tool_input
70
70
  callIdPath: call_id
71
71
  fileEditKind: file.edited
72
+ # Live Cursor Write payloads use file_path; path/filePath match the write
73
+ # formatter's key order so an alternate Cursor dialect still captures.
72
74
  filePathPaths:
73
75
  - file_path
76
+ - filePath
77
+ - path
@@ -1,4 +1,4 @@
1
- contractVersion: "116"
1
+ contractVersion: "117"
2
2
  componentType: connector
3
3
  componentKey: cursor
4
4
  label: "Cursor Connector"
@@ -1,4 +1,4 @@
1
- contractVersion: "116"
1
+ contractVersion: "117"
2
2
  componentType: connector
3
3
  componentKey: opencode
4
4
  label: "OpenCode Connector"
@@ -1,4 +1,4 @@
1
- contractVersion: "116"
1
+ contractVersion: "117"
2
2
  componentType: connector
3
3
  componentKey: pi
4
4
  label: "PI Connector"
@@ -1091,7 +1091,7 @@ process.stdin.on('data', async chunk => {
1091
1091
  result: {
1092
1092
  protocolVersion: PROTOCOL_VERSION,
1093
1093
  capabilities: { tools: { listChanged: false } },
1094
- serverInfo: { name: 'overlord-__OVERLORD_ADAPTER_KEY__', version: '0.3.35' }
1094
+ serverInfo: { name: 'overlord-__OVERLORD_ADAPTER_KEY__', version: '0.3.36' }
1095
1095
  }
1096
1096
  });
1097
1097
  continue;
package/dist/index.js CHANGED
@@ -67,7 +67,44 @@ function isUnlinkableExecutionRequestError(error) {
67
67
  const message = error instanceof Error ? error.message : String(error);
68
68
  return message.includes("invalid_execution_request_transition");
69
69
  }
70
+ function trimmedStringField({
71
+ record,
72
+ key
73
+ }) {
74
+ const value = record[key];
75
+ return typeof value === "string" ? value.trim() : "";
76
+ }
77
+ function looksLikeAlreadyLinkedExecutionRequest({ text }) {
78
+ return text.includes(EXECUTION_REQUEST_ALREADY_LINKED_CODE) || /already linked to another(?: mission)? session/i.test(text);
79
+ }
80
+ function isExecutionRequestAlreadyLinkedPayload(payload) {
81
+ if (!payload || typeof payload !== "object") return false;
82
+ const record = payload;
83
+ if (trimmedStringField({ record, key: "code" }) === EXECUTION_REQUEST_ALREADY_LINKED_CODE) {
84
+ return true;
85
+ }
86
+ const text = [
87
+ trimmedStringField({ record, key: "error" }),
88
+ trimmedStringField({ record, key: "message" }),
89
+ trimmedStringField({ record, key: "detail" })
90
+ ].join(" ");
91
+ return looksLikeAlreadyLinkedExecutionRequest({ text });
92
+ }
93
+ function isExecutionRequestAlreadyLinkedError(error) {
94
+ const message = error instanceof Error ? error.message : String(error);
95
+ return looksLikeAlreadyLinkedExecutionRequest({ text: message });
96
+ }
97
+ function formatExecutionRequestAlreadyLinkedDiagnostic() {
98
+ return [
99
+ "Attach was rejected because this execution request is already linked to another mission session (`execution_request_already_linked`).",
100
+ "This is a stale mission-session binding, not a user authentication failure. Do not run `ovld auth login` or `ovld auth repair`.",
101
+ "Recovery: retry `ovld protocol attach` without `--execution-request-id` (unset OVERLORD_EXECUTION_REQUEST_ID). The existing session remains bound to the request; a new attach can continue the objective without relinking it."
102
+ ].join("\n");
103
+ }
70
104
  function formatCliError(error) {
105
+ if (isExecutionRequestAlreadyLinkedError(error)) {
106
+ return redactSecrets(formatExecutionRequestAlreadyLinkedDiagnostic());
107
+ }
71
108
  if (error instanceof CliError) {
72
109
  return redactSecrets(error.message);
73
110
  }
@@ -76,7 +113,7 @@ function formatCliError(error) {
76
113
  }
77
114
  return redactSecrets(String(error));
78
115
  }
79
- var CliError;
116
+ var CliError, EXECUTION_REQUEST_ALREADY_LINKED_CODE;
80
117
  var init_errors = __esm({
81
118
  "src/errors.ts"() {
82
119
  "use strict";
@@ -89,6 +126,7 @@ var init_errors = __esm({
89
126
  this.exitCode = exitCode;
90
127
  }
91
128
  };
129
+ EXECUTION_REQUEST_ALREADY_LINKED_CODE = "execution_request_already_linked";
92
130
  }
93
131
  });
94
132
 
@@ -2593,7 +2631,7 @@ function configureCursorHarness({
2593
2631
  event: "postToolUse",
2594
2632
  command: CURSOR_HOOK_COMMANDS.postToolUse,
2595
2633
  marker: "overlord-post-tool-use",
2596
- extra: { matcher: "Shell|Write|Edit|MultiEdit|NotebookEdit" }
2634
+ extra: { matcher: "Shell|Write|Edit|MultiEdit|NotebookEdit|StrReplace" }
2597
2635
  }) && "postToolUse",
2598
2636
  mergeHook({
2599
2637
  event: "stop",
@@ -3186,6 +3224,9 @@ Start the Desktop/local backend or run \`ovld config set cloud <url>\`.
3186
3224
  }
3187
3225
  const payload = await readResponseJson2(response);
3188
3226
  if (!response.ok) {
3227
+ if (isExecutionRequestAlreadyLinkedPayload(payload)) {
3228
+ throw new CliError({ message: formatExecutionRequestAlreadyLinkedDiagnostic() });
3229
+ }
3189
3230
  if (response.status === 401 && auth.headers.Authorization) {
3190
3231
  const detail = errorMessageFromJson2(payload) ?? `Backend request failed: ${method} ${path36} (${response.status})`;
3191
3232
  throw new CliError({
@@ -7492,6 +7533,8 @@ var init_tool_normalize = __esm({
7492
7533
  editfile: "edit",
7493
7534
  edit_file: "edit",
7494
7535
  str_replace: "edit",
7536
+ // Cursor/IDE lowercases StrReplace → "strreplace" (no underscore).
7537
+ strreplace: "edit",
7495
7538
  str_replace_editor: "edit",
7496
7539
  apply_patch: "edit",
7497
7540
  applypatch: "edit",
@@ -8074,7 +8117,9 @@ var init_codec_registry_generated = __esm({
8074
8117
  "callIdPath": "call_id",
8075
8118
  "fileEditKind": "file.edited",
8076
8119
  "filePathPaths": [
8077
- "file_path"
8120
+ "file_path",
8121
+ "filePath",
8122
+ "path"
8078
8123
  ]
8079
8124
  }
8080
8125
  ]
@@ -19366,7 +19411,7 @@ var init_change_ledger = __esm({
19366
19411
 
19367
19412
  // src/active-objective-sessions.ts
19368
19413
  import { createHash as createHash9 } from "node:crypto";
19369
- import { existsSync as existsSync24 } from "node:fs";
19414
+ import { existsSync as existsSync24, readdirSync as readdirSync4 } from "node:fs";
19370
19415
  import path22 from "node:path";
19371
19416
  function normalizeIdentifier(value) {
19372
19417
  return typeof value === "string" ? value.trim() : "";
@@ -19529,6 +19574,63 @@ function readObjectiveSessions({
19529
19574
  const aliases = entries.filter((entry) => entry.objectiveAliases.includes(requested));
19530
19575
  return new Set(aliases.map((entry) => entry.objectiveId)).size === 1 ? aliases : [];
19531
19576
  }
19577
+ function pathIsUnderWorkingDirectory({
19578
+ absolutePath,
19579
+ workingDirectory
19580
+ }) {
19581
+ const root = canonicalDirectory(workingDirectory);
19582
+ const resolved = path22.resolve(absolutePath);
19583
+ return resolved === root || resolved.startsWith(`${root}${path22.sep}`);
19584
+ }
19585
+ function resolveWorkingDirectoryForObjective({
19586
+ objectiveId,
19587
+ candidateWorkingDirectory,
19588
+ pathHints = []
19589
+ }) {
19590
+ const requested = normalizeIdentifier(objectiveId);
19591
+ if (!isValidIdentifier(requested)) return null;
19592
+ const candidate = path22.resolve(candidateWorkingDirectory);
19593
+ const local = readManifest2(candidate);
19594
+ if (local && resolveEntry(local.entries, requested)) {
19595
+ return local.workingDirectory;
19596
+ }
19597
+ const matches = [];
19598
+ const root = path22.join(resolveGlobalDataDir(), "active-objective-sessions");
19599
+ try {
19600
+ if (!existsSync24(root)) return null;
19601
+ for (const name of readdirSync4(root).slice(0, MAX_ACTIVE_SESSION_MANIFEST_FILES)) {
19602
+ if (!name.endsWith(".json")) continue;
19603
+ try {
19604
+ const raw = readBoundedUtf8File(path22.join(root, name), MAX_ACTIVE_SESSION_MANIFEST_BYTES);
19605
+ if (raw === null) continue;
19606
+ const value = JSON.parse(raw);
19607
+ if (value.schemaVersion !== 3 || typeof value.workingDirectory !== "string" || !Array.isArray(value.entries)) {
19608
+ continue;
19609
+ }
19610
+ const entries = value.entries.flatMap((entry) => {
19611
+ const normalized = normalizeEntry(entry);
19612
+ return normalized ? [normalized] : [];
19613
+ });
19614
+ if (!resolveEntry(entries, requested)) continue;
19615
+ const workingDirectory = canonicalDirectory(value.workingDirectory);
19616
+ if (!matches.includes(workingDirectory)) matches.push(workingDirectory);
19617
+ } catch {
19618
+ }
19619
+ }
19620
+ } catch {
19621
+ return null;
19622
+ }
19623
+ if (matches.length === 0) return null;
19624
+ if (matches.length === 1) return matches[0] ?? null;
19625
+ const absoluteHints = pathHints.filter((hint) => typeof hint === "string" && path22.isAbsolute(hint)).map((hint) => path22.resolve(hint));
19626
+ if (absoluteHints.length === 0) return null;
19627
+ const hinted = matches.filter(
19628
+ (workingDirectory) => absoluteHints.some(
19629
+ (absolutePath) => pathIsUnderWorkingDirectory({ absolutePath, workingDirectory })
19630
+ )
19631
+ );
19632
+ return hinted.length === 1 ? hinted[0] ?? null : null;
19633
+ }
19532
19634
  function withActiveObjectiveSession({
19533
19635
  workingDirectory,
19534
19636
  objectiveId,
@@ -19591,7 +19693,7 @@ function finalizeActiveSession({
19591
19693
  return false;
19592
19694
  }
19593
19695
  }
19594
- var MAX_ACTIVE_OBJECTIVE_SESSION_BINDINGS, MAX_OBJECTIVE_ALIASES, MAX_ID_LENGTH, MAX_ACTIVE_SESSION_MANIFEST_BYTES;
19696
+ var MAX_ACTIVE_OBJECTIVE_SESSION_BINDINGS, MAX_OBJECTIVE_ALIASES, MAX_ID_LENGTH, MAX_ACTIVE_SESSION_MANIFEST_BYTES, MAX_ACTIVE_SESSION_MANIFEST_FILES;
19595
19697
  var init_active_objective_sessions = __esm({
19596
19698
  "src/active-objective-sessions.ts"() {
19597
19699
  "use strict";
@@ -19602,6 +19704,7 @@ var init_active_objective_sessions = __esm({
19602
19704
  MAX_OBJECTIVE_ALIASES = 16;
19603
19705
  MAX_ID_LENGTH = 200;
19604
19706
  MAX_ACTIVE_SESSION_MANIFEST_BYTES = 512 * 1024;
19707
+ MAX_ACTIVE_SESSION_MANIFEST_FILES = 256;
19605
19708
  }
19606
19709
  });
19607
19710
 
@@ -20156,6 +20259,42 @@ function resolveHookWorkingDirectory({
20156
20259
  }
20157
20260
  return path26.resolve(fallbackCwd);
20158
20261
  }
20262
+ function withProjectRootFallback({
20263
+ payload,
20264
+ projectRootPaths,
20265
+ workingDirectory
20266
+ }) {
20267
+ const rootKey = projectRootPaths?.[0];
20268
+ if (!rootKey || rootKey.includes(".") || !payload || typeof payload !== "object") {
20269
+ return payload;
20270
+ }
20271
+ if (Array.isArray(payload)) return payload;
20272
+ const record = payload;
20273
+ if (exactProjectDirectory(record[rootKey])) return payload;
20274
+ return { ...record, [rootKey]: workingDirectory };
20275
+ }
20276
+ function collectAbsolutePathHints(payload) {
20277
+ const hints = [];
20278
+ const visit = (value, depth) => {
20279
+ if (hints.length >= MAX_PATH_HINTS || depth > 4) return;
20280
+ if (typeof value === "string") {
20281
+ if (path26.isAbsolute(value) && value.length <= 4096 && !hasControlCharacters(value)) {
20282
+ hints.push(path26.resolve(value));
20283
+ }
20284
+ return;
20285
+ }
20286
+ if (!value || typeof value !== "object") return;
20287
+ if (Array.isArray(value)) {
20288
+ for (const entry of value.slice(0, 8)) visit(entry, depth + 1);
20289
+ return;
20290
+ }
20291
+ for (const entry of Object.values(value).slice(0, 16)) {
20292
+ visit(entry, depth + 1);
20293
+ }
20294
+ };
20295
+ visit(payload, 0);
20296
+ return hints;
20297
+ }
20159
20298
  function captureChangeFromPayload({
20160
20299
  agent,
20161
20300
  rawPayload,
@@ -20185,12 +20324,22 @@ function captureChangeFromPayload({
20185
20324
  payloadFailure = "invalid JSON payload";
20186
20325
  }
20187
20326
  }
20188
- const workingDirectory = resolveHookWorkingDirectory({
20327
+ const candidateWorkingDirectory = resolveHookWorkingDirectory({
20189
20328
  payload: nativePayload,
20190
20329
  projectRootPaths: codec.projectRootPaths,
20191
20330
  fallbackCwd
20192
20331
  });
20193
- const normalized = payloadFailure ? null : normalizeForAdapter({ codec, payload: nativePayload });
20332
+ const workingDirectory = resolveWorkingDirectoryForObjective({
20333
+ objectiveId,
20334
+ candidateWorkingDirectory,
20335
+ pathHints: collectAbsolutePathHints(nativePayload)
20336
+ }) ?? candidateWorkingDirectory;
20337
+ const payloadForNormalize = withProjectRootFallback({
20338
+ payload: nativePayload,
20339
+ projectRootPaths: codec.projectRootPaths,
20340
+ workingDirectory
20341
+ });
20342
+ const normalized = payloadFailure ? null : normalizeForAdapter({ codec, payload: payloadForNormalize });
20194
20343
  const directPaths = normalized?.kind === "file.edited" && Array.isArray(normalized.payload.paths) ? normalized.payload.paths.filter((value) => typeof value === "string") : [];
20195
20344
  const confidentlyNonMutating = normalized?.kind === "tool.completed" && (normalized.payload.tool === "read" || normalized.payload.tool === "search" || normalized.payload.tool === "fetch");
20196
20345
  return withActiveObjectiveSession({
@@ -20239,6 +20388,7 @@ function captureChangeFromPayload({
20239
20388
  }
20240
20389
  });
20241
20390
  }
20391
+ var MAX_PATH_HINTS;
20242
20392
  var init_capture_change = __esm({
20243
20393
  "src/capture-change.ts"() {
20244
20394
  "use strict";
@@ -20248,6 +20398,7 @@ var init_capture_change = __esm({
20248
20398
  init_event();
20249
20399
  init_active_objective_sessions();
20250
20400
  init_change_ledger();
20401
+ MAX_PATH_HINTS = 16;
20251
20402
  }
20252
20403
  });
20253
20404
 
@@ -20658,6 +20809,9 @@ var init_pre_launch = __esm({
20658
20809
  });
20659
20810
 
20660
20811
  // src/terminal-launcher.ts
20812
+ function terminalOpenSpawnTimeoutMs(execution) {
20813
+ return execution.terminal ? TERMINAL_OPEN_SPAWN_TIMEOUT_MS : void 0;
20814
+ }
20661
20815
  function shellQuote2(value) {
20662
20816
  return `'${value.replace(/'/g, `'\\''`)}'`;
20663
20817
  }
@@ -20975,11 +21129,14 @@ function resolveLaunchExecution({
20975
21129
  });
20976
21130
  return { command: full, args: [], useShell: true, terminal: launcher, display: full };
20977
21131
  }
21132
+ var TERMINAL_OPEN_SPAWN_TIMEOUT_MS, TERMINAL_OPEN_SPAWN_KILL_SIGNAL;
20978
21133
  var init_terminal_launcher = __esm({
20979
21134
  "src/terminal-launcher.ts"() {
20980
21135
  "use strict";
20981
21136
  init_project_tmp();
20982
21137
  init_terminal_launch_chord();
21138
+ TERMINAL_OPEN_SPAWN_TIMEOUT_MS = 45e3;
21139
+ TERMINAL_OPEN_SPAWN_KILL_SIGNAL = "SIGKILL";
20983
21140
  }
20984
21141
  });
20985
21142
 
@@ -20991,6 +21148,40 @@ function interactiveLoginShellInvocation(command, configuredShell = process.env.
20991
21148
  const shell = configuredShell?.trim() || "/bin/bash";
20992
21149
  return { command: shell, args: ["-ilc", command] };
20993
21150
  }
21151
+ function spawnSyncOptionsForLaunch({
21152
+ cwd,
21153
+ env,
21154
+ timeoutMs
21155
+ }) {
21156
+ return {
21157
+ cwd,
21158
+ env,
21159
+ stdio: "inherit",
21160
+ ...timeoutMs === void 0 ? {} : { timeout: timeoutMs, killSignal: TERMINAL_OPEN_SPAWN_KILL_SIGNAL }
21161
+ };
21162
+ }
21163
+ function isSpawnSyncTimedOut(result) {
21164
+ return result.error?.code === "ETIMEDOUT";
21165
+ }
21166
+ function terminalOpenTimeoutMessage({
21167
+ terminal,
21168
+ timeoutMs
21169
+ }) {
21170
+ const seconds = Math.round(timeoutMs / 1e3);
21171
+ return `osascript/Apple Event timed out after ${seconds}s while opening ${terminal}. The execution request failed so the runner can keep claiming. ${terminal} may still open after this timeout (orphan window).`;
21172
+ }
21173
+ function throwIfTerminalOpenTimedOut({
21174
+ result,
21175
+ execution
21176
+ }) {
21177
+ if (!execution.terminal || !isSpawnSyncTimedOut(result)) return;
21178
+ throw new Error(
21179
+ terminalOpenTimeoutMessage({
21180
+ terminal: execution.terminal,
21181
+ timeoutMs: TERMINAL_OPEN_SPAWN_TIMEOUT_MS
21182
+ })
21183
+ );
21184
+ }
20994
21185
  function overlordLaunchEnv({
20995
21186
  backendUrl,
20996
21187
  missionId,
@@ -21422,16 +21613,15 @@ async function launchAgent({
21422
21613
  };
21423
21614
  }
21424
21615
  const { execution } = plan;
21425
- const shellInvocation = execution.useShell ? interactiveLoginShellInvocation(execution.command) : null;
21426
- const result = shellInvocation ? spawnSync3(shellInvocation.command, shellInvocation.args, {
21427
- cwd: options.workingDirectory,
21428
- env,
21429
- stdio: "inherit"
21430
- }) : spawnSync3(execution.command, execution.args, {
21616
+ const timeoutMs = terminalOpenSpawnTimeoutMs(execution);
21617
+ const spawnOptions = spawnSyncOptionsForLaunch({
21431
21618
  cwd: options.workingDirectory,
21432
21619
  env,
21433
- stdio: "inherit"
21620
+ timeoutMs
21434
21621
  });
21622
+ const shellInvocation = execution.useShell ? interactiveLoginShellInvocation(execution.command) : null;
21623
+ const result = shellInvocation ? spawnSync3(shellInvocation.command, shellInvocation.args, spawnOptions) : spawnSync3(execution.command, execution.args, spawnOptions);
21624
+ throwIfTerminalOpenTimedOut({ result, execution });
21435
21625
  return {
21436
21626
  plan,
21437
21627
  status: result.status,
@@ -21464,7 +21654,7 @@ var init_launch = __esm({
21464
21654
  });
21465
21655
 
21466
21656
  // src/launch-bootstrap.ts
21467
- import { existsSync as existsSync26, readdirSync as readdirSync4, readFileSync as readFileSync16, statSync as statSync7 } from "node:fs";
21657
+ import { existsSync as existsSync26, readdirSync as readdirSync5, readFileSync as readFileSync16, statSync as statSync7 } from "node:fs";
21468
21658
  import path30 from "node:path";
21469
21659
  function firstCapture(match) {
21470
21660
  if (!match) return null;
@@ -21491,7 +21681,7 @@ function recoverLaunchBootstrapFromProjectTmp({
21491
21681
  const prefix = `launch-${safeMissionPart(trimmedMission)}-`;
21492
21682
  let newest = null;
21493
21683
  try {
21494
- for (const name of readdirSync4(tmpDir)) {
21684
+ for (const name of readdirSync5(tmpDir)) {
21495
21685
  if (!name.startsWith(prefix) || !name.endsWith(".sh")) continue;
21496
21686
  const filePath = path30.join(tmpDir, name);
21497
21687
  try {
@@ -22644,13 +22834,66 @@ function resolveOvldInvocation(argv = process.argv, execPath = process.execPath)
22644
22834
  }
22645
22835
  return { program: execPath, args: ["runner", "supervise"] };
22646
22836
  }
22837
+ function composeRunnerServicePath({
22838
+ homeDir = os5.homedir()
22839
+ } = {}) {
22840
+ const localBin = path33.join(homeDir, ".local", "bin");
22841
+ const dirs = [...RUNNER_SERVICE_PATH_PREFIX_DIRS, localBin, ...RUNNER_SERVICE_SYSTEM_PATH_DIRS];
22842
+ return [...new Set(dirs)].join(":");
22843
+ }
22844
+ function parseLaunchdProcessType(plist) {
22845
+ const match = /<key>ProcessType<\/key>\s*<string>([^<]*)<\/string>/.exec(plist);
22846
+ return match?.[1] ?? null;
22847
+ }
22848
+ function parseLaunchdEnvPath(plist) {
22849
+ const match = /<key>PATH<\/key>\s*<string>([^<]*)<\/string>/.exec(plist);
22850
+ return match?.[1] ?? null;
22851
+ }
22852
+ function inspectInstalledLaunchdPlist(plistPath) {
22853
+ if (!existsSync28(plistPath)) return { processType: null, path: null };
22854
+ try {
22855
+ const plist = readFileSync19(plistPath, "utf8");
22856
+ return {
22857
+ processType: parseLaunchdProcessType(plist),
22858
+ path: parseLaunchdEnvPath(plist)
22859
+ };
22860
+ } catch {
22861
+ return { processType: null, path: null };
22862
+ }
22863
+ }
22864
+ function runnerServiceReinstallHint({
22865
+ installed,
22866
+ publisher,
22867
+ processType,
22868
+ path: path36
22869
+ }) {
22870
+ if (!installed) return null;
22871
+ const hints = [];
22872
+ if (publisher === "node") {
22873
+ hints.push(
22874
+ '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".'
22875
+ );
22876
+ }
22877
+ if (processType && processType !== LAUNCHD_PROCESS_TYPE) {
22878
+ hints.push(
22879
+ `This LaunchAgent is ProcessType=${processType}. App auto-update respawns the supervisor but does not rewrite ProcessType, so Apple Events to Terminal/iTerm stay App-Napped. Reinstall with \`ovld runner service install\` (or Desktop \u2192 Reinstall service) to switch to ${LAUNCHD_PROCESS_TYPE}.`
22880
+ );
22881
+ }
22882
+ if (path36 !== void 0 && path36 !== composeRunnerServicePath()) {
22883
+ hints.push(
22884
+ "This LaunchAgent PATH is not the deterministic prefix (`/opt/homebrew/bin:/usr/local/bin:$HOME/.local/bin` plus system dirs). App auto-update respawns the supervisor but does not rewrite PATH, so Latch/`open`/agent binaries spawned by the service may be missing. Reinstall with `ovld runner service install` (or Desktop \u2192 Reinstall service) to apply it."
22885
+ );
22886
+ }
22887
+ return hints.length > 0 ? hints.join(" ") : null;
22888
+ }
22647
22889
  function buildRunnerServiceEnv({
22648
22890
  backendUrl,
22649
22891
  overlordHome,
22650
- runAsElectronNode
22892
+ runAsElectronNode,
22893
+ homeDir
22651
22894
  }) {
22652
22895
  const env = {
22653
- PATH: process.env.PATH?.trim() || "/usr/local/bin:/usr/bin:/bin"
22896
+ PATH: composeRunnerServicePath({ homeDir })
22654
22897
  };
22655
22898
  if (!isLoopbackBackendUrl(backendUrl)) env.OVERLORD_BACKEND_URL = backendUrl;
22656
22899
  const home = overlordHome?.trim() || process.env.OVLD_HOME?.trim();
@@ -22697,7 +22940,7 @@ ${envEntries}
22697
22940
  <key>StandardErrorPath</key>
22698
22941
  <string>${xmlEscape(path33.join(logDir, "runner-service.err.log"))}</string>
22699
22942
  <key>ProcessType</key>
22700
- <string>Background</string>
22943
+ <string>${LAUNCHD_PROCESS_TYPE}</string>
22701
22944
  </dict>
22702
22945
  </plist>
22703
22946
  `;
@@ -22762,7 +23005,7 @@ function resolveServiceManager(platform2 = process.platform) {
22762
23005
  if (platform2 === "linux") return new SystemdUserManager();
22763
23006
  return null;
22764
23007
  }
22765
- var execFileAsync, RUNNER_SERVICE_STATE_FILENAME, LAUNCHD_LABEL, SYSTEMD_UNIT_NAME, FALLBACK_POLL_INTERVAL_MS, LaunchdManager, SystemdUserManager;
23008
+ var execFileAsync, RUNNER_SERVICE_STATE_FILENAME, LAUNCHD_LABEL, SYSTEMD_UNIT_NAME, FALLBACK_POLL_INTERVAL_MS, LAUNCHD_PROCESS_TYPE, RUNNER_SERVICE_SYSTEM_PATH_DIRS, RUNNER_SERVICE_PATH_PREFIX_DIRS, LaunchdManager, SystemdUserManager;
22766
23009
  var init_runner_service = __esm({
22767
23010
  "src/runner-service.ts"() {
22768
23011
  "use strict";
@@ -22772,6 +23015,9 @@ var init_runner_service = __esm({
22772
23015
  LAUNCHD_LABEL = "io.overlord.runner";
22773
23016
  SYSTEMD_UNIT_NAME = "overlord-runner";
22774
23017
  FALLBACK_POLL_INTERVAL_MS = 5e3;
23018
+ LAUNCHD_PROCESS_TYPE = "Interactive";
23019
+ RUNNER_SERVICE_SYSTEM_PATH_DIRS = ["/usr/bin", "/bin", "/usr/sbin", "/sbin"];
23020
+ RUNNER_SERVICE_PATH_PREFIX_DIRS = ["/opt/homebrew/bin", "/usr/local/bin"];
22775
23021
  LaunchdManager = class {
22776
23022
  kind = "launchd";
22777
23023
  identifier = LAUNCHD_LABEL;
@@ -23655,6 +23901,8 @@ async function runProtocolCommand({
23655
23901
  path: protocolPath,
23656
23902
  body: { ...protocolBody, flags }
23657
23903
  });
23904
+ } else if (isExecutionRequestAlreadyLinkedError(error)) {
23905
+ throw new CliError({ message: formatExecutionRequestAlreadyLinkedDiagnostic() });
23658
23906
  } else {
23659
23907
  throw error;
23660
23908
  }
@@ -24668,7 +24916,14 @@ async function runRunnerServiceCommand({
24668
24916
  const runState = await manager.status();
24669
24917
  const state = readRunnerServiceState();
24670
24918
  const publisher = process.platform !== "darwin" || !state.execProgram ? "unknown" : state.execProgram.includes(".app/Contents/MacOS/") ? "overlord" : "node";
24671
- 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;
24919
+ const inspected = manager.kind === "launchd" && runState.installed ? inspectInstalledLaunchdPlist(manager.unitPath()) : null;
24920
+ const processType = inspected?.processType ?? null;
24921
+ const reinstallHint = runnerServiceReinstallHint({
24922
+ installed: runState.installed,
24923
+ publisher,
24924
+ processType,
24925
+ ...inspected ? { path: inspected.path } : {}
24926
+ });
24672
24927
  const payload = {
24673
24928
  supported: true,
24674
24929
  kind: manager.kind,
@@ -24684,6 +24939,8 @@ async function runRunnerServiceCommand({
24684
24939
  lastError: state.lastError,
24685
24940
  currentPollIntervalMs: state.currentPollIntervalMs,
24686
24941
  publisher,
24942
+ processType,
24943
+ path: inspected?.path ?? null,
24687
24944
  reinstallHint
24688
24945
  };
24689
24946
  if (json) printJson(payload);
@@ -24693,6 +24950,7 @@ async function runRunnerServiceCommand({
24693
24950
  Installed: runState.installed ? "yes" : "no",
24694
24951
  Running: runState.running,
24695
24952
  Publisher: publisher === "unknown" ? "(unknown)" : publisher,
24953
+ ...processType ? { "Process type": processType } : {},
24696
24954
  Backend: state.backendUrl ?? "(unknown)",
24697
24955
  "Last heartbeat": state.lastHeartbeatAt ?? "(never)",
24698
24956
  "Last launched": state.lastLaunchedAt ?? "(never)",
@@ -24728,7 +24986,7 @@ Note: ${reinstallHint}`);
24728
24986
  if (json) printJson({ started: autoStart, ...runState });
24729
24987
  else
24730
24988
  console.log(
24731
- `Persistent runner installed (${manager.kind}) and ${autoStart ? "started" : "not started"}.`
24989
+ `Persistent runner installed (${manager.kind}) and ${autoStart ? "started" : "not started"}.` + (manager.kind === "launchd" ? " Re-running install rewrites the LaunchAgent (ProcessType Interactive and PATH prefix); app auto-update respawns the process but does not rewrite the plist." : "")
24732
24990
  );
24733
24991
  return;
24734
24992
  }