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

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.
@@ -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 "";
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
+ };
4422
4130
  }
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() : "";
4432
- }
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 };
4585
- }
4586
- if (preferKey) {
4587
- return { source: CLAUDE_CREDENTIAL_SOURCE.KEYCHAIN_PREFER_KEY, key };
4588
- }
4589
- if (probeLogin() === true) {
4590
- return { source: CLAUDE_CREDENTIAL_SOURCE.SUBSCRIPTION_WINS, key: null };
4241
+ async function getTaskKnowledgeContextRequest(req, taskId, { query, knowledgeRequestId } = {}, {
4242
+ taskRequestTimeoutMs,
4243
+ invalidateToken = () => {
4244
+ },
4245
+ sleep: sleep3 = defaultSleep,
4246
+ log: log2 = () => {
4591
4247
  }
4592
- return { source: CLAUDE_CREDENTIAL_SOURCE.KEYCHAIN, key };
4593
- }
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"() {
4597
- "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
- });
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 path24 = `/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", path24, 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);
4279
+ }
4280
+ throw new Error("knowledge-context retry loop exited unexpectedly");
4281
+ }
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"() {
4285
+ "use strict";
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 path24 = `/api/v1/code-task/${encodeURIComponent(taskId)}/prepared-job?${query}`;
4328
+ let res;
4329
+ try {
4330
+ res = await req("GET", path24, 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, path24, 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}${path24}`, {
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 ${path24} 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, path24, body, options = {}) => req(method, path24, 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 path24 = `/api/v1/code-task/${encodeURIComponent(taskId)}/attachment/${encodeURIComponent(attachmentId)}`;
4629
+ const res = await taskReq("GET", path24);
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;
@@ -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"]);
@@ -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
@@ -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
  });
@@ -18142,6 +18753,8 @@ async function processOneTask(client, task, cfg, runnerInstanceId, swarmAdmissio
18142
18753
  env: buildAgentProcessEnv(process.env, { agent: sel.agent, runnerId: cfg.runnerId, taskId: id, repo: task.repo, githubReadToken: agentGithubReadToken, swarmAdmission }),
18143
18754
  // swarmAdmission mints VO_SWARM_TIER_BINDING: ONE tier decision for this task's whole agent tree
18144
18755
  sandbox,
18756
+ allowApiBilling: task.allow_api_billing === true,
18757
+ // per-TASK grant; AND-ed with the per-machine VO_RUNNER_ALLOW_API_BILLING inside applyApiBillingPolicy
18145
18758
  onProgress: (text, checkpoint) => {
18146
18759
  const usage = checkpoint?.tokenUsage ? { token_usage: checkpoint.tokenUsage } : {};
18147
18760
  const patch = text ? runnerStagePatch("agent_working", text, usage) : { stage: "agent_working", ...usage };
@@ -18372,7 +18985,8 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
18372
18985
  const agentAvailability = makeAgentAvailabilityProvider({ onError: (e) => log(`agent probe failed: ${e.message}`) });
18373
18986
  await agentAvailability.ready();
18374
18987
  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() });
18988
+ const hostGate = makeHostPreflightGate({ cwd: process.cwd(), runGit: runProcess2, runAgent: runProcess2, bin: cfg.runnerBin, agent: cfg.agent, env: env2, log });
18989
+ 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
18990
  const backoff = makeReconnectBackoff({ baseMs: cfg.pollSec * 1e3, log });
18377
18991
  let detachedFlushRunning = false;
18378
18992
  while (!stopping) {
@@ -18382,9 +18996,10 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
18382
18996
  detachedFlushRunning = false;
18383
18997
  });
18384
18998
  }
18999
+ await hostGate.ensure();
18385
19000
  const heartbeatCompletion = loopTick();
18386
19001
  if (watchCyclesEnabled) watchCoordinator.start();
18387
- const claimAgents = resolveAgentClaimContext(agentAvailability, cfg.agent);
19002
+ const claimAgents = hostGate.allowClaim() ? resolveAgentClaimContext(agentAvailability, cfg.agent) : null;
18388
19003
  if (!claimAgents) {
18389
19004
  await heartbeatCompletion;
18390
19005
  await sleep2(cfg.pollSec * 1e3);
@@ -18401,7 +19016,7 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
18401
19016
  }
18402
19017
  let task;
18403
19018
  try {
18404
- task = await client.claim(cfg.runnerId, cfg.servedRepos, cfg.servedOperators, { runnerInstanceId, reconcileStale, ...claimAgents });
19019
+ task = await client.claim(cfg.runnerId, cfg.servedRepos, cfg.servedOperators, { runnerInstanceId, reconcileStale, ...claimAgents, agentAuthSource: resolveRunnerAttestedAuthSource(process.env) });
18405
19020
  reconcileStale = false;
18406
19021
  backoff.onSuccess();
18407
19022
  } catch (err) {
@@ -18421,6 +19036,7 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
18421
19036
  continue;
18422
19037
  }
18423
19038
  log(`claimed task ${task.code_task_id} (${task.repo})`);
19039
+ log(`task ${task.code_task_id} ${describeTaskAuthSource(process.env, task)}`);
18424
19040
  active += 1;
18425
19041
  activeTaskIds.add(task.code_task_id);
18426
19042
  const runTask = selectTaskProcessor(task, {
@@ -18493,6 +19109,7 @@ var init_code_runner_daemon = __esm({
18493
19109
  init_task_helpers();
18494
19110
  init_prepared_job_shadow();
18495
19111
  init_agent_process_env();
19112
+ init_agent_auth_attestation();
18496
19113
  init_sandbox_config();
18497
19114
  init_inference_task_runner();
18498
19115
  init_skill_task_runner();
@@ -18510,6 +19127,8 @@ var init_code_runner_daemon = __esm({
18510
19127
  init_daemon_config();
18511
19128
  init_publication_scope();
18512
19129
  init_task_worktree_preparation();
19130
+ init_process_runner2();
19131
+ init_host_preflight();
18513
19132
  init_detached_economics_spool();
18514
19133
  RATE_LIMIT_RESUME_ENABLED = process.env.VO_RATE_LIMIT_RESUME !== "0";
18515
19134
  sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));