@algosuite/vo-mcp 0.2.0-beta.72 → 0.2.0-beta.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-auth-probe-cli.mjs +18 -0
- package/dist/cli.js +110 -8
- package/dist/cli.js.map +2 -2
- package/dist/index.js +110 -8
- package/dist/index.js.map +2 -2
- package/dist/runner-cli.js +1791 -1105
- package/dist/runner-cli.js.map +3 -3
- package/dist/runner-supervisor.js +116 -57
- package/dist/runner-supervisor.js.map +3 -3
- package/package.json +1 -1
package/dist/runner-cli.js
CHANGED
|
@@ -314,21 +314,21 @@ function backupConfigOnce(configPath) {
|
|
|
314
314
|
copyFileSync(configPath, backupPath);
|
|
315
315
|
return backupPath;
|
|
316
316
|
}
|
|
317
|
-
function writeFileAtomic(
|
|
318
|
-
sweepStaleTempFiles(
|
|
319
|
-
const temp = `${
|
|
317
|
+
function writeFileAtomic(path25, content) {
|
|
318
|
+
sweepStaleTempFiles(path25);
|
|
319
|
+
const temp = `${path25}.vo-mcp-tmp-${process.pid}-${Date.now()}`;
|
|
320
320
|
try {
|
|
321
321
|
writeFileSync2(temp, content, { encoding: "utf8", mode: 384 });
|
|
322
|
-
if (existsSync3(
|
|
322
|
+
if (existsSync3(path25)) {
|
|
323
323
|
try {
|
|
324
|
-
chmodSync2(temp, statSync(
|
|
324
|
+
chmodSync2(temp, statSync(path25).mode & 511);
|
|
325
325
|
} catch {
|
|
326
326
|
}
|
|
327
327
|
}
|
|
328
328
|
let lastErr = null;
|
|
329
329
|
for (let attempt = 0; attempt < RENAME_RETRIES; attempt += 1) {
|
|
330
330
|
try {
|
|
331
|
-
renameSync(temp,
|
|
331
|
+
renameSync(temp, path25);
|
|
332
332
|
return;
|
|
333
333
|
} catch (err) {
|
|
334
334
|
lastErr = err;
|
|
@@ -337,7 +337,7 @@ function writeFileAtomic(path24, content) {
|
|
|
337
337
|
sleepSync(RENAME_RETRY_MS);
|
|
338
338
|
}
|
|
339
339
|
}
|
|
340
|
-
writeFileSync2(
|
|
340
|
+
writeFileSync2(path25, content, "utf8");
|
|
341
341
|
try {
|
|
342
342
|
unlinkSync2(temp);
|
|
343
343
|
} catch {
|
|
@@ -503,8 +503,8 @@ function tablePath(line) {
|
|
|
503
503
|
function tableSections(lines) {
|
|
504
504
|
const starts = [];
|
|
505
505
|
for (let index = 0; index < lines.length; index += 1) {
|
|
506
|
-
const
|
|
507
|
-
if (
|
|
506
|
+
const path25 = tablePath(lines[index] ?? "");
|
|
507
|
+
if (path25) starts.push({ path: path25, start: index });
|
|
508
508
|
}
|
|
509
509
|
return starts.map((section, index) => ({
|
|
510
510
|
...section,
|
|
@@ -696,11 +696,11 @@ function resolveLinuxConfigHome(home, env2) {
|
|
|
696
696
|
const configured = env2["XDG_CONFIG_HOME"]?.trim();
|
|
697
697
|
return configured && isAbsolute2(configured) ? configured : join5(home, ".config");
|
|
698
698
|
}
|
|
699
|
-
function launcherIsCurrent(
|
|
700
|
-
if (!existsSync6(
|
|
701
|
-
if (readFileSync5(
|
|
702
|
-
const backupPath = `${
|
|
703
|
-
copyFileSync2(
|
|
699
|
+
function launcherIsCurrent(path25, desiredContent, label, log2) {
|
|
700
|
+
if (!existsSync6(path25)) return false;
|
|
701
|
+
if (readFileSync5(path25, "utf8") === desiredContent) return true;
|
|
702
|
+
const backupPath = `${path25}.backup-${Date.now()}`;
|
|
703
|
+
copyFileSync2(path25, backupPath);
|
|
704
704
|
log2(` Backed up existing ${label} to: ${backupPath}`);
|
|
705
705
|
return false;
|
|
706
706
|
}
|
|
@@ -916,17 +916,17 @@ function resolveDesktopConfigPath(home, plat, appData) {
|
|
|
916
916
|
}
|
|
917
917
|
return join6(home, ".config", "Claude", "claude_desktop_config.json");
|
|
918
918
|
}
|
|
919
|
-
function readClaudeConfig(
|
|
920
|
-
if (!existsSync7(
|
|
919
|
+
function readClaudeConfig(path25) {
|
|
920
|
+
if (!existsSync7(path25)) return { kind: "absent", config: {}, mtimeMs: null };
|
|
921
921
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
922
|
-
const before = statSync3(
|
|
922
|
+
const before = statSync3(path25).mtimeMs;
|
|
923
923
|
let raw;
|
|
924
924
|
try {
|
|
925
|
-
raw = readFileSync6(
|
|
925
|
+
raw = readFileSync6(path25, "utf8");
|
|
926
926
|
} catch {
|
|
927
927
|
return { kind: "invalid", config: {}, mtimeMs: before };
|
|
928
928
|
}
|
|
929
|
-
if (!existsSync7(
|
|
929
|
+
if (!existsSync7(path25) || statSync3(path25).mtimeMs !== before) continue;
|
|
930
930
|
const text = raw.replace(/^\uFEFF/u, "");
|
|
931
931
|
if (!text.trim()) return { kind: "empty", config: {}, mtimeMs: before };
|
|
932
932
|
try {
|
|
@@ -938,9 +938,9 @@ function readClaudeConfig(path24) {
|
|
|
938
938
|
}
|
|
939
939
|
return { kind: "invalid", config: {}, mtimeMs: null };
|
|
940
940
|
}
|
|
941
|
-
function writeClaudeConfig(
|
|
942
|
-
mkdirSync6(dirname4(
|
|
943
|
-
writeFileAtomic(
|
|
941
|
+
function writeClaudeConfig(path25, config) {
|
|
942
|
+
mkdirSync6(dirname4(path25), { recursive: true });
|
|
943
|
+
writeFileAtomic(path25, `${JSON.stringify(config, null, 2)}
|
|
944
944
|
`);
|
|
945
945
|
}
|
|
946
946
|
function carriedEntryKeys(entry) {
|
|
@@ -3515,1185 +3515,1271 @@ var init_installation_token = __esm({
|
|
|
3515
3515
|
}
|
|
3516
3516
|
});
|
|
3517
3517
|
|
|
3518
|
-
// ../../scripts/virtual-office/code-runner/
|
|
3519
|
-
function
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
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 (
|
|
3569
|
-
|
|
3534
|
+
if (baseEnv.ANTHROPIC_API_KEY) {
|
|
3535
|
+
return { source: CLAUDE_CREDENTIAL_SOURCE.ENV_KEY, key: null };
|
|
3570
3536
|
}
|
|
3571
|
-
|
|
3572
|
-
|
|
3537
|
+
const key = getKey();
|
|
3538
|
+
if (!key) {
|
|
3539
|
+
return { source: CLAUDE_CREDENTIAL_SOURCE.NO_KEY, key: null };
|
|
3573
3540
|
}
|
|
3574
|
-
if (
|
|
3575
|
-
|
|
3541
|
+
if (preferKey) {
|
|
3542
|
+
return { source: CLAUDE_CREDENTIAL_SOURCE.KEYCHAIN_PREFER_KEY, key };
|
|
3576
3543
|
}
|
|
3577
|
-
if (
|
|
3578
|
-
|
|
3544
|
+
if (probeLogin() === true) {
|
|
3545
|
+
return { source: CLAUDE_CREDENTIAL_SOURCE.SUBSCRIPTION_WINS, key: null };
|
|
3579
3546
|
}
|
|
3580
|
-
return
|
|
3547
|
+
return { source: CLAUDE_CREDENTIAL_SOURCE.KEYCHAIN, key };
|
|
3581
3548
|
}
|
|
3582
|
-
var
|
|
3583
|
-
|
|
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/
|
|
3589
|
-
|
|
3590
|
-
}
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3611
|
-
"
|
|
3612
|
-
|
|
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
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
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
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
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
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
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
|
-
|
|
3683
|
-
"
|
|
3684
|
-
|
|
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
|
-
|
|
3689
|
-
|
|
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
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
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
|
-
|
|
3723
|
-
|
|
3724
|
-
"
|
|
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
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
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
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
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
|
|
3761
|
-
|
|
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/
|
|
3767
|
-
|
|
3768
|
-
}
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
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
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
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
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
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
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
});
|
|
3803
|
-
|
|
3804
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3782
|
+
return null;
|
|
3809
3783
|
}
|
|
3810
|
-
return { status: res.status, body };
|
|
3811
3784
|
}
|
|
3812
|
-
var
|
|
3813
|
-
var
|
|
3814
|
-
"../../scripts/virtual-office/code-runner/
|
|
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
|
-
|
|
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/
|
|
3821
|
-
function
|
|
3822
|
-
|
|
3823
|
-
|
|
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
|
|
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
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
"
|
|
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
|
|
3863
|
-
|
|
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
|
|
3866
|
-
return
|
|
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
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
}
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
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
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
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
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
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-
|
|
3923
|
-
function
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
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
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3957
|
-
|
|
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 (
|
|
3964
|
-
|
|
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 (
|
|
3971
|
-
|
|
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
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
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
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
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
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
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
|
|
3992
|
-
|
|
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
|
-
//
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
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
|
|
4020
|
-
|
|
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-
|
|
4025
|
-
async function
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
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
|
-
|
|
4037
|
-
|
|
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
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
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
|
|
4086
|
-
const
|
|
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
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
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
|
-
|
|
4120
|
-
throw new Error("
|
|
4070
|
+
onUnauthorized();
|
|
4071
|
+
throw new Error("autonomous dispatch admission unauthorized (401)");
|
|
4121
4072
|
}
|
|
4122
|
-
if (!res.ok) throw new Error(`
|
|
4123
|
-
const
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
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
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
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
|
-
|
|
4149
|
-
throw new Error("
|
|
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
|
|
4387
|
-
|
|
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/
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
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
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4111
|
+
const json = await res.json().catch(() => ({}));
|
|
4112
|
+
if (res.ok && json?.ok === true) {
|
|
4113
|
+
const result = json?.result && typeof json.result === "object" ? json.result : {};
|
|
4114
|
+
const status = result.merged === true || result.status === "merged" ? "merged" : result.status === "auto-merge-enabled" || String(result.action || "").includes("auto-merge") ? "queued" : "accepted";
|
|
4115
|
+
return {
|
|
4116
|
+
status,
|
|
4117
|
+
detail: typeof result.detail === "string" ? result.detail : null,
|
|
4118
|
+
actionReceiptId: typeof json.action_receipt_id === "string" ? json.action_receipt_id : null
|
|
4119
|
+
};
|
|
4120
|
+
}
|
|
4121
|
+
const captureStoreRetry = json?.action_status === "not_attempted" && (json?.error === "capture_preflight_unavailable" || json?.error === "decision_outcome_intent_failed");
|
|
4122
|
+
if (res.status === 503 && (json?.error === "verify_unavailable" || json?.error === "merge_unavailable" || captureStoreRetry)) {
|
|
4123
|
+
return { status: "retry", reason: json.reason || json.message || json.error || "verification unavailable" };
|
|
4124
|
+
}
|
|
4125
|
+
return {
|
|
4126
|
+
status: "blocked",
|
|
4127
|
+
reason: json?.reason || json?.message || json?.error || `HTTP ${res.status}`,
|
|
4128
|
+
actionReceiptId: typeof json?.action_receipt_id === "string" ? json.action_receipt_id : null
|
|
4129
|
+
};
|
|
4432
4130
|
}
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
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
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
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
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
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
|
-
|
|
4478
|
-
if (!exists(candidate)) return null;
|
|
4174
|
+
if (res.status === 401) onUnauthorized();
|
|
4175
|
+
let body = null;
|
|
4479
4176
|
try {
|
|
4480
|
-
|
|
4177
|
+
body = await res.json();
|
|
4481
4178
|
} catch {
|
|
4482
|
-
|
|
4179
|
+
body = null;
|
|
4483
4180
|
}
|
|
4181
|
+
return { status: res.status, body };
|
|
4484
4182
|
}
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
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
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
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
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
} = {}) {
|
|
4198
|
+
function makeClaimGateNotice({ log: log2 = () => {
|
|
4199
|
+
} } = {}) {
|
|
4200
|
+
let last = null;
|
|
4201
|
+
let current = null;
|
|
4514
4202
|
return {
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
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
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
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
|
-
|
|
4554
|
-
"
|
|
4555
|
-
"
|
|
4556
|
-
"
|
|
4557
|
-
"
|
|
4558
|
-
|
|
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/
|
|
4564
|
-
function
|
|
4565
|
-
|
|
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
|
|
4569
|
-
return
|
|
4233
|
+
function isRetryableStatus(status) {
|
|
4234
|
+
return status >= 500 && status <= 599;
|
|
4570
4235
|
}
|
|
4571
|
-
function
|
|
4572
|
-
return
|
|
4236
|
+
function defaultSleep(ms) {
|
|
4237
|
+
return new Promise((resolve3) => {
|
|
4238
|
+
setTimeout(resolve3, ms);
|
|
4239
|
+
});
|
|
4573
4240
|
}
|
|
4574
|
-
function
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
return { source: CLAUDE_CREDENTIAL_SOURCE.ENV_KEY, key: null };
|
|
4581
|
-
}
|
|
4582
|
-
const key = getKey();
|
|
4583
|
-
if (!key) {
|
|
4584
|
-
return { source: CLAUDE_CREDENTIAL_SOURCE.NO_KEY, key: null };
|
|
4241
|
+
async function getTaskKnowledgeContextRequest(req, taskId, { query, knowledgeRequestId } = {}, {
|
|
4242
|
+
taskRequestTimeoutMs,
|
|
4243
|
+
invalidateToken = () => {
|
|
4244
|
+
},
|
|
4245
|
+
sleep: sleep3 = defaultSleep,
|
|
4246
|
+
log: log2 = () => {
|
|
4585
4247
|
}
|
|
4586
|
-
|
|
4587
|
-
|
|
4248
|
+
} = {}) {
|
|
4249
|
+
const body = {};
|
|
4250
|
+
const canonicalQuery = canonicalizeKnowledgeContextQuery(query);
|
|
4251
|
+
if (canonicalQuery.trim()) body.query = canonicalQuery;
|
|
4252
|
+
if (typeof knowledgeRequestId === "string" && knowledgeRequestId) body.knowledge_request_id = knowledgeRequestId;
|
|
4253
|
+
const path25 = `/api/v1/code-task/${encodeURIComponent(taskId)}/knowledge-context`;
|
|
4254
|
+
const timeoutMs = Math.max(Number(taskRequestTimeoutMs) || 0, MIN_KNOWLEDGE_CONTEXT_TIMEOUT_MS);
|
|
4255
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt += 1) {
|
|
4256
|
+
let res;
|
|
4257
|
+
let cause;
|
|
4258
|
+
try {
|
|
4259
|
+
res = await req("POST", path25, body, { timeoutMs });
|
|
4260
|
+
} catch (err) {
|
|
4261
|
+
cause = err;
|
|
4262
|
+
}
|
|
4263
|
+
if (!cause) {
|
|
4264
|
+
if (res.status === 401) {
|
|
4265
|
+
invalidateToken();
|
|
4266
|
+
throw new Error("knowledge-context unauthorized (401)");
|
|
4267
|
+
}
|
|
4268
|
+
if (res.status === 404) return null;
|
|
4269
|
+
if (res.ok) return res.json();
|
|
4270
|
+
if (!isRetryableStatus(res.status)) {
|
|
4271
|
+
throw new Error(`knowledge-context failed: HTTP ${res.status}`);
|
|
4272
|
+
}
|
|
4273
|
+
cause = new Error(`knowledge-context failed: HTTP ${res.status}`);
|
|
4274
|
+
}
|
|
4275
|
+
if (attempt === MAX_ATTEMPTS) throw cause;
|
|
4276
|
+
const delayMs = RETRY_DELAYS_MS[attempt - 1];
|
|
4277
|
+
log2(`knowledge-context attempt ${attempt}/${MAX_ATTEMPTS} failed (${cause.message}); retrying in ${delayMs}ms`);
|
|
4278
|
+
await sleep3(delayMs);
|
|
4588
4279
|
}
|
|
4589
|
-
|
|
4590
|
-
return { source: CLAUDE_CREDENTIAL_SOURCE.SUBSCRIPTION_WINS, key: null };
|
|
4591
|
-
}
|
|
4592
|
-
return { source: CLAUDE_CREDENTIAL_SOURCE.KEYCHAIN, key };
|
|
4280
|
+
throw new Error("knowledge-context retry loop exited unexpectedly");
|
|
4593
4281
|
}
|
|
4594
|
-
var
|
|
4595
|
-
var
|
|
4596
|
-
"../../scripts/virtual-office/code-runner/
|
|
4282
|
+
var MIN_KNOWLEDGE_CONTEXT_TIMEOUT_MS, KNOWLEDGE_CONTEXT_QUERY_MAX_CHARS, RETRY_DELAYS_MS, MAX_ATTEMPTS;
|
|
4283
|
+
var init_control_plane_knowledge_context = __esm({
|
|
4284
|
+
"../../scripts/virtual-office/code-runner/control-plane-knowledge-context.mjs"() {
|
|
4597
4285
|
"use strict";
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
CLAUDE_CREDENTIAL_SOURCE = Object.freeze({
|
|
4603
|
-
/** PREFER_LOGIN set (and not overridden): any API key is ignored. */
|
|
4604
|
-
PREFER_LOGIN: "prefer_login",
|
|
4605
|
-
/** An explicit ANTHROPIC_API_KEY in the environment — the manual override. */
|
|
4606
|
-
ENV_KEY: "env_key",
|
|
4607
|
-
/** No key anywhere; the spawn falls through to the login session. */
|
|
4608
|
-
NO_KEY: "no_key",
|
|
4609
|
-
/** A stored key, used because the operator explicitly opted out of tier 1. */
|
|
4610
|
-
KEYCHAIN_PREFER_KEY: "keychain_prefer_key",
|
|
4611
|
-
/** A stored key exists but a proven live subscription outranks it. */
|
|
4612
|
-
SUBSCRIPTION_WINS: "subscription_wins",
|
|
4613
|
-
/** A stored key, used because no live subscription was proven. */
|
|
4614
|
-
KEYCHAIN: "keychain"
|
|
4615
|
-
});
|
|
4286
|
+
MIN_KNOWLEDGE_CONTEXT_TIMEOUT_MS = 15e3;
|
|
4287
|
+
KNOWLEDGE_CONTEXT_QUERY_MAX_CHARS = 2e3;
|
|
4288
|
+
RETRY_DELAYS_MS = [2e3, 6e3];
|
|
4289
|
+
MAX_ATTEMPTS = RETRY_DELAYS_MS.length + 1;
|
|
4616
4290
|
}
|
|
4617
4291
|
});
|
|
4618
4292
|
|
|
4619
|
-
// ../../scripts/virtual-office/code-runner/
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
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
|
|
4309
|
+
return { query: params.toString(), sent, dropped };
|
|
4631
4310
|
}
|
|
4632
|
-
function
|
|
4633
|
-
if (!EntryCtor) return null;
|
|
4311
|
+
async function refusalCode(res) {
|
|
4634
4312
|
try {
|
|
4635
|
-
|
|
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
|
|
4641
|
-
|
|
4642
|
-
const
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
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
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4327
|
+
const path25 = `/api/v1/code-task/${encodeURIComponent(taskId)}/prepared-job?${query}`;
|
|
4328
|
+
let res;
|
|
4329
|
+
try {
|
|
4330
|
+
res = await req("GET", path25, void 0, { timeoutMs });
|
|
4331
|
+
} catch (err) {
|
|
4332
|
+
return { ok: false, reason: `transport: ${err?.message || String(err)}`, status: 0, ...envMeta };
|
|
4333
|
+
}
|
|
4334
|
+
if (res?.status === 401) {
|
|
4335
|
+
try {
|
|
4336
|
+
invalidateToken();
|
|
4337
|
+
} catch {
|
|
4338
|
+
}
|
|
4339
|
+
return { ok: false, reason: "unauthorized", status: 401, ...envMeta };
|
|
4340
|
+
}
|
|
4341
|
+
if (!res?.ok) {
|
|
4342
|
+
const code = await refusalCode(res);
|
|
4343
|
+
return { ok: false, reason: code || `http_${res?.status ?? "unknown"}`, status: res?.status ?? 0, ...envMeta };
|
|
4344
|
+
}
|
|
4345
|
+
let body;
|
|
4346
|
+
try {
|
|
4347
|
+
body = await res.json();
|
|
4348
|
+
} catch (err) {
|
|
4349
|
+
return { ok: false, reason: `unreadable_body: ${err?.message || String(err)}`, status: res.status, ...envMeta };
|
|
4350
|
+
}
|
|
4351
|
+
const job = body?.prepared_job;
|
|
4352
|
+
if (!job || typeof job !== "object") {
|
|
4353
|
+
return { ok: false, reason: "no_prepared_job_in_body", status: res.status, ...envMeta };
|
|
4354
|
+
}
|
|
4355
|
+
return {
|
|
4356
|
+
ok: true,
|
|
4357
|
+
job,
|
|
4358
|
+
composition: body?.composition && typeof body.composition === "object" ? body.composition : {},
|
|
4359
|
+
...envMeta
|
|
4360
|
+
};
|
|
4652
4361
|
}
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
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
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
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
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
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
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4426
|
+
const resolvedBaseUrl = baseUrl ?? env2.VO_CONTROL_PLANE_URL ?? "";
|
|
4427
|
+
if (!resolvedBaseUrl) throw new Error("VO_CONTROL_PLANE_URL is required for the code-runner daemon");
|
|
4428
|
+
const root = resolvedBaseUrl.replace(/\/+$/, "");
|
|
4429
|
+
const claimOccurrences = /* @__PURE__ */ new Map();
|
|
4430
|
+
async function req(method, path25, body, { timeoutMs } = {}) {
|
|
4431
|
+
const bearer = await resolveBearer(env2);
|
|
4432
|
+
const controller = timeoutMs ? new AbortController() : null;
|
|
4433
|
+
let timeoutId;
|
|
4434
|
+
const request = Promise.resolve(fetchImpl(`${root}${path25}`, {
|
|
4435
|
+
method,
|
|
4436
|
+
headers: {
|
|
4437
|
+
"content-type": "application/json",
|
|
4438
|
+
authorization: `Bearer ${bearer}`
|
|
4439
|
+
},
|
|
4440
|
+
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
4441
|
+
...controller ? { signal: controller.signal } : {}
|
|
4442
|
+
}));
|
|
4443
|
+
if (!timeoutMs) return request;
|
|
4444
|
+
const timeout = new Promise((_, reject) => {
|
|
4445
|
+
timeoutId = setTimeout(() => {
|
|
4446
|
+
controller.abort();
|
|
4447
|
+
reject(new Error(`control-plane ${path25} timed out after ${timeoutMs}ms`));
|
|
4448
|
+
}, timeoutMs);
|
|
4449
|
+
});
|
|
4450
|
+
try {
|
|
4451
|
+
return await Promise.race([request, timeout]);
|
|
4452
|
+
} finally {
|
|
4453
|
+
clearTimeout(timeoutId);
|
|
4454
|
+
}
|
|
4675
4455
|
}
|
|
4456
|
+
const taskReq = (method, path25, body, options = {}) => req(method, path25, body, { timeoutMs: taskRequestTimeoutMs, ...options });
|
|
4457
|
+
const claimGate = makeClaimGateNotice({ log: (m) => console.warn(`[code-runner ${(/* @__PURE__ */ new Date()).toISOString()}] ${m}`) });
|
|
4458
|
+
return {
|
|
4459
|
+
getClaimGate: () => claimGate.current(),
|
|
4460
|
+
// last DENIED claim-gate verdict (null when allowed) — for /status + tests
|
|
4461
|
+
...makeAutonomousDispatchAdmissionClient(
|
|
4462
|
+
req,
|
|
4463
|
+
taskRequestTimeoutMs,
|
|
4464
|
+
() => {
|
|
4465
|
+
cachedFirebaseToken = null;
|
|
4466
|
+
}
|
|
4467
|
+
),
|
|
4468
|
+
/**
|
|
4469
|
+
* Claim the next pending task. Returns the task or null (empty queue).
|
|
4470
|
+
* `repos` (optional `owner/name` list) and `operatorIds` (optional
|
|
4471
|
+
* `operator_id` list) scope the claim so this daemon only picks up tasks it
|
|
4472
|
+
* serves — the control-plane filters by both (logical AND), so another
|
|
4473
|
+
* operator's task never lands on (or bills) this machine.
|
|
4474
|
+
*/
|
|
4475
|
+
async claim(runnerId2, repos, operatorIds, session = {}) {
|
|
4476
|
+
const body = { runner_id: runnerId2 };
|
|
4477
|
+
if (Array.isArray(repos) && repos.length > 0) body.repos = repos;
|
|
4478
|
+
if (Array.isArray(operatorIds) && operatorIds.length > 0) body.operator_ids = operatorIds;
|
|
4479
|
+
if (session.runnerInstanceId) {
|
|
4480
|
+
body.runner_instance_id = session.runnerInstanceId;
|
|
4481
|
+
body.runner_progress_protocol_version = 2;
|
|
4482
|
+
}
|
|
4483
|
+
if (session.runnerInstanceId && session.reconcileStale) body.reconcile_stale = true;
|
|
4484
|
+
if (session.defaultAgent) body.default_agent = session.defaultAgent;
|
|
4485
|
+
if (AGENT_AUTH_SOURCES.includes(session.agentAuthSource)) body.agent_auth_source = session.agentAuthSource;
|
|
4486
|
+
if (Array.isArray(session.availableAgents)) {
|
|
4487
|
+
body.available_agents = session.availableAgents.filter((entry) => entry?.installed === true && entry?.authenticated === true).map((entry) => entry.agent);
|
|
4488
|
+
}
|
|
4489
|
+
const res = await taskReq("POST", "/api/v1/code-task/claim", body);
|
|
4490
|
+
if (res.status === 401) {
|
|
4491
|
+
cachedFirebaseToken = null;
|
|
4492
|
+
throw new Error("claim unauthorized (401)");
|
|
4493
|
+
}
|
|
4494
|
+
if (!res.ok) throw new Error(`claim failed: HTTP ${res.status}`);
|
|
4495
|
+
const json = await res.json();
|
|
4496
|
+
claimGate.observe(json);
|
|
4497
|
+
const task = json && json.task ? json.task : null;
|
|
4498
|
+
if (task?.claim_occurrence_id) claimOccurrences.set(task.code_task_id, task.claim_occurrence_id);
|
|
4499
|
+
return task;
|
|
4500
|
+
},
|
|
4501
|
+
/**
|
|
4502
|
+
* Enqueue a new code-task (used by the PR watcher to auto-dispatch a CI fix).
|
|
4503
|
+
* Server derives operator/tenant from the daemon's authenticated principal.
|
|
4504
|
+
* Returns the created task, or throws on a non-2xx response.
|
|
4505
|
+
*/
|
|
4506
|
+
async enqueueCodeTask({ repo, prompt, max_budget_usd, max_turns, dispatch_mode, tier, agent, model, dispatch_occurrence_key, autonomous_reservation_id, on_behalf_of_operator_id, repair_pr_number, repair_kind, repair_head_sha, repair_chain }) {
|
|
4507
|
+
const body = { repo, prompt };
|
|
4508
|
+
if (typeof max_budget_usd === "number") body.max_budget_usd = max_budget_usd;
|
|
4509
|
+
if (typeof max_turns === "number") body.max_turns = max_turns;
|
|
4510
|
+
for (const [key, value] of Object.entries({ dispatch_mode, tier, agent, model, repair_kind, repair_head_sha })) {
|
|
4511
|
+
if (value) body[key] = value;
|
|
4512
|
+
}
|
|
4513
|
+
if (dispatch_occurrence_key) body.dispatch_occurrence_key = dispatch_occurrence_key;
|
|
4514
|
+
if (autonomous_reservation_id) body.autonomous_reservation_id = autonomous_reservation_id;
|
|
4515
|
+
if (on_behalf_of_operator_id) body.on_behalf_of_operator_id = on_behalf_of_operator_id;
|
|
4516
|
+
if (Number.isInteger(repair_pr_number) && repair_pr_number > 0) body.repair_pr_number = repair_pr_number;
|
|
4517
|
+
if (repair_chain) body.repair_chain = repair_chain;
|
|
4518
|
+
const res = await taskReq("POST", "/api/v1/code-task", body);
|
|
4519
|
+
if (res.status === 401) {
|
|
4520
|
+
cachedFirebaseToken = null;
|
|
4521
|
+
throw new Error("enqueue unauthorized (401)");
|
|
4522
|
+
}
|
|
4523
|
+
if (!res.ok) {
|
|
4524
|
+
let code = null;
|
|
4525
|
+
try {
|
|
4526
|
+
const errBody = await res.json();
|
|
4527
|
+
code = typeof errBody?.error === "string" ? errBody.error : null;
|
|
4528
|
+
} catch {
|
|
4529
|
+
}
|
|
4530
|
+
const err = new Error(`enqueue failed: HTTP ${res.status}${code ? ` (${code})` : ""}`);
|
|
4531
|
+
err.status = res.status;
|
|
4532
|
+
err.code = code;
|
|
4533
|
+
throw err;
|
|
4534
|
+
}
|
|
4535
|
+
const json = await res.json();
|
|
4536
|
+
const task = json && json.task ? json.task : null;
|
|
4537
|
+
if (task && typeof json.deduplicated === "boolean") Object.defineProperty(task, "deduplicated", { value: json.deduplicated, enumerable: false });
|
|
4538
|
+
return task;
|
|
4539
|
+
},
|
|
4540
|
+
/**
|
|
4541
|
+
* Resume a failed/cancelled/max-turn partial code-task. The PR watcher uses
|
|
4542
|
+
* this after the runner opens a partial draft PR and CI is no longer pending.
|
|
4543
|
+
*/
|
|
4544
|
+
async resumeCodeTask(taskId, { automaticRateLimit = false, automaticContinuation = false } = {}) {
|
|
4545
|
+
return resumeCodeTaskRequest(taskReq, taskId, { automaticRateLimit, automaticContinuation }, () => {
|
|
4546
|
+
cachedFirebaseToken = null;
|
|
4547
|
+
});
|
|
4548
|
+
},
|
|
4549
|
+
/**
|
|
4550
|
+
* Send a CI-green PR through the production verify-before-act merge route.
|
|
4551
|
+
* The server inspects the current diff, applies deterministic blockers, runs
|
|
4552
|
+
* consensus, records a receipt, and direct-merges only the inspected SHA.
|
|
4553
|
+
*/
|
|
4554
|
+
/** F35: promote a PARTIAL draft to READY via the plane (admin-only; server re-checks; never merges). */
|
|
4555
|
+
promoteDraftPr: (prNumber, automationContext) => promoteDraftPrRequest(req, prNumber, automationContext, () => {
|
|
4556
|
+
cachedFirebaseToken = null;
|
|
4557
|
+
}),
|
|
4558
|
+
async mergeVerifiedPr(prNumber, automationContext) {
|
|
4559
|
+
return mergeVerifiedPrRequest(
|
|
4560
|
+
req,
|
|
4561
|
+
prNumber,
|
|
4562
|
+
automationContext,
|
|
4563
|
+
() => {
|
|
4564
|
+
cachedFirebaseToken = null;
|
|
4565
|
+
}
|
|
4566
|
+
);
|
|
4567
|
+
},
|
|
4568
|
+
async postProgress(taskId, patch) {
|
|
4569
|
+
const progress = {
|
|
4570
|
+
...patch,
|
|
4571
|
+
...patch.runner_id ? {} : runnerId ? { runner_id: runnerId } : {},
|
|
4572
|
+
...patch.runner_instance_id ? {} : runnerInstanceId ? { runner_instance_id: runnerInstanceId } : {},
|
|
4573
|
+
...patch.claim_occurrence_id ? {} : claimOccurrences.has(taskId) ? { claim_occurrence_id: claimOccurrences.get(taskId) } : {}
|
|
4574
|
+
};
|
|
4575
|
+
const res = await taskReq("PATCH", `/api/v1/code-task/${taskId}/progress`, progress);
|
|
4576
|
+
if (res.status === 409) {
|
|
4577
|
+
const conflict = await res.json().catch(() => ({}));
|
|
4578
|
+
if (conflict?.error === "code_task_claim_authority_changed") {
|
|
4579
|
+
throw new ClaimAuthorityChangedError();
|
|
4580
|
+
}
|
|
4581
|
+
return { terminal: true };
|
|
4582
|
+
}
|
|
4583
|
+
if (res.status === 404) return { terminal: true, missing: true };
|
|
4584
|
+
if (!res.ok) throw new Error(`progress failed: HTTP ${res.status}`);
|
|
4585
|
+
const json = await res.json();
|
|
4586
|
+
return { task: json && json.task };
|
|
4587
|
+
},
|
|
4588
|
+
async getTask(taskId) {
|
|
4589
|
+
const res = await taskReq("GET", `/api/v1/code-task/${taskId}`);
|
|
4590
|
+
if (res.status === 404) return null;
|
|
4591
|
+
if (!res.ok) throw new Error(`getTask failed: HTTP ${res.status}`);
|
|
4592
|
+
const json = await res.json();
|
|
4593
|
+
return json ? json.task : null;
|
|
4594
|
+
},
|
|
4595
|
+
async getAssignedSkill(task) {
|
|
4596
|
+
const name = task?.skill_invocation?.skill;
|
|
4597
|
+
if (!runnerId || !runnerInstanceId || task?.claimed_by !== runnerId || task?.runner_instance_id !== runnerInstanceId || !task?.claim_occurrence_id || !name) {
|
|
4598
|
+
throw new ClaimAuthorityChangedError();
|
|
4599
|
+
}
|
|
4600
|
+
const res = await taskReq("POST", `/api/v1/code-task/${encodeURIComponent(task.code_task_id)}/assigned-skill`, {
|
|
4601
|
+
runner_id: runnerId,
|
|
4602
|
+
runner_instance_id: runnerInstanceId,
|
|
4603
|
+
claim_occurrence_id: task.claim_occurrence_id,
|
|
4604
|
+
skill: name
|
|
4605
|
+
});
|
|
4606
|
+
if (res.status === 401) {
|
|
4607
|
+
cachedFirebaseToken = null;
|
|
4608
|
+
throw new Error("skill corpus unauthorized (401)");
|
|
4609
|
+
}
|
|
4610
|
+
if (res.status === 409) {
|
|
4611
|
+
const conflict = await res.json().catch(() => ({}));
|
|
4612
|
+
if (conflict?.error === "code_task_claim_authority_changed") {
|
|
4613
|
+
throw new ClaimAuthorityChangedError();
|
|
4614
|
+
}
|
|
4615
|
+
}
|
|
4616
|
+
if (res.status === 404) throw new Error(`skill not found: ${name}`);
|
|
4617
|
+
if (!res.ok) throw new Error(`skill corpus failed: HTTP ${res.status}`);
|
|
4618
|
+
const json = await res.json();
|
|
4619
|
+
if (json?.corpus_available !== true || !json.skill) {
|
|
4620
|
+
throw new Error(`skill corpus unavailable: ${String(json?.reason || "unknown")}`);
|
|
4621
|
+
}
|
|
4622
|
+
return json.skill;
|
|
4623
|
+
},
|
|
4624
|
+
async listPrOpenedTasks() {
|
|
4625
|
+
return listAllPrOpenedTasks(taskReq);
|
|
4626
|
+
},
|
|
4627
|
+
async downloadTaskAttachment(taskId, attachmentId) {
|
|
4628
|
+
const path25 = `/api/v1/code-task/${encodeURIComponent(taskId)}/attachment/${encodeURIComponent(attachmentId)}`;
|
|
4629
|
+
const res = await taskReq("GET", path25);
|
|
4630
|
+
if (res.status === 401) cachedFirebaseToken = null;
|
|
4631
|
+
if (!res.ok) throw new Error(`attachment download failed: HTTP ${res.status}`);
|
|
4632
|
+
return Buffer.from(await res.arrayBuffer());
|
|
4633
|
+
},
|
|
4634
|
+
// Raised per-attempt timeout (>=15s) + bounded retry — see control-plane-knowledge-context.mjs.
|
|
4635
|
+
async getTaskKnowledgeContext(taskId, { query, knowledgeRequestId } = {}) {
|
|
4636
|
+
return getTaskKnowledgeContextRequest(req, taskId, { query, knowledgeRequestId }, {
|
|
4637
|
+
taskRequestTimeoutMs,
|
|
4638
|
+
invalidateToken: () => {
|
|
4639
|
+
cachedFirebaseToken = null;
|
|
4640
|
+
},
|
|
4641
|
+
sleep: sleep3,
|
|
4642
|
+
log: (m) => console.warn(`[code-runner ${(/* @__PURE__ */ new Date()).toISOString()}] ${m}`)
|
|
4643
|
+
});
|
|
4644
|
+
},
|
|
4645
|
+
/** ADR-004 § 11.1b: the plane's prepared job, for SHADOW comparison. Never throws. */
|
|
4646
|
+
getPreparedJob: (taskId, options) => getPreparedJobRequest(req, taskId, options, () => {
|
|
4647
|
+
cachedFirebaseToken = null;
|
|
4648
|
+
}),
|
|
4649
|
+
/** Weekly Claude token usage report — see control-plane-weekly-tokens.mjs. */
|
|
4650
|
+
async postWeeklyTokens(report) {
|
|
4651
|
+
return postWeeklyTokensRequest(taskReq, report, () => {
|
|
4652
|
+
cachedFirebaseToken = null;
|
|
4653
|
+
});
|
|
4654
|
+
},
|
|
4655
|
+
/**
|
|
4656
|
+
* Relay a batch of this machine's local vo-mcp events to vo-telemetry via
|
|
4657
|
+
* the control plane (telemetry-forwarder.mjs). Returns { status, body };
|
|
4658
|
+
* the forwarder owns backoff/disable policy. See control-plane-telemetry-relay.mjs.
|
|
4659
|
+
*/
|
|
4660
|
+
async relayTelemetryEvents(batch) {
|
|
4661
|
+
return relayTelemetryEventsRequest(req, batch, () => {
|
|
4662
|
+
cachedFirebaseToken = null;
|
|
4663
|
+
});
|
|
4664
|
+
},
|
|
4665
|
+
/**
|
|
4666
|
+
* Send a liveness heartbeat (M2). The control-plane upserts it under the
|
|
4667
|
+
* authenticated operator so the web shows a TRUE "runner online" signal.
|
|
4668
|
+
* Best-effort caller; throws on 401/non-ok so the daemon can log + retry.
|
|
4669
|
+
*/
|
|
4670
|
+
async postHeartbeat(heartbeat) {
|
|
4671
|
+
const body = buildRunnerHeartbeatBody(heartbeat);
|
|
4672
|
+
const res = await req("POST", "/api/v1/runner/heartbeat", body, {
|
|
4673
|
+
timeoutMs: heartbeatTimeoutMs
|
|
4674
|
+
});
|
|
4675
|
+
if (res.status === 401) {
|
|
4676
|
+
cachedFirebaseToken = null;
|
|
4677
|
+
throw new Error("heartbeat unauthorized (401)");
|
|
4678
|
+
}
|
|
4679
|
+
if (!res.ok) {
|
|
4680
|
+
let detail = "";
|
|
4681
|
+
try {
|
|
4682
|
+
const body2 = await res.json();
|
|
4683
|
+
if (Array.isArray(body2?.issue_paths) && body2.issue_paths.length > 0) {
|
|
4684
|
+
detail = ` (rejected fields: ${body2.issue_paths.join(", ")})`;
|
|
4685
|
+
}
|
|
4686
|
+
} catch {
|
|
4687
|
+
}
|
|
4688
|
+
throw new Error(`heartbeat failed: HTTP ${res.status}${detail}`);
|
|
4689
|
+
}
|
|
4690
|
+
return res.json();
|
|
4691
|
+
},
|
|
4692
|
+
async getRunnerStatus({ operatorId } = {}) {
|
|
4693
|
+
const query = operatorId ? `?operator_id=${encodeURIComponent(operatorId)}` : "";
|
|
4694
|
+
const res = await req("GET", `/api/v1/runner/status${query}`, void 0, {
|
|
4695
|
+
timeoutMs: heartbeatTimeoutMs
|
|
4696
|
+
});
|
|
4697
|
+
if (res.status === 401) {
|
|
4698
|
+
cachedFirebaseToken = null;
|
|
4699
|
+
throw new Error("runner status unauthorized (401)");
|
|
4700
|
+
}
|
|
4701
|
+
if (!res.ok) throw new Error(`runner status failed: HTTP ${res.status}`);
|
|
4702
|
+
const body = await res.json();
|
|
4703
|
+
return Array.isArray(body?.runners) ? body.runners : [];
|
|
4704
|
+
},
|
|
4705
|
+
async pollRunnerControl({ runnerId: runnerId2, operatorId, supervisorInstanceId, supervisorVersion, capabilities }) {
|
|
4706
|
+
const body = { runner_id: runnerId2 };
|
|
4707
|
+
if (operatorId) body.operator_id = operatorId;
|
|
4708
|
+
if (supervisorInstanceId) body.supervisor_instance_id = supervisorInstanceId;
|
|
4709
|
+
if (supervisorVersion) body.supervisor_version = supervisorVersion;
|
|
4710
|
+
if (Array.isArray(capabilities) && capabilities.length > 0) body.capabilities = capabilities;
|
|
4711
|
+
const res = await taskReq("POST", "/api/v1/runner/control/poll", body);
|
|
4712
|
+
if (res.status === 401) {
|
|
4713
|
+
cachedFirebaseToken = null;
|
|
4714
|
+
throw new Error("runner control poll unauthorized (401)");
|
|
4715
|
+
}
|
|
4716
|
+
if (!res.ok) throw new Error(`runner control poll failed: HTTP ${res.status}`);
|
|
4717
|
+
const json = await res.json();
|
|
4718
|
+
const action = json?.action;
|
|
4719
|
+
return action && typeof action.action_id === "string" && action.action_id ? { ...action, actionId: action.action_id } : null;
|
|
4720
|
+
},
|
|
4721
|
+
async completeRunnerControl(actionId, { runnerId: runnerId2, operatorId, supervisorInstanceId, supervisorVersion, capabilities, status, detail }) {
|
|
4722
|
+
const body = { runner_id: runnerId2, status };
|
|
4723
|
+
if (operatorId) body.operator_id = operatorId;
|
|
4724
|
+
if (supervisorInstanceId) body.supervisor_instance_id = supervisorInstanceId;
|
|
4725
|
+
if (supervisorVersion) body.supervisor_version = supervisorVersion;
|
|
4726
|
+
if (Array.isArray(capabilities) && capabilities.length > 0) body.capabilities = capabilities;
|
|
4727
|
+
if (detail) body.detail = detail;
|
|
4728
|
+
const res = await taskReq("POST", `/api/v1/runner/control/${encodeURIComponent(actionId)}/complete`, body);
|
|
4729
|
+
if (res.status === 401) {
|
|
4730
|
+
cachedFirebaseToken = null;
|
|
4731
|
+
throw new Error("runner control completion unauthorized (401)");
|
|
4732
|
+
}
|
|
4733
|
+
if (!res.ok) throw new Error(`runner control completion failed: HTTP ${res.status}`);
|
|
4734
|
+
const json = await res.json();
|
|
4735
|
+
return json?.action || null;
|
|
4736
|
+
},
|
|
4737
|
+
/** Mint a GitHub App installation token — see installation-token.mjs. */
|
|
4738
|
+
async getInstallationToken({ required = false, readOnly = false, repo = null } = {}) {
|
|
4739
|
+
return fetchInstallationToken({ req: taskReq, required, readOnly, repo });
|
|
4740
|
+
},
|
|
4741
|
+
/**
|
|
4742
|
+
* Read the operator's dispatch-mode config (Fast→Ultracode effort setting).
|
|
4743
|
+
* Returns the mode string ('fast'|'standard'|'deep'|'ultra'|'marathon'; 'ultracode' legacy),
|
|
4744
|
+
* defaulting to 'standard' on any error. Never throws — best-effort.
|
|
4745
|
+
*/
|
|
4746
|
+
async getDispatchMode() {
|
|
4747
|
+
try {
|
|
4748
|
+
const res = await taskReq("GET", "/api/v1/dispatch-mode-config");
|
|
4749
|
+
if (!res.ok) return "standard";
|
|
4750
|
+
const json = await res.json();
|
|
4751
|
+
return json?.dispatchMode || "standard";
|
|
4752
|
+
} catch {
|
|
4753
|
+
return "standard";
|
|
4754
|
+
}
|
|
4755
|
+
}
|
|
4756
|
+
};
|
|
4676
4757
|
}
|
|
4677
|
-
var
|
|
4678
|
-
var
|
|
4679
|
-
"../../scripts/virtual-office/code-runner/
|
|
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
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
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
|
|
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
|
|
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 (
|
|
6525
|
+
if (isTruthyFlag3(env2[CODEX_PREFER_LOGIN_ENV]) || isTruthyFlag3(env2[LEGACY_PREFER_LOGIN_ENV])) {
|
|
6436
6526
|
const out = { ...env2 };
|
|
6437
6527
|
delete out.OPENAI_API_KEY;
|
|
6438
6528
|
delete out.CODEX_API_KEY;
|
|
@@ -7870,14 +7960,14 @@ function parsePorcelainZ(out) {
|
|
|
7870
7960
|
for (let i = 0; i < tokens.length; i += 1) {
|
|
7871
7961
|
const token2 = tokens[i];
|
|
7872
7962
|
if (!token2) continue;
|
|
7873
|
-
const
|
|
7874
|
-
if (
|
|
7963
|
+
const path25 = token2.slice(3);
|
|
7964
|
+
if (path25) files.push(path25);
|
|
7875
7965
|
if (token2[0] === "R" || token2[0] === "C") i += 1;
|
|
7876
7966
|
}
|
|
7877
7967
|
return files;
|
|
7878
7968
|
}
|
|
7879
|
-
function isAgentScratch(
|
|
7880
|
-
const normalized = String(
|
|
7969
|
+
function isAgentScratch(path25) {
|
|
7970
|
+
const normalized = String(path25 || "");
|
|
7881
7971
|
return SCRATCH_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
7882
7972
|
}
|
|
7883
7973
|
var SCRATCH_PATTERNS;
|
|
@@ -10213,9 +10303,9 @@ async function readSpool(spoolDir = SPOOL_DIR) {
|
|
|
10213
10303
|
}
|
|
10214
10304
|
return out;
|
|
10215
10305
|
}
|
|
10216
|
-
async function readCloudMap(
|
|
10306
|
+
async function readCloudMap(path25) {
|
|
10217
10307
|
try {
|
|
10218
|
-
return JSON.parse(await readFile2(
|
|
10308
|
+
return JSON.parse(await readFile2(path25, "utf8"));
|
|
10219
10309
|
} catch {
|
|
10220
10310
|
return {};
|
|
10221
10311
|
}
|
|
@@ -10516,9 +10606,9 @@ function backoffMs(streak, baseMs) {
|
|
|
10516
10606
|
if (streak <= 0) return 0;
|
|
10517
10607
|
return Math.min(baseMs * 2 ** Math.min(streak - 1, 20), MAX_BACKOFF_MS);
|
|
10518
10608
|
}
|
|
10519
|
-
async function loadState(
|
|
10609
|
+
async function loadState(path25) {
|
|
10520
10610
|
try {
|
|
10521
|
-
const parsed = JSON.parse(await readFile3(
|
|
10611
|
+
const parsed = JSON.parse(await readFile3(path25, "utf8"));
|
|
10522
10612
|
if (parsed && typeof parsed === "object" && Number.isInteger(parsed.byte_offset) && parsed.byte_offset >= 0) {
|
|
10523
10613
|
return { ...parsed, byte_offset: parsed.byte_offset };
|
|
10524
10614
|
}
|
|
@@ -10526,15 +10616,15 @@ async function loadState(path24) {
|
|
|
10526
10616
|
}
|
|
10527
10617
|
return { byte_offset: 0, last_event_id: null, forwarded_total: 0, rejected_total: 0, rejected_event_ids: [] };
|
|
10528
10618
|
}
|
|
10529
|
-
async function saveState(
|
|
10530
|
-
await mkdir2(dirname9(
|
|
10531
|
-
await writeFile3(
|
|
10619
|
+
async function saveState(path25, state) {
|
|
10620
|
+
await mkdir2(dirname9(path25), { recursive: true });
|
|
10621
|
+
await writeFile3(path25, JSON.stringify(state, null, 2), "utf8");
|
|
10532
10622
|
}
|
|
10533
|
-
async function readNewBytes(
|
|
10534
|
-
const st = await stat2(
|
|
10623
|
+
async function readNewBytes(path25, offset, max) {
|
|
10624
|
+
const st = await stat2(path25);
|
|
10535
10625
|
if (st.size <= offset) return { buf: Buffer.alloc(0), size: st.size };
|
|
10536
10626
|
const length = Math.min(st.size - offset, max);
|
|
10537
|
-
const fh = await open(
|
|
10627
|
+
const fh = await open(path25, "r");
|
|
10538
10628
|
try {
|
|
10539
10629
|
const buf = Buffer.alloc(length);
|
|
10540
10630
|
const { bytesRead } = await fh.read(buf, 0, length, offset);
|
|
@@ -10822,6 +10912,16 @@ function makeLoopTicks({
|
|
|
10822
10912
|
getAgentAvailability = () => null,
|
|
10823
10913
|
// Cached account-usage provider (account-usage.mjs); [] omits the field.
|
|
10824
10914
|
getAccountUsage = () => [],
|
|
10915
|
+
// Host-preflight verdict (host-preflight.mjs) — why this host is or is not
|
|
10916
|
+
// claiming. GATED OFF BY DEFAULT and that is deliberate: the plane's
|
|
10917
|
+
// heartbeat input schema is `.strict()`, so sending `host_health` before
|
|
10918
|
+
// that schema accepts it would 400 EVERY beat and take the machine off the
|
|
10919
|
+
// fleet — the 2026-07-25 `version` outage, and it would fire hardest on
|
|
10920
|
+
// exactly the broken hosts this field describes. Flip
|
|
10921
|
+
// VO_HOST_HEALTH_HEARTBEAT=1 once runner-host-health-v1 is wired into
|
|
10922
|
+
// runner-heartbeat-v1.ts and deployed. The claim GATE does not depend on
|
|
10923
|
+
// this: a blocked host stops claiming either way.
|
|
10924
|
+
getHostHealth = () => null,
|
|
10825
10925
|
// Injectable for tests; default to the real scheduler + wall clock.
|
|
10826
10926
|
runResumeScheduler = runScheduler,
|
|
10827
10927
|
now: nowFn = () => Date.now(),
|
|
@@ -10922,10 +11022,17 @@ function makeLoopTicks({
|
|
|
10922
11022
|
...servedOperators.length > 0 ? { servedOperators } : {},
|
|
10923
11023
|
...Array.isArray(availableAgents) && availableAgents.length > 0 ? { availableAgents } : {},
|
|
10924
11024
|
...Array.isArray(accountUsage) && accountUsage.length > 0 ? { accountUsage } : {},
|
|
11025
|
+
...env2.VO_HOST_HEALTH_HEARTBEAT === "1" && getHostHealth() ? { hostHealth: getHostHealth() } : {},
|
|
10925
11026
|
uptimeSec: Math.floor(process.uptime()),
|
|
10926
11027
|
activeTasks: getActive(),
|
|
10927
11028
|
maxConcurrency: cfg.maxConcurrency,
|
|
10928
11029
|
supportedTaskKinds: supportedTaskKindsFor(availableAgents),
|
|
11030
|
+
// Attested EVERY beat, from the daemon's own env + keychain, so the plane's
|
|
11031
|
+
// runner-api-billing-gate can refuse a metered machine BEFORE it claims.
|
|
11032
|
+
// Never throws (see resolveRunnerAttestedAuthSource) — a heartbeat that
|
|
11033
|
+
// 500s on its own attestation would take the host off the fleet, which is
|
|
11034
|
+
// strictly worse than the spend it was trying to prevent.
|
|
11035
|
+
agentAuthSource: resolveRunnerAttestedAuthSource(env2),
|
|
10929
11036
|
...capacityFields,
|
|
10930
11037
|
...localModelFields,
|
|
10931
11038
|
...preparedJobFields
|
|
@@ -10957,6 +11064,7 @@ var init_loop_ticks = __esm({
|
|
|
10957
11064
|
init_session_spool_forwarder();
|
|
10958
11065
|
init_rate_limit_resume_scheduler();
|
|
10959
11066
|
init_telemetry_forwarder();
|
|
11067
|
+
init_agent_auth_attestation();
|
|
10960
11068
|
HEARTBEAT_MS = 6e4;
|
|
10961
11069
|
DEFAULT_RESUME_SCHEDULE_SEC = 300;
|
|
10962
11070
|
RUNNER_SUPPORTED_TASK_KINDS = Object.freeze(["code", "inference", "skill"]);
|
|
@@ -11634,10 +11742,10 @@ function formatShadowLogLine(record) {
|
|
|
11634
11742
|
const loud = record.unexplained_fields?.length ? "!! " : "";
|
|
11635
11743
|
return `${loud}[prepared-job-shadow] ${parts.join(" ")}`;
|
|
11636
11744
|
}
|
|
11637
|
-
function appendShadowRecord(record, { path:
|
|
11745
|
+
function appendShadowRecord(record, { path: path25 = PREPARED_JOB_SHADOW_SINK, append = appendFileSync, mkdir: mkdir5 = mkdirSync8 } = {}) {
|
|
11638
11746
|
try {
|
|
11639
|
-
mkdir5(dirname10(
|
|
11640
|
-
append(
|
|
11747
|
+
mkdir5(dirname10(path25), { recursive: true });
|
|
11748
|
+
append(path25, `${JSON.stringify(record)}
|
|
11641
11749
|
`, "utf8");
|
|
11642
11750
|
return true;
|
|
11643
11751
|
} catch {
|
|
@@ -13101,7 +13209,7 @@ function noteCiViaRest(log2) {
|
|
|
13101
13209
|
log2("watch: CI status read via REST check-runs/status (gh's GraphQL rollup needs actions:read for checkSuite.workflowRun, which the read scope does not carry)");
|
|
13102
13210
|
}
|
|
13103
13211
|
async function readCommitCiViaRest(repo, sha, { run, env: env2 }) {
|
|
13104
|
-
const api = async (
|
|
13212
|
+
const api = async (path25) => JSON.parse(await run("gh", ["api", path25], { timeout: 3e4, env: env2 }) || "{}");
|
|
13105
13213
|
const rollup = [];
|
|
13106
13214
|
let total = null;
|
|
13107
13215
|
for (let page = 1; page <= REST_MAX_PAGES && (total === null || rollup.length < total); page += 1) {
|
|
@@ -13765,9 +13873,9 @@ function buildControlHandler({ getStatus, requestStop, allowedOrigin }) {
|
|
|
13765
13873
|
res.end();
|
|
13766
13874
|
return;
|
|
13767
13875
|
}
|
|
13768
|
-
const
|
|
13876
|
+
const path25 = String(req.url || "").split("?")[0];
|
|
13769
13877
|
res.setHeader("content-type", "application/json");
|
|
13770
|
-
if (req.method === "GET" &&
|
|
13878
|
+
if (req.method === "GET" && path25 === "/status") {
|
|
13771
13879
|
let status;
|
|
13772
13880
|
try {
|
|
13773
13881
|
status = getStatus();
|
|
@@ -13778,7 +13886,7 @@ function buildControlHandler({ getStatus, requestStop, allowedOrigin }) {
|
|
|
13778
13886
|
res.end(JSON.stringify({ ok: true, ...status }));
|
|
13779
13887
|
return;
|
|
13780
13888
|
}
|
|
13781
|
-
if (req.method === "POST" &&
|
|
13889
|
+
if (req.method === "POST" && path25 === "/stop") {
|
|
13782
13890
|
if (!isControlOriginAllowed(req.headers.origin, allowedOrigin) || !req.headers["x-vo-control"]) {
|
|
13783
13891
|
res.statusCode = 403;
|
|
13784
13892
|
res.end(JSON.stringify({ ok: false, error: "forbidden" }));
|
|
@@ -14813,9 +14921,9 @@ function operatorTierToRung(tier, difficulty, thresholds) {
|
|
|
14813
14921
|
if (tier === "best" && difficulty >= thresholds.rungBounds.R5) return "R5";
|
|
14814
14922
|
return base;
|
|
14815
14923
|
}
|
|
14816
|
-
function readCodexModelsCache({ path:
|
|
14924
|
+
function readCodexModelsCache({ path: path25 = DEFAULT_CODEX_MODELS_CACHE, read = readFileSync9 } = {}) {
|
|
14817
14925
|
try {
|
|
14818
|
-
const parsed = JSON.parse(read(
|
|
14926
|
+
const parsed = JSON.parse(read(path25, "utf8"));
|
|
14819
14927
|
return Array.isArray(parsed?.models) ? parsed : null;
|
|
14820
14928
|
} catch {
|
|
14821
14929
|
return null;
|
|
@@ -15072,15 +15180,15 @@ function formatDecisionReason(decision, maxLen = 480) {
|
|
|
15072
15180
|
const s = `[${decision.routerVersion}] ${decision.taskClass} d=${decision.difficulty} c=${decision.confidence} \u2192 ${decision.rung}/${decision.tier}${decision.effort ? ` effort=${decision.effort}` : ""} turns=${decision.maxTurns} $${decision.maxBudgetUsd}${decision.flags.length ? ` [${decision.flags.join(",")}]` : ""} :: ${decision.reasons.join("; ")}`;
|
|
15073
15181
|
return s.length > maxLen ? `${s.slice(0, maxLen - 1)}\u2026` : s;
|
|
15074
15182
|
}
|
|
15075
|
-
function appendDecisionFallback(decision, { path:
|
|
15183
|
+
function appendDecisionFallback(decision, { path: path25 = DECISION_FALLBACK_PATH, append = appendFileSync2, mkdir: mkdir5 = mkdirSync9, task, thresholds, roleCostInputs } = {}) {
|
|
15076
15184
|
try {
|
|
15077
|
-
mkdir5(dirname12(
|
|
15078
|
-
append(
|
|
15185
|
+
mkdir5(dirname12(path25), { recursive: true });
|
|
15186
|
+
append(path25, `${JSON.stringify(decision)}
|
|
15079
15187
|
`, "utf8");
|
|
15080
15188
|
if (isRouterDecision(decision)) {
|
|
15081
15189
|
try {
|
|
15082
15190
|
const records = buildShadowRecords({ decision, task, thresholds: thresholds || loadThresholds(), roleCostInputs });
|
|
15083
|
-
for (const record of records) append(
|
|
15191
|
+
for (const record of records) append(path25, `${JSON.stringify(record)}
|
|
15084
15192
|
`, "utf8");
|
|
15085
15193
|
} catch {
|
|
15086
15194
|
}
|
|
@@ -15612,6 +15720,17 @@ var init_agent_process_env = __esm({
|
|
|
15612
15720
|
// escape hatch inert — an operator who set it still got the subscription.
|
|
15613
15721
|
"VO_RUNNER_PREFER_KEY",
|
|
15614
15722
|
"VO_RUNNER_CLAUDE_PREFER_KEY",
|
|
15723
|
+
// The per-MACHINE metered-billing opt-in (RUNNER_ALLOW_API_BILLING_ENV in
|
|
15724
|
+
// agent-auth-attestation.mjs). Listed for the SAME reason as the line above:
|
|
15725
|
+
// applyApiBillingPolicy reads it off the env handed to runAgentTask, which is
|
|
15726
|
+
// this function's output — omit it and the operator's opt-in is stripped before
|
|
15727
|
+
// the policy ever sees it, so `permitted` is permanently false. That fails in
|
|
15728
|
+
// the SAFE direction (login), unlike #9242, but it is still an inert flag.
|
|
15729
|
+
//
|
|
15730
|
+
// A boolean flag, not a credential: it grants nothing on its own. The keys it
|
|
15731
|
+
// gates (ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN / CLAUDE_API_KEY) stay absent
|
|
15732
|
+
// from this allow-list and are re-injected, if at all, only by applyAuthEnv.
|
|
15733
|
+
"VO_RUNNER_ALLOW_API_BILLING",
|
|
15615
15734
|
// The swarm tier binding (SWARM_TIER_BINDING_ENV in
|
|
15616
15735
|
// packages/vo-mcp/src/swarm/tier-binding.ts). A fan-out resolves its billing
|
|
15617
15736
|
// tier ONCE at admission and exports the binding so every subagent inherits
|
|
@@ -16592,7 +16711,7 @@ async function processSkillTask(client, task, cfg, {
|
|
|
16592
16711
|
resolveDispatch = resolveEffortDispatch,
|
|
16593
16712
|
checkSkillCapability = ({ runner, bin, env: capabilityEnv }) => typeof runner.checkSkillCapability === "function" ? runner.checkSkillCapability({ bin, env: capabilityEnv }) : { compatible: false, reason: "resolved runner has no restricted-skill capability probe" },
|
|
16594
16713
|
createScratch = () => mkdtemp2(join19(tmpdir(), "algohq-skill-task-")),
|
|
16595
|
-
removeScratch = (
|
|
16714
|
+
removeScratch = (path25) => rm2(path25, { recursive: true, force: true })
|
|
16596
16715
|
} = {}) {
|
|
16597
16716
|
const id = task.code_task_id;
|
|
16598
16717
|
let run = null;
|
|
@@ -17099,6 +17218,174 @@ var init_outcome_commit = __esm({
|
|
|
17099
17218
|
}
|
|
17100
17219
|
});
|
|
17101
17220
|
|
|
17221
|
+
// ../../scripts/virtual-office/code-runner/capped-run-classification.mjs
|
|
17222
|
+
function isCappedRunSummary(summary) {
|
|
17223
|
+
return CAPPED_RESULT_SUBTYPES.includes(String(summary ?? "").trim());
|
|
17224
|
+
}
|
|
17225
|
+
function agentRequestedContinuation(run = {}) {
|
|
17226
|
+
return `${run?.summary ?? ""}
|
|
17227
|
+
${run?.lastAgentMessage ?? ""}`.includes(PARTIAL_PR_CONTINUATION_MARKER);
|
|
17228
|
+
}
|
|
17229
|
+
function leftoverWorkAfterPublish(changedFiles) {
|
|
17230
|
+
if (!Array.isArray(changedFiles)) return null;
|
|
17231
|
+
return changedFiles.filter((file) => file && !isAgentScratch(String(file)));
|
|
17232
|
+
}
|
|
17233
|
+
function classifyCappedRunOutcome({
|
|
17234
|
+
partial = false,
|
|
17235
|
+
run = {},
|
|
17236
|
+
prPublished = false,
|
|
17237
|
+
prCarriesCommits = false,
|
|
17238
|
+
changedFilesAfterPublish = null,
|
|
17239
|
+
rateLimited = false
|
|
17240
|
+
} = {}) {
|
|
17241
|
+
const verdict = (reason, extra = {}) => ({
|
|
17242
|
+
needsContinuation: true,
|
|
17243
|
+
complete: false,
|
|
17244
|
+
reason,
|
|
17245
|
+
note: null,
|
|
17246
|
+
leftover: [],
|
|
17247
|
+
...extra
|
|
17248
|
+
});
|
|
17249
|
+
if (!partial) {
|
|
17250
|
+
return { needsContinuation: false, complete: true, reason: "clean_finish", note: null, leftover: [] };
|
|
17251
|
+
}
|
|
17252
|
+
if (agentRequestedContinuation(run)) return verdict("agent_requested_continuation");
|
|
17253
|
+
if (rateLimited) return verdict("rate_limited_resume_pending");
|
|
17254
|
+
if (run?.failure) return verdict("runner_failure");
|
|
17255
|
+
if (run?.gateFailureNote) return verdict("completion_gate_not_passing");
|
|
17256
|
+
if (!isCappedRunSummary(run?.summary)) return verdict("not_a_cap_stop");
|
|
17257
|
+
if (!prPublished) return verdict("no_published_pr");
|
|
17258
|
+
if (!prCarriesCommits) return verdict("published_pr_carries_no_commits");
|
|
17259
|
+
const leftover = leftoverWorkAfterPublish(changedFilesAfterPublish);
|
|
17260
|
+
if (leftover === null) return verdict("worktree_state_unknown");
|
|
17261
|
+
if (leftover.length > 0) return verdict("uncommitted_work_remains", { leftover });
|
|
17262
|
+
return {
|
|
17263
|
+
needsContinuation: false,
|
|
17264
|
+
complete: true,
|
|
17265
|
+
reason: "cap_after_publish",
|
|
17266
|
+
note: CAP_AFTER_PUBLISH_NOTE,
|
|
17267
|
+
leftover: []
|
|
17268
|
+
};
|
|
17269
|
+
}
|
|
17270
|
+
var CAP_AFTER_PUBLISH_NOTE;
|
|
17271
|
+
var init_capped_run_classification = __esm({
|
|
17272
|
+
"../../scripts/virtual-office/code-runner/capped-run-classification.mjs"() {
|
|
17273
|
+
"use strict";
|
|
17274
|
+
init_claude_result_event();
|
|
17275
|
+
init_partial_pr_continuation();
|
|
17276
|
+
init_publish_file_state();
|
|
17277
|
+
CAP_AFTER_PUBLISH_NOTE = "cap reached after publish";
|
|
17278
|
+
}
|
|
17279
|
+
});
|
|
17280
|
+
|
|
17281
|
+
// ../../scripts/virtual-office/code-runner/capped-run-publication-repair.mjs
|
|
17282
|
+
function stripPartialTitlePrefix(title) {
|
|
17283
|
+
const text = String(title ?? "").trim();
|
|
17284
|
+
if (!PARTIAL_TITLE_PREFIX_RE.test(text)) return null;
|
|
17285
|
+
const stripped = text.replace(PARTIAL_TITLE_PREFIX_RE, "").trim();
|
|
17286
|
+
return stripped || null;
|
|
17287
|
+
}
|
|
17288
|
+
async function measureWorktreeChanges(worktreeDir, runCommand) {
|
|
17289
|
+
try {
|
|
17290
|
+
const out = await runCommand(
|
|
17291
|
+
"git",
|
|
17292
|
+
["-c", "core.quotepath=false", "status", "--porcelain", "-z"],
|
|
17293
|
+
worktreeDir,
|
|
17294
|
+
{ timeout: 6e4, raw: true }
|
|
17295
|
+
);
|
|
17296
|
+
return parsePorcelainZ(out);
|
|
17297
|
+
} catch {
|
|
17298
|
+
return null;
|
|
17299
|
+
}
|
|
17300
|
+
}
|
|
17301
|
+
async function branchCarriesCommits(worktreeDir, branch, runCommand, base = "origin/main") {
|
|
17302
|
+
if (!branch) return false;
|
|
17303
|
+
try {
|
|
17304
|
+
const out = await runCommand(
|
|
17305
|
+
"git",
|
|
17306
|
+
["rev-list", "--count", `${base}..${branch}`],
|
|
17307
|
+
worktreeDir,
|
|
17308
|
+
{ timeout: 6e4 }
|
|
17309
|
+
);
|
|
17310
|
+
return Number.parseInt(String(out).trim(), 10) > 0;
|
|
17311
|
+
} catch {
|
|
17312
|
+
return false;
|
|
17313
|
+
}
|
|
17314
|
+
}
|
|
17315
|
+
async function resolveCappedRunVerdict({
|
|
17316
|
+
partial,
|
|
17317
|
+
run,
|
|
17318
|
+
pr,
|
|
17319
|
+
worktreeDir,
|
|
17320
|
+
runCommand,
|
|
17321
|
+
rateLimited = false
|
|
17322
|
+
}) {
|
|
17323
|
+
const optimistic = classifyCappedRunOutcome({
|
|
17324
|
+
partial,
|
|
17325
|
+
run,
|
|
17326
|
+
rateLimited,
|
|
17327
|
+
prPublished: true,
|
|
17328
|
+
prCarriesCommits: true,
|
|
17329
|
+
changedFilesAfterPublish: []
|
|
17330
|
+
});
|
|
17331
|
+
if (optimistic.reason !== "cap_after_publish") return optimistic;
|
|
17332
|
+
const prPublished = Number.isInteger(pr?.prNumber) && pr.prNumber > 0;
|
|
17333
|
+
const [changedFilesAfterPublish, prCarriesCommits] = await Promise.all([
|
|
17334
|
+
measureWorktreeChanges(worktreeDir, runCommand),
|
|
17335
|
+
prPublished ? branchCarriesCommits(worktreeDir, pr.branch, runCommand) : Promise.resolve(false)
|
|
17336
|
+
]);
|
|
17337
|
+
return classifyCappedRunOutcome({
|
|
17338
|
+
partial: true,
|
|
17339
|
+
run,
|
|
17340
|
+
prPublished,
|
|
17341
|
+
prCarriesCommits,
|
|
17342
|
+
changedFilesAfterPublish,
|
|
17343
|
+
rateLimited
|
|
17344
|
+
});
|
|
17345
|
+
}
|
|
17346
|
+
async function repairCompletePrPresentation({
|
|
17347
|
+
pr,
|
|
17348
|
+
worktreeDir,
|
|
17349
|
+
githubToken = null,
|
|
17350
|
+
runCommand,
|
|
17351
|
+
log: log2 = () => {
|
|
17352
|
+
},
|
|
17353
|
+
promoteReady = true
|
|
17354
|
+
}) {
|
|
17355
|
+
const env2 = githubToken ? installationTokenEnv(githubToken) : void 0;
|
|
17356
|
+
const result = { retitled: null, markedReady: false };
|
|
17357
|
+
try {
|
|
17358
|
+
const raw = await runCommand("gh", ["pr", "view", String(pr.prNumber), "--json", "title"], worktreeDir, { env: env2, timeout: 6e4 });
|
|
17359
|
+
const stripped = stripPartialTitlePrefix(JSON.parse(String(raw || "{}"))?.title);
|
|
17360
|
+
if (stripped) {
|
|
17361
|
+
await runCommand("gh", ["pr", "edit", String(pr.prNumber), "--title", stripped], worktreeDir, { env: env2, timeout: 6e4 });
|
|
17362
|
+
result.retitled = stripped;
|
|
17363
|
+
log2(`task PR #${pr.prNumber}: dropped the PARTIAL title prefix \u2014 the cap landed after publication`);
|
|
17364
|
+
}
|
|
17365
|
+
} catch (error) {
|
|
17366
|
+
log2(`PR #${pr.prNumber}: could not drop the PARTIAL title prefix (${String(error?.message || error).slice(0, 160)}); outcome is still recorded complete`);
|
|
17367
|
+
}
|
|
17368
|
+
if (!promoteReady) return result;
|
|
17369
|
+
try {
|
|
17370
|
+
await markExistingPrReady(worktreeDir, pr.prNumber, { env: env2, runFn: runCommand });
|
|
17371
|
+
result.markedReady = true;
|
|
17372
|
+
} catch (error) {
|
|
17373
|
+
log2(`PR #${pr.prNumber}: could not promote the salvage draft to ready (${String(error?.message || error).slice(0, 160)})`);
|
|
17374
|
+
}
|
|
17375
|
+
return result;
|
|
17376
|
+
}
|
|
17377
|
+
var PARTIAL_TITLE_PREFIX_RE;
|
|
17378
|
+
var init_capped_run_publication_repair = __esm({
|
|
17379
|
+
"../../scripts/virtual-office/code-runner/capped-run-publication-repair.mjs"() {
|
|
17380
|
+
"use strict";
|
|
17381
|
+
init_publish();
|
|
17382
|
+
init_publish_file_state();
|
|
17383
|
+
init_existing_pr_publication();
|
|
17384
|
+
init_capped_run_classification();
|
|
17385
|
+
PARTIAL_TITLE_PREFIX_RE = /^⚠ PARTIAL \([^)]*\)\s*—\s*/u;
|
|
17386
|
+
}
|
|
17387
|
+
});
|
|
17388
|
+
|
|
17102
17389
|
// ../../scripts/virtual-office/code-runner/publication-outcome.mjs
|
|
17103
17390
|
async function closeCancelledReplacementPr({
|
|
17104
17391
|
pr,
|
|
@@ -17225,6 +17512,20 @@ async function finalizePublishedPr({
|
|
|
17225
17512
|
const runnerFailureRecord = serializedRunnerFailure ? JSON.parse(serializedRunnerFailure) : null;
|
|
17226
17513
|
const runnerFailureMessage = runnerFailureRecord ? `blocked by ${runnerFailureRecord.code}; operator review required: ${runnerFailureRecord.operator_next_action}` : "";
|
|
17227
17514
|
const fixDispatchGuard = overlapBlocked || runnerFailure ? { allowFixDispatch: false } : {};
|
|
17515
|
+
const exec = runCommand ?? defaultRunCommand4;
|
|
17516
|
+
const capVerdict = await resolveCappedRunVerdict({
|
|
17517
|
+
partial,
|
|
17518
|
+
run,
|
|
17519
|
+
pr,
|
|
17520
|
+
worktreeDir,
|
|
17521
|
+
runCommand: exec,
|
|
17522
|
+
rateLimited: Boolean(rateLimitResume)
|
|
17523
|
+
});
|
|
17524
|
+
const reclassified = partial && capVerdict.reason === "cap_after_publish";
|
|
17525
|
+
const effectivePartial = partial && !reclassified;
|
|
17526
|
+
if (partial) {
|
|
17527
|
+
log2(reclassified ? `task ${id}: ${CAP_AFTER_PUBLISH_NOTE} with a clean worktree \u2014 recording pr_opened, not needs_continuation` : `task ${id}: partial draft stands (${capVerdict.reason})`);
|
|
17528
|
+
}
|
|
17228
17529
|
let resumeQueued = false;
|
|
17229
17530
|
if (cfg.watchEnabled) {
|
|
17230
17531
|
try {
|
|
@@ -17235,8 +17536,8 @@ async function finalizePublishedPr({
|
|
|
17235
17536
|
taskId: id,
|
|
17236
17537
|
operatorId: task.operator_id,
|
|
17237
17538
|
tenantId: task.tenant_id,
|
|
17238
|
-
needsContinuation:
|
|
17239
|
-
continuationExhausted:
|
|
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
|
-
|
|
17604
|
+
if (reclassified) {
|
|
17605
|
+
const report = String(run.lastAgentMessage || run.summary || "").trim();
|
|
17606
|
+
return `${prefix}${`${CAP_AFTER_PUBLISH_NOTE}
|
|
17607
|
+
${report}`.slice(0, room)}`;
|
|
17608
|
+
}
|
|
17609
|
+
return `${prefix}${effectivePartial || runnerFailure ? partialPrContinuationResult(run, room, rateLimitResume ? "rate_limited" : null) : String(run.summary).slice(0, room)}`;
|
|
17304
17610
|
})(),
|
|
17305
17611
|
...runOutcomePatch(run),
|
|
17306
17612
|
...terminalLedgerPatch(run)
|
|
@@ -17328,6 +17634,16 @@ async function finalizePublishedPr({
|
|
|
17328
17634
|
});
|
|
17329
17635
|
return posted.cancelled;
|
|
17330
17636
|
}
|
|
17637
|
+
if (reclassified) {
|
|
17638
|
+
await repairCompletePrPresentation({
|
|
17639
|
+
pr,
|
|
17640
|
+
worktreeDir,
|
|
17641
|
+
githubToken,
|
|
17642
|
+
runCommand: exec,
|
|
17643
|
+
log: log2,
|
|
17644
|
+
promoteReady: !overlapBlocked && !runnerFailure
|
|
17645
|
+
});
|
|
17646
|
+
}
|
|
17331
17647
|
log2(`task ${id} \u2192 PR ${pr.prUrl}`);
|
|
17332
17648
|
return false;
|
|
17333
17649
|
}
|
|
@@ -17345,6 +17661,8 @@ var init_publication_outcome = __esm({
|
|
|
17345
17661
|
init_terminal_delivery();
|
|
17346
17662
|
init_rate_limit_resume();
|
|
17347
17663
|
init_error_message();
|
|
17664
|
+
init_capped_run_classification();
|
|
17665
|
+
init_capped_run_publication_repair();
|
|
17348
17666
|
defaultRunCommand4 = (cmd, args, cwd, opts = {}) => runProcess2(cmd, args, { cwd, ...opts });
|
|
17349
17667
|
}
|
|
17350
17668
|
});
|
|
@@ -17662,6 +17980,290 @@ var init_recovery_ledger = __esm({
|
|
|
17662
17980
|
}
|
|
17663
17981
|
});
|
|
17664
17982
|
|
|
17983
|
+
// ../../scripts/virtual-office/code-runner/host-preflight.mjs
|
|
17984
|
+
function joinOutput(observation = {}) {
|
|
17985
|
+
return [observation.stdout, observation.stderr, observation.message].map((part) => String(part ?? "")).join("\n");
|
|
17986
|
+
}
|
|
17987
|
+
function classifyGitFetchObservation(observation = {}) {
|
|
17988
|
+
const text = joinOutput(observation);
|
|
17989
|
+
if (GIT_AUTH_RE.test(text)) return GIT_AUTH_REQUIRED;
|
|
17990
|
+
if (GIT_NETWORK_RE.test(text)) return GIT_NETWORK;
|
|
17991
|
+
return GIT_OTHER;
|
|
17992
|
+
}
|
|
17993
|
+
function classifyLoginPing(observation = {}) {
|
|
17994
|
+
const text = joinOutput(observation);
|
|
17995
|
+
if (LOGIN_EXPIRED_RE.test(text)) return LOGIN_EXPIRED;
|
|
17996
|
+
if (RATE_LIMITED_RE.test(text)) return RATE_LIMITED;
|
|
17997
|
+
if (observation.ok === true && String(observation.stdout ?? "").trim().length > 0) return OK;
|
|
17998
|
+
return UNKNOWN;
|
|
17999
|
+
}
|
|
18000
|
+
function reportingLines(raw) {
|
|
18001
|
+
return String(raw ?? "").split(/\r?\n/u).filter((line) => line.trim() !== "" && !NON_REPORTING_LINE.test(line)).map((line) => line.trim()).filter((line) => !/[`'"]$/u.test(line));
|
|
18002
|
+
}
|
|
18003
|
+
function matchSignature(line, signatures) {
|
|
18004
|
+
for (const signature of signatures) {
|
|
18005
|
+
if (signature.pattern.test(line)) return signature;
|
|
18006
|
+
}
|
|
18007
|
+
return null;
|
|
18008
|
+
}
|
|
18009
|
+
function classifyHostFailure(text) {
|
|
18010
|
+
for (const line of reportingLines(text)) {
|
|
18011
|
+
const login = matchSignature(line, LOGIN_SIGNATURES);
|
|
18012
|
+
if (login) return { kind: "login_expired", signature: login.id, line };
|
|
18013
|
+
const git4 = matchSignature(line, GIT_SIGNATURES);
|
|
18014
|
+
if (git4) return { kind: git4.kind, signature: git4.id, line };
|
|
18015
|
+
}
|
|
18016
|
+
return null;
|
|
18017
|
+
}
|
|
18018
|
+
function classifyHostFailureSignature(text) {
|
|
18019
|
+
const value = String(text ?? "");
|
|
18020
|
+
if (!value.trim()) return null;
|
|
18021
|
+
const tokenMatch = new RegExp(`${HOST_ENV_FAILURE_TOKEN}:(${LOGIN_EXPIRED}|${GIT_AUTH_REQUIRED})`, "u").exec(value);
|
|
18022
|
+
if (tokenMatch) return tokenMatch[1];
|
|
18023
|
+
const found = classifyHostFailure(value);
|
|
18024
|
+
if (!found) return null;
|
|
18025
|
+
if (found.kind === "login_expired") return LOGIN_EXPIRED;
|
|
18026
|
+
if (found.kind === "git_credentials") return GIT_AUTH_REQUIRED;
|
|
18027
|
+
return null;
|
|
18028
|
+
}
|
|
18029
|
+
function hostFailureResultText(reason, detail = "") {
|
|
18030
|
+
const trimmed = String(detail ?? "").replace(/\s+/gu, " ").trim().slice(0, 400);
|
|
18031
|
+
return `${HOST_ENV_FAILURE_TOKEN}:${reason} \u2014 ${hostHealthRemedy(reason)}${trimmed ? ` [${trimmed}]` : ""}`;
|
|
18032
|
+
}
|
|
18033
|
+
function hostHealthRemedy(reason) {
|
|
18034
|
+
switch (reason) {
|
|
18035
|
+
case LOGIN_EXPIRED:
|
|
18036
|
+
return "login expired: run claude auth login on this host";
|
|
18037
|
+
case RATE_LIMITED:
|
|
18038
|
+
return "agent account is rate limited: work resumes when the window resets";
|
|
18039
|
+
case GIT_AUTH_REQUIRED:
|
|
18040
|
+
return "git auth required: refresh the git credential on this host";
|
|
18041
|
+
case GIT_NETWORK:
|
|
18042
|
+
return "git cannot reach the remote: check this host network";
|
|
18043
|
+
case GIT_OTHER:
|
|
18044
|
+
return "git fetch failed on this host: see the runner log";
|
|
18045
|
+
case UNKNOWN:
|
|
18046
|
+
return "host check could not be completed";
|
|
18047
|
+
default:
|
|
18048
|
+
return "host is ready";
|
|
18049
|
+
}
|
|
18050
|
+
}
|
|
18051
|
+
async function capture(run, cmd, args, options) {
|
|
18052
|
+
try {
|
|
18053
|
+
const stdout = await run(cmd, args, options);
|
|
18054
|
+
return { ok: true, status: 0, stdout: String(stdout ?? ""), stderr: "", timedOut: false };
|
|
18055
|
+
} catch (error) {
|
|
18056
|
+
return {
|
|
18057
|
+
ok: false,
|
|
18058
|
+
status: typeof error?.status === "number" ? error.status : null,
|
|
18059
|
+
stdout: String(error?.stdout ?? ""),
|
|
18060
|
+
stderr: String(error?.stderr ?? ""),
|
|
18061
|
+
message: String(error?.message ?? error ?? ""),
|
|
18062
|
+
timedOut: error?.code === "ETIMEDOUT"
|
|
18063
|
+
};
|
|
18064
|
+
}
|
|
18065
|
+
}
|
|
18066
|
+
function nonInteractiveGitEnv(env2 = process.env) {
|
|
18067
|
+
return {
|
|
18068
|
+
...env2,
|
|
18069
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
18070
|
+
GCM_INTERACTIVE: "never",
|
|
18071
|
+
GIT_OPTIONAL_LOCKS: "0"
|
|
18072
|
+
};
|
|
18073
|
+
}
|
|
18074
|
+
async function checkGitFetch({
|
|
18075
|
+
cwd,
|
|
18076
|
+
run,
|
|
18077
|
+
env: env2 = process.env,
|
|
18078
|
+
remote = "origin",
|
|
18079
|
+
branch = "main",
|
|
18080
|
+
timeoutMs = GIT_FETCH_TIMEOUT_MS
|
|
18081
|
+
} = {}) {
|
|
18082
|
+
if (typeof run !== "function" || !cwd) {
|
|
18083
|
+
return { status: UNKNOWN, detail: "no runner clone to fetch from" };
|
|
18084
|
+
}
|
|
18085
|
+
const observation = await capture(run, "git", ["fetch", remote, branch], {
|
|
18086
|
+
cwd,
|
|
18087
|
+
timeout: timeoutMs,
|
|
18088
|
+
env: nonInteractiveGitEnv(env2)
|
|
18089
|
+
});
|
|
18090
|
+
if (observation.ok) return { status: OK, detail: `git fetch ${remote} ${branch} succeeded` };
|
|
18091
|
+
const reason = classifyGitFetchObservation(observation);
|
|
18092
|
+
const raw = joinOutput(observation).replace(/\s+/gu, " ").trim();
|
|
18093
|
+
return {
|
|
18094
|
+
status: reason,
|
|
18095
|
+
detail: observation.timedOut ? `git fetch ${remote} ${branch} exceeded ${Math.round(timeoutMs / 1e3)}s${raw ? `: ${raw.slice(0, 200)}` : " with no output"}` : raw.slice(0, 300) || `git fetch ${remote} ${branch} failed`
|
|
18096
|
+
};
|
|
18097
|
+
}
|
|
18098
|
+
async function checkAgentLogin({
|
|
18099
|
+
run,
|
|
18100
|
+
bin = "claude",
|
|
18101
|
+
env: env2 = process.env,
|
|
18102
|
+
timeoutMs = LOGIN_PING_TIMEOUT_MS,
|
|
18103
|
+
prompt = "Reply with the single word: ready"
|
|
18104
|
+
} = {}) {
|
|
18105
|
+
if (typeof run !== "function") {
|
|
18106
|
+
return { status: UNKNOWN, detail: "no login probe available on this host" };
|
|
18107
|
+
}
|
|
18108
|
+
const observation = await capture(run, bin, ["-p", prompt, "--max-turns", "1"], {
|
|
18109
|
+
timeout: timeoutMs,
|
|
18110
|
+
env: env2,
|
|
18111
|
+
input: ""
|
|
18112
|
+
});
|
|
18113
|
+
const reason = classifyLoginPing(observation);
|
|
18114
|
+
if (reason === OK) return { status: OK, detail: "one-turn agent ping authenticated" };
|
|
18115
|
+
const raw = joinOutput(observation).replace(/\s+/gu, " ").trim();
|
|
18116
|
+
return {
|
|
18117
|
+
status: reason,
|
|
18118
|
+
detail: observation.timedOut ? `agent ping exceeded ${Math.round(timeoutMs / 1e3)}s` : raw.slice(0, 300) || "agent ping produced no output"
|
|
18119
|
+
};
|
|
18120
|
+
}
|
|
18121
|
+
function preflightAllowsClaim(health) {
|
|
18122
|
+
if (!health) return true;
|
|
18123
|
+
return !CLAIM_BLOCKING_GIT.has(health.git) && !CLAIM_BLOCKING_LOGIN.has(health.login);
|
|
18124
|
+
}
|
|
18125
|
+
function blockingReason(health) {
|
|
18126
|
+
if (!health) return null;
|
|
18127
|
+
if (CLAIM_BLOCKING_GIT.has(health.git)) return health.git;
|
|
18128
|
+
if (CLAIM_BLOCKING_LOGIN.has(health.login)) return health.login;
|
|
18129
|
+
return null;
|
|
18130
|
+
}
|
|
18131
|
+
async function runHostPreflight({
|
|
18132
|
+
cwd,
|
|
18133
|
+
runGit,
|
|
18134
|
+
runAgent,
|
|
18135
|
+
bin = "claude",
|
|
18136
|
+
agent = "claude",
|
|
18137
|
+
env: env2 = process.env,
|
|
18138
|
+
now = () => Date.now(),
|
|
18139
|
+
gitTimeoutMs = GIT_FETCH_TIMEOUT_MS,
|
|
18140
|
+
loginTimeoutMs = LOGIN_PING_TIMEOUT_MS
|
|
18141
|
+
} = {}) {
|
|
18142
|
+
const git4 = await checkGitFetch({ cwd, run: runGit, env: env2, timeoutMs: gitTimeoutMs });
|
|
18143
|
+
const login = CLAIM_BLOCKING_GIT.has(git4.status) ? { status: UNKNOWN, detail: "skipped: git check already blocked this host" } : agent !== "claude" ? { status: UNKNOWN, detail: `login ping not implemented for agent ${agent}` } : await checkAgentLogin({ run: runAgent, bin, env: env2, timeoutMs: loginTimeoutMs });
|
|
18144
|
+
const health = {
|
|
18145
|
+
git: git4.status,
|
|
18146
|
+
login: login.status,
|
|
18147
|
+
checked_at: new Date(now()).toISOString()
|
|
18148
|
+
};
|
|
18149
|
+
const blocking = blockingReason(health);
|
|
18150
|
+
const detail = blocking ? `${hostHealthRemedy(blocking)} \u2014 ${blocking === git4.status ? git4.detail : login.detail}` : login.status === RATE_LIMITED ? `${hostHealthRemedy(RATE_LIMITED)} \u2014 ${login.detail}` : `${git4.detail}; ${login.detail}`;
|
|
18151
|
+
return { ...health, detail: detail.slice(0, 500) };
|
|
18152
|
+
}
|
|
18153
|
+
function positiveSeconds(raw, fallbackMs) {
|
|
18154
|
+
const value = Number(raw);
|
|
18155
|
+
return Number.isFinite(value) && value > 0 ? value * 1e3 : fallbackMs;
|
|
18156
|
+
}
|
|
18157
|
+
function makeHostPreflightGate({
|
|
18158
|
+
preflight = runHostPreflight,
|
|
18159
|
+
now = () => Date.now(),
|
|
18160
|
+
env: env2 = process.env,
|
|
18161
|
+
log: log2 = () => {
|
|
18162
|
+
},
|
|
18163
|
+
...preflightOptions
|
|
18164
|
+
} = {}) {
|
|
18165
|
+
const okIntervalMs = positiveSeconds(env2.VO_HOST_PREFLIGHT_OK_SEC, PREFLIGHT_OK_INTERVAL_MS);
|
|
18166
|
+
const retryIntervalMs = positiveSeconds(env2.VO_HOST_PREFLIGHT_RETRY_SEC, PREFLIGHT_RETRY_MS);
|
|
18167
|
+
let health = null;
|
|
18168
|
+
let checkedAtMs = 0;
|
|
18169
|
+
let running = false;
|
|
18170
|
+
let lastLoggedReason;
|
|
18171
|
+
const intervalMs = () => preflightAllowsClaim(health) ? okIntervalMs : retryIntervalMs;
|
|
18172
|
+
return {
|
|
18173
|
+
/** ms of the next scheduled check; 0 before the first one has ever run. */
|
|
18174
|
+
nextCheckAt: () => checkedAtMs === 0 ? 0 : checkedAtMs + intervalMs(),
|
|
18175
|
+
get: () => health,
|
|
18176
|
+
allowClaim: () => preflightAllowsClaim(health),
|
|
18177
|
+
blockingReason: () => blockingReason(health),
|
|
18178
|
+
async ensure(nowMs = now()) {
|
|
18179
|
+
if (running) return health;
|
|
18180
|
+
if (checkedAtMs !== 0 && nowMs - checkedAtMs < intervalMs()) return health;
|
|
18181
|
+
running = true;
|
|
18182
|
+
try {
|
|
18183
|
+
health = await preflight({ ...preflightOptions, env: env2, now });
|
|
18184
|
+
} catch (error) {
|
|
18185
|
+
health = {
|
|
18186
|
+
git: UNKNOWN,
|
|
18187
|
+
login: UNKNOWN,
|
|
18188
|
+
checked_at: new Date(nowMs).toISOString(),
|
|
18189
|
+
detail: `host preflight could not run: ${String(error?.message ?? error).slice(0, 200)}`
|
|
18190
|
+
};
|
|
18191
|
+
log2(`host preflight error: ${String(error?.message ?? error)}`);
|
|
18192
|
+
} finally {
|
|
18193
|
+
checkedAtMs = nowMs;
|
|
18194
|
+
running = false;
|
|
18195
|
+
}
|
|
18196
|
+
const reason = blockingReason(health);
|
|
18197
|
+
if (reason !== lastLoggedReason) {
|
|
18198
|
+
lastLoggedReason = reason;
|
|
18199
|
+
log2(reason ? `host preflight BLOCKED claiming \u2014 ${hostHealthRemedy(reason)} (${health.detail})` : "host preflight passed \u2014 git fetch and agent login are healthy");
|
|
18200
|
+
}
|
|
18201
|
+
return health;
|
|
18202
|
+
}
|
|
18203
|
+
};
|
|
18204
|
+
}
|
|
18205
|
+
var DEFAULT_BLOCKED_RECHECK_MS, DEFAULT_HEALTHY_RECHECK_MS, MAX_RECHECK_MS, GIT_FETCH_TIMEOUT_MS, LOGIN_PING_TIMEOUT_MS, PREFLIGHT_RETRY_MS, PREFLIGHT_OK_INTERVAL_MS, GIT_AUTH_REQUIRED, GIT_NETWORK, GIT_OTHER, LOGIN_EXPIRED, RATE_LIMITED, OK, UNKNOWN, HOST_ENV_FAILURE_TOKEN, CLAIM_BLOCKING_GIT, CLAIM_BLOCKING_LOGIN, GIT_AUTH_RE, GIT_NETWORK_RE, LOGIN_EXPIRED_RE, RATE_LIMITED_RE, SEVERITY_PREFIX, LOGIN_SIGNATURES, GIT_SIGNATURES, NON_REPORTING_LINE;
|
|
18206
|
+
var init_host_preflight = __esm({
|
|
18207
|
+
"../../scripts/virtual-office/code-runner/host-preflight.mjs"() {
|
|
18208
|
+
"use strict";
|
|
18209
|
+
DEFAULT_BLOCKED_RECHECK_MS = 5 * 6e4;
|
|
18210
|
+
DEFAULT_HEALTHY_RECHECK_MS = 30 * 6e4;
|
|
18211
|
+
MAX_RECHECK_MS = 6 * 60 * 6e4;
|
|
18212
|
+
GIT_FETCH_TIMEOUT_MS = 6e4;
|
|
18213
|
+
LOGIN_PING_TIMEOUT_MS = 9e4;
|
|
18214
|
+
PREFLIGHT_RETRY_MS = DEFAULT_BLOCKED_RECHECK_MS;
|
|
18215
|
+
PREFLIGHT_OK_INTERVAL_MS = DEFAULT_HEALTHY_RECHECK_MS;
|
|
18216
|
+
GIT_AUTH_REQUIRED = "git_auth_required";
|
|
18217
|
+
GIT_NETWORK = "git_network";
|
|
18218
|
+
GIT_OTHER = "git_other";
|
|
18219
|
+
LOGIN_EXPIRED = "login_expired";
|
|
18220
|
+
RATE_LIMITED = "rate_limited";
|
|
18221
|
+
OK = "ok";
|
|
18222
|
+
UNKNOWN = "unknown";
|
|
18223
|
+
HOST_ENV_FAILURE_TOKEN = "HOST_ENV_FAILURE";
|
|
18224
|
+
CLAIM_BLOCKING_GIT = /* @__PURE__ */ new Set([GIT_AUTH_REQUIRED, GIT_NETWORK, GIT_OTHER]);
|
|
18225
|
+
CLAIM_BLOCKING_LOGIN = /* @__PURE__ */ new Set([LOGIN_EXPIRED]);
|
|
18226
|
+
GIT_AUTH_RE = /could not read (?:Username|Password)|terminal prompts disabled|Authentication failed|authentication failed|Permission denied \(publickey\)|Invalid username or password|Support for password authentication was removed|HTTP Basic: Access denied|\b401\b|\b403\b|Logon failed|credential(?:s)? (?:helper|manager)|no credentials|Repository not found/i;
|
|
18227
|
+
GIT_NETWORK_RE = /could not resolve host|couldn'?t resolve host|failed to connect|unable to access|connection (?:reset|refused|closed|timed out)|operation timed out|remote end hung up|early eof|rpc failed|recv failure|gnutls_handshake|ssl_read|\b50[234]\b|temporary failure|ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|ENETUNREACH|EHOSTUNREACH/i;
|
|
18228
|
+
LOGIN_EXPIRED_RE = /OAuth (?:session|token) (?:has )?expired|could not be refreshed|Failed to authenticate|Invalid API key|invalid[^.\n]{0,24}(?:credential|authentication)|authentication_error|\bunauthorized\b|\b401\b|not (?:logged in|authenticated)|please run [`'"]?claude (?:auth )?login|run [`'"]?claude (?:auth )?login/i;
|
|
18229
|
+
RATE_LIMITED_RE = /rate limit|rate[_-]limited|usage limit|\b429\b|too many requests|quota (?:exceeded|exhausted)|limit reached|out of (?:credits|quota)|resource[_ ]exhausted/i;
|
|
18230
|
+
SEVERITY_PREFIX = String.raw`(?:\[?(?:error|Error|ERROR)\]?:?\s+)?`;
|
|
18231
|
+
LOGIN_SIGNATURES = [
|
|
18232
|
+
{
|
|
18233
|
+
id: "oauth_session_expired",
|
|
18234
|
+
pattern: new RegExp(
|
|
18235
|
+
`^${SEVERITY_PREFIX}Failed to authenticate: OAuth session expired and could not be refreshed\\.?$`,
|
|
18236
|
+
"u"
|
|
18237
|
+
)
|
|
18238
|
+
},
|
|
18239
|
+
{
|
|
18240
|
+
id: "invalid_credentials",
|
|
18241
|
+
pattern: new RegExp(`^${SEVERITY_PREFIX}Invalid authentication credentials\\.?$`, "u")
|
|
18242
|
+
}
|
|
18243
|
+
];
|
|
18244
|
+
GIT_SIGNATURES = [
|
|
18245
|
+
{
|
|
18246
|
+
id: "git_fetch_timeout",
|
|
18247
|
+
kind: "git_unreachable",
|
|
18248
|
+
pattern: new RegExp(
|
|
18249
|
+
`^${SEVERITY_PREFIX}git fetch origin main failed: timed out\\.?$`,
|
|
18250
|
+
"u"
|
|
18251
|
+
)
|
|
18252
|
+
},
|
|
18253
|
+
{
|
|
18254
|
+
// fatal: could not read Username for 'https://github.com': terminal prompts disabled
|
|
18255
|
+
id: "git_credential_prompt",
|
|
18256
|
+
kind: "git_credentials",
|
|
18257
|
+
pattern: new RegExp(
|
|
18258
|
+
String.raw`^fatal:\s+could not read (?:Username|Password) for '[^']*':\s*` + String.raw`(?:terminal prompts disabled|No such device or address|Device not configured)\.?$`,
|
|
18259
|
+
"u"
|
|
18260
|
+
)
|
|
18261
|
+
}
|
|
18262
|
+
];
|
|
18263
|
+
NON_REPORTING_LINE = /^\s*(?:[+\->*#|]|\/\/|\/\*|\*\/|\d+[.)]\s|`{1,3}|'|")/u;
|
|
18264
|
+
}
|
|
18265
|
+
});
|
|
18266
|
+
|
|
17665
18267
|
// ../../scripts/virtual-office/code-runner/no-changes-terminal-status.mjs
|
|
17666
18268
|
function defaultRunCommand5(cmd, args, cwd, opts = {}) {
|
|
17667
18269
|
return runProcess2(cmd, args, { cwd, ...opts });
|
|
@@ -17683,6 +18285,14 @@ function isMaxTurnExhaustion(run = {}, maxTurns) {
|
|
|
17683
18285
|
}
|
|
17684
18286
|
function decideNoChangesTerminalStatus({ partial, run = {}, maxTurns } = {}) {
|
|
17685
18287
|
const structuredFailure = serializeRunnerFailure(run.failure);
|
|
18288
|
+
const hostFailure = classifyHostFailureSignature(run.summary) ?? classifyHostFailureSignature(structuredFailure);
|
|
18289
|
+
if (hostFailure) {
|
|
18290
|
+
return {
|
|
18291
|
+
status: "failed",
|
|
18292
|
+
message: `host cannot run work \u2014 ${hostHealthRemedy(hostFailure)}`,
|
|
18293
|
+
result: hostFailureResultText(hostFailure, run.summary).slice(0, RESULT_LIMIT)
|
|
18294
|
+
};
|
|
18295
|
+
}
|
|
17686
18296
|
if (!partial) {
|
|
17687
18297
|
if (structuredFailure) {
|
|
17688
18298
|
const failure = JSON.parse(structuredFailure);
|
|
@@ -17851,6 +18461,7 @@ var init_no_changes_terminal_status = __esm({
|
|
|
17851
18461
|
init_terminal_delivery();
|
|
17852
18462
|
init_error_message();
|
|
17853
18463
|
init_partial_pr_continuation();
|
|
18464
|
+
init_host_preflight();
|
|
17854
18465
|
RESULT_LIMIT = 2e3;
|
|
17855
18466
|
}
|
|
17856
18467
|
});
|
|
@@ -18047,6 +18658,72 @@ var init_task_worktree_preparation = __esm({
|
|
|
18047
18658
|
}
|
|
18048
18659
|
});
|
|
18049
18660
|
|
|
18661
|
+
// ../../scripts/virtual-office/code-runner/preflight-clone-dir.mjs
|
|
18662
|
+
import fs14 from "node:fs";
|
|
18663
|
+
import path24 from "node:path";
|
|
18664
|
+
function sanitize2(value, fallback) {
|
|
18665
|
+
const cleaned = String(value || "").trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
18666
|
+
return cleaned || fallback;
|
|
18667
|
+
}
|
|
18668
|
+
function cloneDirForServedRepo(repoSlug, clonesRootDir) {
|
|
18669
|
+
if (!clonesRootDir || !path24.isAbsolute(String(clonesRootDir))) return null;
|
|
18670
|
+
if (!repoSlug || !VALID_REPO_SLUG2.test(String(repoSlug))) return null;
|
|
18671
|
+
const [owner, name] = String(repoSlug).split("/");
|
|
18672
|
+
if (owner === "." || owner === ".." || name === "." || name === "..") return null;
|
|
18673
|
+
if (owner.startsWith("-") || name.startsWith("-")) return null;
|
|
18674
|
+
return path24.join(clonesRootDir, `${sanitize2(owner, "owner")}__${sanitize2(name, "repo")}`);
|
|
18675
|
+
}
|
|
18676
|
+
function resolvePreflightCwd(cfg = {}, env2 = process.env) {
|
|
18677
|
+
const clonesRoot2 = String(env2?.VO_CODE_RUNNER_CLONES_ROOT || "").trim();
|
|
18678
|
+
const served = Array.isArray(cfg?.servedRepos) ? cfg.servedRepos : [];
|
|
18679
|
+
for (const repo of served) {
|
|
18680
|
+
const dir = cloneDirForServedRepo(repo, clonesRoot2);
|
|
18681
|
+
if (dir) return dir;
|
|
18682
|
+
}
|
|
18683
|
+
return String(env2?.VO_CODE_RUNNER_REPO || "").trim() || process.cwd();
|
|
18684
|
+
}
|
|
18685
|
+
function isGitWorkingTree(dir, { exists = fs14.existsSync } = {}) {
|
|
18686
|
+
if (!dir) return false;
|
|
18687
|
+
try {
|
|
18688
|
+
return exists(path24.join(dir, ".git")) === true;
|
|
18689
|
+
} catch {
|
|
18690
|
+
return false;
|
|
18691
|
+
}
|
|
18692
|
+
}
|
|
18693
|
+
function describeMissingClone(health, dir) {
|
|
18694
|
+
const note = `no git clone at ${dir} yet \u2014 the first task creates it; git fetch not probed`;
|
|
18695
|
+
if (!health) return health;
|
|
18696
|
+
if (blockingReason(health)) return { ...health, git: UNKNOWN };
|
|
18697
|
+
return { ...health, git: UNKNOWN, detail: `${note}; ${health.detail ?? ""}`.slice(0, 500) };
|
|
18698
|
+
}
|
|
18699
|
+
function makeServedClonePreflight({
|
|
18700
|
+
cfg = {},
|
|
18701
|
+
preflight = runHostPreflight,
|
|
18702
|
+
exists = fs14.existsSync
|
|
18703
|
+
} = {}) {
|
|
18704
|
+
return async function servedClonePreflight(options = {}) {
|
|
18705
|
+
const env2 = options.env ?? process.env;
|
|
18706
|
+
const dir = resolvePreflightCwd(cfg, env2);
|
|
18707
|
+
if (!isGitWorkingTree(dir, { exists })) {
|
|
18708
|
+
return describeMissingClone(await preflight({ ...options, cwd: null }), dir);
|
|
18709
|
+
}
|
|
18710
|
+
const health = await preflight({ ...options, cwd: dir });
|
|
18711
|
+
if (health && blockingReason(health) === health.git && NOT_A_REPOSITORY_RE.test(String(health.detail ?? ""))) {
|
|
18712
|
+
return describeMissingClone(await preflight({ ...options, cwd: null }), dir);
|
|
18713
|
+
}
|
|
18714
|
+
return health;
|
|
18715
|
+
};
|
|
18716
|
+
}
|
|
18717
|
+
var VALID_REPO_SLUG2, NOT_A_REPOSITORY_RE;
|
|
18718
|
+
var init_preflight_clone_dir = __esm({
|
|
18719
|
+
"../../scripts/virtual-office/code-runner/preflight-clone-dir.mjs"() {
|
|
18720
|
+
"use strict";
|
|
18721
|
+
init_host_preflight();
|
|
18722
|
+
VALID_REPO_SLUG2 = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/u;
|
|
18723
|
+
NOT_A_REPOSITORY_RE = /not a git repository|does not appear to be a git repos/iu;
|
|
18724
|
+
}
|
|
18725
|
+
});
|
|
18726
|
+
|
|
18050
18727
|
// ../../scripts/virtual-office/code-runner-daemon.mjs
|
|
18051
18728
|
var code_runner_daemon_exports = {};
|
|
18052
18729
|
__export(code_runner_daemon_exports, {
|
|
@@ -18142,6 +18819,8 @@ async function processOneTask(client, task, cfg, runnerInstanceId, swarmAdmissio
|
|
|
18142
18819
|
env: buildAgentProcessEnv(process.env, { agent: sel.agent, runnerId: cfg.runnerId, taskId: id, repo: task.repo, githubReadToken: agentGithubReadToken, swarmAdmission }),
|
|
18143
18820
|
// swarmAdmission mints VO_SWARM_TIER_BINDING: ONE tier decision for this task's whole agent tree
|
|
18144
18821
|
sandbox,
|
|
18822
|
+
allowApiBilling: task.allow_api_billing === true,
|
|
18823
|
+
// per-TASK grant; AND-ed with the per-machine VO_RUNNER_ALLOW_API_BILLING inside applyApiBillingPolicy
|
|
18145
18824
|
onProgress: (text, checkpoint) => {
|
|
18146
18825
|
const usage = checkpoint?.tokenUsage ? { token_usage: checkpoint.tokenUsage } : {};
|
|
18147
18826
|
const patch = text ? runnerStagePatch("agent_working", text, usage) : { stage: "agent_working", ...usage };
|
|
@@ -18372,7 +19051,8 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
|
|
|
18372
19051
|
const agentAvailability = makeAgentAvailabilityProvider({ onError: (e) => log(`agent probe failed: ${e.message}`) });
|
|
18373
19052
|
await agentAvailability.ready();
|
|
18374
19053
|
const accountUsage = makeAccountUsageProvider();
|
|
18375
|
-
const
|
|
19054
|
+
const hostGate = makeHostPreflightGate({ preflight: makeServedClonePreflight({ cfg }), runGit: runProcess2, runAgent: runProcess2, bin: cfg.runnerBin, agent: cfg.agent, env: env2, log });
|
|
19055
|
+
const loopTick = makeLoopTicks({ client, cfg, env: env2, log, getActive: () => active, runnerInstanceId, capacityController, localModelController: createLocalModelRemoteController({ env: env2, log }), preparedJobController: createPreparedJobRemoteController({ log }), getAgentAvailability: () => agentAvailability.get(), getAccountUsage: () => accountUsage.get(), getHostHealth: () => hostGate.get() });
|
|
18376
19056
|
const backoff = makeReconnectBackoff({ baseMs: cfg.pollSec * 1e3, log });
|
|
18377
19057
|
let detachedFlushRunning = false;
|
|
18378
19058
|
while (!stopping) {
|
|
@@ -18382,9 +19062,10 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
|
|
|
18382
19062
|
detachedFlushRunning = false;
|
|
18383
19063
|
});
|
|
18384
19064
|
}
|
|
19065
|
+
await hostGate.ensure();
|
|
18385
19066
|
const heartbeatCompletion = loopTick();
|
|
18386
19067
|
if (watchCyclesEnabled) watchCoordinator.start();
|
|
18387
|
-
const claimAgents = resolveAgentClaimContext(agentAvailability, cfg.agent);
|
|
19068
|
+
const claimAgents = hostGate.allowClaim() ? resolveAgentClaimContext(agentAvailability, cfg.agent) : null;
|
|
18388
19069
|
if (!claimAgents) {
|
|
18389
19070
|
await heartbeatCompletion;
|
|
18390
19071
|
await sleep2(cfg.pollSec * 1e3);
|
|
@@ -18401,7 +19082,7 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
|
|
|
18401
19082
|
}
|
|
18402
19083
|
let task;
|
|
18403
19084
|
try {
|
|
18404
|
-
task = await client.claim(cfg.runnerId, cfg.servedRepos, cfg.servedOperators, { runnerInstanceId, reconcileStale, ...claimAgents });
|
|
19085
|
+
task = await client.claim(cfg.runnerId, cfg.servedRepos, cfg.servedOperators, { runnerInstanceId, reconcileStale, ...claimAgents, agentAuthSource: resolveRunnerAttestedAuthSource(process.env) });
|
|
18405
19086
|
reconcileStale = false;
|
|
18406
19087
|
backoff.onSuccess();
|
|
18407
19088
|
} catch (err) {
|
|
@@ -18421,6 +19102,7 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
|
|
|
18421
19102
|
continue;
|
|
18422
19103
|
}
|
|
18423
19104
|
log(`claimed task ${task.code_task_id} (${task.repo})`);
|
|
19105
|
+
log(`task ${task.code_task_id} ${describeTaskAuthSource(process.env, task)}`);
|
|
18424
19106
|
active += 1;
|
|
18425
19107
|
activeTaskIds.add(task.code_task_id);
|
|
18426
19108
|
const runTask = selectTaskProcessor(task, {
|
|
@@ -18493,6 +19175,7 @@ var init_code_runner_daemon = __esm({
|
|
|
18493
19175
|
init_task_helpers();
|
|
18494
19176
|
init_prepared_job_shadow();
|
|
18495
19177
|
init_agent_process_env();
|
|
19178
|
+
init_agent_auth_attestation();
|
|
18496
19179
|
init_sandbox_config();
|
|
18497
19180
|
init_inference_task_runner();
|
|
18498
19181
|
init_skill_task_runner();
|
|
@@ -18510,6 +19193,9 @@ var init_code_runner_daemon = __esm({
|
|
|
18510
19193
|
init_daemon_config();
|
|
18511
19194
|
init_publication_scope();
|
|
18512
19195
|
init_task_worktree_preparation();
|
|
19196
|
+
init_process_runner2();
|
|
19197
|
+
init_host_preflight();
|
|
19198
|
+
init_preflight_clone_dir();
|
|
18513
19199
|
init_detached_economics_spool();
|
|
18514
19200
|
RATE_LIMIT_RESUME_ENABLED = process.env.VO_RATE_LIMIT_RESUME !== "0";
|
|
18515
19201
|
sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|