@algosuite/vo-mcp 0.2.0-beta.72 → 0.2.0-beta.74

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.
@@ -314,21 +314,21 @@ function backupConfigOnce(configPath) {
314
314
  copyFileSync(configPath, backupPath);
315
315
  return backupPath;
316
316
  }
317
- function writeFileAtomic(path24, content) {
318
- sweepStaleTempFiles(path24);
319
- const temp = `${path24}.vo-mcp-tmp-${process.pid}-${Date.now()}`;
317
+ function writeFileAtomic(path25, content) {
318
+ sweepStaleTempFiles(path25);
319
+ const temp = `${path25}.vo-mcp-tmp-${process.pid}-${Date.now()}`;
320
320
  try {
321
321
  writeFileSync2(temp, content, { encoding: "utf8", mode: 384 });
322
- if (existsSync3(path24)) {
322
+ if (existsSync3(path25)) {
323
323
  try {
324
- chmodSync2(temp, statSync(path24).mode & 511);
324
+ chmodSync2(temp, statSync(path25).mode & 511);
325
325
  } catch {
326
326
  }
327
327
  }
328
328
  let lastErr = null;
329
329
  for (let attempt = 0; attempt < RENAME_RETRIES; attempt += 1) {
330
330
  try {
331
- renameSync(temp, path24);
331
+ renameSync(temp, path25);
332
332
  return;
333
333
  } catch (err) {
334
334
  lastErr = err;
@@ -337,7 +337,7 @@ function writeFileAtomic(path24, content) {
337
337
  sleepSync(RENAME_RETRY_MS);
338
338
  }
339
339
  }
340
- writeFileSync2(path24, content, "utf8");
340
+ writeFileSync2(path25, content, "utf8");
341
341
  try {
342
342
  unlinkSync2(temp);
343
343
  } catch {
@@ -503,8 +503,8 @@ function tablePath(line) {
503
503
  function tableSections(lines) {
504
504
  const starts = [];
505
505
  for (let index = 0; index < lines.length; index += 1) {
506
- const path24 = tablePath(lines[index] ?? "");
507
- if (path24) starts.push({ path: path24, start: index });
506
+ const path25 = tablePath(lines[index] ?? "");
507
+ if (path25) starts.push({ path: path25, start: index });
508
508
  }
509
509
  return starts.map((section, index) => ({
510
510
  ...section,
@@ -696,11 +696,11 @@ function resolveLinuxConfigHome(home, env2) {
696
696
  const configured = env2["XDG_CONFIG_HOME"]?.trim();
697
697
  return configured && isAbsolute2(configured) ? configured : join5(home, ".config");
698
698
  }
699
- function launcherIsCurrent(path24, desiredContent, label, log2) {
700
- if (!existsSync6(path24)) return false;
701
- if (readFileSync5(path24, "utf8") === desiredContent) return true;
702
- const backupPath = `${path24}.backup-${Date.now()}`;
703
- copyFileSync2(path24, backupPath);
699
+ function launcherIsCurrent(path25, desiredContent, label, log2) {
700
+ if (!existsSync6(path25)) return false;
701
+ if (readFileSync5(path25, "utf8") === desiredContent) return true;
702
+ const backupPath = `${path25}.backup-${Date.now()}`;
703
+ copyFileSync2(path25, backupPath);
704
704
  log2(` Backed up existing ${label} to: ${backupPath}`);
705
705
  return false;
706
706
  }
@@ -916,17 +916,17 @@ function resolveDesktopConfigPath(home, plat, appData) {
916
916
  }
917
917
  return join6(home, ".config", "Claude", "claude_desktop_config.json");
918
918
  }
919
- function readClaudeConfig(path24) {
920
- if (!existsSync7(path24)) return { kind: "absent", config: {}, mtimeMs: null };
919
+ function readClaudeConfig(path25) {
920
+ if (!existsSync7(path25)) return { kind: "absent", config: {}, mtimeMs: null };
921
921
  for (let attempt = 0; attempt < 3; attempt += 1) {
922
- const before = statSync3(path24).mtimeMs;
922
+ const before = statSync3(path25).mtimeMs;
923
923
  let raw;
924
924
  try {
925
- raw = readFileSync6(path24, "utf8");
925
+ raw = readFileSync6(path25, "utf8");
926
926
  } catch {
927
927
  return { kind: "invalid", config: {}, mtimeMs: before };
928
928
  }
929
- if (!existsSync7(path24) || statSync3(path24).mtimeMs !== before) continue;
929
+ if (!existsSync7(path25) || statSync3(path25).mtimeMs !== before) continue;
930
930
  const text = raw.replace(/^\uFEFF/u, "");
931
931
  if (!text.trim()) return { kind: "empty", config: {}, mtimeMs: before };
932
932
  try {
@@ -938,9 +938,9 @@ function readClaudeConfig(path24) {
938
938
  }
939
939
  return { kind: "invalid", config: {}, mtimeMs: null };
940
940
  }
941
- function writeClaudeConfig(path24, config) {
942
- mkdirSync6(dirname4(path24), { recursive: true });
943
- writeFileAtomic(path24, `${JSON.stringify(config, null, 2)}
941
+ function writeClaudeConfig(path25, config) {
942
+ mkdirSync6(dirname4(path25), { recursive: true });
943
+ writeFileAtomic(path25, `${JSON.stringify(config, null, 2)}
944
944
  `);
945
945
  }
946
946
  function carriedEntryKeys(entry) {
@@ -3515,1185 +3515,1271 @@ var init_installation_token = __esm({
3515
3515
  }
3516
3516
  });
3517
3517
 
3518
- // ../../scripts/virtual-office/code-runner/control-plane-heartbeat-body.mjs
3519
- function buildRunnerHeartbeatBody({
3520
- runnerId,
3521
- runnerInstanceId,
3522
- operatorId,
3523
- uptimeSec,
3524
- activeTasks,
3525
- maxConcurrency,
3526
- effectiveConcurrency,
3527
- measuredTaskSlots,
3528
- measuredCpuSlots,
3529
- measuredMemorySlots,
3530
- version,
3531
- daemonVersion,
3532
- nodeVersion,
3533
- defaultAgent,
3534
- supervisorInstanceId,
3535
- supervisorVersion,
3536
- supervisorCapabilities,
3537
- servedRepos,
3538
- servedOperators,
3539
- availableAgents,
3540
- accountUsage,
3541
- availableLocalModels,
3542
- supportedTaskKinds,
3543
- prepared_job_shadow: preparedJobShadow
3544
- } = {}) {
3545
- const body = { runner_id: runnerId, ...preparedJobShadow ? { prepared_job_shadow: preparedJobShadow } : {} };
3546
- if (runnerInstanceId) body.runner_instance_id = runnerInstanceId;
3547
- if (operatorId) body.operator_id = operatorId;
3548
- if (typeof uptimeSec === "number") body.uptime_sec = uptimeSec;
3549
- if (typeof activeTasks === "number") body.active_tasks = activeTasks;
3550
- if (typeof maxConcurrency === "number") body.max_concurrency = maxConcurrency;
3551
- if (typeof effectiveConcurrency === "number") body.effective_concurrency = effectiveConcurrency;
3552
- if (typeof measuredTaskSlots === "number") body.measured_task_slots = measuredTaskSlots;
3553
- if (typeof measuredCpuSlots === "number") body.measured_cpu_slots = measuredCpuSlots;
3554
- if (typeof measuredMemorySlots === "number") body.measured_memory_slots = measuredMemorySlots;
3555
- if (version) body.version = version;
3556
- if (daemonVersion) body.daemon_version = daemonVersion;
3557
- if (nodeVersion) body.node_version = nodeVersion;
3558
- if (defaultAgent) body.default_agent = defaultAgent;
3559
- if (supervisorInstanceId) body.supervisor_instance_id = supervisorInstanceId;
3560
- if (supervisorVersion) body.supervisor_version = supervisorVersion;
3561
- if (Array.isArray(supervisorCapabilities) && supervisorCapabilities.length > 0) {
3562
- body.supervisor_capabilities = supervisorCapabilities;
3563
- }
3564
- if (Array.isArray(servedRepos) && servedRepos.length > 0) body.served_repos = servedRepos;
3565
- if (Array.isArray(servedOperators) && servedOperators.length > 0) {
3566
- body.served_operator_ids = servedOperators;
3518
+ // ../../scripts/virtual-office/code-runner/claude-credential-choice.mjs
3519
+ function isTruthyFlag(v) {
3520
+ const s = String(v ?? "").trim().toLowerCase();
3521
+ return s === "1" || s === "true" || s === "yes" || s === "on";
3522
+ }
3523
+ function wantsLogin(env2) {
3524
+ return isTruthyFlag(env2[CLAUDE_PREFER_LOGIN_ENV]) || isTruthyFlag(env2[PREFER_LOGIN_ENV]);
3525
+ }
3526
+ function wantsKey(env2) {
3527
+ return isTruthyFlag(env2[CLAUDE_PREFER_KEY_ENV]) || isTruthyFlag(env2[PREFER_KEY_ENV]);
3528
+ }
3529
+ function classifyClaudeCredential(baseEnv = {}, { getKey, probeLogin } = {}) {
3530
+ const preferKey = wantsKey(baseEnv);
3531
+ if (!preferKey && wantsLogin(baseEnv)) {
3532
+ return { source: CLAUDE_CREDENTIAL_SOURCE.PREFER_LOGIN, key: null };
3567
3533
  }
3568
- if (Array.isArray(availableAgents) && availableAgents.length > 0) {
3569
- body.available_agents = availableAgents;
3534
+ if (baseEnv.ANTHROPIC_API_KEY) {
3535
+ return { source: CLAUDE_CREDENTIAL_SOURCE.ENV_KEY, key: null };
3570
3536
  }
3571
- if (Array.isArray(accountUsage) && accountUsage.length > 0) {
3572
- body.account_usage = accountUsage;
3537
+ const key = getKey();
3538
+ if (!key) {
3539
+ return { source: CLAUDE_CREDENTIAL_SOURCE.NO_KEY, key: null };
3573
3540
  }
3574
- if (Array.isArray(availableLocalModels) && availableLocalModels.length > 0) {
3575
- body.available_local_models = availableLocalModels;
3541
+ if (preferKey) {
3542
+ return { source: CLAUDE_CREDENTIAL_SOURCE.KEYCHAIN_PREFER_KEY, key };
3576
3543
  }
3577
- if (Array.isArray(supportedTaskKinds) && supportedTaskKinds.length > 0) {
3578
- body.supported_task_kinds = supportedTaskKinds;
3544
+ if (probeLogin() === true) {
3545
+ return { source: CLAUDE_CREDENTIAL_SOURCE.SUBSCRIPTION_WINS, key: null };
3579
3546
  }
3580
- return body;
3547
+ return { source: CLAUDE_CREDENTIAL_SOURCE.KEYCHAIN, key };
3581
3548
  }
3582
- var init_control_plane_heartbeat_body = __esm({
3583
- "../../scripts/virtual-office/code-runner/control-plane-heartbeat-body.mjs"() {
3549
+ var PREFER_LOGIN_ENV, CLAUDE_PREFER_LOGIN_ENV, PREFER_KEY_ENV, CLAUDE_PREFER_KEY_ENV, CLAUDE_CREDENTIAL_SOURCE;
3550
+ var init_claude_credential_choice = __esm({
3551
+ "../../scripts/virtual-office/code-runner/claude-credential-choice.mjs"() {
3584
3552
  "use strict";
3553
+ PREFER_LOGIN_ENV = "VO_RUNNER_PREFER_LOGIN";
3554
+ CLAUDE_PREFER_LOGIN_ENV = "VO_RUNNER_CLAUDE_PREFER_LOGIN";
3555
+ PREFER_KEY_ENV = "VO_RUNNER_PREFER_KEY";
3556
+ CLAUDE_PREFER_KEY_ENV = "VO_RUNNER_CLAUDE_PREFER_KEY";
3557
+ CLAUDE_CREDENTIAL_SOURCE = Object.freeze({
3558
+ /** PREFER_LOGIN set (and not overridden): any API key is ignored. */
3559
+ PREFER_LOGIN: "prefer_login",
3560
+ /** An explicit ANTHROPIC_API_KEY in the environment — the manual override. */
3561
+ ENV_KEY: "env_key",
3562
+ /** No key anywhere; the spawn falls through to the login session. */
3563
+ NO_KEY: "no_key",
3564
+ /** A stored key, used because the operator explicitly opted out of tier 1. */
3565
+ KEYCHAIN_PREFER_KEY: "keychain_prefer_key",
3566
+ /** A stored key exists but a proven live subscription outranks it. */
3567
+ SUBSCRIPTION_WINS: "subscription_wins",
3568
+ /** A stored key, used because no live subscription was proven. */
3569
+ KEYCHAIN: "keychain"
3570
+ });
3585
3571
  }
3586
3572
  });
3587
3573
 
3588
- // ../../scripts/virtual-office/code-runner/control-plane-promote.mjs
3589
- async function promoteDraftPrRequest(req, prNumber, automationContext, onUnauthorized = () => {
3590
- }) {
3591
- const res = await req("POST", "/api/v1/admin/pr/promote-draft", { prNumber, automationContext }, { timeoutMs: 6e4 });
3592
- if (res.status === 401) {
3593
- onUnauthorized();
3594
- throw new Error("promote-draft unauthorized (401)");
3595
- }
3596
- const json = await res.json().catch(() => ({}));
3597
- if (res.ok && json?.ok === true) {
3598
- return {
3599
- status: json.promoted === true ? json.auto_merge_disarmed === true ? "promoted (auto-merge disarmed)" : "promoted" : json.already_ready === true ? "already_ready" : "unchanged",
3600
- headSha: typeof json.head_sha === "string" ? json.head_sha : null,
3601
- reason: typeof json.blocked_reason === "string" ? json.blocked_reason : null
3602
- };
3574
+ // ../../scripts/virtual-office/code-runner/windows-claude-launch.mjs
3575
+ import { existsSync as existsSync8, realpathSync } from "node:fs";
3576
+ import { win32 as path12 } from "node:path";
3577
+ import { spawnSync } from "node:child_process";
3578
+ function pathValue(env2) {
3579
+ for (const key of ["Path", "PATH", "path"]) {
3580
+ if (typeof env2?.[key] === "string") return env2[key];
3603
3581
  }
3604
- const code = typeof json?.error === "string" ? json.error : null;
3605
- const err = new Error(`promote-draft failed: HTTP ${res.status}${code ? ` (${code})` : ""}${json?.reason ? ` \u2014 ${json.reason}` : ""}`);
3606
- err.status = res.status;
3607
- err.code = code;
3608
- throw err;
3582
+ return "";
3609
3583
  }
3610
- var init_control_plane_promote = __esm({
3611
- "../../scripts/virtual-office/code-runner/control-plane-promote.mjs"() {
3612
- "use strict";
3584
+ function cleanPathSegment(value) {
3585
+ const trimmed = String(value || "").trim();
3586
+ return trimmed.startsWith('"') && trimmed.endsWith('"') ? trimmed.slice(1, -1) : trimmed;
3587
+ }
3588
+ function envValue(env2, name) {
3589
+ const exact = env2?.[name];
3590
+ if (typeof exact === "string") return exact.trim();
3591
+ const key = Object.keys(env2 || {}).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
3592
+ return typeof env2?.[key] === "string" ? env2[key].trim() : "";
3593
+ }
3594
+ function userClaudeCandidates(bin, env2) {
3595
+ if (!/^claude(?:\.(?:exe|cmd|ps1))?$/iu.test(bin)) return [];
3596
+ const userProfile = envValue(env2, "USERPROFILE");
3597
+ const appData = envValue(env2, "APPDATA") || (userProfile ? path12.join(userProfile, "AppData", "Roaming") : "");
3598
+ const localAppData = envValue(env2, "LOCALAPPDATA") || (userProfile ? path12.join(userProfile, "AppData", "Local") : "");
3599
+ const candidates = [];
3600
+ if (appData) {
3601
+ const npmBin = path12.join(appData, "npm");
3602
+ candidates.push(
3603
+ path12.join(npmBin, "claude.exe"),
3604
+ path12.join(npmBin, "claude.cmd"),
3605
+ path12.join(npmBin, "claude.ps1"),
3606
+ path12.join(npmBin, "claude"),
3607
+ path12.join(npmBin, ...NATIVE_CLAUDE_PARTS)
3608
+ );
3613
3609
  }
3614
- });
3615
-
3616
- // ../../scripts/virtual-office/code-runner/control-plane-task-list.mjs
3617
- async function listAllPrOpenedTasks(request) {
3618
- const tasks = [];
3619
- let beforeCreatedAt = "";
3620
- let beforeId = "";
3621
- for (; ; ) {
3622
- const params = new URLSearchParams({
3623
- status: "pr_opened",
3624
- limit: String(PAGE_SIZE),
3625
- runner_adoption: "1"
3626
- });
3627
- if (beforeCreatedAt) {
3628
- params.set("before_created_at", beforeCreatedAt);
3629
- params.set("before_id", beforeId);
3630
- }
3631
- const res = await request("GET", `/api/v1/code-task?${params}`);
3632
- if (!res.ok) throw new Error(`listPrOpenedTasks failed: HTTP ${res.status}`);
3633
- const json = await res.json();
3634
- const page = Array.isArray(json?.tasks) ? json.tasks : [];
3635
- tasks.push(...page);
3636
- if (page.length < PAGE_SIZE) return tasks;
3637
- const last = page.at(-1);
3638
- if (!last?.created_at || !last?.code_task_id) {
3639
- throw new Error("listPrOpenedTasks pagination cursor missing");
3640
- }
3641
- beforeCreatedAt = last.created_at;
3642
- beforeId = last.code_task_id;
3610
+ if (userProfile) candidates.push(path12.join(userProfile, ".local", "bin", "claude.exe"));
3611
+ if (localAppData) {
3612
+ candidates.push(
3613
+ path12.join(localAppData, "Microsoft", "WinGet", "Links", "claude.exe"),
3614
+ path12.join(localAppData, "Microsoft", "WindowsApps", "claude.exe")
3615
+ );
3643
3616
  }
3617
+ return candidates;
3644
3618
  }
3645
- var PAGE_SIZE;
3646
- var init_control_plane_task_list = __esm({
3647
- "../../scripts/virtual-office/code-runner/control-plane-task-list.mjs"() {
3648
- "use strict";
3649
- PAGE_SIZE = 500;
3650
- }
3651
- });
3652
-
3653
- // ../../scripts/virtual-office/code-runner/control-plane-resume.mjs
3654
- async function resumeCodeTaskRequest(req, taskId, { automaticRateLimit = false, automaticContinuation = false } = {}, onUnauthorized = () => {
3655
- }) {
3656
- const res = await req(
3657
- "POST",
3658
- `/api/v1/code-task/${encodeURIComponent(taskId)}/resume`,
3659
- automaticRateLimit ? { automatic_rate_limit: true } : automaticContinuation ? { automatic_continuation: true } : {}
3660
- );
3661
- if (res.status === 401) {
3662
- onUnauthorized();
3663
- throw new Error("resume unauthorized (401)");
3619
+ function pathCandidates(bin, env2) {
3620
+ if (path12.isAbsolute(bin) || /[\\/]/u.test(bin)) {
3621
+ return [path12.resolve(bin)];
3664
3622
  }
3665
- if (!res.ok) {
3666
- let code = null;
3667
- try {
3668
- const body = await res.json();
3669
- code = typeof body?.error === "string" ? body.error : null;
3670
- } catch {
3671
- }
3672
- const err = new Error(`resume failed: HTTP ${res.status}${code ? ` (${code})` : ""}`);
3673
- err.status = res.status;
3674
- err.code = code;
3675
- throw err;
3623
+ const extension = path12.extname(bin);
3624
+ const fromPath = pathValue(env2).split(";").map(cleanPathSegment).filter(Boolean).flatMap((directory) => extension ? [path12.join(directory, bin)] : [
3625
+ path12.join(directory, `${bin}.exe`),
3626
+ path12.join(directory, `${bin}.cmd`),
3627
+ path12.join(directory, `${bin}.ps1`),
3628
+ path12.join(directory, bin)
3629
+ ]);
3630
+ const seen = /* @__PURE__ */ new Set();
3631
+ return [...fromPath, ...userClaudeCandidates(bin, env2)].filter((candidate) => {
3632
+ const key = candidate.toLowerCase();
3633
+ if (seen.has(key)) return false;
3634
+ seen.add(key);
3635
+ return true;
3636
+ });
3637
+ }
3638
+ function canonicalExistingPath(candidate, exists, canonicalize) {
3639
+ if (!exists(candidate)) return null;
3640
+ try {
3641
+ return canonicalize(candidate);
3642
+ } catch {
3643
+ return null;
3676
3644
  }
3677
- const json = await res.json();
3678
- const task = json && json.task ? json.task : null;
3679
- if (task && typeof json.deduplicated === "boolean") Object.defineProperty(task, "deduplicated", { value: json.deduplicated, enumerable: false });
3680
- return task;
3681
3645
  }
3682
- var init_control_plane_resume = __esm({
3683
- "../../scripts/virtual-office/code-runner/control-plane-resume.mjs"() {
3684
- "use strict";
3646
+ function resolveWindowsClaudeExecutable({
3647
+ bin = "claude",
3648
+ env: env2 = process.env,
3649
+ exists = existsSync8,
3650
+ canonicalize = realpathSync
3651
+ } = {}) {
3652
+ const requested = String(bin || "").trim();
3653
+ if (!requested || requested.includes("\0")) {
3654
+ throw new TypeError("Claude executable must be a non-empty path without NUL bytes");
3685
3655
  }
3686
- });
3687
-
3688
- // ../../scripts/virtual-office/code-runner/control-plane-autonomous-admission.mjs
3689
- function makeAutonomousDispatchAdmissionClient(req, timeoutMs, onUnauthorized = () => {
3690
- }) {
3656
+ for (const candidate of pathCandidates(requested, env2)) {
3657
+ const found = canonicalExistingPath(candidate, exists, canonicalize);
3658
+ if (!found) continue;
3659
+ if (path12.extname(found).toLowerCase() === ".exe") return found;
3660
+ const native = path12.join(path12.dirname(found), ...NATIVE_CLAUDE_PARTS);
3661
+ const resolvedNative = canonicalExistingPath(native, exists, canonicalize);
3662
+ if (resolvedNative) return resolvedNative;
3663
+ }
3664
+ const error = new Error(
3665
+ `Could not resolve a native claude.exe for "${requested}". Install or update Claude Code with the native Windows installer (recommended) or npm install -g @anthropic-ai/claude-code; the HQ runner will not execute a shell-only .cmd/.ps1 shim.`
3666
+ );
3667
+ error.code = "ENOENT";
3668
+ throw error;
3669
+ }
3670
+ function buildWindowsClaudeLaunch({
3671
+ bin = "claude",
3672
+ args = [],
3673
+ env: env2 = process.env
3674
+ } = {}) {
3691
3675
  return {
3692
- async reserveAutonomousDispatchBudget({ requestedBudgetUsd, reservationId, occurrenceKey }) {
3693
- const res = await req("POST", "/api/v1/autonomous-dispatch/admission", {
3694
- requested_budget_usd: requestedBudgetUsd,
3695
- reservation_id: reservationId,
3696
- dispatch_occurrence_key: occurrenceKey
3697
- }, { timeoutMs });
3698
- if (res.status === 401) {
3699
- onUnauthorized();
3700
- throw new Error("autonomous dispatch admission unauthorized (401)");
3701
- }
3702
- if (!res.ok) throw new Error(`autonomous dispatch admission failed: HTTP ${res.status}`);
3703
- const body = await res.json();
3704
- return {
3705
- allowed: body?.allowed === true,
3706
- reason: typeof body?.reason === "string" ? body.reason : ""
3707
- };
3708
- },
3709
- async releaseAutonomousDispatchBudget(reservationId) {
3710
- const res = await req("POST", "/api/v1/autonomous-dispatch/reservation/release", {
3711
- reservation_id: reservationId
3712
- }, { timeoutMs });
3713
- if (res.status === 401) {
3714
- onUnauthorized();
3715
- throw new Error("autonomous dispatch release unauthorized (401)");
3716
- }
3717
- if (!res.ok) throw new Error(`autonomous dispatch release failed: HTTP ${res.status}`);
3718
- return true;
3676
+ bin: resolveWindowsClaudeExecutable({ bin, env: env2 }),
3677
+ args: Array.from(args, (value) => String(value)),
3678
+ spawnOptions: {
3679
+ shell: false,
3680
+ windowsHide: true,
3681
+ windowsVerbatimArguments: false
3719
3682
  }
3720
3683
  };
3721
3684
  }
3722
- var init_control_plane_autonomous_admission = __esm({
3723
- "../../scripts/virtual-office/code-runner/control-plane-autonomous-admission.mjs"() {
3724
- "use strict";
3725
- }
3726
- });
3727
-
3728
- // ../../scripts/virtual-office/code-runner/control-plane-merge.mjs
3729
- async function mergeVerifiedPrRequest(req, prNumber, automationContext, onUnauthorized) {
3730
- const res = await req(
3731
- "POST",
3732
- "/api/v1/admin/pr/merge",
3733
- { prNumber, automationContext },
3734
- { timeoutMs: 12e4 }
3735
- );
3736
- if (res.status === 401) {
3737
- onUnauthorized();
3738
- throw new Error("gated merge unauthorized (401)");
3685
+ function spawnClaudeSync(args = [], options = {}) {
3686
+ if (process.platform !== "win32") {
3687
+ return spawnSync("claude", args, { windowsHide: true, ...options });
3739
3688
  }
3740
- const json = await res.json().catch(() => ({}));
3741
- if (res.ok && json?.ok === true) {
3742
- const result = json?.result && typeof json.result === "object" ? json.result : {};
3743
- const status = result.merged === true || result.status === "merged" ? "merged" : result.status === "auto-merge-enabled" || String(result.action || "").includes("auto-merge") ? "queued" : "accepted";
3689
+ try {
3690
+ const launch = buildWindowsClaudeLaunch({
3691
+ bin: "claude",
3692
+ args,
3693
+ env: options.env || process.env
3694
+ });
3695
+ return spawnSync(launch.bin, launch.args, {
3696
+ ...options,
3697
+ ...launch.spawnOptions
3698
+ });
3699
+ } catch (error) {
3744
3700
  return {
3745
- status,
3746
- detail: typeof result.detail === "string" ? result.detail : null,
3747
- actionReceiptId: typeof json.action_receipt_id === "string" ? json.action_receipt_id : null
3701
+ error,
3702
+ status: null,
3703
+ signal: null,
3704
+ output: null,
3705
+ stdout: null,
3706
+ stderr: null
3748
3707
  };
3749
3708
  }
3750
- const captureStoreRetry = json?.action_status === "not_attempted" && (json?.error === "capture_preflight_unavailable" || json?.error === "decision_outcome_intent_failed");
3751
- if (res.status === 503 && (json?.error === "verify_unavailable" || json?.error === "merge_unavailable" || captureStoreRetry)) {
3752
- return { status: "retry", reason: json.reason || json.message || json.error || "verification unavailable" };
3753
- }
3754
- return {
3755
- status: "blocked",
3756
- reason: json?.reason || json?.message || json?.error || `HTTP ${res.status}`,
3757
- actionReceiptId: typeof json?.action_receipt_id === "string" ? json.action_receipt_id : null
3758
- };
3759
3709
  }
3760
- var init_control_plane_merge = __esm({
3761
- "../../scripts/virtual-office/code-runner/control-plane-merge.mjs"() {
3710
+ var NATIVE_CLAUDE_PARTS;
3711
+ var init_windows_claude_launch = __esm({
3712
+ "../../scripts/virtual-office/code-runner/windows-claude-launch.mjs"() {
3762
3713
  "use strict";
3714
+ NATIVE_CLAUDE_PARTS = [
3715
+ "node_modules",
3716
+ "@anthropic-ai",
3717
+ "claude-code",
3718
+ "bin",
3719
+ "claude.exe"
3720
+ ];
3763
3721
  }
3764
3722
  });
3765
3723
 
3766
- // ../../scripts/virtual-office/code-runner/control-plane-weekly-tokens.mjs
3767
- async function postWeeklyTokensRequest(taskReq, { operatorId, runnerId, tokens, claudeWeeklyPct, claudeWeeklyResetsAt }, onUnauthorized = () => {
3768
- }) {
3769
- const body = {
3770
- operator_id: operatorId,
3771
- runner_id: runnerId,
3772
- input_tokens: tokens.input_tokens,
3773
- output_tokens: tokens.output_tokens,
3774
- cache_creation_tokens: tokens.cache_creation_tokens,
3775
- cache_read_tokens: tokens.cache_read_tokens
3776
- };
3777
- if (typeof claudeWeeklyPct === "number") {
3778
- body.claude_weekly_pct = claudeWeeklyPct;
3779
- }
3780
- if (claudeWeeklyResetsAt !== void 0) {
3781
- body.claude_weekly_resets_at = claudeWeeklyResetsAt;
3724
+ // ../../scripts/virtual-office/code-runner/anthropic-key-store.mjs
3725
+ import { createRequire as createRequire2 } from "node:module";
3726
+ import { spawnSync as spawnSync2 } from "node:child_process";
3727
+ function defaultEntryCtor() {
3728
+ if (_loadTried) return _entryCtor;
3729
+ _loadTried = true;
3730
+ try {
3731
+ _entryCtor = require2("@napi-rs/keyring").Entry;
3732
+ } catch {
3733
+ _entryCtor = null;
3782
3734
  }
3783
- const res = await taskReq("POST", "/api/v1/weekly-tokens", body);
3784
- if (res.status === 401) {
3785
- onUnauthorized();
3786
- throw new Error("weekly-tokens unauthorized (401)");
3735
+ return _entryCtor;
3736
+ }
3737
+ function getAnthropicKey({ EntryCtor = defaultEntryCtor() } = {}) {
3738
+ if (!EntryCtor) return null;
3739
+ try {
3740
+ return new EntryCtor(KEY_SERVICE, KEY_ACCOUNT).getPassword() || null;
3741
+ } catch {
3742
+ return null;
3787
3743
  }
3788
- if (!res.ok) throw new Error(`weekly-tokens failed: HTTP ${res.status}`);
3789
- return true;
3790
3744
  }
3791
- var init_control_plane_weekly_tokens = __esm({
3792
- "../../scripts/virtual-office/code-runner/control-plane-weekly-tokens.mjs"() {
3793
- "use strict";
3745
+ function hasAnthropicKey(opts = {}) {
3746
+ return getAnthropicKey(opts) !== null;
3747
+ }
3748
+ function withAnthropicKey(baseEnv = {}, { getKey = getAnthropicKey, probeLogin = probeClaudeLoginState } = {}) {
3749
+ const { source, key } = classifyClaudeCredential(baseEnv, { getKey, probeLogin });
3750
+ const next = { ...baseEnv };
3751
+ if (source === CLAUDE_CREDENTIAL_SOURCE.PREFER_LOGIN) {
3752
+ delete next.ANTHROPIC_API_KEY;
3753
+ return next;
3794
3754
  }
3795
- });
3796
-
3797
- // ../../scripts/virtual-office/code-runner/control-plane-telemetry-relay.mjs
3798
- async function relayTelemetryEventsRequest(req, { events, source }, onUnauthorized = () => {
3799
- }) {
3800
- const res = await req("POST", "/api/v1/telemetry/relay", { events, ...source ? { source } : {} }, {
3801
- timeoutMs: TELEMETRY_RELAY_TIMEOUT_MS
3802
- });
3803
- if (res.status === 401) onUnauthorized();
3804
- let body = null;
3755
+ if (key !== null) next.ANTHROPIC_API_KEY = key;
3756
+ return next;
3757
+ }
3758
+ function claudeCostBasis(env2 = process.env) {
3759
+ return String(env2.ANTHROPIC_API_KEY || "").trim() ? "vendor_billed" : "subscription_api_equivalent";
3760
+ }
3761
+ function describeAnthropicAuthSource(baseEnv = {}, { getKey = getAnthropicKey, probeLogin = probeClaudeLoginState } = {}) {
3762
+ const { source } = classifyClaudeCredential(baseEnv, { getKey, probeLogin });
3763
+ return AUTH_SOURCE_DESCRIPTION[source];
3764
+ }
3765
+ function augmentAuthError(summary) {
3766
+ const s = String(summary ?? "");
3767
+ if (!AUTH_ERROR_RE.test(s)) return s;
3768
+ return `${s}
3769
+ \u21B3 Anthropic auth failed on the runner. The \`claude\` CLI is a SEPARATE install/login from the Claude Desktop app and the Claude Code IDE extension \u2014 signing into those does NOT authenticate it. Fix: run \`claude auth login\` (Claude subscription) on the runner machine, or clear any stale ANTHROPIC_API_KEY (env / OS keychain / .env.local) and set VO_RUNNER_PREFER_LOGIN=1 \u2014 then restart the runner. Verify with \`claude -p "say hi"\`.`;
3770
+ }
3771
+ function probeClaudeLoginState({
3772
+ spawn: spawn5 = spawnSync2,
3773
+ buildWindowsLaunch = buildWindowsClaudeLaunch,
3774
+ platform: platform4 = process.platform
3775
+ } = {}) {
3805
3776
  try {
3806
- body = await res.json();
3777
+ const launch = platform4 === "win32" ? buildWindowsLaunch({ bin: "claude", args: ["auth", "status"] }) : { bin: "claude", args: ["auth", "status"], spawnOptions: { windowsHide: true } };
3778
+ const st = spawn5(launch.bin, launch.args, { ...launch.spawnOptions, timeout: 5e3, encoding: "utf8" });
3779
+ const parsed = JSON.parse(String(st.stdout || "").trim() || "{}");
3780
+ return typeof parsed.loggedIn === "boolean" ? parsed.loggedIn : null;
3807
3781
  } catch {
3808
- body = null;
3782
+ return null;
3809
3783
  }
3810
- return { status: res.status, body };
3811
3784
  }
3812
- var TELEMETRY_RELAY_TIMEOUT_MS;
3813
- var init_control_plane_telemetry_relay = __esm({
3814
- "../../scripts/virtual-office/code-runner/control-plane-telemetry-relay.mjs"() {
3785
+ var require2, KEY_SERVICE, KEY_ACCOUNT, _entryCtor, _loadTried, AUTH_SOURCE_DESCRIPTION, AUTH_ERROR_RE;
3786
+ var init_anthropic_key_store = __esm({
3787
+ "../../scripts/virtual-office/code-runner/anthropic-key-store.mjs"() {
3815
3788
  "use strict";
3816
- TELEMETRY_RELAY_TIMEOUT_MS = 3e4;
3789
+ init_windows_claude_launch();
3790
+ init_claude_credential_choice();
3791
+ init_claude_credential_choice();
3792
+ require2 = createRequire2(import.meta.url);
3793
+ KEY_SERVICE = "algosuite-vo";
3794
+ KEY_ACCOUNT = "anthropic-api-key";
3795
+ _loadTried = false;
3796
+ AUTH_SOURCE_DESCRIPTION = Object.freeze({
3797
+ [CLAUDE_CREDENTIAL_SOURCE.PREFER_LOGIN]: "claude auth login (VO_RUNNER_PREFER_LOGIN set \u2014 any API key ignored)",
3798
+ [CLAUDE_CREDENTIAL_SOURCE.ENV_KEY]: "ANTHROPIC_API_KEY from environment",
3799
+ [CLAUDE_CREDENTIAL_SOURCE.NO_KEY]: "claude auth login session (no API key set)",
3800
+ [CLAUDE_CREDENTIAL_SOURCE.KEYCHAIN_PREFER_KEY]: "ANTHROPIC_API_KEY from OS keychain (VO_RUNNER_PREFER_KEY set \u2014 subscription ignored)",
3801
+ [CLAUDE_CREDENTIAL_SOURCE.SUBSCRIPTION_WINS]: "claude auth login session (subscription beats the stored keychain key)",
3802
+ [CLAUDE_CREDENTIAL_SOURCE.KEYCHAIN]: "ANTHROPIC_API_KEY from OS keychain"
3803
+ });
3804
+ AUTH_ERROR_RE = /\b401\b|invalid[^.]{0,24}(authentication|credential)|authentication_error|unauthorized|not[ _-]?authenticated/i;
3817
3805
  }
3818
3806
  });
3819
3807
 
3820
- // ../../scripts/virtual-office/code-runner/claim-gate-notice.mjs
3821
- function describeClaimGate(gate) {
3822
- if (!gate || gate.allowed !== false) return null;
3823
- const reason = String(gate.reason || "denied");
3824
- const floor = gate.floor_version ? ` (floor ${gate.floor_version})` : "";
3825
- return `claim gate: DENIED \u2014 ${reason}${floor}: ${(Object.hasOwn(REASON_HELP, reason) ? REASON_HELP[reason] : null) ?? "the control plane refused this runner's claims"}`;
3808
+ // ../../scripts/virtual-office/code-runner/agent-auth-attestation.mjs
3809
+ function isTruthyFlag2(value) {
3810
+ const s = String(value ?? "").trim().toLowerCase();
3811
+ return s === "1" || s === "true" || s === "yes" || s === "on";
3826
3812
  }
3827
- function makeClaimGateNotice({ log: log2 = () => {
3828
- } } = {}) {
3829
- let last = null;
3830
- let current = null;
3831
- return {
3832
- current: () => current,
3833
- observe(json) {
3834
- const gate = json && typeof json === "object" ? json.claim_gate : null;
3835
- const denied = gate && gate.allowed === false ? gate : null;
3836
- current = denied ? { ...denied, observed_at: (/* @__PURE__ */ new Date()).toISOString() } : null;
3837
- const signature = denied ? `${denied.reason}|${denied.floor_version ?? ""}` : null;
3838
- if (signature === last) return;
3839
- if (denied) log2(describeClaimGate(denied));
3840
- else if (last !== null) log2("claim gate: allowed again \u2014 this runner may claim work");
3841
- last = signature;
3842
- }
3843
- };
3813
+ function runnerAllowsApiBilling(env2 = {}) {
3814
+ return isTruthyFlag2(env2[RUNNER_ALLOW_API_BILLING_ENV]);
3844
3815
  }
3845
- var REASON_HELP;
3846
- var init_claim_gate_notice = __esm({
3847
- "../../scripts/virtual-office/code-runner/claim-gate-notice.mjs"() {
3848
- "use strict";
3849
- REASON_HELP = {
3850
- daemon_version_below_floor: "this daemon is older than the approved release target \u2014 it idles until the governed updater brings it current (operator override: VO_RUNNER_CLAIM_MIN_DAEMON_VERSION / VO_RUNNER_CLAIM_VERSION_GATE=off on the control plane)",
3851
- daemon_version_unreported: "this daemon reports no parseable version in its heartbeat \u2014 too old for the updater to manage, so it may not claim work",
3852
- no_fresh_heartbeat: "the control plane has no fresh heartbeat from this runner \u2014 claims resume once heartbeats land",
3853
- runner_denylisted: "this runner id is on the operator quarantine list (VO_RUNNER_CLAIM_DENYLIST)"
3854
- };
3816
+ function hasApiBillingCredential(env2 = {}) {
3817
+ for (const [name, value] of Object.entries(env2)) {
3818
+ if (!API_BILLING_ENV_NAME_SET.has(String(name).toUpperCase())) continue;
3819
+ if (String(value ?? "").trim()) return true;
3855
3820
  }
3856
- });
3857
-
3858
- // ../../scripts/virtual-office/code-runner/control-plane-knowledge-context.mjs
3859
- function canonicalizeKnowledgeContextQuery(value) {
3860
- return typeof value === "string" ? value.trimStart().slice(0, KNOWLEDGE_CONTEXT_QUERY_MAX_CHARS) : "";
3821
+ return false;
3861
3822
  }
3862
- function isRetryableStatus(status) {
3863
- return status >= 500 && status <= 599;
3823
+ function stripApiBillingEnv(env2 = {}) {
3824
+ const next = {};
3825
+ for (const [name, value] of Object.entries(env2)) {
3826
+ if (API_BILLING_ENV_NAME_SET.has(String(name).toUpperCase())) continue;
3827
+ next[name] = value;
3828
+ }
3829
+ return next;
3864
3830
  }
3865
- function defaultSleep(ms) {
3866
- return new Promise((resolve3) => {
3867
- setTimeout(resolve3, ms);
3868
- });
3831
+ function resolveAgentAuthSource(env2 = {}) {
3832
+ return hasApiBillingCredential(env2) ? AGENT_AUTH_SOURCE.API_KEY : AGENT_AUTH_SOURCE.LOGIN;
3869
3833
  }
3870
- async function getTaskKnowledgeContextRequest(req, taskId, { query, knowledgeRequestId } = {}, {
3871
- taskRequestTimeoutMs,
3872
- invalidateToken = () => {
3873
- },
3874
- sleep: sleep3 = defaultSleep,
3875
- log: log2 = () => {
3876
- }
3877
- } = {}) {
3878
- const body = {};
3879
- const canonicalQuery = canonicalizeKnowledgeContextQuery(query);
3880
- if (canonicalQuery.trim()) body.query = canonicalQuery;
3881
- if (typeof knowledgeRequestId === "string" && knowledgeRequestId) body.knowledge_request_id = knowledgeRequestId;
3882
- const path24 = `/api/v1/code-task/${encodeURIComponent(taskId)}/knowledge-context`;
3883
- const timeoutMs = Math.max(Number(taskRequestTimeoutMs) || 0, MIN_KNOWLEDGE_CONTEXT_TIMEOUT_MS);
3884
- for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt += 1) {
3885
- let res;
3886
- let cause;
3887
- try {
3888
- res = await req("POST", path24, body, { timeoutMs });
3889
- } catch (err) {
3890
- cause = err;
3891
- }
3892
- if (!cause) {
3893
- if (res.status === 401) {
3894
- invalidateToken();
3895
- throw new Error("knowledge-context unauthorized (401)");
3896
- }
3897
- if (res.status === 404) return null;
3898
- if (res.ok) return res.json();
3899
- if (!isRetryableStatus(res.status)) {
3900
- throw new Error(`knowledge-context failed: HTTP ${res.status}`);
3901
- }
3902
- cause = new Error(`knowledge-context failed: HTTP ${res.status}`);
3903
- }
3904
- if (attempt === MAX_ATTEMPTS) throw cause;
3905
- const delayMs = RETRY_DELAYS_MS[attempt - 1];
3906
- log2(`knowledge-context attempt ${attempt}/${MAX_ATTEMPTS} failed (${cause.message}); retrying in ${delayMs}ms`);
3907
- await sleep3(delayMs);
3834
+ function applyApiBillingPolicy(spawnEnv = {}, { runnerEnv = spawnEnv, allowApiBilling = false } = {}) {
3835
+ const forcedLogin = wantsLogin(runnerEnv) && !wantsKey(runnerEnv);
3836
+ const permitted = !forcedLogin && allowApiBilling === true && runnerAllowsApiBilling(runnerEnv);
3837
+ const env2 = permitted ? { ...spawnEnv } : stripApiBillingEnv(spawnEnv);
3838
+ return { env: env2, agent_auth_source: resolveAgentAuthSource(env2), permitted };
3839
+ }
3840
+ function resolveRunnerAttestedAuthSource(env2 = process.env, { hasStoredKey = hasAnthropicKey } = {}) {
3841
+ if (wantsLogin(env2) && !wantsKey(env2)) return AGENT_AUTH_SOURCE.LOGIN;
3842
+ if (!runnerAllowsApiBilling(env2)) return AGENT_AUTH_SOURCE.LOGIN;
3843
+ if (hasApiBillingCredential(env2)) return AGENT_AUTH_SOURCE.API_KEY;
3844
+ try {
3845
+ return hasStoredKey() === true ? AGENT_AUTH_SOURCE.API_KEY : AGENT_AUTH_SOURCE.LOGIN;
3846
+ } catch {
3847
+ return AGENT_AUTH_SOURCE.API_KEY;
3908
3848
  }
3909
- throw new Error("knowledge-context retry loop exited unexpectedly");
3910
3849
  }
3911
- var MIN_KNOWLEDGE_CONTEXT_TIMEOUT_MS, KNOWLEDGE_CONTEXT_QUERY_MAX_CHARS, RETRY_DELAYS_MS, MAX_ATTEMPTS;
3912
- var init_control_plane_knowledge_context = __esm({
3913
- "../../scripts/virtual-office/code-runner/control-plane-knowledge-context.mjs"() {
3850
+ function describeTaskAuthSource(env2 = process.env, task = {}) {
3851
+ const { agent_auth_source: authSource, permitted } = applyApiBillingPolicy(env2, {
3852
+ runnerEnv: env2,
3853
+ allowApiBilling: task?.allow_api_billing === true
3854
+ });
3855
+ const reason = permitted ? "explicit_opt_in" : "default";
3856
+ return `auth_source=${authSource} reason=${reason}`;
3857
+ }
3858
+ var AGENT_AUTH_SOURCE, AGENT_AUTH_SOURCES, API_BILLING_ENV_NAMES, RUNNER_ALLOW_API_BILLING_ENV, API_BILLING_ENV_NAME_SET;
3859
+ var init_agent_auth_attestation = __esm({
3860
+ "../../scripts/virtual-office/code-runner/agent-auth-attestation.mjs"() {
3914
3861
  "use strict";
3915
- MIN_KNOWLEDGE_CONTEXT_TIMEOUT_MS = 15e3;
3916
- KNOWLEDGE_CONTEXT_QUERY_MAX_CHARS = 2e3;
3917
- RETRY_DELAYS_MS = [2e3, 6e3];
3918
- MAX_ATTEMPTS = RETRY_DELAYS_MS.length + 1;
3862
+ init_claude_credential_choice();
3863
+ init_anthropic_key_store();
3864
+ AGENT_AUTH_SOURCE = Object.freeze({
3865
+ /** A flat-cost linked account (`claude auth login`). No per-token vendor bill. */
3866
+ LOGIN: "login",
3867
+ /** A metered per-token credential the vendor bills. */
3868
+ API_KEY: "api_key"
3869
+ });
3870
+ AGENT_AUTH_SOURCES = Object.freeze([
3871
+ AGENT_AUTH_SOURCE.LOGIN,
3872
+ AGENT_AUTH_SOURCE.API_KEY
3873
+ ]);
3874
+ API_BILLING_ENV_NAMES = Object.freeze([
3875
+ "ANTHROPIC_API_KEY",
3876
+ "ANTHROPIC_AUTH_TOKEN",
3877
+ "CLAUDE_API_KEY"
3878
+ ]);
3879
+ RUNNER_ALLOW_API_BILLING_ENV = "VO_RUNNER_ALLOW_API_BILLING";
3880
+ API_BILLING_ENV_NAME_SET = new Set(API_BILLING_ENV_NAMES);
3919
3881
  }
3920
3882
  });
3921
3883
 
3922
- // ../../scripts/virtual-office/code-runner/control-plane-prepared-job.mjs
3923
- function preparedJobQuery({ agent = "claude", env: env2 = {} } = {}) {
3924
- const params = new URLSearchParams();
3925
- params.set("agent", String(agent));
3926
- const sent = [];
3927
- const dropped = [];
3928
- for (const key of PREPARED_JOB_ENV_QUERY_KEYS) {
3929
- const raw = env2?.[key];
3930
- if (typeof raw !== "string" || raw.length === 0) continue;
3931
- if (raw.length > PREPARED_JOB_ENV_VALUE_MAX) {
3932
- dropped.push(key);
3933
- continue;
3934
- }
3935
- params.set(key, raw);
3936
- sent.push(key);
3884
+ // ../../scripts/virtual-office/code-runner/control-plane-heartbeat-body.mjs
3885
+ function buildRunnerHeartbeatBody({
3886
+ runnerId,
3887
+ runnerInstanceId,
3888
+ operatorId,
3889
+ uptimeSec,
3890
+ activeTasks,
3891
+ maxConcurrency,
3892
+ effectiveConcurrency,
3893
+ measuredTaskSlots,
3894
+ measuredCpuSlots,
3895
+ measuredMemorySlots,
3896
+ version,
3897
+ daemonVersion,
3898
+ nodeVersion,
3899
+ defaultAgent,
3900
+ supervisorInstanceId,
3901
+ supervisorVersion,
3902
+ supervisorCapabilities,
3903
+ servedRepos,
3904
+ servedOperators,
3905
+ availableAgents,
3906
+ accountUsage,
3907
+ availableLocalModels,
3908
+ supportedTaskKinds,
3909
+ hostHealth,
3910
+ agentAuthSource,
3911
+ prepared_job_shadow: preparedJobShadow
3912
+ } = {}) {
3913
+ const body = { runner_id: runnerId, ...preparedJobShadow ? { prepared_job_shadow: preparedJobShadow } : {} };
3914
+ if (runnerInstanceId) body.runner_instance_id = runnerInstanceId;
3915
+ if (operatorId) body.operator_id = operatorId;
3916
+ if (typeof uptimeSec === "number") body.uptime_sec = uptimeSec;
3917
+ if (typeof activeTasks === "number") body.active_tasks = activeTasks;
3918
+ if (typeof maxConcurrency === "number") body.max_concurrency = maxConcurrency;
3919
+ if (typeof effectiveConcurrency === "number") body.effective_concurrency = effectiveConcurrency;
3920
+ if (typeof measuredTaskSlots === "number") body.measured_task_slots = measuredTaskSlots;
3921
+ if (typeof measuredCpuSlots === "number") body.measured_cpu_slots = measuredCpuSlots;
3922
+ if (typeof measuredMemorySlots === "number") body.measured_memory_slots = measuredMemorySlots;
3923
+ if (version) body.version = version;
3924
+ if (daemonVersion) body.daemon_version = daemonVersion;
3925
+ if (nodeVersion) body.node_version = nodeVersion;
3926
+ if (defaultAgent) body.default_agent = defaultAgent;
3927
+ if (supervisorInstanceId) body.supervisor_instance_id = supervisorInstanceId;
3928
+ if (supervisorVersion) body.supervisor_version = supervisorVersion;
3929
+ if (Array.isArray(supervisorCapabilities) && supervisorCapabilities.length > 0) {
3930
+ body.supervisor_capabilities = supervisorCapabilities;
3937
3931
  }
3938
- return { query: params.toString(), sent, dropped };
3939
- }
3940
- async function refusalCode(res) {
3941
- try {
3942
- const body = await res.json();
3943
- return typeof body?.error === "string" && body.error ? body.error : null;
3944
- } catch {
3945
- return null;
3932
+ if (Array.isArray(servedRepos) && servedRepos.length > 0) body.served_repos = servedRepos;
3933
+ if (Array.isArray(servedOperators) && servedOperators.length > 0) {
3934
+ body.served_operator_ids = servedOperators;
3946
3935
  }
3947
- }
3948
- async function getPreparedJobRequest(req, taskId, options = {}, invalidateToken = () => {
3949
- }) {
3950
- const { agent = "claude", env: env2 = {}, timeoutMs = 15e3 } = options;
3951
- const { query, sent, dropped } = preparedJobQuery({ agent, env: env2 });
3952
- const envMeta = { envSent: sent, envDropped: dropped };
3953
- if (typeof taskId !== "string" || taskId.length === 0) {
3954
- return { ok: false, reason: "missing_task_id", status: 0, ...envMeta };
3936
+ if (Array.isArray(availableAgents) && availableAgents.length > 0) {
3937
+ body.available_agents = availableAgents;
3955
3938
  }
3956
- const path24 = `/api/v1/code-task/${encodeURIComponent(taskId)}/prepared-job?${query}`;
3957
- let res;
3958
- try {
3959
- res = await req("GET", path24, void 0, { timeoutMs });
3960
- } catch (err) {
3961
- return { ok: false, reason: `transport: ${err?.message || String(err)}`, status: 0, ...envMeta };
3939
+ if (Array.isArray(accountUsage) && accountUsage.length > 0) {
3940
+ body.account_usage = accountUsage;
3962
3941
  }
3963
- if (res?.status === 401) {
3964
- try {
3965
- invalidateToken();
3966
- } catch {
3967
- }
3968
- return { ok: false, reason: "unauthorized", status: 401, ...envMeta };
3942
+ if (Array.isArray(availableLocalModels) && availableLocalModels.length > 0) {
3943
+ body.available_local_models = availableLocalModels;
3969
3944
  }
3970
- if (!res?.ok) {
3971
- const code = await refusalCode(res);
3972
- return { ok: false, reason: code || `http_${res?.status ?? "unknown"}`, status: res?.status ?? 0, ...envMeta };
3945
+ if (Array.isArray(supportedTaskKinds) && supportedTaskKinds.length > 0) {
3946
+ body.supported_task_kinds = supportedTaskKinds;
3973
3947
  }
3974
- let body;
3975
- try {
3976
- body = await res.json();
3977
- } catch (err) {
3978
- return { ok: false, reason: `unreadable_body: ${err?.message || String(err)}`, status: res.status, ...envMeta };
3948
+ if (hostHealth && typeof hostHealth === "object") body.host_health = hostHealth;
3949
+ if (AGENT_AUTH_SOURCES.includes(agentAuthSource)) body.agent_auth_source = agentAuthSource;
3950
+ return body;
3951
+ }
3952
+ var init_control_plane_heartbeat_body = __esm({
3953
+ "../../scripts/virtual-office/code-runner/control-plane-heartbeat-body.mjs"() {
3954
+ "use strict";
3955
+ init_agent_auth_attestation();
3979
3956
  }
3980
- const job = body?.prepared_job;
3981
- if (!job || typeof job !== "object") {
3982
- return { ok: false, reason: "no_prepared_job_in_body", status: res.status, ...envMeta };
3957
+ });
3958
+
3959
+ // ../../scripts/virtual-office/code-runner/control-plane-promote.mjs
3960
+ async function promoteDraftPrRequest(req, prNumber, automationContext, onUnauthorized = () => {
3961
+ }) {
3962
+ const res = await req("POST", "/api/v1/admin/pr/promote-draft", { prNumber, automationContext }, { timeoutMs: 6e4 });
3963
+ if (res.status === 401) {
3964
+ onUnauthorized();
3965
+ throw new Error("promote-draft unauthorized (401)");
3983
3966
  }
3984
- return {
3985
- ok: true,
3986
- job,
3987
- composition: body?.composition && typeof body.composition === "object" ? body.composition : {},
3988
- ...envMeta
3989
- };
3967
+ const json = await res.json().catch(() => ({}));
3968
+ if (res.ok && json?.ok === true) {
3969
+ return {
3970
+ status: json.promoted === true ? json.auto_merge_disarmed === true ? "promoted (auto-merge disarmed)" : "promoted" : json.already_ready === true ? "already_ready" : "unchanged",
3971
+ headSha: typeof json.head_sha === "string" ? json.head_sha : null,
3972
+ reason: typeof json.blocked_reason === "string" ? json.blocked_reason : null
3973
+ };
3974
+ }
3975
+ const code = typeof json?.error === "string" ? json.error : null;
3976
+ const err = new Error(`promote-draft failed: HTTP ${res.status}${code ? ` (${code})` : ""}${json?.reason ? ` \u2014 ${json.reason}` : ""}`);
3977
+ err.status = res.status;
3978
+ err.code = code;
3979
+ throw err;
3990
3980
  }
3991
- var PREPARED_JOB_ENV_QUERY_KEYS, PREPARED_JOB_ENV_VALUE_MAX;
3992
- var init_control_plane_prepared_job = __esm({
3993
- "../../scripts/virtual-office/code-runner/control-plane-prepared-job.mjs"() {
3981
+ var init_control_plane_promote = __esm({
3982
+ "../../scripts/virtual-office/code-runner/control-plane-promote.mjs"() {
3994
3983
  "use strict";
3995
- PREPARED_JOB_ENV_QUERY_KEYS = [
3996
- "VO_CODE_RUNNER_NO_WEB",
3997
- "VO_CODE_RUNNER_NO_WORKFLOW",
3998
- "VO_CODE_RUNNER_NO_CONSENSUS",
3999
- "VO_CODE_RUNNER_PERMISSION_MODE",
4000
- "VO_CODE_RUNNER_DEFAULT_BUDGET_USD",
4001
- "VO_CODE_RUNNER_META_REASONING_EFFORT",
4002
- "VO_CODE_RUNNER_ALLOW_MISSING_KNOWLEDGE_CONTEXT",
4003
- "VO_ENABLE_CONTEXT7"
4004
- ];
4005
- PREPARED_JOB_ENV_VALUE_MAX = 64;
4006
3984
  }
4007
3985
  });
4008
3986
 
4009
- // src/runner/control-plane-auth-stub.mjs
4010
- var control_plane_auth_stub_exports = {};
4011
- __export(control_plane_auth_stub_exports, {
4012
- getFirebaseAuth: () => getFirebaseAuth
4013
- });
4014
- async function getFirebaseAuth() {
4015
- throw new Error(
4016
- "vo-mcp runner: no control-plane credential. Run `vo-mcp login` first \u2014 the runner authenticates with your stored vo_credential (or set VO_CONTROL_PLANE_ADMIN_TOKEN)."
4017
- );
3987
+ // ../../scripts/virtual-office/code-runner/control-plane-task-list.mjs
3988
+ async function listAllPrOpenedTasks(request) {
3989
+ const tasks = [];
3990
+ let beforeCreatedAt = "";
3991
+ let beforeId = "";
3992
+ for (; ; ) {
3993
+ const params = new URLSearchParams({
3994
+ status: "pr_opened",
3995
+ limit: String(PAGE_SIZE),
3996
+ runner_adoption: "1"
3997
+ });
3998
+ if (beforeCreatedAt) {
3999
+ params.set("before_created_at", beforeCreatedAt);
4000
+ params.set("before_id", beforeId);
4001
+ }
4002
+ const res = await request("GET", `/api/v1/code-task?${params}`);
4003
+ if (!res.ok) throw new Error(`listPrOpenedTasks failed: HTTP ${res.status}`);
4004
+ const json = await res.json();
4005
+ const page = Array.isArray(json?.tasks) ? json.tasks : [];
4006
+ tasks.push(...page);
4007
+ if (page.length < PAGE_SIZE) return tasks;
4008
+ const last = page.at(-1);
4009
+ if (!last?.created_at || !last?.code_task_id) {
4010
+ throw new Error("listPrOpenedTasks pagination cursor missing");
4011
+ }
4012
+ beforeCreatedAt = last.created_at;
4013
+ beforeId = last.code_task_id;
4014
+ }
4018
4015
  }
4019
- var init_control_plane_auth_stub = __esm({
4020
- "src/runner/control-plane-auth-stub.mjs"() {
4016
+ var PAGE_SIZE;
4017
+ var init_control_plane_task_list = __esm({
4018
+ "../../scripts/virtual-office/code-runner/control-plane-task-list.mjs"() {
4019
+ "use strict";
4020
+ PAGE_SIZE = 500;
4021
4021
  }
4022
4022
  });
4023
4023
 
4024
- // ../../scripts/virtual-office/code-runner/control-plane-client.mjs
4025
- async function resolveBearer(env2) {
4026
- const adminToken = env2.VO_CONTROL_PLANE_ADMIN_TOKEN;
4027
- if (adminToken) return adminToken;
4028
- if (cachedFirebaseToken) return cachedFirebaseToken;
4029
- const { getFirebaseAuth: getFirebaseAuth2 } = await Promise.resolve().then(() => (init_control_plane_auth_stub(), control_plane_auth_stub_exports));
4030
- const auth = await getFirebaseAuth2({ env: env2 });
4031
- if (!auth || !auth.idToken) {
4032
- throw new Error(
4033
- "no control-plane credential: set VO_CONTROL_PLANE_ADMIN_TOKEN, or SMOKE_EMAIL/SMOKE_PASSWORD/SMOKE_API_KEY"
4034
- );
4024
+ // ../../scripts/virtual-office/code-runner/control-plane-resume.mjs
4025
+ async function resumeCodeTaskRequest(req, taskId, { automaticRateLimit = false, automaticContinuation = false } = {}, onUnauthorized = () => {
4026
+ }) {
4027
+ const res = await req(
4028
+ "POST",
4029
+ `/api/v1/code-task/${encodeURIComponent(taskId)}/resume`,
4030
+ automaticRateLimit ? { automatic_rate_limit: true } : automaticContinuation ? { automatic_continuation: true } : {}
4031
+ );
4032
+ if (res.status === 401) {
4033
+ onUnauthorized();
4034
+ throw new Error("resume unauthorized (401)");
4035
4035
  }
4036
- cachedFirebaseToken = auth.idToken;
4037
- return cachedFirebaseToken;
4038
- }
4039
- function createControlPlaneClient({
4040
- baseUrl,
4041
- env: env2 = process.env,
4042
- fetchImpl = fetch,
4043
- heartbeatTimeoutMs = Math.min(
4044
- Math.max(Number(env2.VO_CODE_RUNNER_HEARTBEAT_TIMEOUT_MS) || 15e3, 1e3),
4045
- 6e4
4046
- ),
4047
- taskRequestTimeoutMs = Math.min(
4048
- Math.max(Number(env2.VO_CODE_RUNNER_TASK_REQUEST_TIMEOUT_MS) || 5e3, 100),
4049
- 6e4
4050
- ),
4051
- runnerId,
4052
- runnerInstanceId,
4053
- sleep: sleep3
4054
- } = {}) {
4055
- const resolvedBaseUrl = baseUrl ?? env2.VO_CONTROL_PLANE_URL ?? "";
4056
- if (!resolvedBaseUrl) throw new Error("VO_CONTROL_PLANE_URL is required for the code-runner daemon");
4057
- const root = resolvedBaseUrl.replace(/\/+$/, "");
4058
- const claimOccurrences = /* @__PURE__ */ new Map();
4059
- async function req(method, path24, body, { timeoutMs } = {}) {
4060
- const bearer = await resolveBearer(env2);
4061
- const controller = timeoutMs ? new AbortController() : null;
4062
- let timeoutId;
4063
- const request = Promise.resolve(fetchImpl(`${root}${path24}`, {
4064
- method,
4065
- headers: {
4066
- "content-type": "application/json",
4067
- authorization: `Bearer ${bearer}`
4068
- },
4069
- body: body === void 0 ? void 0 : JSON.stringify(body),
4070
- ...controller ? { signal: controller.signal } : {}
4071
- }));
4072
- if (!timeoutMs) return request;
4073
- const timeout = new Promise((_, reject) => {
4074
- timeoutId = setTimeout(() => {
4075
- controller.abort();
4076
- reject(new Error(`control-plane ${path24} timed out after ${timeoutMs}ms`));
4077
- }, timeoutMs);
4078
- });
4036
+ if (!res.ok) {
4037
+ let code = null;
4079
4038
  try {
4080
- return await Promise.race([request, timeout]);
4081
- } finally {
4082
- clearTimeout(timeoutId);
4039
+ const body = await res.json();
4040
+ code = typeof body?.error === "string" ? body.error : null;
4041
+ } catch {
4083
4042
  }
4043
+ const err = new Error(`resume failed: HTTP ${res.status}${code ? ` (${code})` : ""}`);
4044
+ err.status = res.status;
4045
+ err.code = code;
4046
+ throw err;
4084
4047
  }
4085
- const taskReq = (method, path24, body, options = {}) => req(method, path24, body, { timeoutMs: taskRequestTimeoutMs, ...options });
4086
- const claimGate = makeClaimGateNotice({ log: (m) => console.warn(`[code-runner ${(/* @__PURE__ */ new Date()).toISOString()}] ${m}`) });
4048
+ const json = await res.json();
4049
+ const task = json && json.task ? json.task : null;
4050
+ if (task && typeof json.deduplicated === "boolean") Object.defineProperty(task, "deduplicated", { value: json.deduplicated, enumerable: false });
4051
+ return task;
4052
+ }
4053
+ var init_control_plane_resume = __esm({
4054
+ "../../scripts/virtual-office/code-runner/control-plane-resume.mjs"() {
4055
+ "use strict";
4056
+ }
4057
+ });
4058
+
4059
+ // ../../scripts/virtual-office/code-runner/control-plane-autonomous-admission.mjs
4060
+ function makeAutonomousDispatchAdmissionClient(req, timeoutMs, onUnauthorized = () => {
4061
+ }) {
4087
4062
  return {
4088
- getClaimGate: () => claimGate.current(),
4089
- // last DENIED claim-gate verdict (null when allowed) — for /status + tests
4090
- ...makeAutonomousDispatchAdmissionClient(
4091
- req,
4092
- taskRequestTimeoutMs,
4093
- () => {
4094
- cachedFirebaseToken = null;
4095
- }
4096
- ),
4097
- /**
4098
- * Claim the next pending task. Returns the task or null (empty queue).
4099
- * `repos` (optional `owner/name` list) and `operatorIds` (optional
4100
- * `operator_id` list) scope the claim so this daemon only picks up tasks it
4101
- * serves — the control-plane filters by both (logical AND), so another
4102
- * operator's task never lands on (or bills) this machine.
4103
- */
4104
- async claim(runnerId2, repos, operatorIds, session = {}) {
4105
- const body = { runner_id: runnerId2 };
4106
- if (Array.isArray(repos) && repos.length > 0) body.repos = repos;
4107
- if (Array.isArray(operatorIds) && operatorIds.length > 0) body.operator_ids = operatorIds;
4108
- if (session.runnerInstanceId) {
4109
- body.runner_instance_id = session.runnerInstanceId;
4110
- body.runner_progress_protocol_version = 2;
4111
- }
4112
- if (session.runnerInstanceId && session.reconcileStale) body.reconcile_stale = true;
4113
- if (session.defaultAgent) body.default_agent = session.defaultAgent;
4114
- if (Array.isArray(session.availableAgents)) {
4115
- body.available_agents = session.availableAgents.filter((entry) => entry?.installed === true && entry?.authenticated === true).map((entry) => entry.agent);
4116
- }
4117
- const res = await taskReq("POST", "/api/v1/code-task/claim", body);
4063
+ async reserveAutonomousDispatchBudget({ requestedBudgetUsd, reservationId, occurrenceKey }) {
4064
+ const res = await req("POST", "/api/v1/autonomous-dispatch/admission", {
4065
+ requested_budget_usd: requestedBudgetUsd,
4066
+ reservation_id: reservationId,
4067
+ dispatch_occurrence_key: occurrenceKey
4068
+ }, { timeoutMs });
4118
4069
  if (res.status === 401) {
4119
- cachedFirebaseToken = null;
4120
- throw new Error("claim unauthorized (401)");
4070
+ onUnauthorized();
4071
+ throw new Error("autonomous dispatch admission unauthorized (401)");
4121
4072
  }
4122
- if (!res.ok) throw new Error(`claim failed: HTTP ${res.status}`);
4123
- const json = await res.json();
4124
- claimGate.observe(json);
4125
- const task = json && json.task ? json.task : null;
4126
- if (task?.claim_occurrence_id) claimOccurrences.set(task.code_task_id, task.claim_occurrence_id);
4127
- return task;
4073
+ if (!res.ok) throw new Error(`autonomous dispatch admission failed: HTTP ${res.status}`);
4074
+ const body = await res.json();
4075
+ return {
4076
+ allowed: body?.allowed === true,
4077
+ reason: typeof body?.reason === "string" ? body.reason : ""
4078
+ };
4128
4079
  },
4129
- /**
4130
- * Enqueue a new code-task (used by the PR watcher to auto-dispatch a CI fix).
4131
- * Server derives operator/tenant from the daemon's authenticated principal.
4132
- * Returns the created task, or throws on a non-2xx response.
4133
- */
4134
- async enqueueCodeTask({ repo, prompt, max_budget_usd, max_turns, dispatch_mode, tier, agent, model, dispatch_occurrence_key, autonomous_reservation_id, on_behalf_of_operator_id, repair_pr_number, repair_kind, repair_head_sha, repair_chain }) {
4135
- const body = { repo, prompt };
4136
- if (typeof max_budget_usd === "number") body.max_budget_usd = max_budget_usd;
4137
- if (typeof max_turns === "number") body.max_turns = max_turns;
4138
- for (const [key, value] of Object.entries({ dispatch_mode, tier, agent, model, repair_kind, repair_head_sha })) {
4139
- if (value) body[key] = value;
4140
- }
4141
- if (dispatch_occurrence_key) body.dispatch_occurrence_key = dispatch_occurrence_key;
4142
- if (autonomous_reservation_id) body.autonomous_reservation_id = autonomous_reservation_id;
4143
- if (on_behalf_of_operator_id) body.on_behalf_of_operator_id = on_behalf_of_operator_id;
4144
- if (Number.isInteger(repair_pr_number) && repair_pr_number > 0) body.repair_pr_number = repair_pr_number;
4145
- if (repair_chain) body.repair_chain = repair_chain;
4146
- const res = await taskReq("POST", "/api/v1/code-task", body);
4080
+ async releaseAutonomousDispatchBudget(reservationId) {
4081
+ const res = await req("POST", "/api/v1/autonomous-dispatch/reservation/release", {
4082
+ reservation_id: reservationId
4083
+ }, { timeoutMs });
4147
4084
  if (res.status === 401) {
4148
- cachedFirebaseToken = null;
4149
- throw new Error("enqueue unauthorized (401)");
4150
- }
4151
- if (!res.ok) {
4152
- let code = null;
4153
- try {
4154
- const errBody = await res.json();
4155
- code = typeof errBody?.error === "string" ? errBody.error : null;
4156
- } catch {
4157
- }
4158
- const err = new Error(`enqueue failed: HTTP ${res.status}${code ? ` (${code})` : ""}`);
4159
- err.status = res.status;
4160
- err.code = code;
4161
- throw err;
4162
- }
4163
- const json = await res.json();
4164
- const task = json && json.task ? json.task : null;
4165
- if (task && typeof json.deduplicated === "boolean") Object.defineProperty(task, "deduplicated", { value: json.deduplicated, enumerable: false });
4166
- return task;
4167
- },
4168
- /**
4169
- * Resume a failed/cancelled/max-turn partial code-task. The PR watcher uses
4170
- * this after the runner opens a partial draft PR and CI is no longer pending.
4171
- */
4172
- async resumeCodeTask(taskId, { automaticRateLimit = false, automaticContinuation = false } = {}) {
4173
- return resumeCodeTaskRequest(taskReq, taskId, { automaticRateLimit, automaticContinuation }, () => {
4174
- cachedFirebaseToken = null;
4175
- });
4176
- },
4177
- /**
4178
- * Send a CI-green PR through the production verify-before-act merge route.
4179
- * The server inspects the current diff, applies deterministic blockers, runs
4180
- * consensus, records a receipt, and direct-merges only the inspected SHA.
4181
- */
4182
- /** F35: promote a PARTIAL draft to READY via the plane (admin-only; server re-checks; never merges). */
4183
- promoteDraftPr: (prNumber, automationContext) => promoteDraftPrRequest(req, prNumber, automationContext, () => {
4184
- cachedFirebaseToken = null;
4185
- }),
4186
- async mergeVerifiedPr(prNumber, automationContext) {
4187
- return mergeVerifiedPrRequest(
4188
- req,
4189
- prNumber,
4190
- automationContext,
4191
- () => {
4192
- cachedFirebaseToken = null;
4193
- }
4194
- );
4195
- },
4196
- async postProgress(taskId, patch) {
4197
- const progress = {
4198
- ...patch,
4199
- ...patch.runner_id ? {} : runnerId ? { runner_id: runnerId } : {},
4200
- ...patch.runner_instance_id ? {} : runnerInstanceId ? { runner_instance_id: runnerInstanceId } : {},
4201
- ...patch.claim_occurrence_id ? {} : claimOccurrences.has(taskId) ? { claim_occurrence_id: claimOccurrences.get(taskId) } : {}
4202
- };
4203
- const res = await taskReq("PATCH", `/api/v1/code-task/${taskId}/progress`, progress);
4204
- if (res.status === 409) {
4205
- const conflict = await res.json().catch(() => ({}));
4206
- if (conflict?.error === "code_task_claim_authority_changed") {
4207
- throw new ClaimAuthorityChangedError();
4208
- }
4209
- return { terminal: true };
4210
- }
4211
- if (res.status === 404) return { terminal: true, missing: true };
4212
- if (!res.ok) throw new Error(`progress failed: HTTP ${res.status}`);
4213
- const json = await res.json();
4214
- return { task: json && json.task };
4215
- },
4216
- async getTask(taskId) {
4217
- const res = await taskReq("GET", `/api/v1/code-task/${taskId}`);
4218
- if (res.status === 404) return null;
4219
- if (!res.ok) throw new Error(`getTask failed: HTTP ${res.status}`);
4220
- const json = await res.json();
4221
- return json ? json.task : null;
4222
- },
4223
- async getAssignedSkill(task) {
4224
- const name = task?.skill_invocation?.skill;
4225
- if (!runnerId || !runnerInstanceId || task?.claimed_by !== runnerId || task?.runner_instance_id !== runnerInstanceId || !task?.claim_occurrence_id || !name) {
4226
- throw new ClaimAuthorityChangedError();
4227
- }
4228
- const res = await taskReq("POST", `/api/v1/code-task/${encodeURIComponent(task.code_task_id)}/assigned-skill`, {
4229
- runner_id: runnerId,
4230
- runner_instance_id: runnerInstanceId,
4231
- claim_occurrence_id: task.claim_occurrence_id,
4232
- skill: name
4233
- });
4234
- if (res.status === 401) {
4235
- cachedFirebaseToken = null;
4236
- throw new Error("skill corpus unauthorized (401)");
4237
- }
4238
- if (res.status === 409) {
4239
- const conflict = await res.json().catch(() => ({}));
4240
- if (conflict?.error === "code_task_claim_authority_changed") {
4241
- throw new ClaimAuthorityChangedError();
4242
- }
4243
- }
4244
- if (res.status === 404) throw new Error(`skill not found: ${name}`);
4245
- if (!res.ok) throw new Error(`skill corpus failed: HTTP ${res.status}`);
4246
- const json = await res.json();
4247
- if (json?.corpus_available !== true || !json.skill) {
4248
- throw new Error(`skill corpus unavailable: ${String(json?.reason || "unknown")}`);
4249
- }
4250
- return json.skill;
4251
- },
4252
- async listPrOpenedTasks() {
4253
- return listAllPrOpenedTasks(taskReq);
4254
- },
4255
- async downloadTaskAttachment(taskId, attachmentId) {
4256
- const path24 = `/api/v1/code-task/${encodeURIComponent(taskId)}/attachment/${encodeURIComponent(attachmentId)}`;
4257
- const res = await taskReq("GET", path24);
4258
- if (res.status === 401) cachedFirebaseToken = null;
4259
- if (!res.ok) throw new Error(`attachment download failed: HTTP ${res.status}`);
4260
- return Buffer.from(await res.arrayBuffer());
4261
- },
4262
- // Raised per-attempt timeout (>=15s) + bounded retry — see control-plane-knowledge-context.mjs.
4263
- async getTaskKnowledgeContext(taskId, { query, knowledgeRequestId } = {}) {
4264
- return getTaskKnowledgeContextRequest(req, taskId, { query, knowledgeRequestId }, {
4265
- taskRequestTimeoutMs,
4266
- invalidateToken: () => {
4267
- cachedFirebaseToken = null;
4268
- },
4269
- sleep: sleep3,
4270
- log: (m) => console.warn(`[code-runner ${(/* @__PURE__ */ new Date()).toISOString()}] ${m}`)
4271
- });
4272
- },
4273
- /** ADR-004 § 11.1b: the plane's prepared job, for SHADOW comparison. Never throws. */
4274
- getPreparedJob: (taskId, options) => getPreparedJobRequest(req, taskId, options, () => {
4275
- cachedFirebaseToken = null;
4276
- }),
4277
- /** Weekly Claude token usage report — see control-plane-weekly-tokens.mjs. */
4278
- async postWeeklyTokens(report) {
4279
- return postWeeklyTokensRequest(taskReq, report, () => {
4280
- cachedFirebaseToken = null;
4281
- });
4282
- },
4283
- /**
4284
- * Relay a batch of this machine's local vo-mcp events to vo-telemetry via
4285
- * the control plane (telemetry-forwarder.mjs). Returns { status, body };
4286
- * the forwarder owns backoff/disable policy. See control-plane-telemetry-relay.mjs.
4287
- */
4288
- async relayTelemetryEvents(batch) {
4289
- return relayTelemetryEventsRequest(req, batch, () => {
4290
- cachedFirebaseToken = null;
4291
- });
4292
- },
4293
- /**
4294
- * Send a liveness heartbeat (M2). The control-plane upserts it under the
4295
- * authenticated operator so the web shows a TRUE "runner online" signal.
4296
- * Best-effort caller; throws on 401/non-ok so the daemon can log + retry.
4297
- */
4298
- async postHeartbeat(heartbeat) {
4299
- const body = buildRunnerHeartbeatBody(heartbeat);
4300
- const res = await req("POST", "/api/v1/runner/heartbeat", body, {
4301
- timeoutMs: heartbeatTimeoutMs
4302
- });
4303
- if (res.status === 401) {
4304
- cachedFirebaseToken = null;
4305
- throw new Error("heartbeat unauthorized (401)");
4306
- }
4307
- if (!res.ok) {
4308
- let detail = "";
4309
- try {
4310
- const body2 = await res.json();
4311
- if (Array.isArray(body2?.issue_paths) && body2.issue_paths.length > 0) {
4312
- detail = ` (rejected fields: ${body2.issue_paths.join(", ")})`;
4313
- }
4314
- } catch {
4315
- }
4316
- throw new Error(`heartbeat failed: HTTP ${res.status}${detail}`);
4317
- }
4318
- return res.json();
4319
- },
4320
- async getRunnerStatus({ operatorId } = {}) {
4321
- const query = operatorId ? `?operator_id=${encodeURIComponent(operatorId)}` : "";
4322
- const res = await req("GET", `/api/v1/runner/status${query}`, void 0, {
4323
- timeoutMs: heartbeatTimeoutMs
4324
- });
4325
- if (res.status === 401) {
4326
- cachedFirebaseToken = null;
4327
- throw new Error("runner status unauthorized (401)");
4328
- }
4329
- if (!res.ok) throw new Error(`runner status failed: HTTP ${res.status}`);
4330
- const body = await res.json();
4331
- return Array.isArray(body?.runners) ? body.runners : [];
4332
- },
4333
- async pollRunnerControl({ runnerId: runnerId2, operatorId, supervisorInstanceId, supervisorVersion, capabilities }) {
4334
- const body = { runner_id: runnerId2 };
4335
- if (operatorId) body.operator_id = operatorId;
4336
- if (supervisorInstanceId) body.supervisor_instance_id = supervisorInstanceId;
4337
- if (supervisorVersion) body.supervisor_version = supervisorVersion;
4338
- if (Array.isArray(capabilities) && capabilities.length > 0) body.capabilities = capabilities;
4339
- const res = await taskReq("POST", "/api/v1/runner/control/poll", body);
4340
- if (res.status === 401) {
4341
- cachedFirebaseToken = null;
4342
- throw new Error("runner control poll unauthorized (401)");
4343
- }
4344
- if (!res.ok) throw new Error(`runner control poll failed: HTTP ${res.status}`);
4345
- const json = await res.json();
4346
- const action = json?.action;
4347
- return action && typeof action.action_id === "string" && action.action_id ? { ...action, actionId: action.action_id } : null;
4348
- },
4349
- async completeRunnerControl(actionId, { runnerId: runnerId2, operatorId, supervisorInstanceId, supervisorVersion, capabilities, status, detail }) {
4350
- const body = { runner_id: runnerId2, status };
4351
- if (operatorId) body.operator_id = operatorId;
4352
- if (supervisorInstanceId) body.supervisor_instance_id = supervisorInstanceId;
4353
- if (supervisorVersion) body.supervisor_version = supervisorVersion;
4354
- if (Array.isArray(capabilities) && capabilities.length > 0) body.capabilities = capabilities;
4355
- if (detail) body.detail = detail;
4356
- const res = await taskReq("POST", `/api/v1/runner/control/${encodeURIComponent(actionId)}/complete`, body);
4357
- if (res.status === 401) {
4358
- cachedFirebaseToken = null;
4359
- throw new Error("runner control completion unauthorized (401)");
4360
- }
4361
- if (!res.ok) throw new Error(`runner control completion failed: HTTP ${res.status}`);
4362
- const json = await res.json();
4363
- return json?.action || null;
4364
- },
4365
- /** Mint a GitHub App installation token — see installation-token.mjs. */
4366
- async getInstallationToken({ required = false, readOnly = false, repo = null } = {}) {
4367
- return fetchInstallationToken({ req: taskReq, required, readOnly, repo });
4368
- },
4369
- /**
4370
- * Read the operator's dispatch-mode config (Fast→Ultracode effort setting).
4371
- * Returns the mode string ('fast'|'standard'|'deep'|'ultra'|'marathon'; 'ultracode' legacy),
4372
- * defaulting to 'standard' on any error. Never throws — best-effort.
4373
- */
4374
- async getDispatchMode() {
4375
- try {
4376
- const res = await taskReq("GET", "/api/v1/dispatch-mode-config");
4377
- if (!res.ok) return "standard";
4378
- const json = await res.json();
4379
- return json?.dispatchMode || "standard";
4380
- } catch {
4381
- return "standard";
4085
+ onUnauthorized();
4086
+ throw new Error("autonomous dispatch release unauthorized (401)");
4382
4087
  }
4088
+ if (!res.ok) throw new Error(`autonomous dispatch release failed: HTTP ${res.status}`);
4089
+ return true;
4383
4090
  }
4384
4091
  };
4385
4092
  }
4386
- var cachedFirebaseToken, ClaimAuthorityChangedError;
4387
- var init_control_plane_client = __esm({
4388
- "../../scripts/virtual-office/code-runner/control-plane-client.mjs"() {
4093
+ var init_control_plane_autonomous_admission = __esm({
4094
+ "../../scripts/virtual-office/code-runner/control-plane-autonomous-admission.mjs"() {
4389
4095
  "use strict";
4390
- init_installation_token();
4391
- init_control_plane_heartbeat_body();
4392
- init_control_plane_promote();
4393
- init_control_plane_task_list();
4394
- init_control_plane_resume();
4395
- init_control_plane_autonomous_admission();
4396
- init_control_plane_merge();
4397
- init_control_plane_weekly_tokens();
4398
- init_control_plane_telemetry_relay();
4399
- init_claim_gate_notice();
4400
- init_control_plane_knowledge_context();
4401
- init_control_plane_prepared_job();
4402
- cachedFirebaseToken = null;
4403
- ClaimAuthorityChangedError = class extends Error {
4404
- constructor() {
4405
- super("code-task claim authority changed");
4406
- this.name = "ClaimAuthorityChangedError";
4407
- this.code = "code_task_claim_authority_changed";
4408
- }
4409
- };
4410
4096
  }
4411
4097
  });
4412
4098
 
4413
- // ../../scripts/virtual-office/code-runner/windows-claude-launch.mjs
4414
- import { existsSync as existsSync8, realpathSync } from "node:fs";
4415
- import { win32 as path12 } from "node:path";
4416
- import { spawnSync } from "node:child_process";
4417
- function pathValue(env2) {
4418
- for (const key of ["Path", "PATH", "path"]) {
4419
- if (typeof env2?.[key] === "string") return env2[key];
4099
+ // ../../scripts/virtual-office/code-runner/control-plane-merge.mjs
4100
+ async function mergeVerifiedPrRequest(req, prNumber, automationContext, onUnauthorized) {
4101
+ const res = await req(
4102
+ "POST",
4103
+ "/api/v1/admin/pr/merge",
4104
+ { prNumber, automationContext },
4105
+ { timeoutMs: 12e4 }
4106
+ );
4107
+ if (res.status === 401) {
4108
+ onUnauthorized();
4109
+ throw new Error("gated merge unauthorized (401)");
4420
4110
  }
4421
- return "";
4422
- }
4423
- function cleanPathSegment(value) {
4424
- const trimmed = String(value || "").trim();
4425
- return trimmed.startsWith('"') && trimmed.endsWith('"') ? trimmed.slice(1, -1) : trimmed;
4426
- }
4427
- function envValue(env2, name) {
4428
- const exact = env2?.[name];
4429
- if (typeof exact === "string") return exact.trim();
4430
- const key = Object.keys(env2 || {}).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
4431
- return typeof env2?.[key] === "string" ? env2[key].trim() : "";
4111
+ const json = await res.json().catch(() => ({}));
4112
+ if (res.ok && json?.ok === true) {
4113
+ const result = json?.result && typeof json.result === "object" ? json.result : {};
4114
+ const status = result.merged === true || result.status === "merged" ? "merged" : result.status === "auto-merge-enabled" || String(result.action || "").includes("auto-merge") ? "queued" : "accepted";
4115
+ return {
4116
+ status,
4117
+ detail: typeof result.detail === "string" ? result.detail : null,
4118
+ actionReceiptId: typeof json.action_receipt_id === "string" ? json.action_receipt_id : null
4119
+ };
4120
+ }
4121
+ const captureStoreRetry = json?.action_status === "not_attempted" && (json?.error === "capture_preflight_unavailable" || json?.error === "decision_outcome_intent_failed");
4122
+ if (res.status === 503 && (json?.error === "verify_unavailable" || json?.error === "merge_unavailable" || captureStoreRetry)) {
4123
+ return { status: "retry", reason: json.reason || json.message || json.error || "verification unavailable" };
4124
+ }
4125
+ return {
4126
+ status: "blocked",
4127
+ reason: json?.reason || json?.message || json?.error || `HTTP ${res.status}`,
4128
+ actionReceiptId: typeof json?.action_receipt_id === "string" ? json.action_receipt_id : null
4129
+ };
4432
4130
  }
4433
- function userClaudeCandidates(bin, env2) {
4434
- if (!/^claude(?:\.(?:exe|cmd|ps1))?$/iu.test(bin)) return [];
4435
- const userProfile = envValue(env2, "USERPROFILE");
4436
- const appData = envValue(env2, "APPDATA") || (userProfile ? path12.join(userProfile, "AppData", "Roaming") : "");
4437
- const localAppData = envValue(env2, "LOCALAPPDATA") || (userProfile ? path12.join(userProfile, "AppData", "Local") : "");
4438
- const candidates = [];
4439
- if (appData) {
4440
- const npmBin = path12.join(appData, "npm");
4441
- candidates.push(
4442
- path12.join(npmBin, "claude.exe"),
4443
- path12.join(npmBin, "claude.cmd"),
4444
- path12.join(npmBin, "claude.ps1"),
4445
- path12.join(npmBin, "claude"),
4446
- path12.join(npmBin, ...NATIVE_CLAUDE_PARTS)
4447
- );
4131
+ var init_control_plane_merge = __esm({
4132
+ "../../scripts/virtual-office/code-runner/control-plane-merge.mjs"() {
4133
+ "use strict";
4448
4134
  }
4449
- if (userProfile) candidates.push(path12.join(userProfile, ".local", "bin", "claude.exe"));
4450
- if (localAppData) {
4451
- candidates.push(
4452
- path12.join(localAppData, "Microsoft", "WinGet", "Links", "claude.exe"),
4453
- path12.join(localAppData, "Microsoft", "WindowsApps", "claude.exe")
4454
- );
4135
+ });
4136
+
4137
+ // ../../scripts/virtual-office/code-runner/control-plane-weekly-tokens.mjs
4138
+ async function postWeeklyTokensRequest(taskReq, { operatorId, runnerId, tokens, claudeWeeklyPct, claudeWeeklyResetsAt }, onUnauthorized = () => {
4139
+ }) {
4140
+ const body = {
4141
+ operator_id: operatorId,
4142
+ runner_id: runnerId,
4143
+ input_tokens: tokens.input_tokens,
4144
+ output_tokens: tokens.output_tokens,
4145
+ cache_creation_tokens: tokens.cache_creation_tokens,
4146
+ cache_read_tokens: tokens.cache_read_tokens
4147
+ };
4148
+ if (typeof claudeWeeklyPct === "number") {
4149
+ body.claude_weekly_pct = claudeWeeklyPct;
4455
4150
  }
4456
- return candidates;
4151
+ if (claudeWeeklyResetsAt !== void 0) {
4152
+ body.claude_weekly_resets_at = claudeWeeklyResetsAt;
4153
+ }
4154
+ const res = await taskReq("POST", "/api/v1/weekly-tokens", body);
4155
+ if (res.status === 401) {
4156
+ onUnauthorized();
4157
+ throw new Error("weekly-tokens unauthorized (401)");
4158
+ }
4159
+ if (!res.ok) throw new Error(`weekly-tokens failed: HTTP ${res.status}`);
4160
+ return true;
4457
4161
  }
4458
- function pathCandidates(bin, env2) {
4459
- if (path12.isAbsolute(bin) || /[\\/]/u.test(bin)) {
4460
- return [path12.resolve(bin)];
4162
+ var init_control_plane_weekly_tokens = __esm({
4163
+ "../../scripts/virtual-office/code-runner/control-plane-weekly-tokens.mjs"() {
4164
+ "use strict";
4461
4165
  }
4462
- const extension = path12.extname(bin);
4463
- const fromPath = pathValue(env2).split(";").map(cleanPathSegment).filter(Boolean).flatMap((directory) => extension ? [path12.join(directory, bin)] : [
4464
- path12.join(directory, `${bin}.exe`),
4465
- path12.join(directory, `${bin}.cmd`),
4466
- path12.join(directory, `${bin}.ps1`),
4467
- path12.join(directory, bin)
4468
- ]);
4469
- const seen = /* @__PURE__ */ new Set();
4470
- return [...fromPath, ...userClaudeCandidates(bin, env2)].filter((candidate) => {
4471
- const key = candidate.toLowerCase();
4472
- if (seen.has(key)) return false;
4473
- seen.add(key);
4474
- return true;
4166
+ });
4167
+
4168
+ // ../../scripts/virtual-office/code-runner/control-plane-telemetry-relay.mjs
4169
+ async function relayTelemetryEventsRequest(req, { events, source }, onUnauthorized = () => {
4170
+ }) {
4171
+ const res = await req("POST", "/api/v1/telemetry/relay", { events, ...source ? { source } : {} }, {
4172
+ timeoutMs: TELEMETRY_RELAY_TIMEOUT_MS
4475
4173
  });
4476
- }
4477
- function canonicalExistingPath(candidate, exists, canonicalize) {
4478
- if (!exists(candidate)) return null;
4174
+ if (res.status === 401) onUnauthorized();
4175
+ let body = null;
4479
4176
  try {
4480
- return canonicalize(candidate);
4177
+ body = await res.json();
4481
4178
  } catch {
4482
- return null;
4179
+ body = null;
4483
4180
  }
4181
+ return { status: res.status, body };
4484
4182
  }
4485
- function resolveWindowsClaudeExecutable({
4486
- bin = "claude",
4487
- env: env2 = process.env,
4488
- exists = existsSync8,
4489
- canonicalize = realpathSync
4490
- } = {}) {
4491
- const requested = String(bin || "").trim();
4492
- if (!requested || requested.includes("\0")) {
4493
- throw new TypeError("Claude executable must be a non-empty path without NUL bytes");
4494
- }
4495
- for (const candidate of pathCandidates(requested, env2)) {
4496
- const found = canonicalExistingPath(candidate, exists, canonicalize);
4497
- if (!found) continue;
4498
- if (path12.extname(found).toLowerCase() === ".exe") return found;
4499
- const native = path12.join(path12.dirname(found), ...NATIVE_CLAUDE_PARTS);
4500
- const resolvedNative = canonicalExistingPath(native, exists, canonicalize);
4501
- if (resolvedNative) return resolvedNative;
4183
+ var TELEMETRY_RELAY_TIMEOUT_MS;
4184
+ var init_control_plane_telemetry_relay = __esm({
4185
+ "../../scripts/virtual-office/code-runner/control-plane-telemetry-relay.mjs"() {
4186
+ "use strict";
4187
+ TELEMETRY_RELAY_TIMEOUT_MS = 3e4;
4502
4188
  }
4503
- const error = new Error(
4504
- `Could not resolve a native claude.exe for "${requested}". Install or update Claude Code with the native Windows installer (recommended) or npm install -g @anthropic-ai/claude-code; the HQ runner will not execute a shell-only .cmd/.ps1 shim.`
4505
- );
4506
- error.code = "ENOENT";
4507
- throw error;
4189
+ });
4190
+
4191
+ // ../../scripts/virtual-office/code-runner/claim-gate-notice.mjs
4192
+ function describeClaimGate(gate) {
4193
+ if (!gate || gate.allowed !== false) return null;
4194
+ const reason = String(gate.reason || "denied");
4195
+ const floor = gate.floor_version ? ` (floor ${gate.floor_version})` : "";
4196
+ return `claim gate: DENIED \u2014 ${reason}${floor}: ${(Object.hasOwn(REASON_HELP, reason) ? REASON_HELP[reason] : null) ?? "the control plane refused this runner's claims"}`;
4508
4197
  }
4509
- function buildWindowsClaudeLaunch({
4510
- bin = "claude",
4511
- args = [],
4512
- env: env2 = process.env
4513
- } = {}) {
4198
+ function makeClaimGateNotice({ log: log2 = () => {
4199
+ } } = {}) {
4200
+ let last = null;
4201
+ let current = null;
4514
4202
  return {
4515
- bin: resolveWindowsClaudeExecutable({ bin, env: env2 }),
4516
- args: Array.from(args, (value) => String(value)),
4517
- spawnOptions: {
4518
- shell: false,
4519
- windowsHide: true,
4520
- windowsVerbatimArguments: false
4203
+ current: () => current,
4204
+ observe(json) {
4205
+ const gate = json && typeof json === "object" ? json.claim_gate : null;
4206
+ const denied = gate && gate.allowed === false ? gate : null;
4207
+ current = denied ? { ...denied, observed_at: (/* @__PURE__ */ new Date()).toISOString() } : null;
4208
+ const signature = denied ? `${denied.reason}|${denied.floor_version ?? ""}` : null;
4209
+ if (signature === last) return;
4210
+ if (denied) log2(describeClaimGate(denied));
4211
+ else if (last !== null) log2("claim gate: allowed again \u2014 this runner may claim work");
4212
+ last = signature;
4521
4213
  }
4522
4214
  };
4523
4215
  }
4524
- function spawnClaudeSync(args = [], options = {}) {
4525
- if (process.platform !== "win32") {
4526
- return spawnSync("claude", args, { windowsHide: true, ...options });
4527
- }
4528
- try {
4529
- const launch = buildWindowsClaudeLaunch({
4530
- bin: "claude",
4531
- args,
4532
- env: options.env || process.env
4533
- });
4534
- return spawnSync(launch.bin, launch.args, {
4535
- ...options,
4536
- ...launch.spawnOptions
4537
- });
4538
- } catch (error) {
4539
- return {
4540
- error,
4541
- status: null,
4542
- signal: null,
4543
- output: null,
4544
- stdout: null,
4545
- stderr: null
4546
- };
4547
- }
4548
- }
4549
- var NATIVE_CLAUDE_PARTS;
4550
- var init_windows_claude_launch = __esm({
4551
- "../../scripts/virtual-office/code-runner/windows-claude-launch.mjs"() {
4216
+ var REASON_HELP;
4217
+ var init_claim_gate_notice = __esm({
4218
+ "../../scripts/virtual-office/code-runner/claim-gate-notice.mjs"() {
4552
4219
  "use strict";
4553
- NATIVE_CLAUDE_PARTS = [
4554
- "node_modules",
4555
- "@anthropic-ai",
4556
- "claude-code",
4557
- "bin",
4558
- "claude.exe"
4559
- ];
4220
+ REASON_HELP = {
4221
+ daemon_version_below_floor: "this daemon is older than the approved release target \u2014 it idles until the governed updater brings it current (operator override: VO_RUNNER_CLAIM_MIN_DAEMON_VERSION / VO_RUNNER_CLAIM_VERSION_GATE=off on the control plane)",
4222
+ daemon_version_unreported: "this daemon reports no parseable version in its heartbeat \u2014 too old for the updater to manage, so it may not claim work",
4223
+ no_fresh_heartbeat: "the control plane has no fresh heartbeat from this runner \u2014 claims resume once heartbeats land",
4224
+ runner_denylisted: "this runner id is on the operator quarantine list (VO_RUNNER_CLAIM_DENYLIST)"
4225
+ };
4560
4226
  }
4561
4227
  });
4562
4228
 
4563
- // ../../scripts/virtual-office/code-runner/claude-credential-choice.mjs
4564
- function isTruthyFlag(v) {
4565
- const s = String(v ?? "").trim().toLowerCase();
4566
- return s === "1" || s === "true" || s === "yes" || s === "on";
4229
+ // ../../scripts/virtual-office/code-runner/control-plane-knowledge-context.mjs
4230
+ function canonicalizeKnowledgeContextQuery(value) {
4231
+ return typeof value === "string" ? value.trimStart().slice(0, KNOWLEDGE_CONTEXT_QUERY_MAX_CHARS) : "";
4567
4232
  }
4568
- function wantsLogin(env2) {
4569
- return isTruthyFlag(env2[CLAUDE_PREFER_LOGIN_ENV]) || isTruthyFlag(env2[PREFER_LOGIN_ENV]);
4233
+ function isRetryableStatus(status) {
4234
+ return status >= 500 && status <= 599;
4570
4235
  }
4571
- function wantsKey(env2) {
4572
- return isTruthyFlag(env2[CLAUDE_PREFER_KEY_ENV]) || isTruthyFlag(env2[PREFER_KEY_ENV]);
4236
+ function defaultSleep(ms) {
4237
+ return new Promise((resolve3) => {
4238
+ setTimeout(resolve3, ms);
4239
+ });
4573
4240
  }
4574
- function classifyClaudeCredential(baseEnv = {}, { getKey, probeLogin } = {}) {
4575
- const preferKey = wantsKey(baseEnv);
4576
- if (!preferKey && wantsLogin(baseEnv)) {
4577
- return { source: CLAUDE_CREDENTIAL_SOURCE.PREFER_LOGIN, key: null };
4578
- }
4579
- if (baseEnv.ANTHROPIC_API_KEY) {
4580
- return { source: CLAUDE_CREDENTIAL_SOURCE.ENV_KEY, key: null };
4581
- }
4582
- const key = getKey();
4583
- if (!key) {
4584
- return { source: CLAUDE_CREDENTIAL_SOURCE.NO_KEY, key: null };
4241
+ async function getTaskKnowledgeContextRequest(req, taskId, { query, knowledgeRequestId } = {}, {
4242
+ taskRequestTimeoutMs,
4243
+ invalidateToken = () => {
4244
+ },
4245
+ sleep: sleep3 = defaultSleep,
4246
+ log: log2 = () => {
4585
4247
  }
4586
- if (preferKey) {
4587
- return { source: CLAUDE_CREDENTIAL_SOURCE.KEYCHAIN_PREFER_KEY, key };
4248
+ } = {}) {
4249
+ const body = {};
4250
+ const canonicalQuery = canonicalizeKnowledgeContextQuery(query);
4251
+ if (canonicalQuery.trim()) body.query = canonicalQuery;
4252
+ if (typeof knowledgeRequestId === "string" && knowledgeRequestId) body.knowledge_request_id = knowledgeRequestId;
4253
+ const path25 = `/api/v1/code-task/${encodeURIComponent(taskId)}/knowledge-context`;
4254
+ const timeoutMs = Math.max(Number(taskRequestTimeoutMs) || 0, MIN_KNOWLEDGE_CONTEXT_TIMEOUT_MS);
4255
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt += 1) {
4256
+ let res;
4257
+ let cause;
4258
+ try {
4259
+ res = await req("POST", path25, body, { timeoutMs });
4260
+ } catch (err) {
4261
+ cause = err;
4262
+ }
4263
+ if (!cause) {
4264
+ if (res.status === 401) {
4265
+ invalidateToken();
4266
+ throw new Error("knowledge-context unauthorized (401)");
4267
+ }
4268
+ if (res.status === 404) return null;
4269
+ if (res.ok) return res.json();
4270
+ if (!isRetryableStatus(res.status)) {
4271
+ throw new Error(`knowledge-context failed: HTTP ${res.status}`);
4272
+ }
4273
+ cause = new Error(`knowledge-context failed: HTTP ${res.status}`);
4274
+ }
4275
+ if (attempt === MAX_ATTEMPTS) throw cause;
4276
+ const delayMs = RETRY_DELAYS_MS[attempt - 1];
4277
+ log2(`knowledge-context attempt ${attempt}/${MAX_ATTEMPTS} failed (${cause.message}); retrying in ${delayMs}ms`);
4278
+ await sleep3(delayMs);
4588
4279
  }
4589
- if (probeLogin() === true) {
4590
- return { source: CLAUDE_CREDENTIAL_SOURCE.SUBSCRIPTION_WINS, key: null };
4591
- }
4592
- return { source: CLAUDE_CREDENTIAL_SOURCE.KEYCHAIN, key };
4280
+ throw new Error("knowledge-context retry loop exited unexpectedly");
4593
4281
  }
4594
- var PREFER_LOGIN_ENV, CLAUDE_PREFER_LOGIN_ENV, PREFER_KEY_ENV, CLAUDE_PREFER_KEY_ENV, CLAUDE_CREDENTIAL_SOURCE;
4595
- var init_claude_credential_choice = __esm({
4596
- "../../scripts/virtual-office/code-runner/claude-credential-choice.mjs"() {
4282
+ var MIN_KNOWLEDGE_CONTEXT_TIMEOUT_MS, KNOWLEDGE_CONTEXT_QUERY_MAX_CHARS, RETRY_DELAYS_MS, MAX_ATTEMPTS;
4283
+ var init_control_plane_knowledge_context = __esm({
4284
+ "../../scripts/virtual-office/code-runner/control-plane-knowledge-context.mjs"() {
4597
4285
  "use strict";
4598
- PREFER_LOGIN_ENV = "VO_RUNNER_PREFER_LOGIN";
4599
- CLAUDE_PREFER_LOGIN_ENV = "VO_RUNNER_CLAUDE_PREFER_LOGIN";
4600
- PREFER_KEY_ENV = "VO_RUNNER_PREFER_KEY";
4601
- CLAUDE_PREFER_KEY_ENV = "VO_RUNNER_CLAUDE_PREFER_KEY";
4602
- CLAUDE_CREDENTIAL_SOURCE = Object.freeze({
4603
- /** PREFER_LOGIN set (and not overridden): any API key is ignored. */
4604
- PREFER_LOGIN: "prefer_login",
4605
- /** An explicit ANTHROPIC_API_KEY in the environment — the manual override. */
4606
- ENV_KEY: "env_key",
4607
- /** No key anywhere; the spawn falls through to the login session. */
4608
- NO_KEY: "no_key",
4609
- /** A stored key, used because the operator explicitly opted out of tier 1. */
4610
- KEYCHAIN_PREFER_KEY: "keychain_prefer_key",
4611
- /** A stored key exists but a proven live subscription outranks it. */
4612
- SUBSCRIPTION_WINS: "subscription_wins",
4613
- /** A stored key, used because no live subscription was proven. */
4614
- KEYCHAIN: "keychain"
4615
- });
4286
+ MIN_KNOWLEDGE_CONTEXT_TIMEOUT_MS = 15e3;
4287
+ KNOWLEDGE_CONTEXT_QUERY_MAX_CHARS = 2e3;
4288
+ RETRY_DELAYS_MS = [2e3, 6e3];
4289
+ MAX_ATTEMPTS = RETRY_DELAYS_MS.length + 1;
4616
4290
  }
4617
4291
  });
4618
4292
 
4619
- // ../../scripts/virtual-office/code-runner/anthropic-key-store.mjs
4620
- import { createRequire as createRequire2 } from "node:module";
4621
- import { spawnSync as spawnSync2 } from "node:child_process";
4622
- function defaultEntryCtor() {
4623
- if (_loadTried) return _entryCtor;
4624
- _loadTried = true;
4625
- try {
4626
- _entryCtor = require2("@napi-rs/keyring").Entry;
4627
- } catch {
4628
- _entryCtor = null;
4293
+ // ../../scripts/virtual-office/code-runner/control-plane-prepared-job.mjs
4294
+ function preparedJobQuery({ agent = "claude", env: env2 = {} } = {}) {
4295
+ const params = new URLSearchParams();
4296
+ params.set("agent", String(agent));
4297
+ const sent = [];
4298
+ const dropped = [];
4299
+ for (const key of PREPARED_JOB_ENV_QUERY_KEYS) {
4300
+ const raw = env2?.[key];
4301
+ if (typeof raw !== "string" || raw.length === 0) continue;
4302
+ if (raw.length > PREPARED_JOB_ENV_VALUE_MAX) {
4303
+ dropped.push(key);
4304
+ continue;
4305
+ }
4306
+ params.set(key, raw);
4307
+ sent.push(key);
4629
4308
  }
4630
- return _entryCtor;
4309
+ return { query: params.toString(), sent, dropped };
4631
4310
  }
4632
- function getAnthropicKey({ EntryCtor = defaultEntryCtor() } = {}) {
4633
- if (!EntryCtor) return null;
4311
+ async function refusalCode(res) {
4634
4312
  try {
4635
- return new EntryCtor(KEY_SERVICE, KEY_ACCOUNT).getPassword() || null;
4313
+ const body = await res.json();
4314
+ return typeof body?.error === "string" && body.error ? body.error : null;
4636
4315
  } catch {
4637
4316
  return null;
4638
4317
  }
4639
4318
  }
4640
- function withAnthropicKey(baseEnv = {}, { getKey = getAnthropicKey, probeLogin = probeClaudeLoginState } = {}) {
4641
- const { source, key } = classifyClaudeCredential(baseEnv, { getKey, probeLogin });
4642
- const next = { ...baseEnv };
4643
- if (source === CLAUDE_CREDENTIAL_SOURCE.PREFER_LOGIN) {
4644
- delete next.ANTHROPIC_API_KEY;
4645
- return next;
4319
+ async function getPreparedJobRequest(req, taskId, options = {}, invalidateToken = () => {
4320
+ }) {
4321
+ const { agent = "claude", env: env2 = {}, timeoutMs = 15e3 } = options;
4322
+ const { query, sent, dropped } = preparedJobQuery({ agent, env: env2 });
4323
+ const envMeta = { envSent: sent, envDropped: dropped };
4324
+ if (typeof taskId !== "string" || taskId.length === 0) {
4325
+ return { ok: false, reason: "missing_task_id", status: 0, ...envMeta };
4646
4326
  }
4647
- if (key !== null) next.ANTHROPIC_API_KEY = key;
4648
- return next;
4649
- }
4650
- function claudeCostBasis(env2 = process.env) {
4651
- return String(env2.ANTHROPIC_API_KEY || "").trim() ? "vendor_billed" : "subscription_api_equivalent";
4327
+ const path25 = `/api/v1/code-task/${encodeURIComponent(taskId)}/prepared-job?${query}`;
4328
+ let res;
4329
+ try {
4330
+ res = await req("GET", path25, void 0, { timeoutMs });
4331
+ } catch (err) {
4332
+ return { ok: false, reason: `transport: ${err?.message || String(err)}`, status: 0, ...envMeta };
4333
+ }
4334
+ if (res?.status === 401) {
4335
+ try {
4336
+ invalidateToken();
4337
+ } catch {
4338
+ }
4339
+ return { ok: false, reason: "unauthorized", status: 401, ...envMeta };
4340
+ }
4341
+ if (!res?.ok) {
4342
+ const code = await refusalCode(res);
4343
+ return { ok: false, reason: code || `http_${res?.status ?? "unknown"}`, status: res?.status ?? 0, ...envMeta };
4344
+ }
4345
+ let body;
4346
+ try {
4347
+ body = await res.json();
4348
+ } catch (err) {
4349
+ return { ok: false, reason: `unreadable_body: ${err?.message || String(err)}`, status: res.status, ...envMeta };
4350
+ }
4351
+ const job = body?.prepared_job;
4352
+ if (!job || typeof job !== "object") {
4353
+ return { ok: false, reason: "no_prepared_job_in_body", status: res.status, ...envMeta };
4354
+ }
4355
+ return {
4356
+ ok: true,
4357
+ job,
4358
+ composition: body?.composition && typeof body.composition === "object" ? body.composition : {},
4359
+ ...envMeta
4360
+ };
4652
4361
  }
4653
- function describeAnthropicAuthSource(baseEnv = {}, { getKey = getAnthropicKey, probeLogin = probeClaudeLoginState } = {}) {
4654
- const { source } = classifyClaudeCredential(baseEnv, { getKey, probeLogin });
4655
- return AUTH_SOURCE_DESCRIPTION[source];
4362
+ var PREPARED_JOB_ENV_QUERY_KEYS, PREPARED_JOB_ENV_VALUE_MAX;
4363
+ var init_control_plane_prepared_job = __esm({
4364
+ "../../scripts/virtual-office/code-runner/control-plane-prepared-job.mjs"() {
4365
+ "use strict";
4366
+ PREPARED_JOB_ENV_QUERY_KEYS = [
4367
+ "VO_CODE_RUNNER_NO_WEB",
4368
+ "VO_CODE_RUNNER_NO_WORKFLOW",
4369
+ "VO_CODE_RUNNER_NO_CONSENSUS",
4370
+ "VO_CODE_RUNNER_PERMISSION_MODE",
4371
+ "VO_CODE_RUNNER_DEFAULT_BUDGET_USD",
4372
+ "VO_CODE_RUNNER_META_REASONING_EFFORT",
4373
+ "VO_CODE_RUNNER_ALLOW_MISSING_KNOWLEDGE_CONTEXT",
4374
+ "VO_ENABLE_CONTEXT7"
4375
+ ];
4376
+ PREPARED_JOB_ENV_VALUE_MAX = 64;
4377
+ }
4378
+ });
4379
+
4380
+ // src/runner/control-plane-auth-stub.mjs
4381
+ var control_plane_auth_stub_exports = {};
4382
+ __export(control_plane_auth_stub_exports, {
4383
+ getFirebaseAuth: () => getFirebaseAuth
4384
+ });
4385
+ async function getFirebaseAuth() {
4386
+ throw new Error(
4387
+ "vo-mcp runner: no control-plane credential. Run `vo-mcp login` first \u2014 the runner authenticates with your stored vo_credential (or set VO_CONTROL_PLANE_ADMIN_TOKEN)."
4388
+ );
4656
4389
  }
4657
- function augmentAuthError(summary) {
4658
- const s = String(summary ?? "");
4659
- if (!AUTH_ERROR_RE.test(s)) return s;
4660
- return `${s}
4661
- \u21B3 Anthropic auth failed on the runner. The \`claude\` CLI is a SEPARATE install/login from the Claude Desktop app and the Claude Code IDE extension \u2014 signing into those does NOT authenticate it. Fix: run \`claude auth login\` (Claude subscription) on the runner machine, or clear any stale ANTHROPIC_API_KEY (env / OS keychain / .env.local) and set VO_RUNNER_PREFER_LOGIN=1 \u2014 then restart the runner. Verify with \`claude -p "say hi"\`.`;
4390
+ var init_control_plane_auth_stub = __esm({
4391
+ "src/runner/control-plane-auth-stub.mjs"() {
4392
+ }
4393
+ });
4394
+
4395
+ // ../../scripts/virtual-office/code-runner/control-plane-client.mjs
4396
+ async function resolveBearer(env2) {
4397
+ const adminToken = env2.VO_CONTROL_PLANE_ADMIN_TOKEN;
4398
+ if (adminToken) return adminToken;
4399
+ if (cachedFirebaseToken) return cachedFirebaseToken;
4400
+ const { getFirebaseAuth: getFirebaseAuth2 } = await Promise.resolve().then(() => (init_control_plane_auth_stub(), control_plane_auth_stub_exports));
4401
+ const auth = await getFirebaseAuth2({ env: env2 });
4402
+ if (!auth || !auth.idToken) {
4403
+ throw new Error(
4404
+ "no control-plane credential: set VO_CONTROL_PLANE_ADMIN_TOKEN, or SMOKE_EMAIL/SMOKE_PASSWORD/SMOKE_API_KEY"
4405
+ );
4406
+ }
4407
+ cachedFirebaseToken = auth.idToken;
4408
+ return cachedFirebaseToken;
4662
4409
  }
4663
- function probeClaudeLoginState({
4664
- spawn: spawn5 = spawnSync2,
4665
- buildWindowsLaunch = buildWindowsClaudeLaunch,
4666
- platform: platform4 = process.platform
4410
+ function createControlPlaneClient({
4411
+ baseUrl,
4412
+ env: env2 = process.env,
4413
+ fetchImpl = fetch,
4414
+ heartbeatTimeoutMs = Math.min(
4415
+ Math.max(Number(env2.VO_CODE_RUNNER_HEARTBEAT_TIMEOUT_MS) || 15e3, 1e3),
4416
+ 6e4
4417
+ ),
4418
+ taskRequestTimeoutMs = Math.min(
4419
+ Math.max(Number(env2.VO_CODE_RUNNER_TASK_REQUEST_TIMEOUT_MS) || 5e3, 100),
4420
+ 6e4
4421
+ ),
4422
+ runnerId,
4423
+ runnerInstanceId,
4424
+ sleep: sleep3
4667
4425
  } = {}) {
4668
- try {
4669
- const launch = platform4 === "win32" ? buildWindowsLaunch({ bin: "claude", args: ["auth", "status"] }) : { bin: "claude", args: ["auth", "status"], spawnOptions: { windowsHide: true } };
4670
- const st = spawn5(launch.bin, launch.args, { ...launch.spawnOptions, timeout: 5e3, encoding: "utf8" });
4671
- const parsed = JSON.parse(String(st.stdout || "").trim() || "{}");
4672
- return typeof parsed.loggedIn === "boolean" ? parsed.loggedIn : null;
4673
- } catch {
4674
- return null;
4426
+ const resolvedBaseUrl = baseUrl ?? env2.VO_CONTROL_PLANE_URL ?? "";
4427
+ if (!resolvedBaseUrl) throw new Error("VO_CONTROL_PLANE_URL is required for the code-runner daemon");
4428
+ const root = resolvedBaseUrl.replace(/\/+$/, "");
4429
+ const claimOccurrences = /* @__PURE__ */ new Map();
4430
+ async function req(method, path25, body, { timeoutMs } = {}) {
4431
+ const bearer = await resolveBearer(env2);
4432
+ const controller = timeoutMs ? new AbortController() : null;
4433
+ let timeoutId;
4434
+ const request = Promise.resolve(fetchImpl(`${root}${path25}`, {
4435
+ method,
4436
+ headers: {
4437
+ "content-type": "application/json",
4438
+ authorization: `Bearer ${bearer}`
4439
+ },
4440
+ body: body === void 0 ? void 0 : JSON.stringify(body),
4441
+ ...controller ? { signal: controller.signal } : {}
4442
+ }));
4443
+ if (!timeoutMs) return request;
4444
+ const timeout = new Promise((_, reject) => {
4445
+ timeoutId = setTimeout(() => {
4446
+ controller.abort();
4447
+ reject(new Error(`control-plane ${path25} timed out after ${timeoutMs}ms`));
4448
+ }, timeoutMs);
4449
+ });
4450
+ try {
4451
+ return await Promise.race([request, timeout]);
4452
+ } finally {
4453
+ clearTimeout(timeoutId);
4454
+ }
4675
4455
  }
4456
+ const taskReq = (method, path25, body, options = {}) => req(method, path25, body, { timeoutMs: taskRequestTimeoutMs, ...options });
4457
+ const claimGate = makeClaimGateNotice({ log: (m) => console.warn(`[code-runner ${(/* @__PURE__ */ new Date()).toISOString()}] ${m}`) });
4458
+ return {
4459
+ getClaimGate: () => claimGate.current(),
4460
+ // last DENIED claim-gate verdict (null when allowed) — for /status + tests
4461
+ ...makeAutonomousDispatchAdmissionClient(
4462
+ req,
4463
+ taskRequestTimeoutMs,
4464
+ () => {
4465
+ cachedFirebaseToken = null;
4466
+ }
4467
+ ),
4468
+ /**
4469
+ * Claim the next pending task. Returns the task or null (empty queue).
4470
+ * `repos` (optional `owner/name` list) and `operatorIds` (optional
4471
+ * `operator_id` list) scope the claim so this daemon only picks up tasks it
4472
+ * serves — the control-plane filters by both (logical AND), so another
4473
+ * operator's task never lands on (or bills) this machine.
4474
+ */
4475
+ async claim(runnerId2, repos, operatorIds, session = {}) {
4476
+ const body = { runner_id: runnerId2 };
4477
+ if (Array.isArray(repos) && repos.length > 0) body.repos = repos;
4478
+ if (Array.isArray(operatorIds) && operatorIds.length > 0) body.operator_ids = operatorIds;
4479
+ if (session.runnerInstanceId) {
4480
+ body.runner_instance_id = session.runnerInstanceId;
4481
+ body.runner_progress_protocol_version = 2;
4482
+ }
4483
+ if (session.runnerInstanceId && session.reconcileStale) body.reconcile_stale = true;
4484
+ if (session.defaultAgent) body.default_agent = session.defaultAgent;
4485
+ if (AGENT_AUTH_SOURCES.includes(session.agentAuthSource)) body.agent_auth_source = session.agentAuthSource;
4486
+ if (Array.isArray(session.availableAgents)) {
4487
+ body.available_agents = session.availableAgents.filter((entry) => entry?.installed === true && entry?.authenticated === true).map((entry) => entry.agent);
4488
+ }
4489
+ const res = await taskReq("POST", "/api/v1/code-task/claim", body);
4490
+ if (res.status === 401) {
4491
+ cachedFirebaseToken = null;
4492
+ throw new Error("claim unauthorized (401)");
4493
+ }
4494
+ if (!res.ok) throw new Error(`claim failed: HTTP ${res.status}`);
4495
+ const json = await res.json();
4496
+ claimGate.observe(json);
4497
+ const task = json && json.task ? json.task : null;
4498
+ if (task?.claim_occurrence_id) claimOccurrences.set(task.code_task_id, task.claim_occurrence_id);
4499
+ return task;
4500
+ },
4501
+ /**
4502
+ * Enqueue a new code-task (used by the PR watcher to auto-dispatch a CI fix).
4503
+ * Server derives operator/tenant from the daemon's authenticated principal.
4504
+ * Returns the created task, or throws on a non-2xx response.
4505
+ */
4506
+ async enqueueCodeTask({ repo, prompt, max_budget_usd, max_turns, dispatch_mode, tier, agent, model, dispatch_occurrence_key, autonomous_reservation_id, on_behalf_of_operator_id, repair_pr_number, repair_kind, repair_head_sha, repair_chain }) {
4507
+ const body = { repo, prompt };
4508
+ if (typeof max_budget_usd === "number") body.max_budget_usd = max_budget_usd;
4509
+ if (typeof max_turns === "number") body.max_turns = max_turns;
4510
+ for (const [key, value] of Object.entries({ dispatch_mode, tier, agent, model, repair_kind, repair_head_sha })) {
4511
+ if (value) body[key] = value;
4512
+ }
4513
+ if (dispatch_occurrence_key) body.dispatch_occurrence_key = dispatch_occurrence_key;
4514
+ if (autonomous_reservation_id) body.autonomous_reservation_id = autonomous_reservation_id;
4515
+ if (on_behalf_of_operator_id) body.on_behalf_of_operator_id = on_behalf_of_operator_id;
4516
+ if (Number.isInteger(repair_pr_number) && repair_pr_number > 0) body.repair_pr_number = repair_pr_number;
4517
+ if (repair_chain) body.repair_chain = repair_chain;
4518
+ const res = await taskReq("POST", "/api/v1/code-task", body);
4519
+ if (res.status === 401) {
4520
+ cachedFirebaseToken = null;
4521
+ throw new Error("enqueue unauthorized (401)");
4522
+ }
4523
+ if (!res.ok) {
4524
+ let code = null;
4525
+ try {
4526
+ const errBody = await res.json();
4527
+ code = typeof errBody?.error === "string" ? errBody.error : null;
4528
+ } catch {
4529
+ }
4530
+ const err = new Error(`enqueue failed: HTTP ${res.status}${code ? ` (${code})` : ""}`);
4531
+ err.status = res.status;
4532
+ err.code = code;
4533
+ throw err;
4534
+ }
4535
+ const json = await res.json();
4536
+ const task = json && json.task ? json.task : null;
4537
+ if (task && typeof json.deduplicated === "boolean") Object.defineProperty(task, "deduplicated", { value: json.deduplicated, enumerable: false });
4538
+ return task;
4539
+ },
4540
+ /**
4541
+ * Resume a failed/cancelled/max-turn partial code-task. The PR watcher uses
4542
+ * this after the runner opens a partial draft PR and CI is no longer pending.
4543
+ */
4544
+ async resumeCodeTask(taskId, { automaticRateLimit = false, automaticContinuation = false } = {}) {
4545
+ return resumeCodeTaskRequest(taskReq, taskId, { automaticRateLimit, automaticContinuation }, () => {
4546
+ cachedFirebaseToken = null;
4547
+ });
4548
+ },
4549
+ /**
4550
+ * Send a CI-green PR through the production verify-before-act merge route.
4551
+ * The server inspects the current diff, applies deterministic blockers, runs
4552
+ * consensus, records a receipt, and direct-merges only the inspected SHA.
4553
+ */
4554
+ /** F35: promote a PARTIAL draft to READY via the plane (admin-only; server re-checks; never merges). */
4555
+ promoteDraftPr: (prNumber, automationContext) => promoteDraftPrRequest(req, prNumber, automationContext, () => {
4556
+ cachedFirebaseToken = null;
4557
+ }),
4558
+ async mergeVerifiedPr(prNumber, automationContext) {
4559
+ return mergeVerifiedPrRequest(
4560
+ req,
4561
+ prNumber,
4562
+ automationContext,
4563
+ () => {
4564
+ cachedFirebaseToken = null;
4565
+ }
4566
+ );
4567
+ },
4568
+ async postProgress(taskId, patch) {
4569
+ const progress = {
4570
+ ...patch,
4571
+ ...patch.runner_id ? {} : runnerId ? { runner_id: runnerId } : {},
4572
+ ...patch.runner_instance_id ? {} : runnerInstanceId ? { runner_instance_id: runnerInstanceId } : {},
4573
+ ...patch.claim_occurrence_id ? {} : claimOccurrences.has(taskId) ? { claim_occurrence_id: claimOccurrences.get(taskId) } : {}
4574
+ };
4575
+ const res = await taskReq("PATCH", `/api/v1/code-task/${taskId}/progress`, progress);
4576
+ if (res.status === 409) {
4577
+ const conflict = await res.json().catch(() => ({}));
4578
+ if (conflict?.error === "code_task_claim_authority_changed") {
4579
+ throw new ClaimAuthorityChangedError();
4580
+ }
4581
+ return { terminal: true };
4582
+ }
4583
+ if (res.status === 404) return { terminal: true, missing: true };
4584
+ if (!res.ok) throw new Error(`progress failed: HTTP ${res.status}`);
4585
+ const json = await res.json();
4586
+ return { task: json && json.task };
4587
+ },
4588
+ async getTask(taskId) {
4589
+ const res = await taskReq("GET", `/api/v1/code-task/${taskId}`);
4590
+ if (res.status === 404) return null;
4591
+ if (!res.ok) throw new Error(`getTask failed: HTTP ${res.status}`);
4592
+ const json = await res.json();
4593
+ return json ? json.task : null;
4594
+ },
4595
+ async getAssignedSkill(task) {
4596
+ const name = task?.skill_invocation?.skill;
4597
+ if (!runnerId || !runnerInstanceId || task?.claimed_by !== runnerId || task?.runner_instance_id !== runnerInstanceId || !task?.claim_occurrence_id || !name) {
4598
+ throw new ClaimAuthorityChangedError();
4599
+ }
4600
+ const res = await taskReq("POST", `/api/v1/code-task/${encodeURIComponent(task.code_task_id)}/assigned-skill`, {
4601
+ runner_id: runnerId,
4602
+ runner_instance_id: runnerInstanceId,
4603
+ claim_occurrence_id: task.claim_occurrence_id,
4604
+ skill: name
4605
+ });
4606
+ if (res.status === 401) {
4607
+ cachedFirebaseToken = null;
4608
+ throw new Error("skill corpus unauthorized (401)");
4609
+ }
4610
+ if (res.status === 409) {
4611
+ const conflict = await res.json().catch(() => ({}));
4612
+ if (conflict?.error === "code_task_claim_authority_changed") {
4613
+ throw new ClaimAuthorityChangedError();
4614
+ }
4615
+ }
4616
+ if (res.status === 404) throw new Error(`skill not found: ${name}`);
4617
+ if (!res.ok) throw new Error(`skill corpus failed: HTTP ${res.status}`);
4618
+ const json = await res.json();
4619
+ if (json?.corpus_available !== true || !json.skill) {
4620
+ throw new Error(`skill corpus unavailable: ${String(json?.reason || "unknown")}`);
4621
+ }
4622
+ return json.skill;
4623
+ },
4624
+ async listPrOpenedTasks() {
4625
+ return listAllPrOpenedTasks(taskReq);
4626
+ },
4627
+ async downloadTaskAttachment(taskId, attachmentId) {
4628
+ const path25 = `/api/v1/code-task/${encodeURIComponent(taskId)}/attachment/${encodeURIComponent(attachmentId)}`;
4629
+ const res = await taskReq("GET", path25);
4630
+ if (res.status === 401) cachedFirebaseToken = null;
4631
+ if (!res.ok) throw new Error(`attachment download failed: HTTP ${res.status}`);
4632
+ return Buffer.from(await res.arrayBuffer());
4633
+ },
4634
+ // Raised per-attempt timeout (>=15s) + bounded retry — see control-plane-knowledge-context.mjs.
4635
+ async getTaskKnowledgeContext(taskId, { query, knowledgeRequestId } = {}) {
4636
+ return getTaskKnowledgeContextRequest(req, taskId, { query, knowledgeRequestId }, {
4637
+ taskRequestTimeoutMs,
4638
+ invalidateToken: () => {
4639
+ cachedFirebaseToken = null;
4640
+ },
4641
+ sleep: sleep3,
4642
+ log: (m) => console.warn(`[code-runner ${(/* @__PURE__ */ new Date()).toISOString()}] ${m}`)
4643
+ });
4644
+ },
4645
+ /** ADR-004 § 11.1b: the plane's prepared job, for SHADOW comparison. Never throws. */
4646
+ getPreparedJob: (taskId, options) => getPreparedJobRequest(req, taskId, options, () => {
4647
+ cachedFirebaseToken = null;
4648
+ }),
4649
+ /** Weekly Claude token usage report — see control-plane-weekly-tokens.mjs. */
4650
+ async postWeeklyTokens(report) {
4651
+ return postWeeklyTokensRequest(taskReq, report, () => {
4652
+ cachedFirebaseToken = null;
4653
+ });
4654
+ },
4655
+ /**
4656
+ * Relay a batch of this machine's local vo-mcp events to vo-telemetry via
4657
+ * the control plane (telemetry-forwarder.mjs). Returns { status, body };
4658
+ * the forwarder owns backoff/disable policy. See control-plane-telemetry-relay.mjs.
4659
+ */
4660
+ async relayTelemetryEvents(batch) {
4661
+ return relayTelemetryEventsRequest(req, batch, () => {
4662
+ cachedFirebaseToken = null;
4663
+ });
4664
+ },
4665
+ /**
4666
+ * Send a liveness heartbeat (M2). The control-plane upserts it under the
4667
+ * authenticated operator so the web shows a TRUE "runner online" signal.
4668
+ * Best-effort caller; throws on 401/non-ok so the daemon can log + retry.
4669
+ */
4670
+ async postHeartbeat(heartbeat) {
4671
+ const body = buildRunnerHeartbeatBody(heartbeat);
4672
+ const res = await req("POST", "/api/v1/runner/heartbeat", body, {
4673
+ timeoutMs: heartbeatTimeoutMs
4674
+ });
4675
+ if (res.status === 401) {
4676
+ cachedFirebaseToken = null;
4677
+ throw new Error("heartbeat unauthorized (401)");
4678
+ }
4679
+ if (!res.ok) {
4680
+ let detail = "";
4681
+ try {
4682
+ const body2 = await res.json();
4683
+ if (Array.isArray(body2?.issue_paths) && body2.issue_paths.length > 0) {
4684
+ detail = ` (rejected fields: ${body2.issue_paths.join(", ")})`;
4685
+ }
4686
+ } catch {
4687
+ }
4688
+ throw new Error(`heartbeat failed: HTTP ${res.status}${detail}`);
4689
+ }
4690
+ return res.json();
4691
+ },
4692
+ async getRunnerStatus({ operatorId } = {}) {
4693
+ const query = operatorId ? `?operator_id=${encodeURIComponent(operatorId)}` : "";
4694
+ const res = await req("GET", `/api/v1/runner/status${query}`, void 0, {
4695
+ timeoutMs: heartbeatTimeoutMs
4696
+ });
4697
+ if (res.status === 401) {
4698
+ cachedFirebaseToken = null;
4699
+ throw new Error("runner status unauthorized (401)");
4700
+ }
4701
+ if (!res.ok) throw new Error(`runner status failed: HTTP ${res.status}`);
4702
+ const body = await res.json();
4703
+ return Array.isArray(body?.runners) ? body.runners : [];
4704
+ },
4705
+ async pollRunnerControl({ runnerId: runnerId2, operatorId, supervisorInstanceId, supervisorVersion, capabilities }) {
4706
+ const body = { runner_id: runnerId2 };
4707
+ if (operatorId) body.operator_id = operatorId;
4708
+ if (supervisorInstanceId) body.supervisor_instance_id = supervisorInstanceId;
4709
+ if (supervisorVersion) body.supervisor_version = supervisorVersion;
4710
+ if (Array.isArray(capabilities) && capabilities.length > 0) body.capabilities = capabilities;
4711
+ const res = await taskReq("POST", "/api/v1/runner/control/poll", body);
4712
+ if (res.status === 401) {
4713
+ cachedFirebaseToken = null;
4714
+ throw new Error("runner control poll unauthorized (401)");
4715
+ }
4716
+ if (!res.ok) throw new Error(`runner control poll failed: HTTP ${res.status}`);
4717
+ const json = await res.json();
4718
+ const action = json?.action;
4719
+ return action && typeof action.action_id === "string" && action.action_id ? { ...action, actionId: action.action_id } : null;
4720
+ },
4721
+ async completeRunnerControl(actionId, { runnerId: runnerId2, operatorId, supervisorInstanceId, supervisorVersion, capabilities, status, detail }) {
4722
+ const body = { runner_id: runnerId2, status };
4723
+ if (operatorId) body.operator_id = operatorId;
4724
+ if (supervisorInstanceId) body.supervisor_instance_id = supervisorInstanceId;
4725
+ if (supervisorVersion) body.supervisor_version = supervisorVersion;
4726
+ if (Array.isArray(capabilities) && capabilities.length > 0) body.capabilities = capabilities;
4727
+ if (detail) body.detail = detail;
4728
+ const res = await taskReq("POST", `/api/v1/runner/control/${encodeURIComponent(actionId)}/complete`, body);
4729
+ if (res.status === 401) {
4730
+ cachedFirebaseToken = null;
4731
+ throw new Error("runner control completion unauthorized (401)");
4732
+ }
4733
+ if (!res.ok) throw new Error(`runner control completion failed: HTTP ${res.status}`);
4734
+ const json = await res.json();
4735
+ return json?.action || null;
4736
+ },
4737
+ /** Mint a GitHub App installation token — see installation-token.mjs. */
4738
+ async getInstallationToken({ required = false, readOnly = false, repo = null } = {}) {
4739
+ return fetchInstallationToken({ req: taskReq, required, readOnly, repo });
4740
+ },
4741
+ /**
4742
+ * Read the operator's dispatch-mode config (Fast→Ultracode effort setting).
4743
+ * Returns the mode string ('fast'|'standard'|'deep'|'ultra'|'marathon'; 'ultracode' legacy),
4744
+ * defaulting to 'standard' on any error. Never throws — best-effort.
4745
+ */
4746
+ async getDispatchMode() {
4747
+ try {
4748
+ const res = await taskReq("GET", "/api/v1/dispatch-mode-config");
4749
+ if (!res.ok) return "standard";
4750
+ const json = await res.json();
4751
+ return json?.dispatchMode || "standard";
4752
+ } catch {
4753
+ return "standard";
4754
+ }
4755
+ }
4756
+ };
4676
4757
  }
4677
- var require2, KEY_SERVICE, KEY_ACCOUNT, _entryCtor, _loadTried, AUTH_SOURCE_DESCRIPTION, AUTH_ERROR_RE;
4678
- var init_anthropic_key_store = __esm({
4679
- "../../scripts/virtual-office/code-runner/anthropic-key-store.mjs"() {
4758
+ var cachedFirebaseToken, ClaimAuthorityChangedError;
4759
+ var init_control_plane_client = __esm({
4760
+ "../../scripts/virtual-office/code-runner/control-plane-client.mjs"() {
4680
4761
  "use strict";
4681
- init_windows_claude_launch();
4682
- init_claude_credential_choice();
4683
- init_claude_credential_choice();
4684
- require2 = createRequire2(import.meta.url);
4685
- KEY_SERVICE = "algosuite-vo";
4686
- KEY_ACCOUNT = "anthropic-api-key";
4687
- _loadTried = false;
4688
- AUTH_SOURCE_DESCRIPTION = Object.freeze({
4689
- [CLAUDE_CREDENTIAL_SOURCE.PREFER_LOGIN]: "claude auth login (VO_RUNNER_PREFER_LOGIN set \u2014 any API key ignored)",
4690
- [CLAUDE_CREDENTIAL_SOURCE.ENV_KEY]: "ANTHROPIC_API_KEY from environment",
4691
- [CLAUDE_CREDENTIAL_SOURCE.NO_KEY]: "claude auth login session (no API key set)",
4692
- [CLAUDE_CREDENTIAL_SOURCE.KEYCHAIN_PREFER_KEY]: "ANTHROPIC_API_KEY from OS keychain (VO_RUNNER_PREFER_KEY set \u2014 subscription ignored)",
4693
- [CLAUDE_CREDENTIAL_SOURCE.SUBSCRIPTION_WINS]: "claude auth login session (subscription beats the stored keychain key)",
4694
- [CLAUDE_CREDENTIAL_SOURCE.KEYCHAIN]: "ANTHROPIC_API_KEY from OS keychain"
4695
- });
4696
- AUTH_ERROR_RE = /\b401\b|invalid[^.]{0,24}(authentication|credential)|authentication_error|unauthorized|not[ _-]?authenticated/i;
4762
+ init_installation_token();
4763
+ init_control_plane_heartbeat_body();
4764
+ init_control_plane_promote();
4765
+ init_control_plane_task_list();
4766
+ init_control_plane_resume();
4767
+ init_control_plane_autonomous_admission();
4768
+ init_control_plane_merge();
4769
+ init_control_plane_weekly_tokens();
4770
+ init_control_plane_telemetry_relay();
4771
+ init_claim_gate_notice();
4772
+ init_agent_auth_attestation();
4773
+ init_control_plane_knowledge_context();
4774
+ init_control_plane_prepared_job();
4775
+ cachedFirebaseToken = null;
4776
+ ClaimAuthorityChangedError = class extends Error {
4777
+ constructor() {
4778
+ super("code-task claim authority changed");
4779
+ this.name = "ClaimAuthorityChangedError";
4780
+ this.code = "code_task_claim_authority_changed";
4781
+ }
4782
+ };
4697
4783
  }
4698
4784
  });
4699
4785
 
@@ -5827,11 +5913,14 @@ function runAgentTask({
5827
5913
  exitDrainGraceMs = 300,
5828
5914
  armTerminalCleanup = armTerminalProcessCleanup,
5829
5915
  spawnImpl = spawn2,
5830
- sandbox = null
5916
+ sandbox = null,
5917
+ allowApiBilling = false
5831
5918
  }) {
5832
5919
  return new Promise((resolve3) => {
5833
5920
  const args = runner.buildArgs({ permissionMode, maxTurns, model, effort, maxBudgetUsd, researchHarness, toolPolicy, structuredOutputSchema, prompt });
5834
- const spawnEnv = typeof runner.applyAuthEnv === "function" ? runner.applyAuthEnv(env2) : env2;
5921
+ const authedEnv = typeof runner.applyAuthEnv === "function" ? runner.applyAuthEnv(env2) : env2;
5922
+ const billing = applyApiBillingPolicy(authedEnv, { runnerEnv: env2, allowApiBilling });
5923
+ const spawnEnv = billing.env;
5835
5924
  const costBasis = typeof runner.costBasis === "function" ? runner.costBasis(spawnEnv) : "unknown";
5836
5925
  if (costBasis === "vendor_billed" && runner.enforcesBudgetCap !== true && env2.VO_CODE_RUNNER_ALLOW_UNCAPPED_VENDOR_BILLED !== "1") {
5837
5926
  throw new Error(
@@ -5875,7 +5964,7 @@ function runAgentTask({
5875
5964
  } catch {
5876
5965
  }
5877
5966
  let buffer = "";
5878
- let result = { ok: false, costUsd: null, costBasis, summary: "", lastAgentMessage: null, structuredOutput: null, terminalSubtype: null, numTurns: null, tokenUsage: null, modelUsage: null, executionStarted: false, killed: false };
5967
+ let result = { ok: false, costUsd: null, costBasis, summary: "", lastAgentMessage: null, structuredOutput: null, terminalSubtype: null, numTurns: null, tokenUsage: null, modelUsage: null, executionStarted: false, killed: false, agentAuthSource: billing.agent_auth_source };
5879
5968
  child.once("spawn", () => {
5880
5969
  result = { ...result, executionStarted: true };
5881
5970
  Promise.resolve(onSpawn()).catch(() => {
@@ -6060,6 +6149,7 @@ var init_claude_runner = __esm({
6060
6149
  init_claude_result_event();
6061
6150
  init_claude_auth_check();
6062
6151
  init_claude_skill_capability();
6152
+ init_agent_auth_attestation();
6063
6153
  ClaudeRunner = class {
6064
6154
  get enforcesBudgetCap() {
6065
6155
  return true;
@@ -6281,7 +6371,7 @@ var init_error_message = __esm({
6281
6371
  import { spawnSync as spawnSync7 } from "node:child_process";
6282
6372
  import { existsSync as existsSync10 } from "node:fs";
6283
6373
  import { win32 as win322 } from "node:path";
6284
- function isTruthyFlag2(value) {
6374
+ function isTruthyFlag3(value) {
6285
6375
  return ["1", "true", "yes", "on"].includes(String(value ?? "").trim().toLowerCase());
6286
6376
  }
6287
6377
  function resolveCodexBinary({
@@ -6432,7 +6522,7 @@ var init_codex_runner = __esm({
6432
6522
  };
6433
6523
  }
6434
6524
  applyAuthEnv(env2 = process.env) {
6435
- if (isTruthyFlag2(env2[CODEX_PREFER_LOGIN_ENV]) || isTruthyFlag2(env2[LEGACY_PREFER_LOGIN_ENV])) {
6525
+ if (isTruthyFlag3(env2[CODEX_PREFER_LOGIN_ENV]) || isTruthyFlag3(env2[LEGACY_PREFER_LOGIN_ENV])) {
6436
6526
  const out = { ...env2 };
6437
6527
  delete out.OPENAI_API_KEY;
6438
6528
  delete out.CODEX_API_KEY;
@@ -7870,14 +7960,14 @@ function parsePorcelainZ(out) {
7870
7960
  for (let i = 0; i < tokens.length; i += 1) {
7871
7961
  const token2 = tokens[i];
7872
7962
  if (!token2) continue;
7873
- const path24 = token2.slice(3);
7874
- if (path24) files.push(path24);
7963
+ const path25 = token2.slice(3);
7964
+ if (path25) files.push(path25);
7875
7965
  if (token2[0] === "R" || token2[0] === "C") i += 1;
7876
7966
  }
7877
7967
  return files;
7878
7968
  }
7879
- function isAgentScratch(path24) {
7880
- const normalized = String(path24 || "");
7969
+ function isAgentScratch(path25) {
7970
+ const normalized = String(path25 || "");
7881
7971
  return SCRATCH_PATTERNS.some((pattern) => pattern.test(normalized));
7882
7972
  }
7883
7973
  var SCRATCH_PATTERNS;
@@ -10213,9 +10303,9 @@ async function readSpool(spoolDir = SPOOL_DIR) {
10213
10303
  }
10214
10304
  return out;
10215
10305
  }
10216
- async function readCloudMap(path24) {
10306
+ async function readCloudMap(path25) {
10217
10307
  try {
10218
- return JSON.parse(await readFile2(path24, "utf8"));
10308
+ return JSON.parse(await readFile2(path25, "utf8"));
10219
10309
  } catch {
10220
10310
  return {};
10221
10311
  }
@@ -10516,9 +10606,9 @@ function backoffMs(streak, baseMs) {
10516
10606
  if (streak <= 0) return 0;
10517
10607
  return Math.min(baseMs * 2 ** Math.min(streak - 1, 20), MAX_BACKOFF_MS);
10518
10608
  }
10519
- async function loadState(path24) {
10609
+ async function loadState(path25) {
10520
10610
  try {
10521
- const parsed = JSON.parse(await readFile3(path24, "utf8"));
10611
+ const parsed = JSON.parse(await readFile3(path25, "utf8"));
10522
10612
  if (parsed && typeof parsed === "object" && Number.isInteger(parsed.byte_offset) && parsed.byte_offset >= 0) {
10523
10613
  return { ...parsed, byte_offset: parsed.byte_offset };
10524
10614
  }
@@ -10526,15 +10616,15 @@ async function loadState(path24) {
10526
10616
  }
10527
10617
  return { byte_offset: 0, last_event_id: null, forwarded_total: 0, rejected_total: 0, rejected_event_ids: [] };
10528
10618
  }
10529
- async function saveState(path24, state) {
10530
- await mkdir2(dirname9(path24), { recursive: true });
10531
- await writeFile3(path24, JSON.stringify(state, null, 2), "utf8");
10619
+ async function saveState(path25, state) {
10620
+ await mkdir2(dirname9(path25), { recursive: true });
10621
+ await writeFile3(path25, JSON.stringify(state, null, 2), "utf8");
10532
10622
  }
10533
- async function readNewBytes(path24, offset, max) {
10534
- const st = await stat2(path24);
10623
+ async function readNewBytes(path25, offset, max) {
10624
+ const st = await stat2(path25);
10535
10625
  if (st.size <= offset) return { buf: Buffer.alloc(0), size: st.size };
10536
10626
  const length = Math.min(st.size - offset, max);
10537
- const fh = await open(path24, "r");
10627
+ const fh = await open(path25, "r");
10538
10628
  try {
10539
10629
  const buf = Buffer.alloc(length);
10540
10630
  const { bytesRead } = await fh.read(buf, 0, length, offset);
@@ -10822,6 +10912,16 @@ function makeLoopTicks({
10822
10912
  getAgentAvailability = () => null,
10823
10913
  // Cached account-usage provider (account-usage.mjs); [] omits the field.
10824
10914
  getAccountUsage = () => [],
10915
+ // Host-preflight verdict (host-preflight.mjs) — why this host is or is not
10916
+ // claiming. GATED OFF BY DEFAULT and that is deliberate: the plane's
10917
+ // heartbeat input schema is `.strict()`, so sending `host_health` before
10918
+ // that schema accepts it would 400 EVERY beat and take the machine off the
10919
+ // fleet — the 2026-07-25 `version` outage, and it would fire hardest on
10920
+ // exactly the broken hosts this field describes. Flip
10921
+ // VO_HOST_HEALTH_HEARTBEAT=1 once runner-host-health-v1 is wired into
10922
+ // runner-heartbeat-v1.ts and deployed. The claim GATE does not depend on
10923
+ // this: a blocked host stops claiming either way.
10924
+ getHostHealth = () => null,
10825
10925
  // Injectable for tests; default to the real scheduler + wall clock.
10826
10926
  runResumeScheduler = runScheduler,
10827
10927
  now: nowFn = () => Date.now(),
@@ -10922,10 +11022,17 @@ function makeLoopTicks({
10922
11022
  ...servedOperators.length > 0 ? { servedOperators } : {},
10923
11023
  ...Array.isArray(availableAgents) && availableAgents.length > 0 ? { availableAgents } : {},
10924
11024
  ...Array.isArray(accountUsage) && accountUsage.length > 0 ? { accountUsage } : {},
11025
+ ...env2.VO_HOST_HEALTH_HEARTBEAT === "1" && getHostHealth() ? { hostHealth: getHostHealth() } : {},
10925
11026
  uptimeSec: Math.floor(process.uptime()),
10926
11027
  activeTasks: getActive(),
10927
11028
  maxConcurrency: cfg.maxConcurrency,
10928
11029
  supportedTaskKinds: supportedTaskKindsFor(availableAgents),
11030
+ // Attested EVERY beat, from the daemon's own env + keychain, so the plane's
11031
+ // runner-api-billing-gate can refuse a metered machine BEFORE it claims.
11032
+ // Never throws (see resolveRunnerAttestedAuthSource) — a heartbeat that
11033
+ // 500s on its own attestation would take the host off the fleet, which is
11034
+ // strictly worse than the spend it was trying to prevent.
11035
+ agentAuthSource: resolveRunnerAttestedAuthSource(env2),
10929
11036
  ...capacityFields,
10930
11037
  ...localModelFields,
10931
11038
  ...preparedJobFields
@@ -10957,6 +11064,7 @@ var init_loop_ticks = __esm({
10957
11064
  init_session_spool_forwarder();
10958
11065
  init_rate_limit_resume_scheduler();
10959
11066
  init_telemetry_forwarder();
11067
+ init_agent_auth_attestation();
10960
11068
  HEARTBEAT_MS = 6e4;
10961
11069
  DEFAULT_RESUME_SCHEDULE_SEC = 300;
10962
11070
  RUNNER_SUPPORTED_TASK_KINDS = Object.freeze(["code", "inference", "skill"]);
@@ -11634,10 +11742,10 @@ function formatShadowLogLine(record) {
11634
11742
  const loud = record.unexplained_fields?.length ? "!! " : "";
11635
11743
  return `${loud}[prepared-job-shadow] ${parts.join(" ")}`;
11636
11744
  }
11637
- function appendShadowRecord(record, { path: path24 = PREPARED_JOB_SHADOW_SINK, append = appendFileSync, mkdir: mkdir5 = mkdirSync8 } = {}) {
11745
+ function appendShadowRecord(record, { path: path25 = PREPARED_JOB_SHADOW_SINK, append = appendFileSync, mkdir: mkdir5 = mkdirSync8 } = {}) {
11638
11746
  try {
11639
- mkdir5(dirname10(path24), { recursive: true });
11640
- append(path24, `${JSON.stringify(record)}
11747
+ mkdir5(dirname10(path25), { recursive: true });
11748
+ append(path25, `${JSON.stringify(record)}
11641
11749
  `, "utf8");
11642
11750
  return true;
11643
11751
  } catch {
@@ -13101,7 +13209,7 @@ function noteCiViaRest(log2) {
13101
13209
  log2("watch: CI status read via REST check-runs/status (gh's GraphQL rollup needs actions:read for checkSuite.workflowRun, which the read scope does not carry)");
13102
13210
  }
13103
13211
  async function readCommitCiViaRest(repo, sha, { run, env: env2 }) {
13104
- const api = async (path24) => JSON.parse(await run("gh", ["api", path24], { timeout: 3e4, env: env2 }) || "{}");
13212
+ const api = async (path25) => JSON.parse(await run("gh", ["api", path25], { timeout: 3e4, env: env2 }) || "{}");
13105
13213
  const rollup = [];
13106
13214
  let total = null;
13107
13215
  for (let page = 1; page <= REST_MAX_PAGES && (total === null || rollup.length < total); page += 1) {
@@ -13765,9 +13873,9 @@ function buildControlHandler({ getStatus, requestStop, allowedOrigin }) {
13765
13873
  res.end();
13766
13874
  return;
13767
13875
  }
13768
- const path24 = String(req.url || "").split("?")[0];
13876
+ const path25 = String(req.url || "").split("?")[0];
13769
13877
  res.setHeader("content-type", "application/json");
13770
- if (req.method === "GET" && path24 === "/status") {
13878
+ if (req.method === "GET" && path25 === "/status") {
13771
13879
  let status;
13772
13880
  try {
13773
13881
  status = getStatus();
@@ -13778,7 +13886,7 @@ function buildControlHandler({ getStatus, requestStop, allowedOrigin }) {
13778
13886
  res.end(JSON.stringify({ ok: true, ...status }));
13779
13887
  return;
13780
13888
  }
13781
- if (req.method === "POST" && path24 === "/stop") {
13889
+ if (req.method === "POST" && path25 === "/stop") {
13782
13890
  if (!isControlOriginAllowed(req.headers.origin, allowedOrigin) || !req.headers["x-vo-control"]) {
13783
13891
  res.statusCode = 403;
13784
13892
  res.end(JSON.stringify({ ok: false, error: "forbidden" }));
@@ -14813,9 +14921,9 @@ function operatorTierToRung(tier, difficulty, thresholds) {
14813
14921
  if (tier === "best" && difficulty >= thresholds.rungBounds.R5) return "R5";
14814
14922
  return base;
14815
14923
  }
14816
- function readCodexModelsCache({ path: path24 = DEFAULT_CODEX_MODELS_CACHE, read = readFileSync9 } = {}) {
14924
+ function readCodexModelsCache({ path: path25 = DEFAULT_CODEX_MODELS_CACHE, read = readFileSync9 } = {}) {
14817
14925
  try {
14818
- const parsed = JSON.parse(read(path24, "utf8"));
14926
+ const parsed = JSON.parse(read(path25, "utf8"));
14819
14927
  return Array.isArray(parsed?.models) ? parsed : null;
14820
14928
  } catch {
14821
14929
  return null;
@@ -15072,15 +15180,15 @@ function formatDecisionReason(decision, maxLen = 480) {
15072
15180
  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("; ")}`;
15073
15181
  return s.length > maxLen ? `${s.slice(0, maxLen - 1)}\u2026` : s;
15074
15182
  }
15075
- function appendDecisionFallback(decision, { path: path24 = DECISION_FALLBACK_PATH, append = appendFileSync2, mkdir: mkdir5 = mkdirSync9, task, thresholds, roleCostInputs } = {}) {
15183
+ function appendDecisionFallback(decision, { path: path25 = DECISION_FALLBACK_PATH, append = appendFileSync2, mkdir: mkdir5 = mkdirSync9, task, thresholds, roleCostInputs } = {}) {
15076
15184
  try {
15077
- mkdir5(dirname12(path24), { recursive: true });
15078
- append(path24, `${JSON.stringify(decision)}
15185
+ mkdir5(dirname12(path25), { recursive: true });
15186
+ append(path25, `${JSON.stringify(decision)}
15079
15187
  `, "utf8");
15080
15188
  if (isRouterDecision(decision)) {
15081
15189
  try {
15082
15190
  const records = buildShadowRecords({ decision, task, thresholds: thresholds || loadThresholds(), roleCostInputs });
15083
- for (const record of records) append(path24, `${JSON.stringify(record)}
15191
+ for (const record of records) append(path25, `${JSON.stringify(record)}
15084
15192
  `, "utf8");
15085
15193
  } catch {
15086
15194
  }
@@ -15612,6 +15720,17 @@ var init_agent_process_env = __esm({
15612
15720
  // escape hatch inert — an operator who set it still got the subscription.
15613
15721
  "VO_RUNNER_PREFER_KEY",
15614
15722
  "VO_RUNNER_CLAUDE_PREFER_KEY",
15723
+ // The per-MACHINE metered-billing opt-in (RUNNER_ALLOW_API_BILLING_ENV in
15724
+ // agent-auth-attestation.mjs). Listed for the SAME reason as the line above:
15725
+ // applyApiBillingPolicy reads it off the env handed to runAgentTask, which is
15726
+ // this function's output — omit it and the operator's opt-in is stripped before
15727
+ // the policy ever sees it, so `permitted` is permanently false. That fails in
15728
+ // the SAFE direction (login), unlike #9242, but it is still an inert flag.
15729
+ //
15730
+ // A boolean flag, not a credential: it grants nothing on its own. The keys it
15731
+ // gates (ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN / CLAUDE_API_KEY) stay absent
15732
+ // from this allow-list and are re-injected, if at all, only by applyAuthEnv.
15733
+ "VO_RUNNER_ALLOW_API_BILLING",
15615
15734
  // The swarm tier binding (SWARM_TIER_BINDING_ENV in
15616
15735
  // packages/vo-mcp/src/swarm/tier-binding.ts). A fan-out resolves its billing
15617
15736
  // tier ONCE at admission and exports the binding so every subagent inherits
@@ -16592,7 +16711,7 @@ async function processSkillTask(client, task, cfg, {
16592
16711
  resolveDispatch = resolveEffortDispatch,
16593
16712
  checkSkillCapability = ({ runner, bin, env: capabilityEnv }) => typeof runner.checkSkillCapability === "function" ? runner.checkSkillCapability({ bin, env: capabilityEnv }) : { compatible: false, reason: "resolved runner has no restricted-skill capability probe" },
16594
16713
  createScratch = () => mkdtemp2(join19(tmpdir(), "algohq-skill-task-")),
16595
- removeScratch = (path24) => rm2(path24, { recursive: true, force: true })
16714
+ removeScratch = (path25) => rm2(path25, { recursive: true, force: true })
16596
16715
  } = {}) {
16597
16716
  const id = task.code_task_id;
16598
16717
  let run = null;
@@ -17099,6 +17218,174 @@ var init_outcome_commit = __esm({
17099
17218
  }
17100
17219
  });
17101
17220
 
17221
+ // ../../scripts/virtual-office/code-runner/capped-run-classification.mjs
17222
+ function isCappedRunSummary(summary) {
17223
+ return CAPPED_RESULT_SUBTYPES.includes(String(summary ?? "").trim());
17224
+ }
17225
+ function agentRequestedContinuation(run = {}) {
17226
+ return `${run?.summary ?? ""}
17227
+ ${run?.lastAgentMessage ?? ""}`.includes(PARTIAL_PR_CONTINUATION_MARKER);
17228
+ }
17229
+ function leftoverWorkAfterPublish(changedFiles) {
17230
+ if (!Array.isArray(changedFiles)) return null;
17231
+ return changedFiles.filter((file) => file && !isAgentScratch(String(file)));
17232
+ }
17233
+ function classifyCappedRunOutcome({
17234
+ partial = false,
17235
+ run = {},
17236
+ prPublished = false,
17237
+ prCarriesCommits = false,
17238
+ changedFilesAfterPublish = null,
17239
+ rateLimited = false
17240
+ } = {}) {
17241
+ const verdict = (reason, extra = {}) => ({
17242
+ needsContinuation: true,
17243
+ complete: false,
17244
+ reason,
17245
+ note: null,
17246
+ leftover: [],
17247
+ ...extra
17248
+ });
17249
+ if (!partial) {
17250
+ return { needsContinuation: false, complete: true, reason: "clean_finish", note: null, leftover: [] };
17251
+ }
17252
+ if (agentRequestedContinuation(run)) return verdict("agent_requested_continuation");
17253
+ if (rateLimited) return verdict("rate_limited_resume_pending");
17254
+ if (run?.failure) return verdict("runner_failure");
17255
+ if (run?.gateFailureNote) return verdict("completion_gate_not_passing");
17256
+ if (!isCappedRunSummary(run?.summary)) return verdict("not_a_cap_stop");
17257
+ if (!prPublished) return verdict("no_published_pr");
17258
+ if (!prCarriesCommits) return verdict("published_pr_carries_no_commits");
17259
+ const leftover = leftoverWorkAfterPublish(changedFilesAfterPublish);
17260
+ if (leftover === null) return verdict("worktree_state_unknown");
17261
+ if (leftover.length > 0) return verdict("uncommitted_work_remains", { leftover });
17262
+ return {
17263
+ needsContinuation: false,
17264
+ complete: true,
17265
+ reason: "cap_after_publish",
17266
+ note: CAP_AFTER_PUBLISH_NOTE,
17267
+ leftover: []
17268
+ };
17269
+ }
17270
+ var CAP_AFTER_PUBLISH_NOTE;
17271
+ var init_capped_run_classification = __esm({
17272
+ "../../scripts/virtual-office/code-runner/capped-run-classification.mjs"() {
17273
+ "use strict";
17274
+ init_claude_result_event();
17275
+ init_partial_pr_continuation();
17276
+ init_publish_file_state();
17277
+ CAP_AFTER_PUBLISH_NOTE = "cap reached after publish";
17278
+ }
17279
+ });
17280
+
17281
+ // ../../scripts/virtual-office/code-runner/capped-run-publication-repair.mjs
17282
+ function stripPartialTitlePrefix(title) {
17283
+ const text = String(title ?? "").trim();
17284
+ if (!PARTIAL_TITLE_PREFIX_RE.test(text)) return null;
17285
+ const stripped = text.replace(PARTIAL_TITLE_PREFIX_RE, "").trim();
17286
+ return stripped || null;
17287
+ }
17288
+ async function measureWorktreeChanges(worktreeDir, runCommand) {
17289
+ try {
17290
+ const out = await runCommand(
17291
+ "git",
17292
+ ["-c", "core.quotepath=false", "status", "--porcelain", "-z"],
17293
+ worktreeDir,
17294
+ { timeout: 6e4, raw: true }
17295
+ );
17296
+ return parsePorcelainZ(out);
17297
+ } catch {
17298
+ return null;
17299
+ }
17300
+ }
17301
+ async function branchCarriesCommits(worktreeDir, branch, runCommand, base = "origin/main") {
17302
+ if (!branch) return false;
17303
+ try {
17304
+ const out = await runCommand(
17305
+ "git",
17306
+ ["rev-list", "--count", `${base}..${branch}`],
17307
+ worktreeDir,
17308
+ { timeout: 6e4 }
17309
+ );
17310
+ return Number.parseInt(String(out).trim(), 10) > 0;
17311
+ } catch {
17312
+ return false;
17313
+ }
17314
+ }
17315
+ async function resolveCappedRunVerdict({
17316
+ partial,
17317
+ run,
17318
+ pr,
17319
+ worktreeDir,
17320
+ runCommand,
17321
+ rateLimited = false
17322
+ }) {
17323
+ const optimistic = classifyCappedRunOutcome({
17324
+ partial,
17325
+ run,
17326
+ rateLimited,
17327
+ prPublished: true,
17328
+ prCarriesCommits: true,
17329
+ changedFilesAfterPublish: []
17330
+ });
17331
+ if (optimistic.reason !== "cap_after_publish") return optimistic;
17332
+ const prPublished = Number.isInteger(pr?.prNumber) && pr.prNumber > 0;
17333
+ const [changedFilesAfterPublish, prCarriesCommits] = await Promise.all([
17334
+ measureWorktreeChanges(worktreeDir, runCommand),
17335
+ prPublished ? branchCarriesCommits(worktreeDir, pr.branch, runCommand) : Promise.resolve(false)
17336
+ ]);
17337
+ return classifyCappedRunOutcome({
17338
+ partial: true,
17339
+ run,
17340
+ prPublished,
17341
+ prCarriesCommits,
17342
+ changedFilesAfterPublish,
17343
+ rateLimited
17344
+ });
17345
+ }
17346
+ async function repairCompletePrPresentation({
17347
+ pr,
17348
+ worktreeDir,
17349
+ githubToken = null,
17350
+ runCommand,
17351
+ log: log2 = () => {
17352
+ },
17353
+ promoteReady = true
17354
+ }) {
17355
+ const env2 = githubToken ? installationTokenEnv(githubToken) : void 0;
17356
+ const result = { retitled: null, markedReady: false };
17357
+ try {
17358
+ const raw = await runCommand("gh", ["pr", "view", String(pr.prNumber), "--json", "title"], worktreeDir, { env: env2, timeout: 6e4 });
17359
+ const stripped = stripPartialTitlePrefix(JSON.parse(String(raw || "{}"))?.title);
17360
+ if (stripped) {
17361
+ await runCommand("gh", ["pr", "edit", String(pr.prNumber), "--title", stripped], worktreeDir, { env: env2, timeout: 6e4 });
17362
+ result.retitled = stripped;
17363
+ log2(`task PR #${pr.prNumber}: dropped the PARTIAL title prefix \u2014 the cap landed after publication`);
17364
+ }
17365
+ } catch (error) {
17366
+ log2(`PR #${pr.prNumber}: could not drop the PARTIAL title prefix (${String(error?.message || error).slice(0, 160)}); outcome is still recorded complete`);
17367
+ }
17368
+ if (!promoteReady) return result;
17369
+ try {
17370
+ await markExistingPrReady(worktreeDir, pr.prNumber, { env: env2, runFn: runCommand });
17371
+ result.markedReady = true;
17372
+ } catch (error) {
17373
+ log2(`PR #${pr.prNumber}: could not promote the salvage draft to ready (${String(error?.message || error).slice(0, 160)})`);
17374
+ }
17375
+ return result;
17376
+ }
17377
+ var PARTIAL_TITLE_PREFIX_RE;
17378
+ var init_capped_run_publication_repair = __esm({
17379
+ "../../scripts/virtual-office/code-runner/capped-run-publication-repair.mjs"() {
17380
+ "use strict";
17381
+ init_publish();
17382
+ init_publish_file_state();
17383
+ init_existing_pr_publication();
17384
+ init_capped_run_classification();
17385
+ PARTIAL_TITLE_PREFIX_RE = /^⚠ PARTIAL \([^)]*\)\s*—\s*/u;
17386
+ }
17387
+ });
17388
+
17102
17389
  // ../../scripts/virtual-office/code-runner/publication-outcome.mjs
17103
17390
  async function closeCancelledReplacementPr({
17104
17391
  pr,
@@ -17225,6 +17512,20 @@ async function finalizePublishedPr({
17225
17512
  const runnerFailureRecord = serializedRunnerFailure ? JSON.parse(serializedRunnerFailure) : null;
17226
17513
  const runnerFailureMessage = runnerFailureRecord ? `blocked by ${runnerFailureRecord.code}; operator review required: ${runnerFailureRecord.operator_next_action}` : "";
17227
17514
  const fixDispatchGuard = overlapBlocked || runnerFailure ? { allowFixDispatch: false } : {};
17515
+ const exec = runCommand ?? defaultRunCommand4;
17516
+ const capVerdict = await resolveCappedRunVerdict({
17517
+ partial,
17518
+ run,
17519
+ pr,
17520
+ worktreeDir,
17521
+ runCommand: exec,
17522
+ rateLimited: Boolean(rateLimitResume)
17523
+ });
17524
+ const reclassified = partial && capVerdict.reason === "cap_after_publish";
17525
+ const effectivePartial = partial && !reclassified;
17526
+ if (partial) {
17527
+ log2(reclassified ? `task ${id}: ${CAP_AFTER_PUBLISH_NOTE} with a clean worktree \u2014 recording pr_opened, not needs_continuation` : `task ${id}: partial draft stands (${capVerdict.reason})`);
17528
+ }
17228
17529
  let resumeQueued = false;
17229
17530
  if (cfg.watchEnabled) {
17230
17531
  try {
@@ -17235,8 +17536,8 @@ async function finalizePublishedPr({
17235
17536
  taskId: id,
17236
17537
  operatorId: task.operator_id,
17237
17538
  tenantId: task.tenant_id,
17238
- needsContinuation: partial && !rateLimitResume && !runnerFailure && (task.continuation_attempt ?? 0) < (task.continuation_max_attempts ?? 3),
17239
- continuationExhausted: partial && (task.continuation_attempt ?? 0) >= (task.continuation_max_attempts ?? 3),
17539
+ needsContinuation: effectivePartial && !rateLimitResume && !runnerFailure && (task.continuation_attempt ?? 0) < (task.continuation_max_attempts ?? 3),
17540
+ continuationExhausted: effectivePartial && (task.continuation_attempt ?? 0) >= (task.continuation_max_attempts ?? 3),
17240
17541
  repairChain: task.repair_chain ?? {
17241
17542
  root_pr_number: pr.prNumber,
17242
17543
  attempt: 0,
@@ -17293,14 +17594,19 @@ async function finalizePublishedPr({
17293
17594
  log: log2,
17294
17595
  patch: {
17295
17596
  status: runnerFailure ? "failed" : "pr_opened",
17296
- message: runnerFailure ? `${runnerFailureMessage}; preserved PR ${pr.prUrl} for owner review` : `opened ${pr.prUrl}${overlapNote}${pr.newCommit === false ? " (no new commits \u2014 the branch already held every change)" : ""}`,
17597
+ message: runnerFailure ? `${runnerFailureMessage}; preserved PR ${pr.prUrl} for owner review` : `opened ${pr.prUrl}${overlapNote}${reclassified ? ` (${CAP_AFTER_PUBLISH_NOTE} \u2014 every deliverable is committed)` : ""}${pr.newCommit === false ? " (no new commits \u2014 the branch already held every change)" : ""}`,
17297
17598
  pr_url: pr.prUrl,
17298
17599
  pr_number: pr.prNumber,
17299
17600
  pr_branch: pr.branch,
17300
17601
  result: (() => {
17301
17602
  const prefix = overlapBlocked ? `[VO-PUBLISH-OVERLAP-BLOCKED: ${blockedRefs}] ` : "";
17302
17603
  const room = 2e3 - prefix.length;
17303
- return `${prefix}${partial || runnerFailure ? partialPrContinuationResult(run, room, rateLimitResume ? "rate_limited" : null) : String(run.summary).slice(0, room)}`;
17604
+ if (reclassified) {
17605
+ const report = String(run.lastAgentMessage || run.summary || "").trim();
17606
+ return `${prefix}${`${CAP_AFTER_PUBLISH_NOTE}
17607
+ ${report}`.slice(0, room)}`;
17608
+ }
17609
+ return `${prefix}${effectivePartial || runnerFailure ? partialPrContinuationResult(run, room, rateLimitResume ? "rate_limited" : null) : String(run.summary).slice(0, room)}`;
17304
17610
  })(),
17305
17611
  ...runOutcomePatch(run),
17306
17612
  ...terminalLedgerPatch(run)
@@ -17328,6 +17634,16 @@ async function finalizePublishedPr({
17328
17634
  });
17329
17635
  return posted.cancelled;
17330
17636
  }
17637
+ if (reclassified) {
17638
+ await repairCompletePrPresentation({
17639
+ pr,
17640
+ worktreeDir,
17641
+ githubToken,
17642
+ runCommand: exec,
17643
+ log: log2,
17644
+ promoteReady: !overlapBlocked && !runnerFailure
17645
+ });
17646
+ }
17331
17647
  log2(`task ${id} \u2192 PR ${pr.prUrl}`);
17332
17648
  return false;
17333
17649
  }
@@ -17345,6 +17661,8 @@ var init_publication_outcome = __esm({
17345
17661
  init_terminal_delivery();
17346
17662
  init_rate_limit_resume();
17347
17663
  init_error_message();
17664
+ init_capped_run_classification();
17665
+ init_capped_run_publication_repair();
17348
17666
  defaultRunCommand4 = (cmd, args, cwd, opts = {}) => runProcess2(cmd, args, { cwd, ...opts });
17349
17667
  }
17350
17668
  });
@@ -17662,6 +17980,290 @@ var init_recovery_ledger = __esm({
17662
17980
  }
17663
17981
  });
17664
17982
 
17983
+ // ../../scripts/virtual-office/code-runner/host-preflight.mjs
17984
+ function joinOutput(observation = {}) {
17985
+ return [observation.stdout, observation.stderr, observation.message].map((part) => String(part ?? "")).join("\n");
17986
+ }
17987
+ function classifyGitFetchObservation(observation = {}) {
17988
+ const text = joinOutput(observation);
17989
+ if (GIT_AUTH_RE.test(text)) return GIT_AUTH_REQUIRED;
17990
+ if (GIT_NETWORK_RE.test(text)) return GIT_NETWORK;
17991
+ return GIT_OTHER;
17992
+ }
17993
+ function classifyLoginPing(observation = {}) {
17994
+ const text = joinOutput(observation);
17995
+ if (LOGIN_EXPIRED_RE.test(text)) return LOGIN_EXPIRED;
17996
+ if (RATE_LIMITED_RE.test(text)) return RATE_LIMITED;
17997
+ if (observation.ok === true && String(observation.stdout ?? "").trim().length > 0) return OK;
17998
+ return UNKNOWN;
17999
+ }
18000
+ function reportingLines(raw) {
18001
+ return String(raw ?? "").split(/\r?\n/u).filter((line) => line.trim() !== "" && !NON_REPORTING_LINE.test(line)).map((line) => line.trim()).filter((line) => !/[`'"]$/u.test(line));
18002
+ }
18003
+ function matchSignature(line, signatures) {
18004
+ for (const signature of signatures) {
18005
+ if (signature.pattern.test(line)) return signature;
18006
+ }
18007
+ return null;
18008
+ }
18009
+ function classifyHostFailure(text) {
18010
+ for (const line of reportingLines(text)) {
18011
+ const login = matchSignature(line, LOGIN_SIGNATURES);
18012
+ if (login) return { kind: "login_expired", signature: login.id, line };
18013
+ const git4 = matchSignature(line, GIT_SIGNATURES);
18014
+ if (git4) return { kind: git4.kind, signature: git4.id, line };
18015
+ }
18016
+ return null;
18017
+ }
18018
+ function classifyHostFailureSignature(text) {
18019
+ const value = String(text ?? "");
18020
+ if (!value.trim()) return null;
18021
+ const tokenMatch = new RegExp(`${HOST_ENV_FAILURE_TOKEN}:(${LOGIN_EXPIRED}|${GIT_AUTH_REQUIRED})`, "u").exec(value);
18022
+ if (tokenMatch) return tokenMatch[1];
18023
+ const found = classifyHostFailure(value);
18024
+ if (!found) return null;
18025
+ if (found.kind === "login_expired") return LOGIN_EXPIRED;
18026
+ if (found.kind === "git_credentials") return GIT_AUTH_REQUIRED;
18027
+ return null;
18028
+ }
18029
+ function hostFailureResultText(reason, detail = "") {
18030
+ const trimmed = String(detail ?? "").replace(/\s+/gu, " ").trim().slice(0, 400);
18031
+ return `${HOST_ENV_FAILURE_TOKEN}:${reason} \u2014 ${hostHealthRemedy(reason)}${trimmed ? ` [${trimmed}]` : ""}`;
18032
+ }
18033
+ function hostHealthRemedy(reason) {
18034
+ switch (reason) {
18035
+ case LOGIN_EXPIRED:
18036
+ return "login expired: run claude auth login on this host";
18037
+ case RATE_LIMITED:
18038
+ return "agent account is rate limited: work resumes when the window resets";
18039
+ case GIT_AUTH_REQUIRED:
18040
+ return "git auth required: refresh the git credential on this host";
18041
+ case GIT_NETWORK:
18042
+ return "git cannot reach the remote: check this host network";
18043
+ case GIT_OTHER:
18044
+ return "git fetch failed on this host: see the runner log";
18045
+ case UNKNOWN:
18046
+ return "host check could not be completed";
18047
+ default:
18048
+ return "host is ready";
18049
+ }
18050
+ }
18051
+ async function capture(run, cmd, args, options) {
18052
+ try {
18053
+ const stdout = await run(cmd, args, options);
18054
+ return { ok: true, status: 0, stdout: String(stdout ?? ""), stderr: "", timedOut: false };
18055
+ } catch (error) {
18056
+ return {
18057
+ ok: false,
18058
+ status: typeof error?.status === "number" ? error.status : null,
18059
+ stdout: String(error?.stdout ?? ""),
18060
+ stderr: String(error?.stderr ?? ""),
18061
+ message: String(error?.message ?? error ?? ""),
18062
+ timedOut: error?.code === "ETIMEDOUT"
18063
+ };
18064
+ }
18065
+ }
18066
+ function nonInteractiveGitEnv(env2 = process.env) {
18067
+ return {
18068
+ ...env2,
18069
+ GIT_TERMINAL_PROMPT: "0",
18070
+ GCM_INTERACTIVE: "never",
18071
+ GIT_OPTIONAL_LOCKS: "0"
18072
+ };
18073
+ }
18074
+ async function checkGitFetch({
18075
+ cwd,
18076
+ run,
18077
+ env: env2 = process.env,
18078
+ remote = "origin",
18079
+ branch = "main",
18080
+ timeoutMs = GIT_FETCH_TIMEOUT_MS
18081
+ } = {}) {
18082
+ if (typeof run !== "function" || !cwd) {
18083
+ return { status: UNKNOWN, detail: "no runner clone to fetch from" };
18084
+ }
18085
+ const observation = await capture(run, "git", ["fetch", remote, branch], {
18086
+ cwd,
18087
+ timeout: timeoutMs,
18088
+ env: nonInteractiveGitEnv(env2)
18089
+ });
18090
+ if (observation.ok) return { status: OK, detail: `git fetch ${remote} ${branch} succeeded` };
18091
+ const reason = classifyGitFetchObservation(observation);
18092
+ const raw = joinOutput(observation).replace(/\s+/gu, " ").trim();
18093
+ return {
18094
+ status: reason,
18095
+ detail: observation.timedOut ? `git fetch ${remote} ${branch} exceeded ${Math.round(timeoutMs / 1e3)}s${raw ? `: ${raw.slice(0, 200)}` : " with no output"}` : raw.slice(0, 300) || `git fetch ${remote} ${branch} failed`
18096
+ };
18097
+ }
18098
+ async function checkAgentLogin({
18099
+ run,
18100
+ bin = "claude",
18101
+ env: env2 = process.env,
18102
+ timeoutMs = LOGIN_PING_TIMEOUT_MS,
18103
+ prompt = "Reply with the single word: ready"
18104
+ } = {}) {
18105
+ if (typeof run !== "function") {
18106
+ return { status: UNKNOWN, detail: "no login probe available on this host" };
18107
+ }
18108
+ const observation = await capture(run, bin, ["-p", prompt, "--max-turns", "1"], {
18109
+ timeout: timeoutMs,
18110
+ env: env2,
18111
+ input: ""
18112
+ });
18113
+ const reason = classifyLoginPing(observation);
18114
+ if (reason === OK) return { status: OK, detail: "one-turn agent ping authenticated" };
18115
+ const raw = joinOutput(observation).replace(/\s+/gu, " ").trim();
18116
+ return {
18117
+ status: reason,
18118
+ detail: observation.timedOut ? `agent ping exceeded ${Math.round(timeoutMs / 1e3)}s` : raw.slice(0, 300) || "agent ping produced no output"
18119
+ };
18120
+ }
18121
+ function preflightAllowsClaim(health) {
18122
+ if (!health) return true;
18123
+ return !CLAIM_BLOCKING_GIT.has(health.git) && !CLAIM_BLOCKING_LOGIN.has(health.login);
18124
+ }
18125
+ function blockingReason(health) {
18126
+ if (!health) return null;
18127
+ if (CLAIM_BLOCKING_GIT.has(health.git)) return health.git;
18128
+ if (CLAIM_BLOCKING_LOGIN.has(health.login)) return health.login;
18129
+ return null;
18130
+ }
18131
+ async function runHostPreflight({
18132
+ cwd,
18133
+ runGit,
18134
+ runAgent,
18135
+ bin = "claude",
18136
+ agent = "claude",
18137
+ env: env2 = process.env,
18138
+ now = () => Date.now(),
18139
+ gitTimeoutMs = GIT_FETCH_TIMEOUT_MS,
18140
+ loginTimeoutMs = LOGIN_PING_TIMEOUT_MS
18141
+ } = {}) {
18142
+ const git4 = await checkGitFetch({ cwd, run: runGit, env: env2, timeoutMs: gitTimeoutMs });
18143
+ const login = CLAIM_BLOCKING_GIT.has(git4.status) ? { status: UNKNOWN, detail: "skipped: git check already blocked this host" } : agent !== "claude" ? { status: UNKNOWN, detail: `login ping not implemented for agent ${agent}` } : await checkAgentLogin({ run: runAgent, bin, env: env2, timeoutMs: loginTimeoutMs });
18144
+ const health = {
18145
+ git: git4.status,
18146
+ login: login.status,
18147
+ checked_at: new Date(now()).toISOString()
18148
+ };
18149
+ const blocking = blockingReason(health);
18150
+ const detail = blocking ? `${hostHealthRemedy(blocking)} \u2014 ${blocking === git4.status ? git4.detail : login.detail}` : login.status === RATE_LIMITED ? `${hostHealthRemedy(RATE_LIMITED)} \u2014 ${login.detail}` : `${git4.detail}; ${login.detail}`;
18151
+ return { ...health, detail: detail.slice(0, 500) };
18152
+ }
18153
+ function positiveSeconds(raw, fallbackMs) {
18154
+ const value = Number(raw);
18155
+ return Number.isFinite(value) && value > 0 ? value * 1e3 : fallbackMs;
18156
+ }
18157
+ function makeHostPreflightGate({
18158
+ preflight = runHostPreflight,
18159
+ now = () => Date.now(),
18160
+ env: env2 = process.env,
18161
+ log: log2 = () => {
18162
+ },
18163
+ ...preflightOptions
18164
+ } = {}) {
18165
+ const okIntervalMs = positiveSeconds(env2.VO_HOST_PREFLIGHT_OK_SEC, PREFLIGHT_OK_INTERVAL_MS);
18166
+ const retryIntervalMs = positiveSeconds(env2.VO_HOST_PREFLIGHT_RETRY_SEC, PREFLIGHT_RETRY_MS);
18167
+ let health = null;
18168
+ let checkedAtMs = 0;
18169
+ let running = false;
18170
+ let lastLoggedReason;
18171
+ const intervalMs = () => preflightAllowsClaim(health) ? okIntervalMs : retryIntervalMs;
18172
+ return {
18173
+ /** ms of the next scheduled check; 0 before the first one has ever run. */
18174
+ nextCheckAt: () => checkedAtMs === 0 ? 0 : checkedAtMs + intervalMs(),
18175
+ get: () => health,
18176
+ allowClaim: () => preflightAllowsClaim(health),
18177
+ blockingReason: () => blockingReason(health),
18178
+ async ensure(nowMs = now()) {
18179
+ if (running) return health;
18180
+ if (checkedAtMs !== 0 && nowMs - checkedAtMs < intervalMs()) return health;
18181
+ running = true;
18182
+ try {
18183
+ health = await preflight({ ...preflightOptions, env: env2, now });
18184
+ } catch (error) {
18185
+ health = {
18186
+ git: UNKNOWN,
18187
+ login: UNKNOWN,
18188
+ checked_at: new Date(nowMs).toISOString(),
18189
+ detail: `host preflight could not run: ${String(error?.message ?? error).slice(0, 200)}`
18190
+ };
18191
+ log2(`host preflight error: ${String(error?.message ?? error)}`);
18192
+ } finally {
18193
+ checkedAtMs = nowMs;
18194
+ running = false;
18195
+ }
18196
+ const reason = blockingReason(health);
18197
+ if (reason !== lastLoggedReason) {
18198
+ lastLoggedReason = reason;
18199
+ log2(reason ? `host preflight BLOCKED claiming \u2014 ${hostHealthRemedy(reason)} (${health.detail})` : "host preflight passed \u2014 git fetch and agent login are healthy");
18200
+ }
18201
+ return health;
18202
+ }
18203
+ };
18204
+ }
18205
+ var DEFAULT_BLOCKED_RECHECK_MS, DEFAULT_HEALTHY_RECHECK_MS, MAX_RECHECK_MS, GIT_FETCH_TIMEOUT_MS, LOGIN_PING_TIMEOUT_MS, PREFLIGHT_RETRY_MS, PREFLIGHT_OK_INTERVAL_MS, GIT_AUTH_REQUIRED, GIT_NETWORK, GIT_OTHER, LOGIN_EXPIRED, RATE_LIMITED, OK, UNKNOWN, HOST_ENV_FAILURE_TOKEN, CLAIM_BLOCKING_GIT, CLAIM_BLOCKING_LOGIN, GIT_AUTH_RE, GIT_NETWORK_RE, LOGIN_EXPIRED_RE, RATE_LIMITED_RE, SEVERITY_PREFIX, LOGIN_SIGNATURES, GIT_SIGNATURES, NON_REPORTING_LINE;
18206
+ var init_host_preflight = __esm({
18207
+ "../../scripts/virtual-office/code-runner/host-preflight.mjs"() {
18208
+ "use strict";
18209
+ DEFAULT_BLOCKED_RECHECK_MS = 5 * 6e4;
18210
+ DEFAULT_HEALTHY_RECHECK_MS = 30 * 6e4;
18211
+ MAX_RECHECK_MS = 6 * 60 * 6e4;
18212
+ GIT_FETCH_TIMEOUT_MS = 6e4;
18213
+ LOGIN_PING_TIMEOUT_MS = 9e4;
18214
+ PREFLIGHT_RETRY_MS = DEFAULT_BLOCKED_RECHECK_MS;
18215
+ PREFLIGHT_OK_INTERVAL_MS = DEFAULT_HEALTHY_RECHECK_MS;
18216
+ GIT_AUTH_REQUIRED = "git_auth_required";
18217
+ GIT_NETWORK = "git_network";
18218
+ GIT_OTHER = "git_other";
18219
+ LOGIN_EXPIRED = "login_expired";
18220
+ RATE_LIMITED = "rate_limited";
18221
+ OK = "ok";
18222
+ UNKNOWN = "unknown";
18223
+ HOST_ENV_FAILURE_TOKEN = "HOST_ENV_FAILURE";
18224
+ CLAIM_BLOCKING_GIT = /* @__PURE__ */ new Set([GIT_AUTH_REQUIRED, GIT_NETWORK, GIT_OTHER]);
18225
+ CLAIM_BLOCKING_LOGIN = /* @__PURE__ */ new Set([LOGIN_EXPIRED]);
18226
+ GIT_AUTH_RE = /could not read (?:Username|Password)|terminal prompts disabled|Authentication failed|authentication failed|Permission denied \(publickey\)|Invalid username or password|Support for password authentication was removed|HTTP Basic: Access denied|\b401\b|\b403\b|Logon failed|credential(?:s)? (?:helper|manager)|no credentials|Repository not found/i;
18227
+ GIT_NETWORK_RE = /could not resolve host|couldn'?t resolve host|failed to connect|unable to access|connection (?:reset|refused|closed|timed out)|operation timed out|remote end hung up|early eof|rpc failed|recv failure|gnutls_handshake|ssl_read|\b50[234]\b|temporary failure|ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|ENETUNREACH|EHOSTUNREACH/i;
18228
+ LOGIN_EXPIRED_RE = /OAuth (?:session|token) (?:has )?expired|could not be refreshed|Failed to authenticate|Invalid API key|invalid[^.\n]{0,24}(?:credential|authentication)|authentication_error|\bunauthorized\b|\b401\b|not (?:logged in|authenticated)|please run [`'"]?claude (?:auth )?login|run [`'"]?claude (?:auth )?login/i;
18229
+ RATE_LIMITED_RE = /rate limit|rate[_-]limited|usage limit|\b429\b|too many requests|quota (?:exceeded|exhausted)|limit reached|out of (?:credits|quota)|resource[_ ]exhausted/i;
18230
+ SEVERITY_PREFIX = String.raw`(?:\[?(?:error|Error|ERROR)\]?:?\s+)?`;
18231
+ LOGIN_SIGNATURES = [
18232
+ {
18233
+ id: "oauth_session_expired",
18234
+ pattern: new RegExp(
18235
+ `^${SEVERITY_PREFIX}Failed to authenticate: OAuth session expired and could not be refreshed\\.?$`,
18236
+ "u"
18237
+ )
18238
+ },
18239
+ {
18240
+ id: "invalid_credentials",
18241
+ pattern: new RegExp(`^${SEVERITY_PREFIX}Invalid authentication credentials\\.?$`, "u")
18242
+ }
18243
+ ];
18244
+ GIT_SIGNATURES = [
18245
+ {
18246
+ id: "git_fetch_timeout",
18247
+ kind: "git_unreachable",
18248
+ pattern: new RegExp(
18249
+ `^${SEVERITY_PREFIX}git fetch origin main failed: timed out\\.?$`,
18250
+ "u"
18251
+ )
18252
+ },
18253
+ {
18254
+ // fatal: could not read Username for 'https://github.com': terminal prompts disabled
18255
+ id: "git_credential_prompt",
18256
+ kind: "git_credentials",
18257
+ pattern: new RegExp(
18258
+ String.raw`^fatal:\s+could not read (?:Username|Password) for '[^']*':\s*` + String.raw`(?:terminal prompts disabled|No such device or address|Device not configured)\.?$`,
18259
+ "u"
18260
+ )
18261
+ }
18262
+ ];
18263
+ NON_REPORTING_LINE = /^\s*(?:[+\->*#|]|\/\/|\/\*|\*\/|\d+[.)]\s|`{1,3}|'|")/u;
18264
+ }
18265
+ });
18266
+
17665
18267
  // ../../scripts/virtual-office/code-runner/no-changes-terminal-status.mjs
17666
18268
  function defaultRunCommand5(cmd, args, cwd, opts = {}) {
17667
18269
  return runProcess2(cmd, args, { cwd, ...opts });
@@ -17683,6 +18285,14 @@ function isMaxTurnExhaustion(run = {}, maxTurns) {
17683
18285
  }
17684
18286
  function decideNoChangesTerminalStatus({ partial, run = {}, maxTurns } = {}) {
17685
18287
  const structuredFailure = serializeRunnerFailure(run.failure);
18288
+ const hostFailure = classifyHostFailureSignature(run.summary) ?? classifyHostFailureSignature(structuredFailure);
18289
+ if (hostFailure) {
18290
+ return {
18291
+ status: "failed",
18292
+ message: `host cannot run work \u2014 ${hostHealthRemedy(hostFailure)}`,
18293
+ result: hostFailureResultText(hostFailure, run.summary).slice(0, RESULT_LIMIT)
18294
+ };
18295
+ }
17686
18296
  if (!partial) {
17687
18297
  if (structuredFailure) {
17688
18298
  const failure = JSON.parse(structuredFailure);
@@ -17851,6 +18461,7 @@ var init_no_changes_terminal_status = __esm({
17851
18461
  init_terminal_delivery();
17852
18462
  init_error_message();
17853
18463
  init_partial_pr_continuation();
18464
+ init_host_preflight();
17854
18465
  RESULT_LIMIT = 2e3;
17855
18466
  }
17856
18467
  });
@@ -18047,6 +18658,72 @@ var init_task_worktree_preparation = __esm({
18047
18658
  }
18048
18659
  });
18049
18660
 
18661
+ // ../../scripts/virtual-office/code-runner/preflight-clone-dir.mjs
18662
+ import fs14 from "node:fs";
18663
+ import path24 from "node:path";
18664
+ function sanitize2(value, fallback) {
18665
+ const cleaned = String(value || "").trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
18666
+ return cleaned || fallback;
18667
+ }
18668
+ function cloneDirForServedRepo(repoSlug, clonesRootDir) {
18669
+ if (!clonesRootDir || !path24.isAbsolute(String(clonesRootDir))) return null;
18670
+ if (!repoSlug || !VALID_REPO_SLUG2.test(String(repoSlug))) return null;
18671
+ const [owner, name] = String(repoSlug).split("/");
18672
+ if (owner === "." || owner === ".." || name === "." || name === "..") return null;
18673
+ if (owner.startsWith("-") || name.startsWith("-")) return null;
18674
+ return path24.join(clonesRootDir, `${sanitize2(owner, "owner")}__${sanitize2(name, "repo")}`);
18675
+ }
18676
+ function resolvePreflightCwd(cfg = {}, env2 = process.env) {
18677
+ const clonesRoot2 = String(env2?.VO_CODE_RUNNER_CLONES_ROOT || "").trim();
18678
+ const served = Array.isArray(cfg?.servedRepos) ? cfg.servedRepos : [];
18679
+ for (const repo of served) {
18680
+ const dir = cloneDirForServedRepo(repo, clonesRoot2);
18681
+ if (dir) return dir;
18682
+ }
18683
+ return String(env2?.VO_CODE_RUNNER_REPO || "").trim() || process.cwd();
18684
+ }
18685
+ function isGitWorkingTree(dir, { exists = fs14.existsSync } = {}) {
18686
+ if (!dir) return false;
18687
+ try {
18688
+ return exists(path24.join(dir, ".git")) === true;
18689
+ } catch {
18690
+ return false;
18691
+ }
18692
+ }
18693
+ function describeMissingClone(health, dir) {
18694
+ const note = `no git clone at ${dir} yet \u2014 the first task creates it; git fetch not probed`;
18695
+ if (!health) return health;
18696
+ if (blockingReason(health)) return { ...health, git: UNKNOWN };
18697
+ return { ...health, git: UNKNOWN, detail: `${note}; ${health.detail ?? ""}`.slice(0, 500) };
18698
+ }
18699
+ function makeServedClonePreflight({
18700
+ cfg = {},
18701
+ preflight = runHostPreflight,
18702
+ exists = fs14.existsSync
18703
+ } = {}) {
18704
+ return async function servedClonePreflight(options = {}) {
18705
+ const env2 = options.env ?? process.env;
18706
+ const dir = resolvePreflightCwd(cfg, env2);
18707
+ if (!isGitWorkingTree(dir, { exists })) {
18708
+ return describeMissingClone(await preflight({ ...options, cwd: null }), dir);
18709
+ }
18710
+ const health = await preflight({ ...options, cwd: dir });
18711
+ if (health && blockingReason(health) === health.git && NOT_A_REPOSITORY_RE.test(String(health.detail ?? ""))) {
18712
+ return describeMissingClone(await preflight({ ...options, cwd: null }), dir);
18713
+ }
18714
+ return health;
18715
+ };
18716
+ }
18717
+ var VALID_REPO_SLUG2, NOT_A_REPOSITORY_RE;
18718
+ var init_preflight_clone_dir = __esm({
18719
+ "../../scripts/virtual-office/code-runner/preflight-clone-dir.mjs"() {
18720
+ "use strict";
18721
+ init_host_preflight();
18722
+ VALID_REPO_SLUG2 = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/u;
18723
+ NOT_A_REPOSITORY_RE = /not a git repository|does not appear to be a git repos/iu;
18724
+ }
18725
+ });
18726
+
18050
18727
  // ../../scripts/virtual-office/code-runner-daemon.mjs
18051
18728
  var code_runner_daemon_exports = {};
18052
18729
  __export(code_runner_daemon_exports, {
@@ -18142,6 +18819,8 @@ async function processOneTask(client, task, cfg, runnerInstanceId, swarmAdmissio
18142
18819
  env: buildAgentProcessEnv(process.env, { agent: sel.agent, runnerId: cfg.runnerId, taskId: id, repo: task.repo, githubReadToken: agentGithubReadToken, swarmAdmission }),
18143
18820
  // swarmAdmission mints VO_SWARM_TIER_BINDING: ONE tier decision for this task's whole agent tree
18144
18821
  sandbox,
18822
+ allowApiBilling: task.allow_api_billing === true,
18823
+ // per-TASK grant; AND-ed with the per-machine VO_RUNNER_ALLOW_API_BILLING inside applyApiBillingPolicy
18145
18824
  onProgress: (text, checkpoint) => {
18146
18825
  const usage = checkpoint?.tokenUsage ? { token_usage: checkpoint.tokenUsage } : {};
18147
18826
  const patch = text ? runnerStagePatch("agent_working", text, usage) : { stage: "agent_working", ...usage };
@@ -18372,7 +19051,8 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
18372
19051
  const agentAvailability = makeAgentAvailabilityProvider({ onError: (e) => log(`agent probe failed: ${e.message}`) });
18373
19052
  await agentAvailability.ready();
18374
19053
  const accountUsage = makeAccountUsageProvider();
18375
- const loopTick = makeLoopTicks({ client, cfg, env: env2, log, getActive: () => active, runnerInstanceId, capacityController, localModelController: createLocalModelRemoteController({ env: env2, log }), preparedJobController: createPreparedJobRemoteController({ log }), getAgentAvailability: () => agentAvailability.get(), getAccountUsage: () => accountUsage.get() });
19054
+ const hostGate = makeHostPreflightGate({ preflight: makeServedClonePreflight({ cfg }), runGit: runProcess2, runAgent: runProcess2, bin: cfg.runnerBin, agent: cfg.agent, env: env2, log });
19055
+ const loopTick = makeLoopTicks({ client, cfg, env: env2, log, getActive: () => active, runnerInstanceId, capacityController, localModelController: createLocalModelRemoteController({ env: env2, log }), preparedJobController: createPreparedJobRemoteController({ log }), getAgentAvailability: () => agentAvailability.get(), getAccountUsage: () => accountUsage.get(), getHostHealth: () => hostGate.get() });
18376
19056
  const backoff = makeReconnectBackoff({ baseMs: cfg.pollSec * 1e3, log });
18377
19057
  let detachedFlushRunning = false;
18378
19058
  while (!stopping) {
@@ -18382,9 +19062,10 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
18382
19062
  detachedFlushRunning = false;
18383
19063
  });
18384
19064
  }
19065
+ await hostGate.ensure();
18385
19066
  const heartbeatCompletion = loopTick();
18386
19067
  if (watchCyclesEnabled) watchCoordinator.start();
18387
- const claimAgents = resolveAgentClaimContext(agentAvailability, cfg.agent);
19068
+ const claimAgents = hostGate.allowClaim() ? resolveAgentClaimContext(agentAvailability, cfg.agent) : null;
18388
19069
  if (!claimAgents) {
18389
19070
  await heartbeatCompletion;
18390
19071
  await sleep2(cfg.pollSec * 1e3);
@@ -18401,7 +19082,7 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
18401
19082
  }
18402
19083
  let task;
18403
19084
  try {
18404
- task = await client.claim(cfg.runnerId, cfg.servedRepos, cfg.servedOperators, { runnerInstanceId, reconcileStale, ...claimAgents });
19085
+ task = await client.claim(cfg.runnerId, cfg.servedRepos, cfg.servedOperators, { runnerInstanceId, reconcileStale, ...claimAgents, agentAuthSource: resolveRunnerAttestedAuthSource(process.env) });
18405
19086
  reconcileStale = false;
18406
19087
  backoff.onSuccess();
18407
19088
  } catch (err) {
@@ -18421,6 +19102,7 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
18421
19102
  continue;
18422
19103
  }
18423
19104
  log(`claimed task ${task.code_task_id} (${task.repo})`);
19105
+ log(`task ${task.code_task_id} ${describeTaskAuthSource(process.env, task)}`);
18424
19106
  active += 1;
18425
19107
  activeTaskIds.add(task.code_task_id);
18426
19108
  const runTask = selectTaskProcessor(task, {
@@ -18493,6 +19175,7 @@ var init_code_runner_daemon = __esm({
18493
19175
  init_task_helpers();
18494
19176
  init_prepared_job_shadow();
18495
19177
  init_agent_process_env();
19178
+ init_agent_auth_attestation();
18496
19179
  init_sandbox_config();
18497
19180
  init_inference_task_runner();
18498
19181
  init_skill_task_runner();
@@ -18510,6 +19193,9 @@ var init_code_runner_daemon = __esm({
18510
19193
  init_daemon_config();
18511
19194
  init_publication_scope();
18512
19195
  init_task_worktree_preparation();
19196
+ init_process_runner2();
19197
+ init_host_preflight();
19198
+ init_preflight_clone_dir();
18513
19199
  init_detached_economics_spool();
18514
19200
  RATE_LIMIT_RESUME_ENABLED = process.env.VO_RATE_LIMIT_RESUME !== "0";
18515
19201
  sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));