@algosuite/vo-mcp 0.2.0-beta.28 → 0.2.0-beta.30

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.
@@ -77,11 +77,11 @@ __export(credential_store_exports, {
77
77
  readStoredCredentialKeychainOnly: () => readStoredCredentialKeychainOnly,
78
78
  writeStoredCredential: () => writeStoredCredential
79
79
  });
80
- import { homedir } from "node:os";
80
+ import { homedir as homedir2 } from "node:os";
81
81
  import { join, dirname } from "node:path";
82
82
  import {
83
- existsSync,
84
- mkdirSync,
83
+ existsSync as existsSync2,
84
+ mkdirSync as mkdirSync2,
85
85
  readFileSync,
86
86
  writeFileSync,
87
87
  chmodSync,
@@ -90,7 +90,7 @@ import {
90
90
  function credentialPath(env2 = process.env) {
91
91
  const override = env2["VO_MCP_CREDENTIALS_PATH"]?.trim();
92
92
  if (override) return override;
93
- return join(homedir(), ".config", "vo-mcp", "credentials.json");
93
+ return join(homedir2(), ".config", "vo-mcp", "credentials.json");
94
94
  }
95
95
  function keychainEnabled(env2, keychain) {
96
96
  const disabled = (env2["VO_MCP_DISABLE_KEYCHAIN"] ?? "").trim().toLowerCase();
@@ -119,7 +119,7 @@ function deserialize(raw) {
119
119
  function readFromFile(env2) {
120
120
  try {
121
121
  const p = credentialPath(env2);
122
- if (!existsSync(p)) return null;
122
+ if (!existsSync2(p)) return null;
123
123
  return deserialize(readFileSync(p, "utf8"));
124
124
  } catch {
125
125
  return null;
@@ -146,7 +146,7 @@ function deleteFile(env2) {
146
146
  }
147
147
  function writeToFile(payload, env2) {
148
148
  const p = credentialPath(env2);
149
- mkdirSync(dirname(p), { recursive: true });
149
+ mkdirSync2(dirname(p), { recursive: true });
150
150
  writeFileSync(p, `${JSON.stringify(payload, null, 2)}
151
151
  `, { mode: 384 });
152
152
  try {
@@ -505,7 +505,8 @@ async function runProcess(command, args = [], options = {}) {
505
505
  });
506
506
  }
507
507
  async function commandExists(command, options = {}) {
508
- const checker = process.platform === "win32" ? "where" : "which";
508
+ const platform = options.platform || process.platform;
509
+ const checker = platform === "win32" ? "where" : "which";
509
510
  const result = await (options.runner || runProcess)(checker, [command], {
510
511
  timeoutMs: 1e4
511
512
  });
@@ -841,9 +842,141 @@ var init_pnpm_canonical_health = __esm({
841
842
  }
842
843
  });
843
844
 
845
+ // src/runner/pnpm-command.mjs
846
+ import fs2 from "node:fs";
847
+ import path3 from "node:path";
848
+ function isWindowsDriveOrUnc(value) {
849
+ return WINDOWS_DRIVE_OR_UNC_RE.test(String(value || ""));
850
+ }
851
+ function portableDirname(value) {
852
+ if (isWindowsDriveOrUnc(value)) return path3.win32.dirname(value);
853
+ if (path3.posix.isAbsolute(value)) return path3.posix.dirname(value);
854
+ return path3.dirname(value);
855
+ }
856
+ function portableJoin(root, ...segments) {
857
+ if (isWindowsDriveOrUnc(root)) return path3.win32.join(root, ...segments);
858
+ if (path3.posix.isAbsolute(root)) return path3.posix.join(root, ...segments);
859
+ return path3.join(root, ...segments);
860
+ }
861
+ function readPackageManager(root) {
862
+ const packagePath = path3.join(root, "package.json");
863
+ if (!fs2.existsSync(packagePath)) return "";
864
+ try {
865
+ return String(JSON.parse(fs2.readFileSync(packagePath, "utf8"))?.packageManager || "").trim();
866
+ } catch {
867
+ return "";
868
+ }
869
+ }
870
+ function validatedPnpmVersionToken(root) {
871
+ const raw = readPackageManager(root);
872
+ const match = /^pnpm@(.+)$/iu.exec(raw);
873
+ if (!match) return "";
874
+ const version = match[1].trim();
875
+ if (!version || /\s/u.test(version) || !SAFE_PNPM_VERSION_RE.test(version)) return "";
876
+ return version;
877
+ }
878
+ function pnpmSelector(root) {
879
+ const packageManager = readPackageManager(root);
880
+ const version = validatedPnpmVersionToken(root);
881
+ if (packageManager && !version) {
882
+ throw new Error(`[vo-mcp runner] invalid pnpm packageManager token in ${path3.join(root, "package.json")}`);
883
+ }
884
+ return version ? `pnpm@${version}` : "pnpm";
885
+ }
886
+ function whereResults(result) {
887
+ if (result?.status !== 0) return [];
888
+ return String(result.stdout || "").split(/\r?\n/u).map((line) => line.trim()).filter((line) => path3.win32.isAbsolute(line));
889
+ }
890
+ async function resolveWindowsNativeCommand(command, runner) {
891
+ const result = await runner("where", [command], { timeoutMs: 1e4 });
892
+ return whereResults(result).find((candidate) => WINDOWS_NATIVE_EXECUTABLE_RE.test(candidate)) || "";
893
+ }
894
+ function trustedCorepackCandidates(options) {
895
+ const env2 = options.env || process.env;
896
+ const execPath = options.execPath || process.execPath;
897
+ const roots = [portableDirname(execPath)];
898
+ for (const key of ["ProgramW6432", "ProgramFiles", "ProgramFiles(x86)"]) {
899
+ const programFiles = String(env2[key] || "").trim();
900
+ if (isWindowsDriveOrUnc(programFiles) || path3.posix.isAbsolute(programFiles)) {
901
+ roots.push(portableJoin(programFiles, "nodejs"));
902
+ }
903
+ }
904
+ return [...new Set(roots)].map((root) => portableJoin(root, "node_modules", "corepack", "dist", "corepack.js"));
905
+ }
906
+ function resolveTrustedCorepackJs(options) {
907
+ const existsSync7 = options.existsSync || fs2.existsSync;
908
+ return trustedCorepackCandidates(options).find((candidate) => existsSync7(candidate)) || "";
909
+ }
910
+ async function resolvePnpmInstallCommand(root, options = {}) {
911
+ const runner = options.runner || runProcess;
912
+ const platform = options.platform || process.platform;
913
+ const selector = pnpmSelector(root);
914
+ if (platform !== "win32") {
915
+ if (await commandExists("pnpm", { runner, platform })) {
916
+ return {
917
+ command: "pnpm",
918
+ args: [...DEFAULT_PNPM_INSTALL_ARGS],
919
+ displayCommand: ["pnpm", ...DEFAULT_PNPM_INSTALL_ARGS]
920
+ };
921
+ }
922
+ if (await commandExists("corepack", { runner, platform })) {
923
+ return {
924
+ command: "corepack",
925
+ args: [selector, ...DEFAULT_PNPM_INSTALL_ARGS],
926
+ displayCommand: ["corepack", selector, ...DEFAULT_PNPM_INSTALL_ARGS]
927
+ };
928
+ }
929
+ throw new Error("[vo-mcp runner] pnpm hydration requires `pnpm` or `corepack` on PATH.");
930
+ }
931
+ const pnpmExecutable = await resolveWindowsNativeCommand("pnpm", runner);
932
+ if (pnpmExecutable) {
933
+ return {
934
+ command: pnpmExecutable,
935
+ args: [...DEFAULT_PNPM_INSTALL_ARGS],
936
+ displayCommand: ["pnpm", ...DEFAULT_PNPM_INSTALL_ARGS]
937
+ };
938
+ }
939
+ const corepackExecutable = await resolveWindowsNativeCommand("corepack", runner);
940
+ if (corepackExecutable) {
941
+ return {
942
+ command: corepackExecutable,
943
+ args: [selector, ...DEFAULT_PNPM_INSTALL_ARGS],
944
+ displayCommand: ["corepack", selector, ...DEFAULT_PNPM_INSTALL_ARGS]
945
+ };
946
+ }
947
+ const corepackJs = resolveTrustedCorepackJs(options);
948
+ if (corepackJs) {
949
+ return {
950
+ command: options.execPath || process.execPath,
951
+ args: [corepackJs, selector, ...DEFAULT_PNPM_INSTALL_ARGS],
952
+ displayCommand: ["corepack", selector, ...DEFAULT_PNPM_INSTALL_ARGS]
953
+ };
954
+ }
955
+ throw new Error(
956
+ "[vo-mcp runner] pnpm hydration on Windows requires a native pnpm/corepack executable or a trusted Corepack installation."
957
+ );
958
+ }
959
+ var DEFAULT_PNPM_INSTALL_ARGS, SAFE_PNPM_VERSION_RE, WINDOWS_NATIVE_EXECUTABLE_RE, WINDOWS_DRIVE_OR_UNC_RE;
960
+ var init_pnpm_command = __esm({
961
+ "src/runner/pnpm-command.mjs"() {
962
+ "use strict";
963
+ init_process_runner();
964
+ DEFAULT_PNPM_INSTALL_ARGS = Object.freeze([
965
+ "install",
966
+ "--frozen-lockfile",
967
+ "--prefer-offline",
968
+ "--ignore-scripts",
969
+ "--config.confirmModulesPurge=false"
970
+ ]);
971
+ SAFE_PNPM_VERSION_RE = /^[0-9A-Za-z._+-]+$/u;
972
+ WINDOWS_NATIVE_EXECUTABLE_RE = /\.(?:com|exe)$/iu;
973
+ WINDOWS_DRIVE_OR_UNC_RE = /^(?:[A-Za-z]:[\\/]|\\\\)/u;
974
+ }
975
+ });
976
+
844
977
  // src/runner/pnpm-materialize.mjs
845
978
  import fsp4 from "node:fs/promises";
846
- import path3 from "node:path";
979
+ import path4 from "node:path";
847
980
  function linkType() {
848
981
  return process.platform === "win32" ? "junction" : "dir";
849
982
  }
@@ -855,13 +988,13 @@ async function realpathOrThrow(target, fsApi) {
855
988
  }
856
989
  }
857
990
  function shouldMapToWorktree(resolvedTarget, canonicalRoot) {
858
- const relative = path3.relative(canonicalRoot, resolvedTarget).replace(/\\/g, "/");
991
+ const relative = path4.relative(canonicalRoot, resolvedTarget).replace(/\\/g, "/");
859
992
  return Boolean(relative && !relative.startsWith("..") && !relative.split("/").includes("node_modules"));
860
993
  }
861
994
  async function resolveEntryTarget(sourceEntry, canonicalRoot, worktreeRoot, fsApi) {
862
995
  const resolved = await realpathOrThrow(sourceEntry, fsApi);
863
996
  if (!shouldMapToWorktree(resolved, canonicalRoot)) return resolved;
864
- const mapped = path3.join(worktreeRoot, path3.relative(canonicalRoot, resolved));
997
+ const mapped = path4.join(worktreeRoot, path4.relative(canonicalRoot, resolved));
865
998
  if (!await fsApi.pathExists(mapped)) {
866
999
  throw new Error(`[vo-mcp runner] task-local workspace target is missing for dependency link: ${mapped}`);
867
1000
  }
@@ -872,7 +1005,7 @@ async function ensureLinkedDirectory(source, target, fsApi) {
872
1005
  if (await fsApi.realpath(target) === await fsApi.realpath(source)) return;
873
1006
  throw new Error(`[vo-mcp runner] refusing to overwrite existing dependency path: ${target}`);
874
1007
  }
875
- await fsApi.mkdir(path3.dirname(target), { recursive: true });
1008
+ await fsApi.mkdir(path4.dirname(target), { recursive: true });
876
1009
  await fsApi.symlink(source, target, linkType());
877
1010
  if (await fsApi.realpath(target) !== await fsApi.realpath(source)) {
878
1011
  throw new Error(`[vo-mcp runner] dependency link validation failed for ${target}`);
@@ -886,8 +1019,8 @@ async function maybeYield2(state) {
886
1019
  async function copyDirRecursive(sourceDir, targetDir, fsApi, yieldState) {
887
1020
  await fsApi.mkdir(targetDir, { recursive: true });
888
1021
  for (const entry of await fsApi.readdir(sourceDir, { withFileTypes: true })) {
889
- const source = path3.join(sourceDir, entry.name);
890
- const target = path3.join(targetDir, entry.name);
1022
+ const source = path4.join(sourceDir, entry.name);
1023
+ const target = path4.join(targetDir, entry.name);
891
1024
  await maybeYield2(yieldState);
892
1025
  if (entry.isDirectory()) {
893
1026
  await copyDirRecursive(source, target, fsApi, yieldState);
@@ -899,17 +1032,17 @@ async function copyDirRecursive(sourceDir, targetDir, fsApi, yieldState) {
899
1032
  async function materializeEntry(sourceEntry, targetEntry, canonicalRoot, worktreeRoot, options) {
900
1033
  await options.beforeEntry?.(sourceEntry, targetEntry);
901
1034
  const stat2 = await options.fsApi.lstat(sourceEntry);
902
- if (stat2.isDirectory() && !stat2.isSymbolicLink() && path3.basename(sourceEntry) === ".bin") {
1035
+ if (stat2.isDirectory() && !stat2.isSymbolicLink() && path4.basename(sourceEntry) === ".bin") {
903
1036
  await copyDirRecursive(sourceEntry, targetEntry, options.fsApi, options.yieldState);
904
1037
  return;
905
1038
  }
906
- if (stat2.isDirectory() && !stat2.isSymbolicLink() && path3.basename(sourceEntry).startsWith("@")) {
1039
+ if (stat2.isDirectory() && !stat2.isSymbolicLink() && path4.basename(sourceEntry).startsWith("@")) {
907
1040
  await options.fsApi.mkdir(targetEntry, { recursive: true });
908
1041
  for (const nested of await options.fsApi.readdir(sourceEntry, { withFileTypes: true })) {
909
1042
  await maybeYield2(options.yieldState);
910
1043
  await materializeEntry(
911
- path3.join(sourceEntry, nested.name),
912
- path3.join(targetEntry, nested.name),
1044
+ path4.join(sourceEntry, nested.name),
1045
+ path4.join(targetEntry, nested.name),
913
1046
  canonicalRoot,
914
1047
  worktreeRoot,
915
1048
  options
@@ -922,7 +1055,7 @@ async function materializeEntry(sourceEntry, targetEntry, canonicalRoot, worktre
922
1055
  await ensureLinkedDirectory(resolvedTarget, targetEntry, options.fsApi);
923
1056
  return;
924
1057
  }
925
- await options.fsApi.mkdir(path3.dirname(targetEntry), { recursive: true });
1058
+ await options.fsApi.mkdir(path4.dirname(targetEntry), { recursive: true });
926
1059
  await options.fsApi.copyFile(sourceEntry, targetEntry);
927
1060
  }
928
1061
  function createPnpmFsApi(pathExists6) {
@@ -946,8 +1079,8 @@ async function materializeNodeModulesForest(sourceNodeModules, targetNodeModules
946
1079
  for (const entry of await options.fsApi.readdir(sourceNodeModules, { withFileTypes: true })) {
947
1080
  await maybeYield2(options.yieldState);
948
1081
  await materializeEntry(
949
- path3.join(sourceNodeModules, entry.name),
950
- path3.join(targetNodeModules, entry.name),
1082
+ path4.join(sourceNodeModules, entry.name),
1083
+ path4.join(targetNodeModules, entry.name),
951
1084
  canonicalRoot,
952
1085
  worktreeRoot,
953
1086
  options
@@ -962,20 +1095,20 @@ var init_pnpm_materialize = __esm({
962
1095
 
963
1096
  // src/runner/pnpm-hydration.mjs
964
1097
  import { createHash } from "node:crypto";
965
- import fs2 from "node:fs";
1098
+ import fs3 from "node:fs";
966
1099
  import fsp5 from "node:fs/promises";
967
- import path4 from "node:path";
1100
+ import path5 from "node:path";
968
1101
  function hashText(text) {
969
1102
  return createHash("sha256").update(String(text)).digest("hex");
970
1103
  }
971
1104
  function statePath(root) {
972
- return path4.join(root, ".agent-worktrees", "runner-pnpm-hydration.json");
1105
+ return path5.join(root, ".agent-worktrees", "runner-pnpm-hydration.json");
973
1106
  }
974
1107
  function packageJson(root) {
975
- const packagePath = path4.join(root, "package.json");
976
- if (!fs2.existsSync(packagePath)) return null;
1108
+ const packagePath = path5.join(root, "package.json");
1109
+ if (!fs3.existsSync(packagePath)) return null;
977
1110
  try {
978
- return JSON.parse(fs2.readFileSync(packagePath, "utf8"));
1111
+ return JSON.parse(fs3.readFileSync(packagePath, "utf8"));
979
1112
  } catch {
980
1113
  return null;
981
1114
  }
@@ -989,23 +1122,23 @@ async function pathExists4(target) {
989
1122
  }
990
1123
  }
991
1124
  function lockfileHash(root) {
992
- const lockPath = path4.join(root, "pnpm-lock.yaml");
993
- if (!fs2.existsSync(lockPath)) return "";
994
- return hashText(fs2.readFileSync(lockPath, "utf8"));
1125
+ const lockPath = path5.join(root, "pnpm-lock.yaml");
1126
+ if (!fs3.existsSync(lockPath)) return "";
1127
+ return hashText(fs3.readFileSync(lockPath, "utf8"));
995
1128
  }
996
1129
  function readHydrationState(root) {
997
1130
  const file = statePath(root);
998
- if (!fs2.existsSync(file)) return null;
1131
+ if (!fs3.existsSync(file)) return null;
999
1132
  try {
1000
- return JSON.parse(fs2.readFileSync(file, "utf8"));
1133
+ return JSON.parse(fs3.readFileSync(file, "utf8"));
1001
1134
  } catch {
1002
1135
  return null;
1003
1136
  }
1004
1137
  }
1005
1138
  function writeHydrationState(root, state) {
1006
1139
  const file = statePath(root);
1007
- fs2.mkdirSync(path4.dirname(file), { recursive: true });
1008
- fs2.writeFileSync(file, `${JSON.stringify({
1140
+ fs3.mkdirSync(path5.dirname(file), { recursive: true });
1141
+ fs3.writeFileSync(file, `${JSON.stringify({
1009
1142
  ...state,
1010
1143
  stateVersion: 2,
1011
1144
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -1013,7 +1146,7 @@ function writeHydrationState(root, state) {
1013
1146
  `, "utf8");
1014
1147
  }
1015
1148
  function voDepsStatePath(root) {
1016
- return path4.join(root, "node_modules", ".vo-deps-state.json");
1149
+ return path5.join(root, "node_modules", ".vo-deps-state.json");
1017
1150
  }
1018
1151
  async function ensureVoDepsState(root, expectedHash, fsApi = fsp5) {
1019
1152
  const marker = voDepsStatePath(root);
@@ -1023,7 +1156,7 @@ async function ensureVoDepsState(root, expectedHash, fsApi = fsp5) {
1023
1156
  } catch {
1024
1157
  }
1025
1158
  const temp = `${marker}.tmp-${process.pid}-${Date.now()}`;
1026
- await fsApi.mkdir(path4.dirname(marker), { recursive: true });
1159
+ await fsApi.mkdir(path5.dirname(marker), { recursive: true });
1027
1160
  await fsApi.writeFile(temp, `${JSON.stringify({ lockfileHash: expectedHash, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
1028
1161
  `, "utf8");
1029
1162
  await fsApi.rename(temp, marker);
@@ -1043,14 +1176,6 @@ async function assertVoDepsState(root, expectedHash, fsApi = fsp5) {
1043
1176
  }
1044
1177
  throw new Error(`[vo-mcp runner] linked vo:deps state marker mismatch for ${root}`);
1045
1178
  }
1046
- function validatedPnpmVersionToken(root) {
1047
- const raw = String(packageJson(root)?.packageManager || "").trim();
1048
- const match = /^pnpm@(.+)$/iu.exec(raw);
1049
- if (!match) return "";
1050
- const version = match[1].trim();
1051
- if (!version || /\s/u.test(version) || !SAFE_PNPM_VERSION_RE.test(version)) return "";
1052
- return version;
1053
- }
1054
1179
  function workspacePatternsFromPackageJson(root) {
1055
1180
  const workspaces = packageJson(root)?.workspaces;
1056
1181
  if (Array.isArray(workspaces)) return workspaces.map(String);
@@ -1058,9 +1183,9 @@ function workspacePatternsFromPackageJson(root) {
1058
1183
  return [];
1059
1184
  }
1060
1185
  function workspacePatternsFromPnpmWorkspace(root) {
1061
- const workspacePath = path4.join(root, "pnpm-workspace.yaml");
1062
- if (!fs2.existsSync(workspacePath)) return [];
1063
- const lines = fs2.readFileSync(workspacePath, "utf8").split(/\r?\n/u);
1186
+ const workspacePath = path5.join(root, "pnpm-workspace.yaml");
1187
+ if (!fs3.existsSync(workspacePath)) return [];
1188
+ const lines = fs3.readFileSync(workspacePath, "utf8").split(/\r?\n/u);
1064
1189
  const patterns = [];
1065
1190
  let inPackages = false;
1066
1191
  for (const rawLine of lines) {
@@ -1117,16 +1242,16 @@ function collectPackageDirs(root, options = {}) {
1117
1242
  while (stack.length > 0) {
1118
1243
  const current = stack.pop();
1119
1244
  if (!current) continue;
1120
- const relativeDir = path4.relative(root, current.dir).replace(/\\/g, "/");
1121
- if (relativeDir && fs2.existsSync(path4.join(current.dir, "package.json"))) {
1245
+ const relativeDir = path5.relative(root, current.dir).replace(/\\/g, "/");
1246
+ if (relativeDir && fs3.existsSync(path5.join(current.dir, "package.json"))) {
1122
1247
  found.push(relativeDir);
1123
1248
  if (found.length >= maxDirs) break;
1124
1249
  }
1125
1250
  if (current.depth >= maxDepth) continue;
1126
- for (const entry of fs2.readdirSync(current.dir, { withFileTypes: true })) {
1251
+ for (const entry of fs3.readdirSync(current.dir, { withFileTypes: true })) {
1127
1252
  if (!entry.isDirectory()) continue;
1128
1253
  if (IGNORED_SCAN_DIRS.has(entry.name)) continue;
1129
- stack.push({ dir: path4.join(current.dir, entry.name), depth: current.depth + 1 });
1254
+ stack.push({ dir: path5.join(current.dir, entry.name), depth: current.depth + 1 });
1130
1255
  }
1131
1256
  }
1132
1257
  return found.sort();
@@ -1136,35 +1261,22 @@ function discoverWorkspacePackageDirs(root, options = {}) {
1136
1261
  if (patterns.length === 0) return [];
1137
1262
  return collectPackageDirs(root, options).filter((relativeDir) => patterns.some((pattern) => matchesWorkspacePattern(relativeDir, pattern)));
1138
1263
  }
1139
- async function resolveInstallCommand(root, runner) {
1140
- if (await commandExists("pnpm", { runner })) {
1141
- return { command: "pnpm", args: [...DEFAULT_INSTALL_ARGS] };
1142
- }
1143
- if (await commandExists("corepack", { runner })) {
1144
- const version = validatedPnpmVersionToken(root);
1145
- if (String(packageJson(root)?.packageManager || "").trim() && !version) {
1146
- throw new Error(`[vo-mcp runner] invalid pnpm packageManager token in ${path4.join(root, "package.json")}`);
1147
- }
1148
- return { command: "corepack", args: [version ? `pnpm@${version}` : "pnpm", ...DEFAULT_INSTALL_ARGS] };
1149
- }
1150
- throw new Error("[vo-mcp runner] pnpm hydration requires `pnpm` or `corepack` on PATH.");
1151
- }
1152
1264
  async function runInstall(root, options = {}) {
1153
1265
  const runner = options.runner || runProcess;
1154
- const tuple = await resolveInstallCommand(root, runner);
1266
+ const tuple = await resolvePnpmInstallCommand(root, { ...options, runner });
1155
1267
  const result = await runner(tuple.command, tuple.args, {
1156
1268
  cwd: root,
1157
1269
  timeoutMs: options.timeoutMs ?? DEFAULT_INSTALL_TIMEOUT_MS
1158
1270
  });
1159
1271
  if (result.status !== 0) {
1160
1272
  throw new Error(
1161
- `[vo-mcp runner] pnpm hydration failed for ${root}: ${summarizeProcessFailure(result)} (command: ${[tuple.command, ...tuple.args].join(" ")})`
1273
+ `[vo-mcp runner] pnpm hydration failed for ${root}: ${summarizeProcessFailure(result)} (command: ${tuple.displayCommand.join(" ")})`
1162
1274
  );
1163
1275
  }
1164
1276
  }
1165
1277
  async function hasReadyNodeModules(root) {
1166
- const nodeModules = path4.join(root, "node_modules");
1167
- return await pathExists4(path4.join(nodeModules, ".modules.yaml")) || await pathExists4(path4.join(nodeModules, ".pnpm"));
1278
+ const nodeModules = path5.join(root, "node_modules");
1279
+ return await pathExists4(path5.join(nodeModules, ".modules.yaml")) || await pathExists4(path5.join(nodeModules, ".pnpm"));
1168
1280
  }
1169
1281
  function hydrationFsApi(overrides = {}) {
1170
1282
  return { ...createPnpmFsApi(pathExists4), ...overrides };
@@ -1201,7 +1313,7 @@ async function ensurePnpmHydration(root, options = {}) {
1201
1313
  expectedHash: hash,
1202
1314
  requireMarker: false
1203
1315
  });
1204
- if (!health.healthy && await fsApi.pathExists(path4.join(root, "node_modules"))) {
1316
+ if (!health.healthy && await fsApi.pathExists(path5.join(root, "node_modules"))) {
1205
1317
  quarantine = await quarantineCanonicalNodeModules(root, health.issues, {
1206
1318
  fsApi,
1207
1319
  logger: options.logger
@@ -1213,7 +1325,7 @@ async function ensurePnpmHydration(root, options = {}) {
1213
1325
  }
1214
1326
  const linkedWorkspaceDirs = [];
1215
1327
  for (const relativeDir of workspaceDirs) {
1216
- if (await pathExists4(path4.join(root, relativeDir, "node_modules"))) {
1328
+ if (await pathExists4(path5.join(root, relativeDir, "node_modules"))) {
1217
1329
  linkedWorkspaceDirs.push(relativeDir);
1218
1330
  }
1219
1331
  }
@@ -1249,18 +1361,18 @@ async function linkHydratedNodeModules({ root, worktreeDir, hydration, options =
1249
1361
  yieldEvery: Math.max(1, options.yieldEvery ?? DEFAULT_YIELD_EVERY2)
1250
1362
  }
1251
1363
  };
1252
- recordOwnedNodeModulesRoot(dependencyOwnership, path4.join(worktreeDir, "node_modules"));
1364
+ recordOwnedNodeModulesRoot(dependencyOwnership, path5.join(worktreeDir, "node_modules"));
1253
1365
  await materializeNodeModulesForest(
1254
- path4.join(root, "node_modules"),
1255
- path4.join(worktreeDir, "node_modules"),
1366
+ path5.join(root, "node_modules"),
1367
+ path5.join(worktreeDir, "node_modules"),
1256
1368
  root,
1257
1369
  worktreeDir,
1258
1370
  materializeOptions
1259
1371
  );
1260
1372
  for (const relativeDir of hydration.linkedWorkspaceDirs) {
1261
- const sourceNodeModules = path4.join(root, relativeDir, "node_modules");
1262
- const targetNodeModules = path4.join(worktreeDir, relativeDir, "node_modules");
1263
- if (!await fsApi.pathExists(path4.join(worktreeDir, relativeDir))) continue;
1373
+ const sourceNodeModules = path5.join(root, relativeDir, "node_modules");
1374
+ const targetNodeModules = path5.join(worktreeDir, relativeDir, "node_modules");
1375
+ if (!await fsApi.pathExists(path5.join(worktreeDir, relativeDir))) continue;
1264
1376
  recordOwnedNodeModulesRoot(dependencyOwnership, targetNodeModules);
1265
1377
  await materializeNodeModulesForest(sourceNodeModules, targetNodeModules, root, worktreeDir, materializeOptions);
1266
1378
  }
@@ -1271,22 +1383,16 @@ async function linkHydratedNodeModules({ root, worktreeDir, hydration, options =
1271
1383
  workspaceCount: hydration.linkedWorkspaceDirs.length
1272
1384
  };
1273
1385
  }
1274
- var DEFAULT_INSTALL_TIMEOUT_MS, DEFAULT_INSTALL_ARGS, DEFAULT_YIELD_EVERY2, IGNORED_SCAN_DIRS, SAFE_PNPM_VERSION_RE;
1386
+ var DEFAULT_INSTALL_TIMEOUT_MS, DEFAULT_YIELD_EVERY2, IGNORED_SCAN_DIRS;
1275
1387
  var init_pnpm_hydration = __esm({
1276
1388
  "src/runner/pnpm-hydration.mjs"() {
1277
1389
  "use strict";
1278
1390
  init_pnpm_canonical_health();
1391
+ init_pnpm_command();
1279
1392
  init_pnpm_materialize();
1280
1393
  init_process_runner();
1281
1394
  init_pnpm_link_detach();
1282
1395
  DEFAULT_INSTALL_TIMEOUT_MS = 20 * 60 * 1e3;
1283
- DEFAULT_INSTALL_ARGS = Object.freeze([
1284
- "install",
1285
- "--frozen-lockfile",
1286
- "--prefer-offline",
1287
- "--ignore-scripts",
1288
- "--config.confirmModulesPurge=false"
1289
- ]);
1290
1396
  DEFAULT_YIELD_EVERY2 = 25;
1291
1397
  IGNORED_SCAN_DIRS = /* @__PURE__ */ new Set([
1292
1398
  ".agent-worktrees",
@@ -1299,34 +1405,33 @@ var init_pnpm_hydration = __esm({
1299
1405
  "dist",
1300
1406
  "node_modules"
1301
1407
  ]);
1302
- SAFE_PNPM_VERSION_RE = /^[0-9A-Za-z._+-]+$/u;
1303
1408
  }
1304
1409
  });
1305
1410
 
1306
1411
  // src/runner/worktree-paths.mjs
1307
1412
  import { createHash as createHash2 } from "node:crypto";
1308
- import path5 from "node:path";
1413
+ import path6 from "node:path";
1309
1414
  function samePath(left, right) {
1310
- const a = path5.resolve(left);
1311
- const b = path5.resolve(right);
1415
+ const a = path6.resolve(left);
1416
+ const b = path6.resolve(right);
1312
1417
  return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
1313
1418
  }
1314
1419
  function worktreePoolForRoot(root, { clonesRootDir = process.env.VO_CODE_RUNNER_CLONES_ROOT || "" } = {}) {
1315
- const canonicalRoot = path5.resolve(root);
1420
+ const canonicalRoot = path6.resolve(root);
1316
1421
  if (clonesRootDir) {
1317
- const clonePool = path5.resolve(clonesRootDir);
1318
- if (samePath(path5.dirname(canonicalRoot), clonePool)) {
1319
- return path5.join(clonePool, ".agent-worktrees", path5.basename(canonicalRoot));
1422
+ const clonePool = path6.resolve(clonesRootDir);
1423
+ if (samePath(path6.dirname(canonicalRoot), clonePool)) {
1424
+ return path6.join(clonePool, ".agent-worktrees", path6.basename(canonicalRoot));
1320
1425
  }
1321
1426
  }
1322
- return path5.join(canonicalRoot, ".agent-worktrees");
1427
+ return path6.join(canonicalRoot, ".agent-worktrees");
1323
1428
  }
1324
1429
  function worktreeDirForName(root, worktreeName, options = {}) {
1325
1430
  const leaf = createHash2("sha256").update(String(worktreeName)).digest("hex").slice(0, 16);
1326
- return path5.join(worktreePoolForRoot(root, options), leaf);
1431
+ return path6.join(worktreePoolForRoot(root, options), leaf);
1327
1432
  }
1328
1433
  function recoveryLedgerPathForRoot(root, options = {}) {
1329
- return path5.join(worktreePoolForRoot(root, options), "recovery-ledger.jsonl");
1434
+ return path6.join(worktreePoolForRoot(root, options), "recovery-ledger.jsonl");
1330
1435
  }
1331
1436
  var init_worktree_paths = __esm({
1332
1437
  "src/runner/worktree-paths.mjs"() {
@@ -1335,9 +1440,9 @@ var init_worktree_paths = __esm({
1335
1440
  });
1336
1441
 
1337
1442
  // src/runner/worktree-cleanup.mjs
1338
- import fs3 from "node:fs";
1443
+ import fs4 from "node:fs";
1339
1444
  import fsp6 from "node:fs/promises";
1340
- import path6 from "node:path";
1445
+ import path7 from "node:path";
1341
1446
  function stateFromEntry(entry) {
1342
1447
  return {
1343
1448
  root: entry.root,
@@ -1378,14 +1483,14 @@ function pruneSuccessfulStates(nowMs = Date.now()) {
1378
1483
  function assertTrackedCleanupPath(entry) {
1379
1484
  const poolRoot = worktreePoolForRoot(entry.root);
1380
1485
  const expected = worktreeDirForName(entry.root, entry.worktreeName);
1381
- const resolvedPool = path6.resolve(poolRoot);
1382
- const resolvedTarget = path6.resolve(entry.worktreeDir);
1383
- if (!resolvedTarget.startsWith(`${resolvedPool}${path6.sep}`)) {
1486
+ const resolvedPool = path7.resolve(poolRoot);
1487
+ const resolvedTarget = path7.resolve(entry.worktreeDir);
1488
+ if (!resolvedTarget.startsWith(`${resolvedPool}${path7.sep}`)) {
1384
1489
  const error = new Error(`cleanup refused outside managed pool: ${entry.worktreeDir}`);
1385
1490
  error.cleanupFatal = true;
1386
1491
  throw error;
1387
1492
  }
1388
- if (resolvedTarget !== path6.resolve(expected)) {
1493
+ if (resolvedTarget !== path7.resolve(expected)) {
1389
1494
  const error = new Error(`cleanup refused for unexpected tracked path: ${entry.worktreeDir}`);
1390
1495
  error.cleanupFatal = true;
1391
1496
  throw error;
@@ -1399,8 +1504,8 @@ async function worktreeStillRegistered2(root, worktreeDir, gitRunner) {
1399
1504
  if (result.status !== 0) {
1400
1505
  throw new Error(`git worktree list --porcelain failed during cleanup verification: ${summarizeProcessFailure(result)}`);
1401
1506
  }
1402
- const registered = String(result.stdout || "").split(/\r?\n/u).filter((line) => line.startsWith("worktree ")).map((line) => path6.resolve(line.slice("worktree ".length).trim()));
1403
- return registered.includes(path6.resolve(worktreeDir));
1507
+ const registered = String(result.stdout || "").split(/\r?\n/u).filter((line) => line.startsWith("worktree ")).map((line) => path7.resolve(line.slice("worktree ".length).trim()));
1508
+ return registered.includes(path7.resolve(worktreeDir));
1404
1509
  }
1405
1510
  function cleanupBackoff(attempt) {
1406
1511
  return 250 * attempt;
@@ -1419,7 +1524,7 @@ async function runCleanupCycle(entry, options) {
1419
1524
  await (options.assertDetachedDependencyLinks || assertDetachedDependencyLinks)(entry.dependencyOwnership);
1420
1525
  }
1421
1526
  const gitRunner = options.gitRunner || runProcess;
1422
- const pathExists6 = options.pathExists || ((target) => fs3.existsSync(target));
1527
+ const pathExists6 = options.pathExists || ((target) => fs4.existsSync(target));
1423
1528
  const removeResult = await gitRunner("git", ["worktree", "remove", "--force", entry.worktreeDir], {
1424
1529
  cwd: entry.root,
1425
1530
  timeoutMs: 12e4
@@ -1510,7 +1615,7 @@ function scheduleTrackedCleanup(entry, options = {}) {
1510
1615
  }
1511
1616
  function pendingCleanupDirs() {
1512
1617
  return new Set(
1513
- [...CLEANUP_STATES.values()].filter((state) => state.status === "pending").map((state) => path6.resolve(state.worktreeDir))
1618
+ [...CLEANUP_STATES.values()].filter((state) => state.status === "pending").map((state) => path7.resolve(state.worktreeDir))
1514
1619
  );
1515
1620
  }
1516
1621
  var CLEANUP_STATES, CLEANUP_PROMISES, SUCCESS_HISTORY_LIMIT, SUCCESS_HISTORY_TTL_MS, DEFAULT_CLEANUP_ATTEMPTS;
@@ -1529,15 +1634,15 @@ var init_worktree_cleanup = __esm({
1529
1634
  });
1530
1635
 
1531
1636
  // src/runner/task-root-prepare.mjs
1532
- import fs4 from "node:fs";
1637
+ import fs5 from "node:fs";
1533
1638
  import fsp7 from "node:fs/promises";
1534
- import path7 from "node:path";
1639
+ import path8 from "node:path";
1535
1640
  function prepLockDir(root) {
1536
- return path7.join(root, ".agent-worktrees", "runner-root-prep.lock");
1641
+ return path8.join(root, ".agent-worktrees", "runner-root-prep.lock");
1537
1642
  }
1538
1643
  function readLockMeta(lockDir) {
1539
1644
  try {
1540
- return JSON.parse(fs4.readFileSync(path7.join(lockDir, "owner.json"), "utf8"));
1645
+ return JSON.parse(fs5.readFileSync(path8.join(lockDir, "owner.json"), "utf8"));
1541
1646
  } catch {
1542
1647
  return null;
1543
1648
  }
@@ -1557,13 +1662,13 @@ async function acquirePrepLock(root, options = {}) {
1557
1662
  const staleMs = options.lockStaleMs ?? PREP_LOCK_STALE_MS;
1558
1663
  const sleep3 = options.sleep || sleepMs;
1559
1664
  const lockDir = prepLockDir(root);
1560
- const ownerPath = path7.join(lockDir, "owner.json");
1665
+ const ownerPath = path8.join(lockDir, "owner.json");
1561
1666
  const deadline = nowMs() + waitMs;
1562
- fs4.mkdirSync(path7.dirname(lockDir), { recursive: true });
1667
+ fs5.mkdirSync(path8.dirname(lockDir), { recursive: true });
1563
1668
  for (; ; ) {
1564
1669
  try {
1565
- fs4.mkdirSync(lockDir);
1566
- fs4.writeFileSync(ownerPath, `${JSON.stringify({
1670
+ fs5.mkdirSync(lockDir);
1671
+ fs5.writeFileSync(ownerPath, `${JSON.stringify({
1567
1672
  pid: process.pid,
1568
1673
  createdAt: new Date(nowMs()).toISOString(),
1569
1674
  root
@@ -1609,16 +1714,16 @@ async function gitText(root, args, options = {}) {
1609
1714
  }
1610
1715
  function canonicalRecoveryDir(root, options = {}) {
1611
1716
  const now = options.now || (() => /* @__PURE__ */ new Date());
1612
- return path7.join(
1613
- options.managedPool || path7.join(root, ".agent-worktrees"),
1717
+ return path8.join(
1718
+ options.managedPool || path8.join(root, ".agent-worktrees"),
1614
1719
  ".canonical-recovery",
1615
1720
  `preexisting-${now().toISOString().replace(/[:.]/gu, "-")}`
1616
1721
  );
1617
1722
  }
1618
1723
  function canonicalPath(root, relative) {
1619
- const resolvedRoot = path7.resolve(root);
1620
- const target = path7.resolve(root, relative);
1621
- const prefix = `${resolvedRoot}${path7.sep}`;
1724
+ const resolvedRoot = path8.resolve(root);
1725
+ const target = path8.resolve(root, relative);
1726
+ const prefix = `${resolvedRoot}${path8.sep}`;
1622
1727
  if (!target.startsWith(prefix)) {
1623
1728
  throw new Error(`canonical recovery path escaped the runner clone: ${relative}`);
1624
1729
  }
@@ -1646,7 +1751,7 @@ async function recoverManagedCanonicalResidue(root, options = {}) {
1646
1751
  if (patchResult.status !== 0) {
1647
1752
  throw new Error(`could not preserve canonical tracked changes: ${summarizeProcessFailure(patchResult)}`);
1648
1753
  }
1649
- await fsp7.writeFile(path7.join(quarantineDir, "tracked.patch"), String(patchResult.stdout || ""), "utf8");
1754
+ await fsp7.writeFile(path8.join(quarantineDir, "tracked.patch"), String(patchResult.stdout || ""), "utf8");
1650
1755
  const symlinks = [];
1651
1756
  for (const relative of paths.untracked) {
1652
1757
  const source = canonicalPath(root, relative);
@@ -1658,13 +1763,13 @@ async function recoverManagedCanonicalResidue(root, options = {}) {
1658
1763
  if (!stat2.isFile()) {
1659
1764
  throw new Error(`canonical recovery refuses unsupported untracked entry: ${relative}`);
1660
1765
  }
1661
- const target = canonicalPath(path7.join(quarantineDir, "untracked"), relative);
1662
- await fsp7.mkdir(path7.dirname(target), { recursive: true });
1766
+ const target = canonicalPath(path8.join(quarantineDir, "untracked"), relative);
1767
+ await fsp7.mkdir(path8.dirname(target), { recursive: true });
1663
1768
  await fsp7.copyFile(source, target);
1664
1769
  }
1665
- await fsp7.writeFile(path7.join(quarantineDir, "manifest.json"), `${JSON.stringify({
1770
+ await fsp7.writeFile(path8.join(quarantineDir, "manifest.json"), `${JSON.stringify({
1666
1771
  recoveredAt: (/* @__PURE__ */ new Date()).toISOString(),
1667
- canonicalRoot: path7.resolve(root),
1772
+ canonicalRoot: path8.resolve(root),
1668
1773
  canonicalHead: headSha,
1669
1774
  tracked: paths.tracked,
1670
1775
  untracked: paths.untracked,
@@ -1736,19 +1841,19 @@ async function registeredWorktreeDirs(root, options = {}) {
1736
1841
  throw new Error(`git worktree list --porcelain failed while checking managed residue: ${summarizeProcessFailure(listed)}`);
1737
1842
  }
1738
1843
  return new Set(
1739
- String(listed.stdout || "").split(/\r?\n/u).filter((line) => line.startsWith("worktree ")).map((line) => path7.resolve(line.slice("worktree ".length).trim()))
1844
+ String(listed.stdout || "").split(/\r?\n/u).filter((line) => line.startsWith("worktree ")).map((line) => path8.resolve(line.slice("worktree ".length).trim()))
1740
1845
  );
1741
1846
  }
1742
1847
  async function reportLegacyResiduals(root, options = {}) {
1743
- const managedRoot = path7.join(root, ".agent-worktrees");
1744
- if (!fs4.existsSync(managedRoot)) return [];
1848
+ const managedRoot = path8.join(root, ".agent-worktrees");
1849
+ if (!fs5.existsSync(managedRoot)) return [];
1745
1850
  const registered = await registeredWorktreeDirs(root, options);
1746
1851
  const pending = pendingCleanupDirs();
1747
1852
  const found = [];
1748
- for (const entry of fs4.readdirSync(managedRoot, { withFileTypes: true })) {
1853
+ for (const entry of fs5.readdirSync(managedRoot, { withFileTypes: true })) {
1749
1854
  if (!entry.isDirectory()) continue;
1750
1855
  if (shouldIgnoreManagedEntry(entry.name)) continue;
1751
- const absolute = path7.resolve(path7.join(managedRoot, entry.name));
1856
+ const absolute = path8.resolve(path8.join(managedRoot, entry.name));
1752
1857
  if (registered.has(absolute)) continue;
1753
1858
  if (pending.has(absolute)) continue;
1754
1859
  found.push(absolute);
@@ -1781,6 +1886,7 @@ var init_task_root_prepare = __esm({
1781
1886
  "src/runner/task-root-prepare.mjs"() {
1782
1887
  "use strict";
1783
1888
  init_pnpm_hydration();
1889
+ init_pnpm_command();
1784
1890
  init_process_runner();
1785
1891
  init_worktree_cleanup();
1786
1892
  PREP_LOCK_WAIT_MS = 20 * 60 * 1e3;
@@ -1816,7 +1922,7 @@ var init_worktree_github_auth = __esm({
1816
1922
 
1817
1923
  // src/runner/worktree-recovery-start.mjs
1818
1924
  import fsp8 from "node:fs/promises";
1819
- import path8 from "node:path";
1925
+ import path9 from "node:path";
1820
1926
  async function recordWorktreeStarted(worktreeTarget, meta = {}) {
1821
1927
  if (!worktreeTarget?.worktreeName || !meta.taskId) return null;
1822
1928
  const entry = {
@@ -1831,7 +1937,7 @@ async function recordWorktreeStarted(worktreeTarget, meta = {}) {
1831
1937
  reason: "worktree allocated before execution"
1832
1938
  };
1833
1939
  const ledger = recoveryLedgerPathForRoot(worktreeTarget.root || process.cwd());
1834
- await fsp8.mkdir(path8.dirname(ledger), { recursive: true });
1940
+ await fsp8.mkdir(path9.dirname(ledger), { recursive: true });
1835
1941
  await fsp8.appendFile(ledger, `${JSON.stringify(entry)}
1836
1942
  `, "utf8");
1837
1943
  return { entry, ledger };
@@ -1844,9 +1950,9 @@ var init_worktree_recovery_start = __esm({
1844
1950
  });
1845
1951
 
1846
1952
  // src/runner/worktree-helper.mjs
1847
- import fs5 from "node:fs";
1953
+ import fs6 from "node:fs";
1848
1954
  import fsp9 from "node:fs/promises";
1849
- import path9 from "node:path";
1955
+ import path10 from "node:path";
1850
1956
  function repoRoot() {
1851
1957
  return process.env.VO_CODE_RUNNER_REPO || process.cwd();
1852
1958
  }
@@ -1862,7 +1968,7 @@ function cloneDirForSlug(repoSlug, clonesRootDir) {
1862
1968
  const [owner, name] = String(repoSlug).split("/");
1863
1969
  if (owner === "." || owner === ".." || name === "." || name === "..") return null;
1864
1970
  if (owner.startsWith("-") || name.startsWith("-")) return null;
1865
- return path9.join(clonesRootDir, `${sanitize(owner, "owner")}__${sanitize(name, "repo")}`);
1971
+ return path10.join(clonesRootDir, `${sanitize(owner, "owner")}__${sanitize(name, "repo")}`);
1866
1972
  }
1867
1973
  function cloneLockDir(dir) {
1868
1974
  return `${dir}.clone-lock`;
@@ -1877,7 +1983,7 @@ async function pathExists5(target) {
1877
1983
  }
1878
1984
  async function readLockMeta2(lockDir) {
1879
1985
  try {
1880
- return JSON.parse(await fsp9.readFile(path9.join(lockDir, "owner.json"), "utf8"));
1986
+ return JSON.parse(await fsp9.readFile(path10.join(lockDir, "owner.json"), "utf8"));
1881
1987
  } catch {
1882
1988
  return null;
1883
1989
  }
@@ -1898,11 +2004,11 @@ async function acquireCloneLock(dir, options = {}) {
1898
2004
  const sleep3 = options.sleep || sleepMs;
1899
2005
  const lockDir = cloneLockDir(dir);
1900
2006
  const deadline = nowMs() + waitMs;
1901
- await fsp9.mkdir(path9.dirname(lockDir), { recursive: true });
2007
+ await fsp9.mkdir(path10.dirname(lockDir), { recursive: true });
1902
2008
  for (; ; ) {
1903
2009
  try {
1904
2010
  await fsp9.mkdir(lockDir);
1905
- await fsp9.writeFile(path9.join(lockDir, "owner.json"), `${JSON.stringify({
2011
+ await fsp9.writeFile(path10.join(lockDir, "owner.json"), `${JSON.stringify({
1906
2012
  pid: process.pid,
1907
2013
  createdAt: new Date(nowMs()).toISOString(),
1908
2014
  dir
@@ -1931,7 +2037,7 @@ async function acquireCloneLock(dir, options = {}) {
1931
2037
  }
1932
2038
  }
1933
2039
  async function isUsableGitClone(dir, runner = runProcess) {
1934
- if (!await pathExists5(path9.join(dir, ".git"))) return false;
2040
+ if (!await pathExists5(path10.join(dir, ".git"))) return false;
1935
2041
  const result = await runner("git", ["-C", dir, "rev-parse", "HEAD"], { timeoutMs: 1e4 });
1936
2042
  return result.status === 0 && Boolean(String(result.stdout || "").trim());
1937
2043
  }
@@ -1952,7 +2058,7 @@ async function ensureUsableClone(repoSlug, dir, options = {}) {
1952
2058
  const maxAttempts = options.maxAttempts || 5;
1953
2059
  const raceWaitMs = options.raceWaitMs ?? 1e4;
1954
2060
  let lastError = null;
1955
- await fsp9.mkdir(path9.dirname(dir), { recursive: true });
2061
+ await fsp9.mkdir(path10.dirname(dir), { recursive: true });
1956
2062
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
1957
2063
  if (await pathExists5(dir)) {
1958
2064
  if (await waitForUsableClone(dir, runner, sleep3, raceWaitMs)) return dir;
@@ -1964,7 +2070,7 @@ async function ensureUsableClone(repoSlug, dir, options = {}) {
1964
2070
  ["clone", "--no-tags", `https://github.com/${owner}/${name}.git`, tmpDir],
1965
2071
  { timeoutMs: 6e5, env: githubGitAuthEnv(options.githubToken) }
1966
2072
  );
1967
- if (clone.status !== 0 || !await pathExists5(path9.join(tmpDir, ".git"))) {
2073
+ if (clone.status !== 0 || !await pathExists5(path10.join(tmpDir, ".git"))) {
1968
2074
  await fsp9.rm(tmpDir, { recursive: true, force: true });
1969
2075
  lastError = new Error(`[vo-mcp runner] clone failed for ${repoSlug}: ${describeGitFailure(clone)}`);
1970
2076
  continue;
@@ -1991,7 +2097,7 @@ async function ensureUsableClone(repoSlug, dir, options = {}) {
1991
2097
  }
1992
2098
  async function resolveTaskRoot(repoSlug, options = {}) {
1993
2099
  const root = clonesRoot();
1994
- if (root && !path9.isAbsolute(root)) {
2100
+ if (root && !path10.isAbsolute(root)) {
1995
2101
  throw new Error(`[vo-mcp runner] VO_CODE_RUNNER_CLONES_ROOT must be an absolute path (got '${root}')`);
1996
2102
  }
1997
2103
  const dir = cloneDirForSlug(repoSlug, root);
@@ -2040,7 +2146,7 @@ async function createFixWorktree(kind, error = {}, options = {}) {
2040
2146
  const dependencyOwnership = createDependencyOwnershipTracker(worktreeDir);
2041
2147
  const prep = await prepare(root, {
2042
2148
  recoverDirtyCanonical: multiRepo,
2043
- managedPool: path9.dirname(worktreeDir)
2149
+ managedPool: path10.dirname(worktreeDir)
2044
2150
  });
2045
2151
  await processRunner("git", ["config", "core.longpaths", "true"], { cwd: root, timeoutMs: 3e4 });
2046
2152
  const add = await addWorktree({ root, branchName, worktreeDir });
@@ -2106,8 +2212,8 @@ function preserveFailedWorktree(worktreeName, meta = {}) {
2106
2212
  };
2107
2213
  try {
2108
2214
  const ledger = recoveryLedgerPathForRoot(root);
2109
- fs5.mkdirSync(path9.dirname(ledger), { recursive: true });
2110
- fs5.appendFileSync(ledger, `${JSON.stringify(entry)}
2215
+ fs6.mkdirSync(path10.dirname(ledger), { recursive: true });
2216
+ fs6.appendFileSync(ledger, `${JSON.stringify(entry)}
2111
2217
  `, "utf8");
2112
2218
  console.error(`[vo-mcp runner] Preserved worktree ${worktreeName} (reason: ${entry.reason})`);
2113
2219
  } catch (error) {
@@ -2152,9 +2258,9 @@ function partitionRecoveryLedger(lines, { nowMs, ttlMs = PRESERVED_WORKTREE_TTL_
2152
2258
  return decisions;
2153
2259
  }
2154
2260
  function isManagedPreservedDir(dir, root = repoRoot()) {
2155
- const normalized = path9.resolve(String(dir || ""));
2156
- const pool = path9.resolve(worktreePoolForRoot(root));
2157
- return normalized.startsWith(pool + path9.sep);
2261
+ const normalized = path10.resolve(String(dir || ""));
2262
+ const pool = path10.resolve(worktreePoolForRoot(root));
2263
+ return normalized.startsWith(pool + path10.sep);
2158
2264
  }
2159
2265
  function mergeAppendedSinceRead(originalRaw, currentRaw, keptLines) {
2160
2266
  const originalSet = new Set(String(originalRaw || "").split(/\r?\n/u).map((l) => l.trim()).filter(Boolean));
@@ -2173,15 +2279,15 @@ function writeLedgerAtomic(ledger, lines, logger) {
2173
2279
  const payload = lines.length ? `${lines.join("\n")}
2174
2280
  ` : "";
2175
2281
  const tmp = `${ledger}.tmp-${process.pid}-${Date.now()}`;
2176
- fs5.writeFileSync(tmp, payload, "utf8");
2282
+ fs6.writeFileSync(tmp, payload, "utf8");
2177
2283
  for (let attempt = 1; ; attempt += 1) {
2178
2284
  try {
2179
- fs5.renameSync(tmp, ledger);
2285
+ fs6.renameSync(tmp, ledger);
2180
2286
  return true;
2181
2287
  } catch (error) {
2182
2288
  if (attempt >= 3) {
2183
2289
  try {
2184
- fs5.rmSync(tmp, { force: true });
2290
+ fs6.rmSync(tmp, { force: true });
2185
2291
  } catch {
2186
2292
  }
2187
2293
  logger(`[vo-mcp runner] GC could not compact recovery ledger: ${String(error?.message || error)}`);
@@ -2203,14 +2309,14 @@ async function sweepPreservedWorktrees({
2203
2309
  const ledger = recoveryLedgerPathForRoot(root);
2204
2310
  let raw;
2205
2311
  try {
2206
- raw = fs5.readFileSync(ledger, "utf8");
2312
+ raw = fs6.readFileSync(ledger, "utf8");
2207
2313
  } catch {
2208
2314
  return { removed: 0, compacted: 0 };
2209
2315
  }
2210
2316
  const decisions = partitionRecoveryLedger(raw.split(/\r?\n/u), {
2211
2317
  nowMs,
2212
2318
  ttlMs,
2213
- dirExists: (dir) => fs5.existsSync(dir)
2319
+ dirExists: (dir) => fs6.existsSync(dir)
2214
2320
  });
2215
2321
  let removed = 0;
2216
2322
  let compacted = 0;
@@ -2250,7 +2356,7 @@ async function sweepPreservedWorktrees({
2250
2356
  if (compacted > 0) {
2251
2357
  let currentRaw = raw;
2252
2358
  try {
2253
- currentRaw = fs5.readFileSync(ledger, "utf8");
2359
+ currentRaw = fs6.readFileSync(ledger, "utf8");
2254
2360
  } catch {
2255
2361
  }
2256
2362
  writeLedgerAtomic(ledger, mergeAppendedSinceRead(raw, currentRaw, output), logger);
@@ -2508,11 +2614,11 @@ function createControlPlaneClient({
2508
2614
  const resolvedBaseUrl = baseUrl ?? env2.VO_CONTROL_PLANE_URL ?? "";
2509
2615
  if (!resolvedBaseUrl) throw new Error("VO_CONTROL_PLANE_URL is required for the code-runner daemon");
2510
2616
  const root = resolvedBaseUrl.replace(/\/+$/, "");
2511
- async function req(method, path19, body, { timeoutMs } = {}) {
2617
+ async function req(method, path20, body, { timeoutMs } = {}) {
2512
2618
  const bearer = await resolveBearer(env2);
2513
2619
  const controller = timeoutMs ? new AbortController() : null;
2514
2620
  let timeoutId;
2515
- const request = Promise.resolve(fetchImpl(`${root}${path19}`, {
2621
+ const request = Promise.resolve(fetchImpl(`${root}${path20}`, {
2516
2622
  method,
2517
2623
  headers: {
2518
2624
  "content-type": "application/json",
@@ -2525,7 +2631,7 @@ function createControlPlaneClient({
2525
2631
  const timeout = new Promise((_, reject) => {
2526
2632
  timeoutId = setTimeout(() => {
2527
2633
  controller.abort();
2528
- reject(new Error(`control-plane ${path19} timed out after ${timeoutMs}ms`));
2634
+ reject(new Error(`control-plane ${path20} timed out after ${timeoutMs}ms`));
2529
2635
  }, timeoutMs);
2530
2636
  });
2531
2637
  try {
@@ -2534,7 +2640,7 @@ function createControlPlaneClient({
2534
2640
  clearTimeout(timeoutId);
2535
2641
  }
2536
2642
  }
2537
- const taskReq = (method, path19, body, options = {}) => req(method, path19, body, { timeoutMs: taskRequestTimeoutMs, ...options });
2643
+ const taskReq = (method, path20, body, options = {}) => req(method, path20, body, { timeoutMs: taskRequestTimeoutMs, ...options });
2538
2644
  return {
2539
2645
  ...makeAutonomousDispatchAdmissionClient(
2540
2646
  req,
@@ -2657,8 +2763,8 @@ function createControlPlaneClient({
2657
2763
  return listAllPrOpenedTasks(taskReq);
2658
2764
  },
2659
2765
  async downloadTaskAttachment(taskId, attachmentId) {
2660
- const path19 = `/api/v1/code-task/${encodeURIComponent(taskId)}/attachment/${encodeURIComponent(attachmentId)}`;
2661
- const res = await taskReq("GET", path19);
2766
+ const path20 = `/api/v1/code-task/${encodeURIComponent(taskId)}/attachment/${encodeURIComponent(attachmentId)}`;
2767
+ const res = await taskReq("GET", path20);
2662
2768
  if (res.status === 401) cachedFirebaseToken = null;
2663
2769
  if (!res.ok) throw new Error(`attachment download failed: HTTP ${res.status}`);
2664
2770
  return Buffer.from(await res.arrayBuffer());
@@ -2852,8 +2958,8 @@ var init_control_plane_client = __esm({
2852
2958
  });
2853
2959
 
2854
2960
  // ../../scripts/virtual-office/code-runner/windows-claude-launch.mjs
2855
- import { existsSync as existsSync2, realpathSync } from "node:fs";
2856
- import { win32 as path10 } from "node:path";
2961
+ import { existsSync as existsSync3, realpathSync } from "node:fs";
2962
+ import { win32 as path11 } from "node:path";
2857
2963
  import { spawnSync } from "node:child_process";
2858
2964
  function pathValue(env2) {
2859
2965
  for (const key of ["Path", "PATH", "path"]) {
@@ -2874,38 +2980,38 @@ function envValue(env2, name) {
2874
2980
  function userClaudeCandidates(bin, env2) {
2875
2981
  if (!/^claude(?:\.(?:exe|cmd|ps1))?$/iu.test(bin)) return [];
2876
2982
  const userProfile = envValue(env2, "USERPROFILE");
2877
- const appData = envValue(env2, "APPDATA") || (userProfile ? path10.join(userProfile, "AppData", "Roaming") : "");
2878
- const localAppData = envValue(env2, "LOCALAPPDATA") || (userProfile ? path10.join(userProfile, "AppData", "Local") : "");
2983
+ const appData = envValue(env2, "APPDATA") || (userProfile ? path11.join(userProfile, "AppData", "Roaming") : "");
2984
+ const localAppData = envValue(env2, "LOCALAPPDATA") || (userProfile ? path11.join(userProfile, "AppData", "Local") : "");
2879
2985
  const candidates = [];
2880
2986
  if (appData) {
2881
- const npmBin = path10.join(appData, "npm");
2987
+ const npmBin = path11.join(appData, "npm");
2882
2988
  candidates.push(
2883
- path10.join(npmBin, "claude.exe"),
2884
- path10.join(npmBin, "claude.cmd"),
2885
- path10.join(npmBin, "claude.ps1"),
2886
- path10.join(npmBin, "claude"),
2887
- path10.join(npmBin, ...NATIVE_CLAUDE_PARTS)
2989
+ path11.join(npmBin, "claude.exe"),
2990
+ path11.join(npmBin, "claude.cmd"),
2991
+ path11.join(npmBin, "claude.ps1"),
2992
+ path11.join(npmBin, "claude"),
2993
+ path11.join(npmBin, ...NATIVE_CLAUDE_PARTS)
2888
2994
  );
2889
2995
  }
2890
- if (userProfile) candidates.push(path10.join(userProfile, ".local", "bin", "claude.exe"));
2996
+ if (userProfile) candidates.push(path11.join(userProfile, ".local", "bin", "claude.exe"));
2891
2997
  if (localAppData) {
2892
2998
  candidates.push(
2893
- path10.join(localAppData, "Microsoft", "WinGet", "Links", "claude.exe"),
2894
- path10.join(localAppData, "Microsoft", "WindowsApps", "claude.exe")
2999
+ path11.join(localAppData, "Microsoft", "WinGet", "Links", "claude.exe"),
3000
+ path11.join(localAppData, "Microsoft", "WindowsApps", "claude.exe")
2895
3001
  );
2896
3002
  }
2897
3003
  return candidates;
2898
3004
  }
2899
3005
  function pathCandidates(bin, env2) {
2900
- if (path10.isAbsolute(bin) || /[\\/]/u.test(bin)) {
2901
- return [path10.resolve(bin)];
2902
- }
2903
- const extension = path10.extname(bin);
2904
- const fromPath = pathValue(env2).split(";").map(cleanPathSegment).filter(Boolean).flatMap((directory) => extension ? [path10.join(directory, bin)] : [
2905
- path10.join(directory, `${bin}.exe`),
2906
- path10.join(directory, `${bin}.cmd`),
2907
- path10.join(directory, `${bin}.ps1`),
2908
- path10.join(directory, bin)
3006
+ if (path11.isAbsolute(bin) || /[\\/]/u.test(bin)) {
3007
+ return [path11.resolve(bin)];
3008
+ }
3009
+ const extension = path11.extname(bin);
3010
+ const fromPath = pathValue(env2).split(";").map(cleanPathSegment).filter(Boolean).flatMap((directory) => extension ? [path11.join(directory, bin)] : [
3011
+ path11.join(directory, `${bin}.exe`),
3012
+ path11.join(directory, `${bin}.cmd`),
3013
+ path11.join(directory, `${bin}.ps1`),
3014
+ path11.join(directory, bin)
2909
3015
  ]);
2910
3016
  const seen = /* @__PURE__ */ new Set();
2911
3017
  return [...fromPath, ...userClaudeCandidates(bin, env2)].filter((candidate) => {
@@ -2926,7 +3032,7 @@ function canonicalExistingPath(candidate, exists, canonicalize) {
2926
3032
  function resolveWindowsClaudeExecutable({
2927
3033
  bin = "claude",
2928
3034
  env: env2 = process.env,
2929
- exists = existsSync2,
3035
+ exists = existsSync3,
2930
3036
  canonicalize = realpathSync
2931
3037
  } = {}) {
2932
3038
  const requested = String(bin || "").trim();
@@ -2936,8 +3042,8 @@ function resolveWindowsClaudeExecutable({
2936
3042
  for (const candidate of pathCandidates(requested, env2)) {
2937
3043
  const found = canonicalExistingPath(candidate, exists, canonicalize);
2938
3044
  if (!found) continue;
2939
- if (path10.extname(found).toLowerCase() === ".exe") return found;
2940
- const native = path10.join(path10.dirname(found), ...NATIVE_CLAUDE_PARTS);
3045
+ if (path11.extname(found).toLowerCase() === ".exe") return found;
3046
+ const native = path11.join(path11.dirname(found), ...NATIVE_CLAUDE_PARTS);
2941
3047
  const resolvedNative = canonicalExistingPath(native, exists, canonicalize);
2942
3048
  if (resolvedNative) return resolvedNative;
2943
3049
  }
@@ -3001,6 +3107,62 @@ var init_windows_claude_launch = __esm({
3001
3107
  }
3002
3108
  });
3003
3109
 
3110
+ // ../../scripts/virtual-office/code-runner/claude-credential-choice.mjs
3111
+ function isTruthyFlag(v) {
3112
+ const s = String(v ?? "").trim().toLowerCase();
3113
+ return s === "1" || s === "true" || s === "yes" || s === "on";
3114
+ }
3115
+ function wantsLogin(env2) {
3116
+ return isTruthyFlag(env2[CLAUDE_PREFER_LOGIN_ENV]) || isTruthyFlag(env2[PREFER_LOGIN_ENV]);
3117
+ }
3118
+ function wantsKey(env2) {
3119
+ return isTruthyFlag(env2[CLAUDE_PREFER_KEY_ENV]) || isTruthyFlag(env2[PREFER_KEY_ENV]);
3120
+ }
3121
+ function classifyClaudeCredential(baseEnv = {}, { getKey, probeLogin } = {}) {
3122
+ const preferKey = wantsKey(baseEnv);
3123
+ if (!preferKey && wantsLogin(baseEnv)) {
3124
+ return { source: CLAUDE_CREDENTIAL_SOURCE.PREFER_LOGIN, key: null };
3125
+ }
3126
+ if (baseEnv.ANTHROPIC_API_KEY) {
3127
+ return { source: CLAUDE_CREDENTIAL_SOURCE.ENV_KEY, key: null };
3128
+ }
3129
+ const key = getKey();
3130
+ if (!key) {
3131
+ return { source: CLAUDE_CREDENTIAL_SOURCE.NO_KEY, key: null };
3132
+ }
3133
+ if (preferKey) {
3134
+ return { source: CLAUDE_CREDENTIAL_SOURCE.KEYCHAIN_PREFER_KEY, key };
3135
+ }
3136
+ if (probeLogin() === true) {
3137
+ return { source: CLAUDE_CREDENTIAL_SOURCE.SUBSCRIPTION_WINS, key: null };
3138
+ }
3139
+ return { source: CLAUDE_CREDENTIAL_SOURCE.KEYCHAIN, key };
3140
+ }
3141
+ var PREFER_LOGIN_ENV, CLAUDE_PREFER_LOGIN_ENV, PREFER_KEY_ENV, CLAUDE_PREFER_KEY_ENV, CLAUDE_CREDENTIAL_SOURCE;
3142
+ var init_claude_credential_choice = __esm({
3143
+ "../../scripts/virtual-office/code-runner/claude-credential-choice.mjs"() {
3144
+ "use strict";
3145
+ PREFER_LOGIN_ENV = "VO_RUNNER_PREFER_LOGIN";
3146
+ CLAUDE_PREFER_LOGIN_ENV = "VO_RUNNER_CLAUDE_PREFER_LOGIN";
3147
+ PREFER_KEY_ENV = "VO_RUNNER_PREFER_KEY";
3148
+ CLAUDE_PREFER_KEY_ENV = "VO_RUNNER_CLAUDE_PREFER_KEY";
3149
+ CLAUDE_CREDENTIAL_SOURCE = Object.freeze({
3150
+ /** PREFER_LOGIN set (and not overridden): any API key is ignored. */
3151
+ PREFER_LOGIN: "prefer_login",
3152
+ /** An explicit ANTHROPIC_API_KEY in the environment — the manual override. */
3153
+ ENV_KEY: "env_key",
3154
+ /** No key anywhere; the spawn falls through to the login session. */
3155
+ NO_KEY: "no_key",
3156
+ /** A stored key, used because the operator explicitly opted out of tier 1. */
3157
+ KEYCHAIN_PREFER_KEY: "keychain_prefer_key",
3158
+ /** A stored key exists but a proven live subscription outranks it. */
3159
+ SUBSCRIPTION_WINS: "subscription_wins",
3160
+ /** A stored key, used because no live subscription was proven. */
3161
+ KEYCHAIN: "keychain"
3162
+ });
3163
+ }
3164
+ });
3165
+
3004
3166
  // ../../scripts/virtual-office/code-runner/anthropic-key-store.mjs
3005
3167
  import { createRequire as createRequire2 } from "node:module";
3006
3168
  import { spawnSync as spawnSync2 } from "node:child_process";
@@ -3022,46 +3184,22 @@ function getAnthropicKey({ EntryCtor = defaultEntryCtor() } = {}) {
3022
3184
  return null;
3023
3185
  }
3024
3186
  }
3025
- function isTruthyFlag(v) {
3026
- const s = String(v ?? "").trim().toLowerCase();
3027
- return s === "1" || s === "true" || s === "yes" || s === "on";
3028
- }
3029
- function wantsLogin(env2) {
3030
- return isTruthyFlag(env2[CLAUDE_PREFER_LOGIN_ENV]) || isTruthyFlag(env2[PREFER_LOGIN_ENV]);
3031
- }
3032
- function wantsKey(env2) {
3033
- return isTruthyFlag(env2[CLAUDE_PREFER_KEY_ENV]) || isTruthyFlag(env2[PREFER_KEY_ENV]);
3034
- }
3035
3187
  function withAnthropicKey(baseEnv = {}, { getKey = getAnthropicKey, probeLogin = probeClaudeLoginState } = {}) {
3036
- const preferKey = wantsKey(baseEnv);
3037
- if (!preferKey && wantsLogin(baseEnv)) {
3038
- const next = { ...baseEnv };
3188
+ const { source, key } = classifyClaudeCredential(baseEnv, { getKey, probeLogin });
3189
+ const next = { ...baseEnv };
3190
+ if (source === CLAUDE_CREDENTIAL_SOURCE.PREFER_LOGIN) {
3039
3191
  delete next.ANTHROPIC_API_KEY;
3040
3192
  return next;
3041
3193
  }
3042
- if (baseEnv.ANTHROPIC_API_KEY) return { ...baseEnv };
3043
- const key = getKey();
3044
- if (!key) return { ...baseEnv };
3045
- if (!preferKey && probeLogin() === true) return { ...baseEnv };
3046
- return { ...baseEnv, ANTHROPIC_API_KEY: key };
3194
+ if (key !== null) next.ANTHROPIC_API_KEY = key;
3195
+ return next;
3047
3196
  }
3048
3197
  function claudeCostBasis(env2 = process.env) {
3049
3198
  return String(env2.ANTHROPIC_API_KEY || "").trim() ? "vendor_billed" : "subscription_api_equivalent";
3050
3199
  }
3051
3200
  function describeAnthropicAuthSource(baseEnv = {}, { getKey = getAnthropicKey, probeLogin = probeClaudeLoginState } = {}) {
3052
- const preferKey = wantsKey(baseEnv);
3053
- if (!preferKey && wantsLogin(baseEnv)) {
3054
- return "claude auth login (VO_RUNNER_PREFER_LOGIN set \u2014 any API key ignored)";
3055
- }
3056
- if (baseEnv.ANTHROPIC_API_KEY) return "ANTHROPIC_API_KEY from environment";
3057
- if (!getKey()) return "claude auth login session (no API key set)";
3058
- if (preferKey) {
3059
- return "ANTHROPIC_API_KEY from OS keychain (VO_RUNNER_PREFER_KEY set \u2014 subscription ignored)";
3060
- }
3061
- if (probeLogin() === true) {
3062
- return "claude auth login session (subscription beats the stored keychain key)";
3063
- }
3064
- return "ANTHROPIC_API_KEY from OS keychain";
3201
+ const { source } = classifyClaudeCredential(baseEnv, { getKey, probeLogin });
3202
+ return AUTH_SOURCE_DESCRIPTION[source];
3065
3203
  }
3066
3204
  function augmentAuthError(summary) {
3067
3205
  const s = String(summary ?? "");
@@ -3083,19 +3221,25 @@ function probeClaudeLoginState({
3083
3221
  return null;
3084
3222
  }
3085
3223
  }
3086
- var require2, KEY_SERVICE, KEY_ACCOUNT, _entryCtor, _loadTried, PREFER_LOGIN_ENV, CLAUDE_PREFER_LOGIN_ENV, PREFER_KEY_ENV, CLAUDE_PREFER_KEY_ENV, AUTH_ERROR_RE;
3224
+ var require2, KEY_SERVICE, KEY_ACCOUNT, _entryCtor, _loadTried, AUTH_SOURCE_DESCRIPTION, AUTH_ERROR_RE;
3087
3225
  var init_anthropic_key_store = __esm({
3088
3226
  "../../scripts/virtual-office/code-runner/anthropic-key-store.mjs"() {
3089
3227
  "use strict";
3090
3228
  init_windows_claude_launch();
3229
+ init_claude_credential_choice();
3230
+ init_claude_credential_choice();
3091
3231
  require2 = createRequire2(import.meta.url);
3092
3232
  KEY_SERVICE = "algosuite-vo";
3093
3233
  KEY_ACCOUNT = "anthropic-api-key";
3094
3234
  _loadTried = false;
3095
- PREFER_LOGIN_ENV = "VO_RUNNER_PREFER_LOGIN";
3096
- CLAUDE_PREFER_LOGIN_ENV = "VO_RUNNER_CLAUDE_PREFER_LOGIN";
3097
- PREFER_KEY_ENV = "VO_RUNNER_PREFER_KEY";
3098
- CLAUDE_PREFER_KEY_ENV = "VO_RUNNER_CLAUDE_PREFER_KEY";
3235
+ AUTH_SOURCE_DESCRIPTION = Object.freeze({
3236
+ [CLAUDE_CREDENTIAL_SOURCE.PREFER_LOGIN]: "claude auth login (VO_RUNNER_PREFER_LOGIN set \u2014 any API key ignored)",
3237
+ [CLAUDE_CREDENTIAL_SOURCE.ENV_KEY]: "ANTHROPIC_API_KEY from environment",
3238
+ [CLAUDE_CREDENTIAL_SOURCE.NO_KEY]: "claude auth login session (no API key set)",
3239
+ [CLAUDE_CREDENTIAL_SOURCE.KEYCHAIN_PREFER_KEY]: "ANTHROPIC_API_KEY from OS keychain (VO_RUNNER_PREFER_KEY set \u2014 subscription ignored)",
3240
+ [CLAUDE_CREDENTIAL_SOURCE.SUBSCRIPTION_WINS]: "claude auth login session (subscription beats the stored keychain key)",
3241
+ [CLAUDE_CREDENTIAL_SOURCE.KEYCHAIN]: "ANTHROPIC_API_KEY from OS keychain"
3242
+ });
3099
3243
  AUTH_ERROR_RE = /\b401\b|invalid[^.]{0,24}(authentication|credential)|authentication_error|unauthorized|not[ _-]?authenticated/i;
3100
3244
  }
3101
3245
  });
@@ -3366,14 +3510,14 @@ var init_terminal_process_cleanup = __esm({
3366
3510
 
3367
3511
  // ../../scripts/virtual-office/code-runner/orphan-agent-reaper.mjs
3368
3512
  import { spawnSync as spawnSync5 } from "node:child_process";
3369
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
3513
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
3370
3514
  import os from "node:os";
3371
- import path11 from "node:path";
3515
+ import path12 from "node:path";
3372
3516
  function registryRoot(tmp = os.tmpdir()) {
3373
- return path11.join(tmp, REGISTRY_ROOT_NAME);
3517
+ return path12.join(tmp, REGISTRY_ROOT_NAME);
3374
3518
  }
3375
3519
  function instanceDir(root, instanceId) {
3376
- return path11.join(root, String(instanceId).replace(/[^A-Za-z0-9_-]/g, ""));
3520
+ return path12.join(root, String(instanceId).replace(/[^A-Za-z0-9_-]/g, ""));
3377
3521
  }
3378
3522
  function registerDaemonInstance({
3379
3523
  root = registryRoot(),
@@ -3383,8 +3527,8 @@ function registerDaemonInstance({
3383
3527
  } = {}) {
3384
3528
  if (!instanceId) return null;
3385
3529
  const dir = instanceDir(root, instanceId);
3386
- mkdirSync2(dir, { recursive: true });
3387
- const file = path11.join(dir, DAEMON_RECORD);
3530
+ mkdirSync3(dir, { recursive: true });
3531
+ const file = path12.join(dir, DAEMON_RECORD);
3388
3532
  writeFileSync2(file, JSON.stringify({ daemonPid, daemonStartedAtMs, instanceId }), {
3389
3533
  encoding: "utf8",
3390
3534
  mode: 384
@@ -3401,9 +3545,9 @@ function recordAgentPid({
3401
3545
  if (!instanceId || !Number.isInteger(pid) || pid <= 0) return false;
3402
3546
  try {
3403
3547
  const dir = instanceDir(root, instanceId);
3404
- mkdirSync2(dir, { recursive: true });
3548
+ mkdirSync3(dir, { recursive: true });
3405
3549
  writeFileSync2(
3406
- path11.join(dir, `${pid}.json`),
3550
+ path12.join(dir, `${pid}.json`),
3407
3551
  JSON.stringify({ pid, agentId, startedAtMs, instanceId }),
3408
3552
  { encoding: "utf8", mode: 384 }
3409
3553
  );
@@ -3419,7 +3563,7 @@ function unrecordAgentPid({
3419
3563
  } = {}) {
3420
3564
  if (!instanceId || !Number.isInteger(pid)) return false;
3421
3565
  try {
3422
- rmSync2(path11.join(instanceDir(root, instanceId), `${pid}.json`), { force: true });
3566
+ rmSync2(path12.join(instanceDir(root, instanceId), `${pid}.json`), { force: true });
3423
3567
  return true;
3424
3568
  } catch {
3425
3569
  return false;
@@ -3437,7 +3581,7 @@ function bootstrapOrphanReaper({ instanceId, log: log2 = () => {
3437
3581
  }
3438
3582
  function readRegistry({ root = registryRoot(), currentInstanceId } = {}) {
3439
3583
  const instances = [];
3440
- if (!existsSync3(root)) return instances;
3584
+ if (!existsSync4(root)) return instances;
3441
3585
  let dirents;
3442
3586
  try {
3443
3587
  dirents = readdirSync(root, { withFileTypes: true });
@@ -3447,7 +3591,7 @@ function readRegistry({ root = registryRoot(), currentInstanceId } = {}) {
3447
3591
  for (const dirent of dirents) {
3448
3592
  if (!dirent.isDirectory()) continue;
3449
3593
  if (currentInstanceId && dirent.name === instanceDirName(currentInstanceId)) continue;
3450
- const dir = path11.join(root, dirent.name);
3594
+ const dir = path12.join(root, dirent.name);
3451
3595
  let daemon = null;
3452
3596
  const agents = [];
3453
3597
  let files;
@@ -3459,7 +3603,7 @@ function readRegistry({ root = registryRoot(), currentInstanceId } = {}) {
3459
3603
  for (const name of files) {
3460
3604
  let parsed;
3461
3605
  try {
3462
- parsed = JSON.parse(readFileSync2(path11.join(dir, name), "utf8"));
3606
+ parsed = JSON.parse(readFileSync2(path12.join(dir, name), "utf8"));
3463
3607
  } catch {
3464
3608
  continue;
3465
3609
  }
@@ -3507,7 +3651,7 @@ function windowsSystemRoot(env2 = process.env) {
3507
3651
  return env2.SystemRoot || env2.WINDIR || "C:\\Windows";
3508
3652
  }
3509
3653
  function windowsPowershellExe(env2 = process.env) {
3510
- return path11.join(windowsSystemRoot(env2), "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
3654
+ return path12.join(windowsSystemRoot(env2), "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
3511
3655
  }
3512
3656
  function listProcessCreationTimes({ platform = process.platform, spawn: spawn5 = spawnSync5, env: env2 = process.env, warn = console.warn } = {}) {
3513
3657
  const map = /* @__PURE__ */ new Map();
@@ -3552,7 +3696,7 @@ function parsePosixPsLine(line) {
3552
3696
  function killProcessTree(pid, { platform = process.platform, spawn: spawn5 = spawnSync5, env: env2 = process.env } = {}) {
3553
3697
  if (!Number.isInteger(pid) || pid <= 0) return false;
3554
3698
  if (platform === "win32") {
3555
- const taskkill = path11.join(windowsSystemRoot(env2), "System32", "taskkill.exe");
3699
+ const taskkill = path12.join(windowsSystemRoot(env2), "System32", "taskkill.exe");
3556
3700
  const r = spawn5(taskkill, ["/PID", String(pid), "/T", "/F"], { windowsHide: true, stdio: "ignore", timeout: 15e3 });
3557
3701
  return !r.error && r.status === 0;
3558
3702
  }
@@ -4341,15 +4485,15 @@ var init_flat_token_usage = __esm({
4341
4485
 
4342
4486
  // ../../scripts/virtual-office/code-runner/codex-runner.mjs
4343
4487
  import { spawnSync as spawnSync6 } from "node:child_process";
4344
- import { existsSync as existsSync4 } from "node:fs";
4345
- import { win32 } from "node:path";
4488
+ import { existsSync as existsSync5 } from "node:fs";
4489
+ import { win32 as win322 } from "node:path";
4346
4490
  function isTruthyFlag2(value) {
4347
4491
  return ["1", "true", "yes", "on"].includes(String(value ?? "").trim().toLowerCase());
4348
4492
  }
4349
4493
  function resolveCodexBinary({
4350
4494
  env: env2 = process.env,
4351
4495
  platform = process.platform,
4352
- exists = existsSync4
4496
+ exists = existsSync5
4353
4497
  } = {}) {
4354
4498
  if (platform !== "win32") return "codex";
4355
4499
  const appData = String(env2.APPDATA || "").trim();
@@ -4357,7 +4501,7 @@ function resolveCodexBinary({
4357
4501
  const localAppData = String(env2.LOCALAPPDATA || "").trim();
4358
4502
  const candidates = [];
4359
4503
  if (appData) {
4360
- candidates.push(win32.join(
4504
+ candidates.push(win322.join(
4361
4505
  appData,
4362
4506
  "npm",
4363
4507
  "node_modules",
@@ -4373,11 +4517,11 @@ function resolveCodexBinary({
4373
4517
  ));
4374
4518
  }
4375
4519
  if (userProfile) {
4376
- candidates.push(win32.join(userProfile, ".local", "bin", "codex.exe"));
4377
- candidates.push(win32.join(userProfile, ".codex", "bin", "codex.exe"));
4520
+ candidates.push(win322.join(userProfile, ".local", "bin", "codex.exe"));
4521
+ candidates.push(win322.join(userProfile, ".codex", "bin", "codex.exe"));
4378
4522
  }
4379
4523
  if (localAppData) {
4380
- candidates.push(win32.join(localAppData, "Microsoft", "WindowsApps", "codex.exe"));
4524
+ candidates.push(win322.join(localAppData, "Microsoft", "WindowsApps", "codex.exe"));
4381
4525
  }
4382
4526
  const absolute = candidates.find((candidate) => exists(candidate));
4383
4527
  if (absolute) return absolute;
@@ -5206,9 +5350,9 @@ var init_rate_limit_detector_core = __esm({
5206
5350
 
5207
5351
  // ../../scripts/virtual-office/code-runner/rate-limit-resume-state.mjs
5208
5352
  import fsp10 from "node:fs/promises";
5209
- import path12 from "node:path";
5353
+ import path13 from "node:path";
5210
5354
  async function atomicWrite(file, content) {
5211
- await fsp10.mkdir(path12.dirname(file), { recursive: true });
5355
+ await fsp10.mkdir(path13.dirname(file), { recursive: true });
5212
5356
  const temp = `${file}.tmp-${process.pid}-${Date.now()}`;
5213
5357
  const handle = await fsp10.open(temp, "wx");
5214
5358
  try {
@@ -5269,7 +5413,7 @@ function writeResumeAttempts(file, store) {
5269
5413
  }
5270
5414
  async function acquireLock(lockFile, { now = Date.now, sleep: sleep3 = delay } = {}) {
5271
5415
  const deadline = now() + LOCK_WAIT_MS;
5272
- await fsp10.mkdir(path12.dirname(lockFile), { recursive: true });
5416
+ await fsp10.mkdir(path13.dirname(lockFile), { recursive: true });
5273
5417
  for (; ; ) {
5274
5418
  let handle;
5275
5419
  try {
@@ -5343,10 +5487,10 @@ var init_rate_limit_resume_state = __esm({
5343
5487
  });
5344
5488
 
5345
5489
  // ../../scripts/virtual-office/code-runner/rate-limit-resume.mjs
5346
- import { homedir as homedir2 } from "node:os";
5490
+ import { homedir as homedir3 } from "node:os";
5347
5491
  import { join as join2 } from "node:path";
5348
5492
  function resumeQueuePath() {
5349
- return join2(homedir2(), ".claude", "resume-queue.jsonl");
5493
+ return join2(homedir3(), ".claude", "resume-queue.jsonl");
5350
5494
  }
5351
5495
  function buildResumeEntry({ task = {}, resumeAfter = null, summary = "", at } = {}) {
5352
5496
  return {
@@ -5547,7 +5691,7 @@ var init_auto_merge = __esm({
5547
5691
 
5548
5692
  // ../../scripts/virtual-office/code-runner/pr-overlap-gate.mjs
5549
5693
  import { spawnSync as spawnSync8 } from "node:child_process";
5550
- import { existsSync as existsSync5 } from "node:fs";
5694
+ import { existsSync as existsSync6 } from "node:fs";
5551
5695
  import { fileURLToPath } from "node:url";
5552
5696
  function stripCredentials(env2 = process.env) {
5553
5697
  const safe = { ...env2 };
@@ -5558,7 +5702,7 @@ function resolveOverlapScript({
5558
5702
  worktreeDir,
5559
5703
  trustedPath = null,
5560
5704
  trustedPaths = TRUSTED_OVERLAP_CANDIDATES,
5561
- existsFn = existsSync5,
5705
+ existsFn = existsSync6,
5562
5706
  joinFn = (dir) => `${dir}/scripts/ci/check-local-pr-overlap.mjs`
5563
5707
  } = {}) {
5564
5708
  const candidates = trustedPath ? [trustedPath] : trustedPaths;
@@ -5627,14 +5771,14 @@ function parsePorcelainZ(out) {
5627
5771
  for (let i = 0; i < tokens.length; i += 1) {
5628
5772
  const token2 = tokens[i];
5629
5773
  if (!token2) continue;
5630
- const path19 = token2.slice(3);
5631
- if (path19) files.push(path19);
5774
+ const path20 = token2.slice(3);
5775
+ if (path20) files.push(path20);
5632
5776
  if (token2[0] === "R" || token2[0] === "C") i += 1;
5633
5777
  }
5634
5778
  return files;
5635
5779
  }
5636
- function isAgentScratch(path19) {
5637
- const normalized = String(path19 || "");
5780
+ function isAgentScratch(path20) {
5781
+ const normalized = String(path20 || "");
5638
5782
  return SCRATCH_PATTERNS.some((pattern) => pattern.test(normalized));
5639
5783
  }
5640
5784
  var SCRATCH_PATTERNS;
@@ -6467,10 +6611,10 @@ var init_task_prompt = __esm({
6467
6611
  });
6468
6612
 
6469
6613
  // ../../scripts/virtual-office/code-runner/task-attachments.mjs
6470
- import { createHash as createHash3, randomUUID } from "node:crypto";
6614
+ import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
6471
6615
  import { chmod, mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
6472
6616
  import os2 from "node:os";
6473
- import path13 from "node:path";
6617
+ import path14 from "node:path";
6474
6618
  function safeTaskToken(taskId) {
6475
6619
  return String(taskId || "task").replace(/[^0-9A-Za-z_-]/gu, "_").slice(0, 48) || "task";
6476
6620
  }
@@ -6480,25 +6624,25 @@ function sanitizeTaskAttachmentName(name, index = 0) {
6480
6624
  return `${String(index + 1).padStart(2, "0")}-${normalized}`;
6481
6625
  }
6482
6626
  function assertGeneratedDirectory(directory, tempRoot) {
6483
- const resolvedDirectory = path13.resolve(directory);
6484
- const resolvedRoot = path13.resolve(tempRoot);
6485
- if (path13.dirname(resolvedDirectory) !== resolvedRoot || !path13.basename(resolvedDirectory).startsWith(DIRECTORY_PREFIX)) {
6627
+ const resolvedDirectory = path14.resolve(directory);
6628
+ const resolvedRoot = path14.resolve(tempRoot);
6629
+ if (path14.dirname(resolvedDirectory) !== resolvedRoot || !path14.basename(resolvedDirectory).startsWith(DIRECTORY_PREFIX)) {
6486
6630
  throw new Error("refusing to clean an unverified task-attachment directory");
6487
6631
  }
6488
6632
  return resolvedDirectory;
6489
6633
  }
6490
6634
  async function createAttachmentDirectory(taskId, tempRoot) {
6491
- const root = path13.resolve(tempRoot);
6635
+ const root = path14.resolve(tempRoot);
6492
6636
  await mkdir(root, { recursive: true });
6493
- const directory = await mkdtemp(path13.join(root, `${DIRECTORY_PREFIX}${safeTaskToken(taskId)}-`));
6494
- const marker = JSON.stringify({ owner: MARKER_OWNER, token: randomUUID(), directory: path13.basename(directory), created_at: (/* @__PURE__ */ new Date()).toISOString() });
6495
- await writeFile(path13.join(directory, MARKER_FILE), marker, { encoding: "utf8", mode: 384 });
6637
+ const directory = await mkdtemp(path14.join(root, `${DIRECTORY_PREFIX}${safeTaskToken(taskId)}-`));
6638
+ const marker = JSON.stringify({ owner: MARKER_OWNER, token: randomUUID2(), directory: path14.basename(directory), created_at: (/* @__PURE__ */ new Date()).toISOString() });
6639
+ await writeFile(path14.join(directory, MARKER_FILE), marker, { encoding: "utf8", mode: 384 });
6496
6640
  return { directory, marker, tempRoot: root };
6497
6641
  }
6498
6642
  async function cleanupGeneratedDirectory(state) {
6499
6643
  if (!state || state.cleaned) return;
6500
6644
  const directory = assertGeneratedDirectory(state.directory, state.tempRoot);
6501
- const marker = await readFile(path13.join(directory, MARKER_FILE), "utf8").catch(() => "");
6645
+ const marker = await readFile(path14.join(directory, MARKER_FILE), "utf8").catch(() => "");
6502
6646
  if (marker !== state.marker) throw new Error("refusing to clean a task-attachment directory without its exact marker");
6503
6647
  await rm(directory, { recursive: true, force: true });
6504
6648
  state.cleaned = true;
@@ -6517,7 +6661,7 @@ async function sweepStaleTaskAttachmentDirectories({
6517
6661
  now = Date.now(),
6518
6662
  maxAgeMs = DEFAULT_STALE_AGE_MS
6519
6663
  } = {}) {
6520
- const root = path13.resolve(tempRoot);
6664
+ const root = path14.resolve(tempRoot);
6521
6665
  if (!Number.isFinite(maxAgeMs) || maxAgeMs <= 0) throw new Error("stale attachment age must be positive");
6522
6666
  const entries = await readdir(root, { withFileTypes: true }).catch((error) => {
6523
6667
  if (error?.code === "ENOENT") return [];
@@ -6526,8 +6670,8 @@ async function sweepStaleTaskAttachmentDirectories({
6526
6670
  let removed = 0;
6527
6671
  for (const entry of entries) {
6528
6672
  if (!entry.isDirectory() || !entry.name.startsWith(DIRECTORY_PREFIX)) continue;
6529
- const directory = assertGeneratedDirectory(path13.join(root, entry.name), root);
6530
- const markerRaw = await readFile(path13.join(directory, MARKER_FILE), "utf8").catch(() => "");
6673
+ const directory = assertGeneratedDirectory(path14.join(root, entry.name), root);
6674
+ const markerRaw = await readFile(path14.join(directory, MARKER_FILE), "utf8").catch(() => "");
6531
6675
  const marker = parseOwnedMarker(markerRaw, entry.name);
6532
6676
  if (!marker) continue;
6533
6677
  const directoryStat = await stat(directory);
@@ -6570,10 +6714,10 @@ async function materializeTaskAttachments(client, task, { tempRoot = os2.tmpdir(
6570
6714
  const sha256 = createHash3("sha256").update(content).digest("hex");
6571
6715
  if (sha256 !== ref.sha256) throw new Error(`attachment ${ref.attachment_id} sha256 mismatch`);
6572
6716
  const name = sanitizeTaskAttachmentName(ref.name, index);
6573
- const filePath = path13.join(state.directory, name);
6717
+ const filePath = path14.join(state.directory, name);
6574
6718
  await writeFile(filePath, content, { flag: "wx", mode: 384 });
6575
6719
  await chmod(filePath, 384);
6576
- files.push({ attachmentId: ref.attachment_id, name, mime: ref.mime, sizeBytes: ref.size_bytes, sha256, path: path13.resolve(filePath) });
6720
+ files.push({ attachmentId: ref.attachment_id, name, mime: ref.mime, sizeBytes: ref.size_bytes, sha256, path: path14.resolve(filePath) });
6577
6721
  }
6578
6722
  return { directory: state.directory, files, manifestMarkdown: buildManifest(files), cleanup: () => cleanupGeneratedDirectory(state) };
6579
6723
  } catch (error) {
@@ -6595,7 +6739,7 @@ var init_task_attachments = __esm({
6595
6739
  });
6596
6740
 
6597
6741
  // ../../scripts/virtual-office/code-runner/session-spool-forwarder.mjs
6598
- import { homedir as homedir3 } from "node:os";
6742
+ import { homedir as homedir4 } from "node:os";
6599
6743
  import { join as join4 } from "node:path";
6600
6744
  import { readdir as readdir2, readFile as readFile2, unlink, writeFile as writeFile2 } from "node:fs/promises";
6601
6745
  import { createHash as createHash4 } from "node:crypto";
@@ -6637,9 +6781,9 @@ async function readSpool(spoolDir = SPOOL_DIR) {
6637
6781
  }
6638
6782
  return out;
6639
6783
  }
6640
- async function readCloudMap(path19) {
6784
+ async function readCloudMap(path20) {
6641
6785
  try {
6642
- return JSON.parse(await readFile2(path19, "utf8"));
6786
+ return JSON.parse(await readFile2(path20, "utf8"));
6643
6787
  } catch {
6644
6788
  return {};
6645
6789
  }
@@ -6712,8 +6856,8 @@ var SPOOL_DIR, CLOUD_MAP_FILE, STALE_MS, ACTIVE_SILENCE_MS;
6712
6856
  var init_session_spool_forwarder = __esm({
6713
6857
  "../../scripts/virtual-office/code-runner/session-spool-forwarder.mjs"() {
6714
6858
  "use strict";
6715
- SPOOL_DIR = join4(homedir3(), ".vo", "session-spool");
6716
- CLOUD_MAP_FILE = join4(homedir3(), ".vo", "session-cloud-map.json");
6859
+ SPOOL_DIR = join4(homedir4(), ".vo", "session-spool");
6860
+ CLOUD_MAP_FILE = join4(homedir4(), ".vo", "session-cloud-map.json");
6717
6861
  STALE_MS = 60 * 60 * 1e3;
6718
6862
  ACTIVE_SILENCE_MS = 10 * 60 * 1e3;
6719
6863
  }
@@ -7396,7 +7540,7 @@ var init_local_model_remote_config = __esm({
7396
7540
 
7397
7541
  // ../../scripts/virtual-office/code-runner/account-usage/shared.mjs
7398
7542
  import crypto from "node:crypto";
7399
- import fs6 from "node:fs";
7543
+ import fs7 from "node:fs";
7400
7544
  function accountKey(agent, rawId) {
7401
7545
  const id = typeof rawId === "string" ? rawId.trim() : "";
7402
7546
  if (!id) return null;
@@ -7443,6 +7587,13 @@ function makeUsageRow({
7443
7587
  }
7444
7588
  return row;
7445
7589
  }
7590
+ function readingAgeMs(row, nowMs = Date.now()) {
7591
+ const at = row && typeof row.captured_at === "string" ? row.captured_at : null;
7592
+ if (!at) return null;
7593
+ const ms = new Date(at).getTime();
7594
+ if (!Number.isFinite(ms)) return null;
7595
+ return Math.max(0, nowMs - ms);
7596
+ }
7446
7597
  var clampPct, readJson, ACCOUNT_KEY_SALT;
7447
7598
  var init_shared = __esm({
7448
7599
  "../../scripts/virtual-office/code-runner/account-usage/shared.mjs"() {
@@ -7453,7 +7604,7 @@ var init_shared = __esm({
7453
7604
  };
7454
7605
  readJson = (p) => {
7455
7606
  try {
7456
- return JSON.parse(fs6.readFileSync(p, "utf8"));
7607
+ return JSON.parse(fs7.readFileSync(p, "utf8"));
7457
7608
  } catch {
7458
7609
  return null;
7459
7610
  }
@@ -7463,14 +7614,23 @@ var init_shared = __esm({
7463
7614
  });
7464
7615
 
7465
7616
  // ../../scripts/virtual-office/code-runner/account-usage/claude.mjs
7617
+ import fs8 from "node:fs";
7466
7618
  import os3 from "node:os";
7467
- import path14 from "node:path";
7619
+ import path15 from "node:path";
7620
+ function fileCaptureTime(filePath, explicit, statFn) {
7621
+ if (typeof explicit === "string" && explicit) return explicit;
7622
+ try {
7623
+ return statFn(filePath).mtime.toISOString();
7624
+ } catch {
7625
+ return null;
7626
+ }
7627
+ }
7468
7628
  function usageBaseUrl(env2 = process.env) {
7469
7629
  const raw = env2.ANTHROPIC_BASE_URL || "https://api.anthropic.com";
7470
7630
  return String(raw).replace(/\/+$/, "");
7471
7631
  }
7472
7632
  function readOAuthToken({ homeDir = os3.homedir(), read = readJson, now = Date.now() } = {}) {
7473
- const creds = read(path14.join(homeDir, ".claude", ".credentials.json"));
7633
+ const creds = read(path15.join(homeDir, ".claude", ".credentials.json"));
7474
7634
  const oauth = creds && typeof creds === "object" ? creds.claudeAiOauth : null;
7475
7635
  if (!oauth || typeof oauth !== "object") return null;
7476
7636
  const token2 = typeof oauth.accessToken === "string" ? oauth.accessToken.trim() : "";
@@ -7480,7 +7640,7 @@ function readOAuthToken({ homeDir = os3.homedir(), read = readJson, now = Date.n
7480
7640
  return token2;
7481
7641
  }
7482
7642
  function readAccountId({ homeDir = os3.homedir(), read = readJson } = {}) {
7483
- const cfg = read(path14.join(homeDir, ".claude.json"));
7643
+ const cfg = read(path15.join(homeDir, ".claude.json"));
7484
7644
  const account = cfg && typeof cfg === "object" ? cfg.oauthAccount : null;
7485
7645
  return account && typeof account.accountUuid === "string" ? account.accountUuid : null;
7486
7646
  }
@@ -7570,7 +7730,12 @@ async function readClaudeOAuthUsage({
7570
7730
  clearTimeout(timer);
7571
7731
  }
7572
7732
  }
7573
- function readClaudeFileUsage({ homeDir = os3.homedir(), read: rawRead = readJson } = {}) {
7733
+ function readClaudeFileUsage({
7734
+ homeDir = os3.homedir(),
7735
+ read: rawRead = readJson,
7736
+ statFn = fs8.statSync,
7737
+ now = () => Date.now()
7738
+ } = {}) {
7574
7739
  const read = (p) => {
7575
7740
  try {
7576
7741
  return rawRead(p);
@@ -7579,31 +7744,39 @@ function readClaudeFileUsage({ homeDir = os3.homedir(), read: rawRead = readJson
7579
7744
  }
7580
7745
  };
7581
7746
  const accountId = readAccountId({ homeDir, read });
7582
- const status = read(path14.join(homeDir, ".claude", "claude-usage.json"));
7747
+ const fresh = (row) => {
7748
+ if (!row) return null;
7749
+ const age = readingAgeMs(row, now());
7750
+ if (age === null || age > MAX_FILE_AGE_MS) return null;
7751
+ return row;
7752
+ };
7753
+ const statusPath = path15.join(homeDir, ".claude", "claude-usage.json");
7754
+ const status = read(statusPath);
7583
7755
  if (status && (status.seven_day || status.five_hour)) {
7584
- const row = makeUsageRow({
7756
+ const row = fresh(makeUsageRow({
7585
7757
  agent: "claude",
7586
7758
  source: "statusline",
7587
- capturedAt: typeof status.capturedAt === "string" ? status.capturedAt : null,
7759
+ capturedAt: fileCaptureTime(statusPath, status.capturedAt, statFn),
7588
7760
  accountId,
7589
7761
  sevenDay: status.seven_day?.used_percentage,
7590
7762
  fiveHour: status.five_hour?.used_percentage,
7591
7763
  sevenDayResetsAt: status.seven_day?.resets_at ?? null,
7592
7764
  fiveHourResetsAt: status.five_hour?.resets_at ?? null
7593
- });
7765
+ }));
7594
7766
  if (row) return row;
7595
7767
  }
7596
- const weekly = read(path14.join(homeDir, ".claude", "claude-weekly-usage.json"));
7768
+ const weeklyPath = path15.join(homeDir, ".claude", "claude-weekly-usage.json");
7769
+ const weekly = read(weeklyPath);
7597
7770
  if (weekly) {
7598
- const row = makeUsageRow({
7771
+ const row = fresh(makeUsageRow({
7599
7772
  agent: "claude",
7600
7773
  source: "file",
7601
- capturedAt: typeof weekly.capturedAt === "string" ? weekly.capturedAt : null,
7774
+ capturedAt: fileCaptureTime(weeklyPath, weekly.capturedAt, statFn),
7602
7775
  accountId,
7603
7776
  sevenDay: weekly.sevenDayPct,
7604
7777
  fiveHour: weekly.fiveHourPct,
7605
7778
  sevenDayResetsAt: weekly.sevenDayResetsAt ?? null
7606
- });
7779
+ }));
7607
7780
  if (row) return row;
7608
7781
  }
7609
7782
  return null;
@@ -7616,11 +7789,12 @@ async function readClaudeUsage(opts = {}) {
7616
7789
  }
7617
7790
  return readClaudeFileUsage(opts);
7618
7791
  }
7619
- var USAGE_PATH, OAUTH_BETA, DEFAULT_TIMEOUT_MS;
7792
+ var MAX_FILE_AGE_MS, USAGE_PATH, OAUTH_BETA, DEFAULT_TIMEOUT_MS;
7620
7793
  var init_claude = __esm({
7621
7794
  "../../scripts/virtual-office/code-runner/account-usage/claude.mjs"() {
7622
7795
  "use strict";
7623
7796
  init_shared();
7797
+ MAX_FILE_AGE_MS = 6 * 60 * 60 * 1e3;
7624
7798
  USAGE_PATH = "/api/oauth/usage";
7625
7799
  OAUTH_BETA = "oauth-2025-04-20";
7626
7800
  DEFAULT_TIMEOUT_MS = 5e3;
@@ -8030,7 +8204,7 @@ var init_watcher_coordination = __esm({
8030
8204
  });
8031
8205
 
8032
8206
  // ../../scripts/virtual-office/code-runner/watcher-state.mjs
8033
- import { randomUUID as randomUUID2 } from "node:crypto";
8207
+ import { randomUUID as randomUUID3 } from "node:crypto";
8034
8208
  import { mkdir as mkdir2, open, readFile as readFile3, rename, unlink as unlink2 } from "node:fs/promises";
8035
8209
  import { dirname as dirname4 } from "node:path";
8036
8210
  async function readWatcherState(stateFile) {
@@ -8050,7 +8224,7 @@ async function readWatcherState(stateFile) {
8050
8224
  async function writeWatcherState(stateFile, state) {
8051
8225
  const directory = dirname4(stateFile);
8052
8226
  await mkdir2(directory, { recursive: true });
8053
- const temp = `${stateFile}.${process.pid}.${randomUUID2()}.tmp`;
8227
+ const temp = `${stateFile}.${process.pid}.${randomUUID3()}.tmp`;
8054
8228
  let handle;
8055
8229
  try {
8056
8230
  handle = await open(temp, "wx");
@@ -8261,7 +8435,7 @@ var init_pr_watcher_github = __esm({
8261
8435
  });
8262
8436
 
8263
8437
  // ../../scripts/virtual-office/code-runner/enqueue-autonomous-code-task.mjs
8264
- import { randomUUID as randomUUID3 } from "node:crypto";
8438
+ import { randomUUID as randomUUID4 } from "node:crypto";
8265
8439
  async function enqueueAutonomousCodeTask(client, task, log2 = () => {
8266
8440
  }) {
8267
8441
  const requestedBudgetUsd = task?.max_budget_usd;
@@ -8275,7 +8449,7 @@ async function enqueueAutonomousCodeTask(client, task, log2 = () => {
8275
8449
  if (typeof client?.reserveAutonomousDispatchBudget !== "function" || typeof client?.releaseAutonomousDispatchBudget !== "function") {
8276
8450
  throw new Error("autonomous dispatch admission client unavailable");
8277
8451
  }
8278
- const reservationId = randomUUID3();
8452
+ const reservationId = randomUUID4();
8279
8453
  const admission = await client.reserveAutonomousDispatchBudget({
8280
8454
  requestedBudgetUsd,
8281
8455
  reservationId,
@@ -8293,7 +8467,7 @@ var init_enqueue_autonomous_code_task = __esm({
8293
8467
  });
8294
8468
 
8295
8469
  // ../../scripts/virtual-office/code-runner/pr-watcher.mjs
8296
- import { homedir as homedir4 } from "node:os";
8470
+ import { homedir as homedir5 } from "node:os";
8297
8471
  import { join as join6 } from "node:path";
8298
8472
  function parsePrCiStatus(view) {
8299
8473
  const state = (view && typeof view.state === "string" ? view.state : "UNKNOWN").toUpperCase();
@@ -8637,7 +8811,7 @@ var init_pr_watcher = __esm({
8637
8811
  init_watcher_state();
8638
8812
  init_superseded_pr_source();
8639
8813
  init_ci_fix_prompt();
8640
- DEFAULT_STATE_FILE = join6(homedir4(), ".vo", "dispatched-prs.json");
8814
+ DEFAULT_STATE_FILE = join6(homedir5(), ".vo", "dispatched-prs.json");
8641
8815
  FAIL_CONCLUSIONS = /* @__PURE__ */ new Set([
8642
8816
  "FAILURE",
8643
8817
  "TIMED_OUT",
@@ -8867,9 +9041,9 @@ function buildControlHandler({ getStatus, requestStop, allowedOrigin }) {
8867
9041
  res.end();
8868
9042
  return;
8869
9043
  }
8870
- const path19 = String(req.url || "").split("?")[0];
9044
+ const path20 = String(req.url || "").split("?")[0];
8871
9045
  res.setHeader("content-type", "application/json");
8872
- if (req.method === "GET" && path19 === "/status") {
9046
+ if (req.method === "GET" && path20 === "/status") {
8873
9047
  let status;
8874
9048
  try {
8875
9049
  status = getStatus();
@@ -8880,7 +9054,7 @@ function buildControlHandler({ getStatus, requestStop, allowedOrigin }) {
8880
9054
  res.end(JSON.stringify({ ok: true, ...status }));
8881
9055
  return;
8882
9056
  }
8883
- if (req.method === "POST" && path19 === "/stop") {
9057
+ if (req.method === "POST" && path20 === "/stop") {
8884
9058
  if (!isControlOriginAllowed(req.headers.origin, allowedOrigin) || !req.headers["x-vo-control"]) {
8885
9059
  res.statusCode = 403;
8886
9060
  res.end(JSON.stringify({ ok: false, error: "forbidden" }));
@@ -9043,8 +9217,8 @@ var init_effort_mode_config = __esm({
9043
9217
  });
9044
9218
 
9045
9219
  // ../../scripts/virtual-office/model-registry.mjs
9046
- import fs7 from "node:fs";
9047
- import path15 from "node:path";
9220
+ import fs9 from "node:fs";
9221
+ import path16 from "node:path";
9048
9222
  import { fileURLToPath as fileURLToPath4 } from "node:url";
9049
9223
  function uniqueModels(models = []) {
9050
9224
  return [...new Set(models.map((model) => String(model || "").trim()).filter(Boolean))];
@@ -9156,9 +9330,9 @@ async function fetchGoogleModels(fetchImpl, env2 = process.env) {
9156
9330
  return (data?.models || []).map((model) => normalizeCatalogModel({ ...model, provider: "google", source: "google" })).filter(Boolean);
9157
9331
  }
9158
9332
  function readCache(cacheFile = DEFAULT_CACHE_FILE, nowMs = Date.now(), ttlMs = DEFAULT_TTL_MS3) {
9159
- if (!fs7.existsSync(cacheFile)) return null;
9333
+ if (!fs9.existsSync(cacheFile)) return null;
9160
9334
  try {
9161
- const parsed = JSON.parse(fs7.readFileSync(cacheFile, "utf-8"));
9335
+ const parsed = JSON.parse(fs9.readFileSync(cacheFile, "utf-8"));
9162
9336
  if (nowMs - Number(parsed.checkedAtMs || 0) > ttlMs) return null;
9163
9337
  if (!Array.isArray(parsed.models)) return null;
9164
9338
  return parsed;
@@ -9167,8 +9341,8 @@ function readCache(cacheFile = DEFAULT_CACHE_FILE, nowMs = Date.now(), ttlMs = D
9167
9341
  }
9168
9342
  }
9169
9343
  function writeCache(cacheFile = DEFAULT_CACHE_FILE, payload) {
9170
- fs7.mkdirSync(path15.dirname(cacheFile), { recursive: true });
9171
- fs7.writeFileSync(cacheFile, JSON.stringify(payload, null, 2));
9344
+ fs9.mkdirSync(path16.dirname(cacheFile), { recursive: true });
9345
+ fs9.writeFileSync(cacheFile, JSON.stringify(payload, null, 2));
9172
9346
  }
9173
9347
  async function fetchRegistryCatalog({
9174
9348
  fetchImpl = fetch,
@@ -9225,10 +9399,10 @@ var __dirname, ROOT, DEFAULT_CACHE_DIR, DEFAULT_CACHE_FILE, DEFAULT_TTL_MS3, ANT
9225
9399
  var init_model_registry = __esm({
9226
9400
  "../../scripts/virtual-office/model-registry.mjs"() {
9227
9401
  "use strict";
9228
- __dirname = path15.dirname(fileURLToPath4(import.meta.url));
9229
- ROOT = path15.resolve(__dirname, "..", "..");
9230
- DEFAULT_CACHE_DIR = path15.join(ROOT, ".virtual-office-cache", "model-registry");
9231
- DEFAULT_CACHE_FILE = path15.join(DEFAULT_CACHE_DIR, "catalog.json");
9402
+ __dirname = path16.dirname(fileURLToPath4(import.meta.url));
9403
+ ROOT = path16.resolve(__dirname, "..", "..");
9404
+ DEFAULT_CACHE_DIR = path16.join(ROOT, ".virtual-office-cache", "model-registry");
9405
+ DEFAULT_CACHE_FILE = path16.join(DEFAULT_CACHE_DIR, "catalog.json");
9232
9406
  DEFAULT_TTL_MS3 = 60 * 60 * 1e3;
9233
9407
  ANTHROPIC_API_VERSION = "2023-06-01";
9234
9408
  FAMILY_DEFINITIONS = {
@@ -9836,7 +10010,7 @@ var init_classify_task = __esm({
9836
10010
 
9837
10011
  // ../../scripts/virtual-office/code-runner/auto-router/effort-policy.mjs
9838
10012
  import { readFileSync as readFileSync4 } from "node:fs";
9839
- import { homedir as homedir5 } from "node:os";
10013
+ import { homedir as homedir6 } from "node:os";
9840
10014
  import { join as join7 } from "node:path";
9841
10015
  function difficultyToRung(difficulty, thresholds) {
9842
10016
  const b = thresholds.rungBounds;
@@ -9862,9 +10036,9 @@ function operatorTierToRung(tier, difficulty, thresholds) {
9862
10036
  if (tier === "best" && difficulty >= thresholds.rungBounds.R5) return "R5";
9863
10037
  return base;
9864
10038
  }
9865
- function readCodexModelsCache({ path: path19 = DEFAULT_CODEX_MODELS_CACHE, read = readFileSync4 } = {}) {
10039
+ function readCodexModelsCache({ path: path20 = DEFAULT_CODEX_MODELS_CACHE, read = readFileSync4 } = {}) {
9866
10040
  try {
9867
- const parsed = JSON.parse(read(path19, "utf8"));
10041
+ const parsed = JSON.parse(read(path20, "utf8"));
9868
10042
  return Array.isArray(parsed?.models) ? parsed : null;
9869
10043
  } catch {
9870
10044
  return null;
@@ -9914,7 +10088,7 @@ var init_effort_policy = __esm({
9914
10088
  init_meta_model_catalog();
9915
10089
  RUNG_ORDER = ["R1", "R2", "R3", "R4", "R5"];
9916
10090
  rungIndex = (rung) => RUNG_ORDER.indexOf(rung);
9917
- DEFAULT_CODEX_MODELS_CACHE = join7(homedir5(), ".codex", "models_cache.json");
10091
+ DEFAULT_CODEX_MODELS_CACHE = join7(homedir6(), ".codex", "models_cache.json");
9918
10092
  }
9919
10093
  });
9920
10094
 
@@ -10044,8 +10218,8 @@ var init_role_cost_shadow = __esm({
10044
10218
  });
10045
10219
 
10046
10220
  // ../../scripts/virtual-office/code-runner/auto-router/auto-router.mjs
10047
- import { readFileSync as readFileSync5, appendFileSync, mkdirSync as mkdirSync3 } from "node:fs";
10048
- import { homedir as homedir6 } from "node:os";
10221
+ import { readFileSync as readFileSync5, appendFileSync, mkdirSync as mkdirSync4 } from "node:fs";
10222
+ import { homedir as homedir7 } from "node:os";
10049
10223
  import { join as join8, dirname as dirname5 } from "node:path";
10050
10224
  import { fileURLToPath as fileURLToPath5 } from "node:url";
10051
10225
  function getAutoRouterMode(env2 = process.env) {
@@ -10121,15 +10295,15 @@ function formatDecisionReason(decision, maxLen = 480) {
10121
10295
  const s = `[${decision.routerVersion}] ${decision.taskClass} d=${decision.difficulty} c=${decision.confidence} \u2192 ${decision.rung}/${decision.tier}${decision.effort ? ` effort=${decision.effort}` : ""} turns=${decision.maxTurns} $${decision.maxBudgetUsd}${decision.flags.length ? ` [${decision.flags.join(",")}]` : ""} :: ${decision.reasons.join("; ")}`;
10122
10296
  return s.length > maxLen ? `${s.slice(0, maxLen - 1)}\u2026` : s;
10123
10297
  }
10124
- function appendDecisionFallback(decision, { path: path19 = DECISION_FALLBACK_PATH, append = appendFileSync, mkdir: mkdir4 = mkdirSync3, task, thresholds, roleCostInputs } = {}) {
10298
+ function appendDecisionFallback(decision, { path: path20 = DECISION_FALLBACK_PATH, append = appendFileSync, mkdir: mkdir4 = mkdirSync4, task, thresholds, roleCostInputs } = {}) {
10125
10299
  try {
10126
- mkdir4(dirname5(path19), { recursive: true });
10127
- append(path19, `${JSON.stringify(decision)}
10300
+ mkdir4(dirname5(path20), { recursive: true });
10301
+ append(path20, `${JSON.stringify(decision)}
10128
10302
  `, "utf8");
10129
10303
  if (isRouterDecision(decision)) {
10130
10304
  try {
10131
10305
  const records = buildShadowRecords({ decision, task, thresholds: thresholds || loadThresholds(), roleCostInputs });
10132
- for (const record of records) append(path19, `${JSON.stringify(record)}
10306
+ for (const record of records) append(path20, `${JSON.stringify(record)}
10133
10307
  `, "utf8");
10134
10308
  } catch {
10135
10309
  }
@@ -10147,7 +10321,7 @@ var init_auto_router = __esm({
10147
10321
  init_effort_policy();
10148
10322
  init_role_cost_shadow();
10149
10323
  ROUTER_VERSION = "0.1.0";
10150
- DECISION_FALLBACK_PATH = join8(homedir6(), ".claude", "vo-auto-router-decisions.jsonl");
10324
+ DECISION_FALLBACK_PATH = join8(homedir7(), ".claude", "vo-auto-router-decisions.jsonl");
10151
10325
  MODES = /* @__PURE__ */ new Set(["off", "shadow", "on"]);
10152
10326
  cachedThresholds = null;
10153
10327
  isRouterDecision = (d) => Boolean(d && typeof d === "object" && typeof d.taskClass === "string" && typeof d.confidence === "number");
@@ -10440,6 +10614,93 @@ var init_task_helpers = __esm({
10440
10614
  }
10441
10615
  });
10442
10616
 
10617
+ // ../../scripts/virtual-office/code-runner/swarm-admission.mjs
10618
+ function canLaunchSuccessorAgent(agent, platform = process.platform) {
10619
+ const name = typeof agent === "string" ? agent.trim() : "";
10620
+ if (!Object.prototype.hasOwnProperty.call(SUCCESSOR_LAUNCH_SHAPES, name)) return false;
10621
+ if (platform === "win32" && !SUCCESSOR_LAUNCH_SHAPES[name]) return false;
10622
+ return true;
10623
+ }
10624
+ function isSubscriptionExhausted(usage) {
10625
+ if (!usage || typeof usage !== "object") return false;
10626
+ const readings = [usage.seven_day_used_pct, usage.five_hour_used_pct, usage.monthly_used_pct];
10627
+ return readings.some((v) => typeof v === "number" && Number.isFinite(v) && v >= SUBSCRIPTION_EXHAUSTED_PCT);
10628
+ }
10629
+ function clampSubagents(requested) {
10630
+ if (!Number.isFinite(requested) || requested < 1) return 0;
10631
+ return Math.min(Math.floor(requested), MAX_BOUND_SUBAGENTS);
10632
+ }
10633
+ function usageFor(accountUsage, agent) {
10634
+ if (!Array.isArray(accountUsage)) return null;
10635
+ return accountUsage.find((row) => row && row.agent === agent) ?? null;
10636
+ }
10637
+ function exhaustedAgents(availableAgents, accountUsage) {
10638
+ if (!Array.isArray(availableAgents)) return [];
10639
+ return availableAgents.filter((row) => row && row.installed === true && row.authenticated === true && row.auth_tier === AUTH_TIER_SUBSCRIPTION && isSubscriptionExhausted(usageFor(accountUsage, row.agent))).map((row) => row.agent);
10640
+ }
10641
+ function resolveRunnerSwarmBinding({
10642
+ swarmId,
10643
+ agent,
10644
+ availableAgents,
10645
+ accountUsage = [],
10646
+ requestedSubagents = MAX_BOUND_SUBAGENTS,
10647
+ nowIso,
10648
+ platform = process.platform
10649
+ } = {}) {
10650
+ const id = typeof swarmId === "string" ? swarmId.trim() : "";
10651
+ const boundAgent = typeof agent === "string" ? agent.trim() : "";
10652
+ if (id.length === 0 || boundAgent.length === 0) return null;
10653
+ if (!canLaunchSuccessorAgent(boundAgent, platform)) return null;
10654
+ const budget = clampSubagents(requestedSubagents);
10655
+ if (budget === 0) return null;
10656
+ const rows = Array.isArray(availableAgents) ? availableAgents : [];
10657
+ const row = rows.find((r) => r && r.agent === boundAgent) ?? null;
10658
+ if (!row || row.installed !== true || row.authenticated !== true) return null;
10659
+ const tier = AUTH_TIER_TO_SWARM_TIER[row.auth_tier];
10660
+ if (!tier) return null;
10661
+ const exhausted = exhaustedAgents(rows, accountUsage);
10662
+ const boundExhausted = tier === "tier1_subscription" && isSubscriptionExhausted(usageFor(accountUsage, boundAgent));
10663
+ const effectiveBudget = boundExhausted ? Math.min(budget, EXHAUSTED_SUBAGENT_BUDGET) : budget;
10664
+ const basis = `code-task dispatch admitted on '${boundAgent}' at auth tier '${row.auth_tier}' (runner-local capability probe)`;
10665
+ return {
10666
+ schema_version: 1,
10667
+ swarm_id: id,
10668
+ tier,
10669
+ agent: boundAgent,
10670
+ reason: boundExhausted ? `${basis}; that subscription window is >=${SUBSCRIPTION_EXHAUSTED_PCT}% spent, so the fan-out is bound at a REDUCED ceiling of ${effectiveBudget} instead of being left unbound and uncapped` : basis,
10671
+ exhausted_agents: exhausted,
10672
+ subagent_budget: effectiveBudget,
10673
+ // Never a platform-billed fan-out in this lane — see the module header.
10674
+ spend_cap_usd: null,
10675
+ resolved_at: typeof nowIso === "string" && nowIso ? nowIso : (/* @__PURE__ */ new Date()).toISOString()
10676
+ };
10677
+ }
10678
+ function mintSwarmTierBindingEnv(input) {
10679
+ const binding = resolveRunnerSwarmBinding(input);
10680
+ if (!binding) return {};
10681
+ return { [SWARM_TIER_BINDING_ENV]: JSON.stringify(binding) };
10682
+ }
10683
+ var SWARM_TIER_BINDING_ENV, MAX_BOUND_SUBAGENTS, SUBSCRIPTION_EXHAUSTED_PCT, EXHAUSTED_SUBAGENT_BUDGET, SUCCESSOR_LAUNCH_SHAPES, AUTH_TIER_TO_SWARM_TIER;
10684
+ var init_swarm_admission = __esm({
10685
+ "../../scripts/virtual-office/code-runner/swarm-admission.mjs"() {
10686
+ "use strict";
10687
+ init_agent_auth_tier();
10688
+ SWARM_TIER_BINDING_ENV = "VO_SWARM_TIER_BINDING";
10689
+ MAX_BOUND_SUBAGENTS = 20;
10690
+ SUBSCRIPTION_EXHAUSTED_PCT = 95;
10691
+ EXHAUSTED_SUBAGENT_BUDGET = 1;
10692
+ SUCCESSOR_LAUNCH_SHAPES = Object.freeze({
10693
+ claude: true,
10694
+ codex: false
10695
+ });
10696
+ AUTH_TIER_TO_SWARM_TIER = Object.freeze({
10697
+ [AUTH_TIER_SUBSCRIPTION]: "tier1_subscription",
10698
+ [AUTH_TIER_LOCAL]: "tier1_local",
10699
+ [AUTH_TIER_API_KEY]: "tier2_user_key"
10700
+ });
10701
+ }
10702
+ });
10703
+
10443
10704
  // ../../scripts/virtual-office/code-runner/agent-process-env.mjs
10444
10705
  function safeIdentityPart(value, fallback) {
10445
10706
  const normalized = String(value || "").trim().replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
@@ -10452,8 +10713,14 @@ function safeBaseEnv(env2 = {}) {
10452
10713
  }
10453
10714
  return result;
10454
10715
  }
10455
- function buildAgentProcessEnv(env2, { agent = "agent", runnerId = "vo-runner", taskId = "task", githubReadToken = null } = {}) {
10716
+ function buildAgentProcessEnv(env2, { agent = "agent", runnerId = "vo-runner", taskId = "task", githubReadToken = null, swarmAdmission = null } = {}) {
10456
10717
  const base = safeBaseEnv(env2);
10718
+ if (swarmAdmission) {
10719
+ for (const key of Object.keys(base)) {
10720
+ if (key.toUpperCase() === SWARM_TIER_BINDING_ENV) delete base[key];
10721
+ }
10722
+ Object.assign(base, mintSwarmTierBindingEnv({ ...swarmAdmission, agent, swarmId: taskId }));
10723
+ }
10457
10724
  if (typeof githubReadToken === "string" && githubReadToken) {
10458
10725
  base.GH_TOKEN = githubReadToken;
10459
10726
  base.GITHUB_TOKEN = githubReadToken;
@@ -10471,6 +10738,7 @@ var SAFE_ENV_NAMES;
10471
10738
  var init_agent_process_env = __esm({
10472
10739
  "../../scripts/virtual-office/code-runner/agent-process-env.mjs"() {
10473
10740
  "use strict";
10741
+ init_swarm_admission();
10474
10742
  SAFE_ENV_NAMES = /* @__PURE__ */ new Set([
10475
10743
  "AGENT_ID",
10476
10744
  "APPDATA",
@@ -10511,7 +10779,31 @@ var init_agent_process_env = __esm({
10511
10779
  // it. #9242 added VO_RUNNER_PREFER_KEY without this line, which left the
10512
10780
  // escape hatch inert — an operator who set it still got the subscription.
10513
10781
  "VO_RUNNER_PREFER_KEY",
10514
- "VO_RUNNER_CLAUDE_PREFER_KEY"
10782
+ "VO_RUNNER_CLAUDE_PREFER_KEY",
10783
+ // The swarm tier binding (SWARM_TIER_BINDING_ENV in
10784
+ // packages/vo-mcp/src/swarm/tier-binding.ts). A fan-out resolves its billing
10785
+ // tier ONCE at admission and exports the binding so every subagent inherits
10786
+ // the same answer instead of re-resolving its own. This line is what lets it
10787
+ // cross the process boundary at all: the daemon builds each child env with
10788
+ // buildAgentProcessEnv(process.env) at
10789
+ // scripts/virtual-office/code-runner-daemon.mjs:117 (sibling dir, not this
10790
+ // one), so a name absent from this set is stripped and the binding binds
10791
+ // NOTHING —
10792
+ // exactly how #9242's VO_RUNNER_PREFER_KEY shipped inert until #9247.
10793
+ //
10794
+ // NOT a credential and NOT an authorization input: it names a tier, it never
10795
+ // grants one, and it carries no key material (see the module's header rule).
10796
+ "VO_SWARM_TIER_BINDING",
10797
+ // Where the swarm SPAWN LEDGER lives (SWARM_LEDGER_DIR_ENV in
10798
+ // packages/vo-mcp/src/swarm/spawn-ledger.ts). The ledger is the durable,
10799
+ // host-shared counter that bounds the TOTAL spawns under one swarm_id; the
10800
+ // binding above only bounds the depth of one chain. If a parent's override
10801
+ // were stripped here, the child would ledger into a DIFFERENT directory,
10802
+ // claim slot 0 again, and the shared ceiling would silently degrade back to a
10803
+ // per-process quota — which is precisely the defect the ledger closes.
10804
+ //
10805
+ // A path, not a credential. Absent means the default ~/.vo/swarm-ledger.
10806
+ "VO_SWARM_LEDGER_DIR"
10515
10807
  ]);
10516
10808
  }
10517
10809
  });
@@ -11002,9 +11294,9 @@ var init_inference_task_runner = __esm({
11002
11294
  });
11003
11295
 
11004
11296
  // ../../scripts/virtual-office/code-runner/isolation-audit.mjs
11005
- import fs8 from "node:fs";
11297
+ import fs10 from "node:fs";
11006
11298
  import fsp11 from "node:fs/promises";
11007
- import path16 from "node:path";
11299
+ import path17 from "node:path";
11008
11300
  async function defaultRun(command, args, cwd, options = {}) {
11009
11301
  return runProcess2(command, args, { cwd, timeout: 6e4, ...options });
11010
11302
  }
@@ -11017,7 +11309,7 @@ async function canonicalRootForWorktree(worktreeDir, run) {
11017
11309
  "--path-format=absolute",
11018
11310
  "--git-common-dir"
11019
11311
  ])).trim();
11020
- const root = path16.dirname(commonDir);
11312
+ const root = path17.dirname(commonDir);
11021
11313
  return samePath2(root, worktreeDir) ? null : root;
11022
11314
  }
11023
11315
  async function snapshot(root, run) {
@@ -11059,21 +11351,21 @@ async function changedPaths(root, run) {
11059
11351
  }
11060
11352
  async function quarantineCanonicalWrites({ baseline, worktreeDir, taskId, run, now }) {
11061
11353
  const paths = await changedPaths(baseline.root, run);
11062
- const quarantineDir = path16.join(
11063
- path16.dirname(worktreeDir),
11354
+ const quarantineDir = path17.join(
11355
+ path17.dirname(worktreeDir),
11064
11356
  ".canonical-recovery",
11065
11357
  `${String(taskId || "unknown").replace(/[^a-z0-9-]/gi, "-")}-${now().toISOString().replace(/[:.]/g, "-")}`
11066
11358
  );
11067
11359
  await fsp11.mkdir(quarantineDir, { recursive: true });
11068
11360
  const patch = await git2(run, baseline.root, ["diff", "--binary", "HEAD"], { raw: true });
11069
- await fsp11.writeFile(path16.join(quarantineDir, "tracked.patch"), patch, "utf8");
11361
+ await fsp11.writeFile(path17.join(quarantineDir, "tracked.patch"), patch, "utf8");
11070
11362
  for (const relative of paths.untracked) {
11071
- const source = path16.join(baseline.root, relative);
11072
- const target = path16.join(quarantineDir, "untracked", relative);
11073
- await fsp11.mkdir(path16.dirname(target), { recursive: true });
11363
+ const source = path17.join(baseline.root, relative);
11364
+ const target = path17.join(quarantineDir, "untracked", relative);
11365
+ await fsp11.mkdir(path17.dirname(target), { recursive: true });
11074
11366
  await fsp11.copyFile(source, target);
11075
11367
  }
11076
- await fsp11.writeFile(path16.join(quarantineDir, "manifest.json"), `${JSON.stringify({
11368
+ await fsp11.writeFile(path17.join(quarantineDir, "manifest.json"), `${JSON.stringify({
11077
11369
  taskId,
11078
11370
  canonicalRoot: baseline.root,
11079
11371
  canonicalHead: baseline.head,
@@ -11095,9 +11387,9 @@ async function restoreExactCanonicalPaths(baseline, evidence, run) {
11095
11387
  ]);
11096
11388
  }
11097
11389
  for (const relative of evidence.untracked) {
11098
- const target = path16.resolve(baseline.root, relative);
11099
- const prefix = `${path16.resolve(baseline.root)}${path16.sep}`;
11100
- if (!target.startsWith(prefix) || !fs8.existsSync(target)) continue;
11390
+ const target = path17.resolve(baseline.root, relative);
11391
+ const prefix = `${path17.resolve(baseline.root)}${path17.sep}`;
11392
+ if (!target.startsWith(prefix) || !fs10.existsSync(target)) continue;
11101
11393
  await fsp11.rm(target, { force: true });
11102
11394
  }
11103
11395
  }
@@ -11133,7 +11425,7 @@ var init_isolation_audit = __esm({
11133
11425
  init_process_runner2();
11134
11426
  splitZ2 = (value) => String(value || "").split("\0").map((item) => item.trim()).filter(Boolean);
11135
11427
  samePath2 = (left, right) => {
11136
- const [a, b] = [left, right].map((value) => path16.resolve(value));
11428
+ const [a, b] = [left, right].map((value) => path17.resolve(value));
11137
11429
  return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
11138
11430
  };
11139
11431
  }
@@ -11493,7 +11785,7 @@ var init_publication_outcome = __esm({
11493
11785
 
11494
11786
  // ../../scripts/virtual-office/code-runner/committed-scratch-cleanup.mjs
11495
11787
  import fsp12 from "node:fs/promises";
11496
- import path17 from "node:path";
11788
+ import path18 from "node:path";
11497
11789
  function defaultRun2(command, args, cwd, options = {}) {
11498
11790
  return runProcess2(command, args, { cwd, ...options });
11499
11791
  }
@@ -11501,13 +11793,13 @@ async function resolveSafeScratchTarget(worktreeDir, file) {
11501
11793
  if (!isAgentScratch(file)) {
11502
11794
  throw new Error(`refusing to remove non-scratch publication path: ${file}`);
11503
11795
  }
11504
- const root = path17.resolve(worktreeDir);
11505
- const target = path17.resolve(root, file);
11506
- const relative = path17.relative(root, target);
11507
- if (!relative || relative.startsWith(`..${path17.sep}`) || path17.isAbsolute(relative)) {
11796
+ const root = path18.resolve(worktreeDir);
11797
+ const target = path18.resolve(root, file);
11798
+ const relative = path18.relative(root, target);
11799
+ if (!relative || relative.startsWith(`..${path18.sep}`) || path18.isAbsolute(relative)) {
11508
11800
  throw new Error(`refusing to remove publication scratch outside worktree: ${file}`);
11509
11801
  }
11510
- for (let cursor = target; cursor !== root; cursor = path17.dirname(cursor)) {
11802
+ for (let cursor = target; cursor !== root; cursor = path18.dirname(cursor)) {
11511
11803
  try {
11512
11804
  if ((await fsp12.lstat(cursor)).isSymbolicLink()) {
11513
11805
  throw new Error(`refusing to follow symlink while removing publication scratch: ${file}`);
@@ -11627,9 +11919,9 @@ var init_publication_scope = __esm({
11627
11919
  });
11628
11920
 
11629
11921
  // ../../scripts/virtual-office/code-runner/recovery-ledger.mjs
11630
- import fs9 from "node:fs";
11922
+ import fs11 from "node:fs";
11631
11923
  import fsp13 from "node:fs/promises";
11632
- import path18 from "node:path";
11924
+ import path19 from "node:path";
11633
11925
  function recoveryTaskId(prompt) {
11634
11926
  const match = String(prompt || "").match(/VO_RECOVERY_FROM_CODE_TASK:\s*([0-9a-f-]{36})/i);
11635
11927
  return match ? match[1].toLowerCase() : null;
@@ -11643,10 +11935,10 @@ function cloneLeaf(repo) {
11643
11935
  function recoveryLedgerCandidates(repo, clonesRoot2) {
11644
11936
  const leaf = cloneLeaf(repo);
11645
11937
  if (!leaf || !clonesRoot2) return [];
11646
- const canonical = path18.join(clonesRoot2, leaf);
11938
+ const canonical = path19.join(clonesRoot2, leaf);
11647
11939
  return [
11648
- path18.join(clonesRoot2, ".agent-worktrees", leaf, "recovery-ledger.jsonl"),
11649
- path18.join(canonical, ".agent-worktrees", "recovery-ledger.jsonl")
11940
+ path19.join(clonesRoot2, ".agent-worktrees", leaf, "recovery-ledger.jsonl"),
11941
+ path19.join(canonical, ".agent-worktrees", "recovery-ledger.jsonl")
11650
11942
  ];
11651
11943
  }
11652
11944
  async function readLedger(file, readFile5) {
@@ -11665,7 +11957,7 @@ async function readLedger(file, readFile5) {
11665
11957
  async function findPreservedRecovery(task, {
11666
11958
  clonesRoot: clonesRoot2 = process.env.VO_CODE_RUNNER_CLONES_ROOT || "",
11667
11959
  readFile: readFile5 = fsp13.readFile,
11668
- exists = fs9.existsSync
11960
+ exists = fs11.existsSync
11669
11961
  } = {}) {
11670
11962
  const resumedFrom = /^[0-9a-f-]{36}$/iu.test(String(task.resumed_from || "")) ? String(task.resumed_from).toLowerCase() : null;
11671
11963
  const originalTaskId = recoveryTaskId(task.prompt) ?? resumedFrom;
@@ -11979,7 +12271,7 @@ var init_cancellation_probe = __esm({
11979
12271
  });
11980
12272
 
11981
12273
  // ../../scripts/virtual-office/code-runner/detached-economics-spool.mjs
11982
- import { homedir as homedir7 } from "node:os";
12274
+ import { homedir as homedir8 } from "node:os";
11983
12275
  import { dirname as dirname6, join as join9 } from "node:path";
11984
12276
  import { mkdir as mkdir3, readFile as readFile4, rename as rename2, writeFile as writeFile3 } from "node:fs/promises";
11985
12277
  function withLock(operation) {
@@ -12044,13 +12336,13 @@ var DEFAULT_FILE, serialized;
12044
12336
  var init_detached_economics_spool = __esm({
12045
12337
  "../../scripts/virtual-office/code-runner/detached-economics-spool.mjs"() {
12046
12338
  "use strict";
12047
- DEFAULT_FILE = join9(homedir7(), ".vo", "detached-run-economics.json");
12339
+ DEFAULT_FILE = join9(homedir8(), ".vo", "detached-run-economics.json");
12048
12340
  serialized = Promise.resolve();
12049
12341
  }
12050
12342
  });
12051
12343
 
12052
12344
  // ../../scripts/virtual-office/code-runner/killed-run-outcome.mjs
12053
- import { randomUUID as randomUUID4 } from "node:crypto";
12345
+ import { randomUUID as randomUUID5 } from "node:crypto";
12054
12346
  async function handleKilledRun({
12055
12347
  client,
12056
12348
  id,
@@ -12087,7 +12379,7 @@ async function handleKilledRun({
12087
12379
  };
12088
12380
  }
12089
12381
  if (reason === "claim_authority_changed") {
12090
- const occurrenceId = randomUUID4();
12382
+ const occurrenceId = randomUUID5();
12091
12383
  const economics = {
12092
12384
  occurrence_id: occurrenceId,
12093
12385
  runner_id: runnerId,
@@ -12384,12 +12676,12 @@ var code_runner_daemon_exports = {};
12384
12676
  __export(code_runner_daemon_exports, {
12385
12677
  main: () => main
12386
12678
  });
12387
- import { randomUUID as randomUUID5 } from "node:crypto";
12679
+ import { randomUUID as randomUUID6 } from "node:crypto";
12388
12680
  import { fileURLToPath as fileURLToPath6 } from "node:url";
12389
12681
  function log(msg) {
12390
12682
  console.log(`[code-runner ${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}`);
12391
12683
  }
12392
- async function processOneTask(client, task, cfg, runnerInstanceId) {
12684
+ async function processOneTask(client, task, cfg, runnerInstanceId, swarmAdmission = null) {
12393
12685
  const id = task.code_task_id;
12394
12686
  let worktreeName = "";
12395
12687
  let preserveReason = null;
@@ -12439,7 +12731,8 @@ async function processOneTask(client, task, cfg, runnerInstanceId) {
12439
12731
  model,
12440
12732
  effort: effectiveEffort,
12441
12733
  maxBudgetUsd: sel.agent === "claude" ? effectiveMaxBudgetUsd : void 0,
12442
- env: buildAgentProcessEnv(process.env, { agent: sel.agent, runnerId: cfg.runnerId, taskId: id, githubReadToken: agentGithubReadToken }),
12734
+ env: buildAgentProcessEnv(process.env, { agent: sel.agent, runnerId: cfg.runnerId, taskId: id, githubReadToken: agentGithubReadToken, swarmAdmission }),
12735
+ // swarmAdmission mints VO_SWARM_TIER_BINDING: ONE tier decision for this task's whole agent tree
12443
12736
  sandbox,
12444
12737
  onProgress: (text, checkpoint) => {
12445
12738
  const usage = checkpoint?.tokenUsage ? { token_usage: checkpoint.tokenUsage } : {};
@@ -12613,7 +12906,7 @@ Closes #${publicationTarget.supersedesPrNumber}` : "";
12613
12906
  async function main({ env: env2 = process.env, once: once2 = false } = {}) {
12614
12907
  const cfg = loadCodeRunnerConfig(env2, { log });
12615
12908
  await sweepStaleTaskAttachmentDirectories().catch((error) => log(`stale attachment cleanup failed: ${error.message}`));
12616
- const runnerInstanceId = randomUUID5();
12909
+ const runnerInstanceId = randomUUID6();
12617
12910
  const client = createControlPlaneClient({
12618
12911
  env: env2,
12619
12912
  runnerId: cfg.runnerId,
@@ -12725,7 +13018,7 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
12725
13018
  }
12726
13019
  log(`claimed task ${task.code_task_id} (${task.repo})`);
12727
13020
  active += 1;
12728
- const runTask = task.kind === "inference" ? processInferenceTask(client, task, cfg, { safeProgress, runnerStagePatch, log }) : processOneTask(client, task, cfg, runnerInstanceId);
13021
+ const runTask = task.kind === "inference" ? processInferenceTask(client, task, cfg, { safeProgress, runnerStagePatch, log }) : processOneTask(client, task, cfg, runnerInstanceId, { availableAgents: claimAgents.availableAgents, accountUsage: accountUsage.get() });
12729
13022
  const done = runTask.catch(async (error) => {
12730
13023
  log(`task ${task.code_task_id} unhandled runner error: ${error.message}`);
12731
13024
  if (task.kind === "inference") await deliverTerminalRun({
@@ -12942,6 +13235,98 @@ function pairedOperatorScope(readiness) {
12942
13235
  return readiness?.ok === true && readiness?.paired === true && typeof readiness.operatorId === "string" && readiness.operatorId.trim() ? readiness.operatorId.trim() : null;
12943
13236
  }
12944
13237
 
13238
+ // src/runner/root-config.mjs
13239
+ import { closeSync, existsSync, mkdirSync, openSync, unlinkSync } from "node:fs";
13240
+ import { homedir } from "node:os";
13241
+ import { posix, win32 } from "node:path";
13242
+ import { randomUUID } from "node:crypto";
13243
+ var APP_IDENTIFIER = "ai.algosuite.vo-runner";
13244
+ var CLONES_DIR = "clones";
13245
+ function pathsFor(platform) {
13246
+ return platform === "win32" ? win32 : posix;
13247
+ }
13248
+ function absoluteOrNull(value, pathApi) {
13249
+ const normalized = String(value || "").trim();
13250
+ return normalized && pathApi.isAbsolute(normalized) ? pathApi.resolve(normalized) : null;
13251
+ }
13252
+ function defaultClonesRoot({
13253
+ platform = process.platform,
13254
+ env: env2 = process.env,
13255
+ home = homedir()
13256
+ } = {}) {
13257
+ const pathApi = pathsFor(platform);
13258
+ if (platform === "win32") {
13259
+ const appData = absoluteOrNull(env2.APPDATA, pathApi);
13260
+ return appData ? pathApi.join(appData, APP_IDENTIFIER, CLONES_DIR) : null;
13261
+ }
13262
+ const absoluteHome = absoluteOrNull(home, pathApi);
13263
+ if (!absoluteHome) return null;
13264
+ if (platform === "darwin") {
13265
+ return pathApi.join(absoluteHome, "Library", "Application Support", APP_IDENTIFIER, CLONES_DIR);
13266
+ }
13267
+ const xdg = absoluteOrNull(env2.XDG_CONFIG_HOME, pathApi);
13268
+ return pathApi.join(xdg || pathApi.join(absoluteHome, ".config"), APP_IDENTIFIER, CLONES_DIR);
13269
+ }
13270
+ function findGitRoot(cwd, { platform, exists = existsSync }) {
13271
+ const pathApi = pathsFor(platform);
13272
+ let cursor = pathApi.resolve(cwd);
13273
+ for (; ; ) {
13274
+ if (exists(pathApi.join(cursor, ".git"))) return cursor;
13275
+ const parent = pathApi.dirname(cursor);
13276
+ if (parent === cursor) return null;
13277
+ cursor = parent;
13278
+ }
13279
+ }
13280
+ function resolveRunnerRootConfig({
13281
+ env: env2 = process.env,
13282
+ cwd = process.cwd(),
13283
+ platform = process.platform,
13284
+ home = homedir(),
13285
+ exists = existsSync
13286
+ } = {}) {
13287
+ const pathApi = pathsFor(platform);
13288
+ const explicitRepoValue = String(env2.VO_CODE_RUNNER_REPO || "").trim();
13289
+ const explicitClonesValue = String(env2.VO_CODE_RUNNER_CLONES_ROOT || "").trim();
13290
+ if (explicitRepoValue && !pathApi.isAbsolute(explicitRepoValue)) {
13291
+ throw new Error(`VO_CODE_RUNNER_REPO must be an absolute path (got '${explicitRepoValue}')`);
13292
+ }
13293
+ if (explicitClonesValue && !pathApi.isAbsolute(explicitClonesValue)) {
13294
+ throw new Error(`VO_CODE_RUNNER_CLONES_ROOT must be an absolute path (got '${explicitClonesValue}')`);
13295
+ }
13296
+ const repoRoot2 = explicitRepoValue ? pathApi.resolve(explicitRepoValue) : findGitRoot(cwd, { platform, exists });
13297
+ const clonesRoot2 = explicitClonesValue ? pathApi.resolve(explicitClonesValue) : repoRoot2 ? null : defaultClonesRoot({ platform, env: env2, home });
13298
+ if (!repoRoot2 && !clonesRoot2) {
13299
+ throw new Error(
13300
+ "No safe runner root is available. Set VO_CODE_RUNNER_REPO or VO_CODE_RUNNER_CLONES_ROOT to an absolute path."
13301
+ );
13302
+ }
13303
+ return { repoRoot: repoRoot2, clonesRoot: clonesRoot2 };
13304
+ }
13305
+ function runnerWorkingDirectory({ repoRoot: repoRoot2, clonesRoot: clonesRoot2 }) {
13306
+ const root = repoRoot2 || clonesRoot2;
13307
+ if (!root) throw new Error("Runner root configuration has no working directory");
13308
+ return root;
13309
+ }
13310
+ function assertWritableRunnerDirectory(root) {
13311
+ mkdirSync(root, { recursive: true });
13312
+ const probe = pathsFor(process.platform).join(root, `.vo-runner-write-probe-${process.pid}-${randomUUID()}`);
13313
+ let handle;
13314
+ try {
13315
+ handle = openSync(probe, "wx", 384);
13316
+ } catch (error) {
13317
+ throw new Error(
13318
+ `Runner clones root is not writable: ${root} (${error instanceof Error ? error.message : String(error)})`,
13319
+ { cause: error }
13320
+ );
13321
+ } finally {
13322
+ if (handle !== void 0) closeSync(handle);
13323
+ try {
13324
+ unlinkSync(probe);
13325
+ } catch {
13326
+ }
13327
+ }
13328
+ }
13329
+
12945
13330
  // src/runner-cli.mjs
12946
13331
  var DEFAULT_CONTROL_PLANE_URL = "https://vo-control-plane-bzjphrajaq-uc.a.run.app";
12947
13332
  function packageVersion() {
@@ -12956,11 +13341,45 @@ if (process.argv.includes("--version") || process.argv.includes("-v")) {
12956
13341
  `);
12957
13342
  process.exit(0);
12958
13343
  }
13344
+ var USAGE = `vo-mcp runner \u2014 bring-your-own agent runner daemon
13345
+
13346
+ Usage:
13347
+ vo-mcp runner poll for tasks forever (default)
13348
+ vo-mcp runner --once claim + run one task, then exit
13349
+ vo-mcp runner --status print pairing/readiness JSON, then exit
13350
+ vo-mcp runner --version print the version, then exit
13351
+ vo-mcp runner --help print this help, then exit
13352
+
13353
+ Env (all optional):
13354
+ VO_CONTROL_PLANE_ADMIN_TOKEN explicit bearer (wins over the stored credential)
13355
+ VO_CONTROL_PLANE_URL control-plane base URL (default: production)
13356
+ VO_CODE_RUNNER_REPO path to your repo clone (default: Git cwd)
13357
+ VO_CODE_RUNNER_CLONES_ROOT managed clone directory (default outside Git cwd)
13358
+ VO_CODE_RUNNER_OPERATOR_IDS operator id(s) this runner serves (your own)
13359
+ VO_CODE_RUNNER_REPOS owner/name repo(s) this runner builds
13360
+
13361
+ Pair this computer first with \`vo-mcp login\`.
13362
+ `;
13363
+ if (process.argv.includes("--help") || process.argv.includes("-h")) {
13364
+ process.stdout.write(USAGE);
13365
+ process.exit(0);
13366
+ }
12959
13367
  var { readStoredCredential: readStoredCredential2 } = await Promise.resolve().then(() => (init_credential_store(), credential_store_exports));
12960
13368
  var storedCredential = readStoredCredential2();
12961
13369
  var explicitAdminToken = process.env.VO_CONTROL_PLANE_ADMIN_TOKEN?.trim();
12962
13370
  var token = explicitAdminToken || storedCredential?.vo_credential;
12963
13371
  var statusOnly = process.argv.includes("--status");
13372
+ function configureRunnerFilesystem() {
13373
+ const config = resolveRunnerRootConfig();
13374
+ if (config.repoRoot) process.env.VO_CODE_RUNNER_REPO = config.repoRoot;
13375
+ else delete process.env.VO_CODE_RUNNER_REPO;
13376
+ if (config.clonesRoot) {
13377
+ assertWritableRunnerDirectory(config.clonesRoot);
13378
+ process.env.VO_CODE_RUNNER_CLONES_ROOT = config.clonesRoot;
13379
+ } else delete process.env.VO_CODE_RUNNER_CLONES_ROOT;
13380
+ process.chdir(runnerWorkingDirectory(config));
13381
+ return config;
13382
+ }
12964
13383
  if (statusOnly) {
12965
13384
  if (!storedCredential?.vo_credential) {
12966
13385
  process.stdout.write(`${JSON.stringify({
@@ -12969,6 +13388,7 @@ if (statusOnly) {
12969
13388
  operatorId: null,
12970
13389
  tenantId: null,
12971
13390
  githubReady: null,
13391
+ filesystemReady: null,
12972
13392
  error: "credential_missing",
12973
13393
  message: "This computer is not paired. Pair it to your AlgoHQ account first."
12974
13394
  })}
@@ -12979,9 +13399,28 @@ if (statusOnly) {
12979
13399
  controlPlaneUrl: process.env.VO_CONTROL_PLANE_URL || DEFAULT_CONTROL_PLANE_URL,
12980
13400
  token: storedCredential.vo_credential
12981
13401
  });
12982
- process.stdout.write(`${JSON.stringify(readiness)}
13402
+ if (!readiness.ok) {
13403
+ process.stdout.write(`${JSON.stringify({ ...readiness, filesystemReady: null })}
12983
13404
  `);
12984
- process.exit(readiness.ok ? 0 : 1);
13405
+ process.exit(1);
13406
+ }
13407
+ try {
13408
+ configureRunnerFilesystem();
13409
+ process.stdout.write(`${JSON.stringify({ ...readiness, filesystemReady: true })}
13410
+ `);
13411
+ process.exit(0);
13412
+ } catch (error) {
13413
+ const detail = error instanceof Error ? error.message : String(error);
13414
+ process.stdout.write(`${JSON.stringify({
13415
+ ...readiness,
13416
+ ok: false,
13417
+ filesystemReady: false,
13418
+ error: "filesystem_not_ready",
13419
+ message: `Filesystem readiness failed: ${detail}`
13420
+ })}
13421
+ `);
13422
+ process.exit(1);
13423
+ }
12985
13424
  }
12986
13425
  if (!token) {
12987
13426
  console.error("[vo-mcp runner] No credential found. Run `vo-mcp login` first.");
@@ -13006,6 +13445,13 @@ if (!explicitAdminToken) {
13006
13445
  process.exit(1);
13007
13446
  }
13008
13447
  }
13448
+ var rootConfig;
13449
+ try {
13450
+ rootConfig = configureRunnerFilesystem();
13451
+ } catch (error) {
13452
+ console.error(`[vo-mcp runner] Filesystem readiness failed: ${error instanceof Error ? error.message : String(error)}`);
13453
+ process.exit(1);
13454
+ }
13009
13455
  var env = {
13010
13456
  ...process.env,
13011
13457
  VO_CONTROL_PLANE_ADMIN_TOKEN: token,
@@ -13014,7 +13460,8 @@ var env = {
13014
13460
  // VO_CODE_RUNNER_VERSION, which a desktop host may set to its own shell
13015
13461
  // release even after runner-control updates this package in place.
13016
13462
  VO_CODE_RUNNER_DAEMON_VERSION: `vo-mcp/${packageVersion()}`,
13017
- VO_CODE_RUNNER_REPO: process.env.VO_CODE_RUNNER_REPO || process.cwd(),
13463
+ ...rootConfig.repoRoot ? { VO_CODE_RUNNER_REPO: rootConfig.repoRoot } : {},
13464
+ ...rootConfig.clonesRoot ? { VO_CODE_RUNNER_CLONES_ROOT: rootConfig.clonesRoot } : {},
13018
13465
  // Server-resolved identity replaces stale/hardcoded desktop scope. Besides
13019
13466
  // filtering heartbeat/claims, this keeps GitHub App auth required for the
13020
13467
  // entire task lifecycle (never ambient-gh fallback after a paired preflight).