@algosuite/vo-mcp 0.2.0-beta.71 → 0.2.0-beta.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-auth-probe-cli.mjs +196 -11
- 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 +1386 -498
- 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(path24, content) {
|
|
318
|
+
sweepStaleTempFiles(path24);
|
|
319
|
+
const temp = `${path24}.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(path24)) {
|
|
323
323
|
try {
|
|
324
|
-
chmodSync2(temp, statSync(
|
|
324
|
+
chmodSync2(temp, statSync(path24).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, path24);
|
|
332
332
|
return;
|
|
333
333
|
} catch (err) {
|
|
334
334
|
lastErr = err;
|
|
@@ -337,7 +337,7 @@ function writeFileAtomic(path23, content) {
|
|
|
337
337
|
sleepSync(RENAME_RETRY_MS);
|
|
338
338
|
}
|
|
339
339
|
}
|
|
340
|
-
writeFileSync2(
|
|
340
|
+
writeFileSync2(path24, 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 path24 = tablePath(lines[index] ?? "");
|
|
507
|
+
if (path24) starts.push({ path: path24, 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(path24, desiredContent, label, log2) {
|
|
700
|
+
if (!existsSync6(path24)) return false;
|
|
701
|
+
if (readFileSync5(path24, "utf8") === desiredContent) return true;
|
|
702
|
+
const backupPath = `${path24}.backup-${Date.now()}`;
|
|
703
|
+
copyFileSync2(path24, backupPath);
|
|
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(path24) {
|
|
920
|
+
if (!existsSync7(path24)) return { kind: "absent", config: {}, mtimeMs: null };
|
|
921
921
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
922
|
-
const before = statSync3(
|
|
922
|
+
const before = statSync3(path24).mtimeMs;
|
|
923
923
|
let raw;
|
|
924
924
|
try {
|
|
925
|
-
raw = readFileSync6(
|
|
925
|
+
raw = readFileSync6(path24, "utf8");
|
|
926
926
|
} catch {
|
|
927
927
|
return { kind: "invalid", config: {}, mtimeMs: before };
|
|
928
928
|
}
|
|
929
|
-
if (!existsSync7(
|
|
929
|
+
if (!existsSync7(path24) || statSync3(path24).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(path23) {
|
|
|
938
938
|
}
|
|
939
939
|
return { kind: "invalid", config: {}, mtimeMs: null };
|
|
940
940
|
}
|
|
941
|
-
function writeClaudeConfig(
|
|
942
|
-
mkdirSync6(dirname4(
|
|
943
|
-
writeFileAtomic(
|
|
941
|
+
function writeClaudeConfig(path24, config) {
|
|
942
|
+
mkdirSync6(dirname4(path24), { recursive: true });
|
|
943
|
+
writeFileAtomic(path24, `${JSON.stringify(config, null, 2)}
|
|
944
944
|
`);
|
|
945
945
|
}
|
|
946
946
|
function carriedEntryKeys(entry) {
|
|
@@ -3515,6 +3515,372 @@ var init_installation_token = __esm({
|
|
|
3515
3515
|
}
|
|
3516
3516
|
});
|
|
3517
3517
|
|
|
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 };
|
|
3533
|
+
}
|
|
3534
|
+
if (baseEnv.ANTHROPIC_API_KEY) {
|
|
3535
|
+
return { source: CLAUDE_CREDENTIAL_SOURCE.ENV_KEY, key: null };
|
|
3536
|
+
}
|
|
3537
|
+
const key = getKey();
|
|
3538
|
+
if (!key) {
|
|
3539
|
+
return { source: CLAUDE_CREDENTIAL_SOURCE.NO_KEY, key: null };
|
|
3540
|
+
}
|
|
3541
|
+
if (preferKey) {
|
|
3542
|
+
return { source: CLAUDE_CREDENTIAL_SOURCE.KEYCHAIN_PREFER_KEY, key };
|
|
3543
|
+
}
|
|
3544
|
+
if (probeLogin() === true) {
|
|
3545
|
+
return { source: CLAUDE_CREDENTIAL_SOURCE.SUBSCRIPTION_WINS, key: null };
|
|
3546
|
+
}
|
|
3547
|
+
return { source: CLAUDE_CREDENTIAL_SOURCE.KEYCHAIN, key };
|
|
3548
|
+
}
|
|
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"() {
|
|
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
|
+
});
|
|
3571
|
+
}
|
|
3572
|
+
});
|
|
3573
|
+
|
|
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];
|
|
3581
|
+
}
|
|
3582
|
+
return "";
|
|
3583
|
+
}
|
|
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
|
+
);
|
|
3609
|
+
}
|
|
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
|
+
);
|
|
3616
|
+
}
|
|
3617
|
+
return candidates;
|
|
3618
|
+
}
|
|
3619
|
+
function pathCandidates(bin, env2) {
|
|
3620
|
+
if (path12.isAbsolute(bin) || /[\\/]/u.test(bin)) {
|
|
3621
|
+
return [path12.resolve(bin)];
|
|
3622
|
+
}
|
|
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;
|
|
3644
|
+
}
|
|
3645
|
+
}
|
|
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");
|
|
3655
|
+
}
|
|
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
|
+
} = {}) {
|
|
3675
|
+
return {
|
|
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
|
|
3682
|
+
}
|
|
3683
|
+
};
|
|
3684
|
+
}
|
|
3685
|
+
function spawnClaudeSync(args = [], options = {}) {
|
|
3686
|
+
if (process.platform !== "win32") {
|
|
3687
|
+
return spawnSync("claude", args, { windowsHide: true, ...options });
|
|
3688
|
+
}
|
|
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) {
|
|
3700
|
+
return {
|
|
3701
|
+
error,
|
|
3702
|
+
status: null,
|
|
3703
|
+
signal: null,
|
|
3704
|
+
output: null,
|
|
3705
|
+
stdout: null,
|
|
3706
|
+
stderr: null
|
|
3707
|
+
};
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3710
|
+
var NATIVE_CLAUDE_PARTS;
|
|
3711
|
+
var init_windows_claude_launch = __esm({
|
|
3712
|
+
"../../scripts/virtual-office/code-runner/windows-claude-launch.mjs"() {
|
|
3713
|
+
"use strict";
|
|
3714
|
+
NATIVE_CLAUDE_PARTS = [
|
|
3715
|
+
"node_modules",
|
|
3716
|
+
"@anthropic-ai",
|
|
3717
|
+
"claude-code",
|
|
3718
|
+
"bin",
|
|
3719
|
+
"claude.exe"
|
|
3720
|
+
];
|
|
3721
|
+
}
|
|
3722
|
+
});
|
|
3723
|
+
|
|
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;
|
|
3734
|
+
}
|
|
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;
|
|
3743
|
+
}
|
|
3744
|
+
}
|
|
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;
|
|
3754
|
+
}
|
|
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
|
+
} = {}) {
|
|
3776
|
+
try {
|
|
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;
|
|
3781
|
+
} catch {
|
|
3782
|
+
return null;
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
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"() {
|
|
3788
|
+
"use strict";
|
|
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;
|
|
3805
|
+
}
|
|
3806
|
+
});
|
|
3807
|
+
|
|
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";
|
|
3812
|
+
}
|
|
3813
|
+
function runnerAllowsApiBilling(env2 = {}) {
|
|
3814
|
+
return isTruthyFlag2(env2[RUNNER_ALLOW_API_BILLING_ENV]);
|
|
3815
|
+
}
|
|
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;
|
|
3820
|
+
}
|
|
3821
|
+
return false;
|
|
3822
|
+
}
|
|
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;
|
|
3830
|
+
}
|
|
3831
|
+
function resolveAgentAuthSource(env2 = {}) {
|
|
3832
|
+
return hasApiBillingCredential(env2) ? AGENT_AUTH_SOURCE.API_KEY : AGENT_AUTH_SOURCE.LOGIN;
|
|
3833
|
+
}
|
|
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;
|
|
3848
|
+
}
|
|
3849
|
+
}
|
|
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"() {
|
|
3861
|
+
"use strict";
|
|
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);
|
|
3881
|
+
}
|
|
3882
|
+
});
|
|
3883
|
+
|
|
3518
3884
|
// ../../scripts/virtual-office/code-runner/control-plane-heartbeat-body.mjs
|
|
3519
3885
|
function buildRunnerHeartbeatBody({
|
|
3520
3886
|
runnerId,
|
|
@@ -3540,6 +3906,8 @@ function buildRunnerHeartbeatBody({
|
|
|
3540
3906
|
accountUsage,
|
|
3541
3907
|
availableLocalModels,
|
|
3542
3908
|
supportedTaskKinds,
|
|
3909
|
+
hostHealth,
|
|
3910
|
+
agentAuthSource,
|
|
3543
3911
|
prepared_job_shadow: preparedJobShadow
|
|
3544
3912
|
} = {}) {
|
|
3545
3913
|
const body = { runner_id: runnerId, ...preparedJobShadow ? { prepared_job_shadow: preparedJobShadow } : {} };
|
|
@@ -3577,11 +3945,14 @@ function buildRunnerHeartbeatBody({
|
|
|
3577
3945
|
if (Array.isArray(supportedTaskKinds) && supportedTaskKinds.length > 0) {
|
|
3578
3946
|
body.supported_task_kinds = supportedTaskKinds;
|
|
3579
3947
|
}
|
|
3948
|
+
if (hostHealth && typeof hostHealth === "object") body.host_health = hostHealth;
|
|
3949
|
+
if (AGENT_AUTH_SOURCES.includes(agentAuthSource)) body.agent_auth_source = agentAuthSource;
|
|
3580
3950
|
return body;
|
|
3581
3951
|
}
|
|
3582
3952
|
var init_control_plane_heartbeat_body = __esm({
|
|
3583
3953
|
"../../scripts/virtual-office/code-runner/control-plane-heartbeat-body.mjs"() {
|
|
3584
3954
|
"use strict";
|
|
3955
|
+
init_agent_auth_attestation();
|
|
3585
3956
|
}
|
|
3586
3957
|
});
|
|
3587
3958
|
|
|
@@ -3879,13 +4250,13 @@ async function getTaskKnowledgeContextRequest(req, taskId, { query, knowledgeReq
|
|
|
3879
4250
|
const canonicalQuery = canonicalizeKnowledgeContextQuery(query);
|
|
3880
4251
|
if (canonicalQuery.trim()) body.query = canonicalQuery;
|
|
3881
4252
|
if (typeof knowledgeRequestId === "string" && knowledgeRequestId) body.knowledge_request_id = knowledgeRequestId;
|
|
3882
|
-
const
|
|
4253
|
+
const path24 = `/api/v1/code-task/${encodeURIComponent(taskId)}/knowledge-context`;
|
|
3883
4254
|
const timeoutMs = Math.max(Number(taskRequestTimeoutMs) || 0, MIN_KNOWLEDGE_CONTEXT_TIMEOUT_MS);
|
|
3884
4255
|
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt += 1) {
|
|
3885
4256
|
let res;
|
|
3886
4257
|
let cause;
|
|
3887
4258
|
try {
|
|
3888
|
-
res = await req("POST",
|
|
4259
|
+
res = await req("POST", path24, body, { timeoutMs });
|
|
3889
4260
|
} catch (err) {
|
|
3890
4261
|
cause = err;
|
|
3891
4262
|
}
|
|
@@ -3953,10 +4324,10 @@ async function getPreparedJobRequest(req, taskId, options = {}, invalidateToken
|
|
|
3953
4324
|
if (typeof taskId !== "string" || taskId.length === 0) {
|
|
3954
4325
|
return { ok: false, reason: "missing_task_id", status: 0, ...envMeta };
|
|
3955
4326
|
}
|
|
3956
|
-
const
|
|
4327
|
+
const path24 = `/api/v1/code-task/${encodeURIComponent(taskId)}/prepared-job?${query}`;
|
|
3957
4328
|
let res;
|
|
3958
4329
|
try {
|
|
3959
|
-
res = await req("GET",
|
|
4330
|
+
res = await req("GET", path24, void 0, { timeoutMs });
|
|
3960
4331
|
} catch (err) {
|
|
3961
4332
|
return { ok: false, reason: `transport: ${err?.message || String(err)}`, status: 0, ...envMeta };
|
|
3962
4333
|
}
|
|
@@ -4056,11 +4427,11 @@ function createControlPlaneClient({
|
|
|
4056
4427
|
if (!resolvedBaseUrl) throw new Error("VO_CONTROL_PLANE_URL is required for the code-runner daemon");
|
|
4057
4428
|
const root = resolvedBaseUrl.replace(/\/+$/, "");
|
|
4058
4429
|
const claimOccurrences = /* @__PURE__ */ new Map();
|
|
4059
|
-
async function req(method,
|
|
4430
|
+
async function req(method, path24, body, { timeoutMs } = {}) {
|
|
4060
4431
|
const bearer = await resolveBearer(env2);
|
|
4061
4432
|
const controller = timeoutMs ? new AbortController() : null;
|
|
4062
4433
|
let timeoutId;
|
|
4063
|
-
const request = Promise.resolve(fetchImpl(`${root}${
|
|
4434
|
+
const request = Promise.resolve(fetchImpl(`${root}${path24}`, {
|
|
4064
4435
|
method,
|
|
4065
4436
|
headers: {
|
|
4066
4437
|
"content-type": "application/json",
|
|
@@ -4073,7 +4444,7 @@ function createControlPlaneClient({
|
|
|
4073
4444
|
const timeout = new Promise((_, reject) => {
|
|
4074
4445
|
timeoutId = setTimeout(() => {
|
|
4075
4446
|
controller.abort();
|
|
4076
|
-
reject(new Error(`control-plane ${
|
|
4447
|
+
reject(new Error(`control-plane ${path24} timed out after ${timeoutMs}ms`));
|
|
4077
4448
|
}, timeoutMs);
|
|
4078
4449
|
});
|
|
4079
4450
|
try {
|
|
@@ -4082,7 +4453,7 @@ function createControlPlaneClient({
|
|
|
4082
4453
|
clearTimeout(timeoutId);
|
|
4083
4454
|
}
|
|
4084
4455
|
}
|
|
4085
|
-
const taskReq = (method,
|
|
4456
|
+
const taskReq = (method, path24, body, options = {}) => req(method, path24, body, { timeoutMs: taskRequestTimeoutMs, ...options });
|
|
4086
4457
|
const claimGate = makeClaimGateNotice({ log: (m) => console.warn(`[code-runner ${(/* @__PURE__ */ new Date()).toISOString()}] ${m}`) });
|
|
4087
4458
|
return {
|
|
4088
4459
|
getClaimGate: () => claimGate.current(),
|
|
@@ -4111,6 +4482,7 @@ function createControlPlaneClient({
|
|
|
4111
4482
|
}
|
|
4112
4483
|
if (session.runnerInstanceId && session.reconcileStale) body.reconcile_stale = true;
|
|
4113
4484
|
if (session.defaultAgent) body.default_agent = session.defaultAgent;
|
|
4485
|
+
if (AGENT_AUTH_SOURCES.includes(session.agentAuthSource)) body.agent_auth_source = session.agentAuthSource;
|
|
4114
4486
|
if (Array.isArray(session.availableAgents)) {
|
|
4115
4487
|
body.available_agents = session.availableAgents.filter((entry) => entry?.installed === true && entry?.authenticated === true).map((entry) => entry.agent);
|
|
4116
4488
|
}
|
|
@@ -4253,8 +4625,8 @@ function createControlPlaneClient({
|
|
|
4253
4625
|
return listAllPrOpenedTasks(taskReq);
|
|
4254
4626
|
},
|
|
4255
4627
|
async downloadTaskAttachment(taskId, attachmentId) {
|
|
4256
|
-
const
|
|
4257
|
-
const res = await taskReq("GET",
|
|
4628
|
+
const path24 = `/api/v1/code-task/${encodeURIComponent(taskId)}/attachment/${encodeURIComponent(attachmentId)}`;
|
|
4629
|
+
const res = await taskReq("GET", path24);
|
|
4258
4630
|
if (res.status === 401) cachedFirebaseToken = null;
|
|
4259
4631
|
if (!res.ok) throw new Error(`attachment download failed: HTTP ${res.status}`);
|
|
4260
4632
|
return Buffer.from(await res.arrayBuffer());
|
|
@@ -4367,333 +4739,47 @@ function createControlPlaneClient({
|
|
|
4367
4739
|
return fetchInstallationToken({ req: taskReq, required, readOnly, repo });
|
|
4368
4740
|
},
|
|
4369
4741
|
/**
|
|
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";
|
|
4382
|
-
}
|
|
4383
|
-
}
|
|
4384
|
-
};
|
|
4385
|
-
}
|
|
4386
|
-
var cachedFirebaseToken, ClaimAuthorityChangedError;
|
|
4387
|
-
var init_control_plane_client = __esm({
|
|
4388
|
-
"../../scripts/virtual-office/code-runner/control-plane-client.mjs"() {
|
|
4389
|
-
"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
|
-
}
|
|
4411
|
-
});
|
|
4412
|
-
|
|
4413
|
-
// ../../scripts/virtual-office/code-runner/windows-claude-launch.mjs
|
|
4414
|
-
import { existsSync as existsSync8, realpathSync } from "node:fs";
|
|
4415
|
-
import { win32 as path12 } from "node:path";
|
|
4416
|
-
import { spawnSync } from "node:child_process";
|
|
4417
|
-
function pathValue(env2) {
|
|
4418
|
-
for (const key of ["Path", "PATH", "path"]) {
|
|
4419
|
-
if (typeof env2?.[key] === "string") return env2[key];
|
|
4420
|
-
}
|
|
4421
|
-
return "";
|
|
4422
|
-
}
|
|
4423
|
-
function cleanPathSegment(value) {
|
|
4424
|
-
const trimmed = String(value || "").trim();
|
|
4425
|
-
return trimmed.startsWith('"') && trimmed.endsWith('"') ? trimmed.slice(1, -1) : trimmed;
|
|
4426
|
-
}
|
|
4427
|
-
function envValue(env2, name) {
|
|
4428
|
-
const exact = env2?.[name];
|
|
4429
|
-
if (typeof exact === "string") return exact.trim();
|
|
4430
|
-
const key = Object.keys(env2 || {}).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
|
|
4431
|
-
return typeof env2?.[key] === "string" ? env2[key].trim() : "";
|
|
4432
|
-
}
|
|
4433
|
-
function userClaudeCandidates(bin, env2) {
|
|
4434
|
-
if (!/^claude(?:\.(?:exe|cmd|ps1))?$/iu.test(bin)) return [];
|
|
4435
|
-
const userProfile = envValue(env2, "USERPROFILE");
|
|
4436
|
-
const appData = envValue(env2, "APPDATA") || (userProfile ? path12.join(userProfile, "AppData", "Roaming") : "");
|
|
4437
|
-
const localAppData = envValue(env2, "LOCALAPPDATA") || (userProfile ? path12.join(userProfile, "AppData", "Local") : "");
|
|
4438
|
-
const candidates = [];
|
|
4439
|
-
if (appData) {
|
|
4440
|
-
const npmBin = path12.join(appData, "npm");
|
|
4441
|
-
candidates.push(
|
|
4442
|
-
path12.join(npmBin, "claude.exe"),
|
|
4443
|
-
path12.join(npmBin, "claude.cmd"),
|
|
4444
|
-
path12.join(npmBin, "claude.ps1"),
|
|
4445
|
-
path12.join(npmBin, "claude"),
|
|
4446
|
-
path12.join(npmBin, ...NATIVE_CLAUDE_PARTS)
|
|
4447
|
-
);
|
|
4448
|
-
}
|
|
4449
|
-
if (userProfile) candidates.push(path12.join(userProfile, ".local", "bin", "claude.exe"));
|
|
4450
|
-
if (localAppData) {
|
|
4451
|
-
candidates.push(
|
|
4452
|
-
path12.join(localAppData, "Microsoft", "WinGet", "Links", "claude.exe"),
|
|
4453
|
-
path12.join(localAppData, "Microsoft", "WindowsApps", "claude.exe")
|
|
4454
|
-
);
|
|
4455
|
-
}
|
|
4456
|
-
return candidates;
|
|
4457
|
-
}
|
|
4458
|
-
function pathCandidates(bin, env2) {
|
|
4459
|
-
if (path12.isAbsolute(bin) || /[\\/]/u.test(bin)) {
|
|
4460
|
-
return [path12.resolve(bin)];
|
|
4461
|
-
}
|
|
4462
|
-
const extension = path12.extname(bin);
|
|
4463
|
-
const fromPath = pathValue(env2).split(";").map(cleanPathSegment).filter(Boolean).flatMap((directory) => extension ? [path12.join(directory, bin)] : [
|
|
4464
|
-
path12.join(directory, `${bin}.exe`),
|
|
4465
|
-
path12.join(directory, `${bin}.cmd`),
|
|
4466
|
-
path12.join(directory, `${bin}.ps1`),
|
|
4467
|
-
path12.join(directory, bin)
|
|
4468
|
-
]);
|
|
4469
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4470
|
-
return [...fromPath, ...userClaudeCandidates(bin, env2)].filter((candidate) => {
|
|
4471
|
-
const key = candidate.toLowerCase();
|
|
4472
|
-
if (seen.has(key)) return false;
|
|
4473
|
-
seen.add(key);
|
|
4474
|
-
return true;
|
|
4475
|
-
});
|
|
4476
|
-
}
|
|
4477
|
-
function canonicalExistingPath(candidate, exists, canonicalize) {
|
|
4478
|
-
if (!exists(candidate)) return null;
|
|
4479
|
-
try {
|
|
4480
|
-
return canonicalize(candidate);
|
|
4481
|
-
} catch {
|
|
4482
|
-
return null;
|
|
4483
|
-
}
|
|
4484
|
-
}
|
|
4485
|
-
function resolveWindowsClaudeExecutable({
|
|
4486
|
-
bin = "claude",
|
|
4487
|
-
env: env2 = process.env,
|
|
4488
|
-
exists = existsSync8,
|
|
4489
|
-
canonicalize = realpathSync
|
|
4490
|
-
} = {}) {
|
|
4491
|
-
const requested = String(bin || "").trim();
|
|
4492
|
-
if (!requested || requested.includes("\0")) {
|
|
4493
|
-
throw new TypeError("Claude executable must be a non-empty path without NUL bytes");
|
|
4494
|
-
}
|
|
4495
|
-
for (const candidate of pathCandidates(requested, env2)) {
|
|
4496
|
-
const found = canonicalExistingPath(candidate, exists, canonicalize);
|
|
4497
|
-
if (!found) continue;
|
|
4498
|
-
if (path12.extname(found).toLowerCase() === ".exe") return found;
|
|
4499
|
-
const native = path12.join(path12.dirname(found), ...NATIVE_CLAUDE_PARTS);
|
|
4500
|
-
const resolvedNative = canonicalExistingPath(native, exists, canonicalize);
|
|
4501
|
-
if (resolvedNative) return resolvedNative;
|
|
4502
|
-
}
|
|
4503
|
-
const error = new Error(
|
|
4504
|
-
`Could not resolve a native claude.exe for "${requested}". Install or update Claude Code with the native Windows installer (recommended) or npm install -g @anthropic-ai/claude-code; the HQ runner will not execute a shell-only .cmd/.ps1 shim.`
|
|
4505
|
-
);
|
|
4506
|
-
error.code = "ENOENT";
|
|
4507
|
-
throw error;
|
|
4508
|
-
}
|
|
4509
|
-
function buildWindowsClaudeLaunch({
|
|
4510
|
-
bin = "claude",
|
|
4511
|
-
args = [],
|
|
4512
|
-
env: env2 = process.env
|
|
4513
|
-
} = {}) {
|
|
4514
|
-
return {
|
|
4515
|
-
bin: resolveWindowsClaudeExecutable({ bin, env: env2 }),
|
|
4516
|
-
args: Array.from(args, (value) => String(value)),
|
|
4517
|
-
spawnOptions: {
|
|
4518
|
-
shell: false,
|
|
4519
|
-
windowsHide: true,
|
|
4520
|
-
windowsVerbatimArguments: false
|
|
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
|
+
}
|
|
4521
4755
|
}
|
|
4522
4756
|
};
|
|
4523
4757
|
}
|
|
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"() {
|
|
4552
|
-
"use strict";
|
|
4553
|
-
NATIVE_CLAUDE_PARTS = [
|
|
4554
|
-
"node_modules",
|
|
4555
|
-
"@anthropic-ai",
|
|
4556
|
-
"claude-code",
|
|
4557
|
-
"bin",
|
|
4558
|
-
"claude.exe"
|
|
4559
|
-
];
|
|
4560
|
-
}
|
|
4561
|
-
});
|
|
4562
|
-
|
|
4563
|
-
// ../../scripts/virtual-office/code-runner/claude-credential-choice.mjs
|
|
4564
|
-
function isTruthyFlag(v) {
|
|
4565
|
-
const s = String(v ?? "").trim().toLowerCase();
|
|
4566
|
-
return s === "1" || s === "true" || s === "yes" || s === "on";
|
|
4567
|
-
}
|
|
4568
|
-
function wantsLogin(env2) {
|
|
4569
|
-
return isTruthyFlag(env2[CLAUDE_PREFER_LOGIN_ENV]) || isTruthyFlag(env2[PREFER_LOGIN_ENV]);
|
|
4570
|
-
}
|
|
4571
|
-
function wantsKey(env2) {
|
|
4572
|
-
return isTruthyFlag(env2[CLAUDE_PREFER_KEY_ENV]) || isTruthyFlag(env2[PREFER_KEY_ENV]);
|
|
4573
|
-
}
|
|
4574
|
-
function classifyClaudeCredential(baseEnv = {}, { getKey, probeLogin } = {}) {
|
|
4575
|
-
const preferKey = wantsKey(baseEnv);
|
|
4576
|
-
if (!preferKey && wantsLogin(baseEnv)) {
|
|
4577
|
-
return { source: CLAUDE_CREDENTIAL_SOURCE.PREFER_LOGIN, key: null };
|
|
4578
|
-
}
|
|
4579
|
-
if (baseEnv.ANTHROPIC_API_KEY) {
|
|
4580
|
-
return { source: CLAUDE_CREDENTIAL_SOURCE.ENV_KEY, key: null };
|
|
4581
|
-
}
|
|
4582
|
-
const key = getKey();
|
|
4583
|
-
if (!key) {
|
|
4584
|
-
return { source: CLAUDE_CREDENTIAL_SOURCE.NO_KEY, key: null };
|
|
4585
|
-
}
|
|
4586
|
-
if (preferKey) {
|
|
4587
|
-
return { source: CLAUDE_CREDENTIAL_SOURCE.KEYCHAIN_PREFER_KEY, key };
|
|
4588
|
-
}
|
|
4589
|
-
if (probeLogin() === true) {
|
|
4590
|
-
return { source: CLAUDE_CREDENTIAL_SOURCE.SUBSCRIPTION_WINS, key: null };
|
|
4591
|
-
}
|
|
4592
|
-
return { source: CLAUDE_CREDENTIAL_SOURCE.KEYCHAIN, key };
|
|
4593
|
-
}
|
|
4594
|
-
var PREFER_LOGIN_ENV, CLAUDE_PREFER_LOGIN_ENV, PREFER_KEY_ENV, CLAUDE_PREFER_KEY_ENV, CLAUDE_CREDENTIAL_SOURCE;
|
|
4595
|
-
var init_claude_credential_choice = __esm({
|
|
4596
|
-
"../../scripts/virtual-office/code-runner/claude-credential-choice.mjs"() {
|
|
4597
|
-
"use strict";
|
|
4598
|
-
PREFER_LOGIN_ENV = "VO_RUNNER_PREFER_LOGIN";
|
|
4599
|
-
CLAUDE_PREFER_LOGIN_ENV = "VO_RUNNER_CLAUDE_PREFER_LOGIN";
|
|
4600
|
-
PREFER_KEY_ENV = "VO_RUNNER_PREFER_KEY";
|
|
4601
|
-
CLAUDE_PREFER_KEY_ENV = "VO_RUNNER_CLAUDE_PREFER_KEY";
|
|
4602
|
-
CLAUDE_CREDENTIAL_SOURCE = Object.freeze({
|
|
4603
|
-
/** PREFER_LOGIN set (and not overridden): any API key is ignored. */
|
|
4604
|
-
PREFER_LOGIN: "prefer_login",
|
|
4605
|
-
/** An explicit ANTHROPIC_API_KEY in the environment — the manual override. */
|
|
4606
|
-
ENV_KEY: "env_key",
|
|
4607
|
-
/** No key anywhere; the spawn falls through to the login session. */
|
|
4608
|
-
NO_KEY: "no_key",
|
|
4609
|
-
/** A stored key, used because the operator explicitly opted out of tier 1. */
|
|
4610
|
-
KEYCHAIN_PREFER_KEY: "keychain_prefer_key",
|
|
4611
|
-
/** A stored key exists but a proven live subscription outranks it. */
|
|
4612
|
-
SUBSCRIPTION_WINS: "subscription_wins",
|
|
4613
|
-
/** A stored key, used because no live subscription was proven. */
|
|
4614
|
-
KEYCHAIN: "keychain"
|
|
4615
|
-
});
|
|
4616
|
-
}
|
|
4617
|
-
});
|
|
4618
|
-
|
|
4619
|
-
// ../../scripts/virtual-office/code-runner/anthropic-key-store.mjs
|
|
4620
|
-
import { createRequire as createRequire2 } from "node:module";
|
|
4621
|
-
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
4622
|
-
function defaultEntryCtor() {
|
|
4623
|
-
if (_loadTried) return _entryCtor;
|
|
4624
|
-
_loadTried = true;
|
|
4625
|
-
try {
|
|
4626
|
-
_entryCtor = require2("@napi-rs/keyring").Entry;
|
|
4627
|
-
} catch {
|
|
4628
|
-
_entryCtor = null;
|
|
4629
|
-
}
|
|
4630
|
-
return _entryCtor;
|
|
4631
|
-
}
|
|
4632
|
-
function getAnthropicKey({ EntryCtor = defaultEntryCtor() } = {}) {
|
|
4633
|
-
if (!EntryCtor) return null;
|
|
4634
|
-
try {
|
|
4635
|
-
return new EntryCtor(KEY_SERVICE, KEY_ACCOUNT).getPassword() || null;
|
|
4636
|
-
} catch {
|
|
4637
|
-
return null;
|
|
4638
|
-
}
|
|
4639
|
-
}
|
|
4640
|
-
function withAnthropicKey(baseEnv = {}, { getKey = getAnthropicKey, probeLogin = probeClaudeLoginState } = {}) {
|
|
4641
|
-
const { source, key } = classifyClaudeCredential(baseEnv, { getKey, probeLogin });
|
|
4642
|
-
const next = { ...baseEnv };
|
|
4643
|
-
if (source === CLAUDE_CREDENTIAL_SOURCE.PREFER_LOGIN) {
|
|
4644
|
-
delete next.ANTHROPIC_API_KEY;
|
|
4645
|
-
return next;
|
|
4646
|
-
}
|
|
4647
|
-
if (key !== null) next.ANTHROPIC_API_KEY = key;
|
|
4648
|
-
return next;
|
|
4649
|
-
}
|
|
4650
|
-
function claudeCostBasis(env2 = process.env) {
|
|
4651
|
-
return String(env2.ANTHROPIC_API_KEY || "").trim() ? "vendor_billed" : "subscription_api_equivalent";
|
|
4652
|
-
}
|
|
4653
|
-
function describeAnthropicAuthSource(baseEnv = {}, { getKey = getAnthropicKey, probeLogin = probeClaudeLoginState } = {}) {
|
|
4654
|
-
const { source } = classifyClaudeCredential(baseEnv, { getKey, probeLogin });
|
|
4655
|
-
return AUTH_SOURCE_DESCRIPTION[source];
|
|
4656
|
-
}
|
|
4657
|
-
function augmentAuthError(summary) {
|
|
4658
|
-
const s = String(summary ?? "");
|
|
4659
|
-
if (!AUTH_ERROR_RE.test(s)) return s;
|
|
4660
|
-
return `${s}
|
|
4661
|
-
\u21B3 Anthropic auth failed on the runner. The \`claude\` CLI is a SEPARATE install/login from the Claude Desktop app and the Claude Code IDE extension \u2014 signing into those does NOT authenticate it. Fix: run \`claude auth login\` (Claude subscription) on the runner machine, or clear any stale ANTHROPIC_API_KEY (env / OS keychain / .env.local) and set VO_RUNNER_PREFER_LOGIN=1 \u2014 then restart the runner. Verify with \`claude -p "say hi"\`.`;
|
|
4662
|
-
}
|
|
4663
|
-
function probeClaudeLoginState({
|
|
4664
|
-
spawn: spawn5 = spawnSync2,
|
|
4665
|
-
buildWindowsLaunch = buildWindowsClaudeLaunch,
|
|
4666
|
-
platform: platform4 = process.platform
|
|
4667
|
-
} = {}) {
|
|
4668
|
-
try {
|
|
4669
|
-
const launch = platform4 === "win32" ? buildWindowsLaunch({ bin: "claude", args: ["auth", "status"] }) : { bin: "claude", args: ["auth", "status"], spawnOptions: { windowsHide: true } };
|
|
4670
|
-
const st = spawn5(launch.bin, launch.args, { ...launch.spawnOptions, timeout: 5e3, encoding: "utf8" });
|
|
4671
|
-
const parsed = JSON.parse(String(st.stdout || "").trim() || "{}");
|
|
4672
|
-
return typeof parsed.loggedIn === "boolean" ? parsed.loggedIn : null;
|
|
4673
|
-
} catch {
|
|
4674
|
-
return null;
|
|
4675
|
-
}
|
|
4676
|
-
}
|
|
4677
|
-
var require2, KEY_SERVICE, KEY_ACCOUNT, _entryCtor, _loadTried, AUTH_SOURCE_DESCRIPTION, AUTH_ERROR_RE;
|
|
4678
|
-
var init_anthropic_key_store = __esm({
|
|
4679
|
-
"../../scripts/virtual-office/code-runner/anthropic-key-store.mjs"() {
|
|
4758
|
+
var cachedFirebaseToken, ClaimAuthorityChangedError;
|
|
4759
|
+
var init_control_plane_client = __esm({
|
|
4760
|
+
"../../scripts/virtual-office/code-runner/control-plane-client.mjs"() {
|
|
4680
4761
|
"use strict";
|
|
4681
|
-
|
|
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
|
|
|
@@ -4907,7 +4993,7 @@ function normalizeClaudePermissionMode(value) {
|
|
|
4907
4993
|
}
|
|
4908
4994
|
return normalized;
|
|
4909
4995
|
}
|
|
4910
|
-
function buildClaudeArgs({ permissionMode = DEFAULT_PERMISSION_MODE, maxTurns, model, effort, maxBudgetUsd, researchHarness = false, toolPolicy = "default", env: env2 = process.env } = {}) {
|
|
4996
|
+
function buildClaudeArgs({ permissionMode = DEFAULT_PERMISSION_MODE, maxTurns, model, effort, maxBudgetUsd, researchHarness = false, toolPolicy = "default", structuredOutputSchema, env: env2 = process.env } = {}) {
|
|
4911
4997
|
const effectivePermissionMode = normalizeClaudePermissionMode(permissionMode);
|
|
4912
4998
|
if (!["default", "skill_readonly", "frozen_inputs_only"].includes(toolPolicy)) {
|
|
4913
4999
|
throw new Error(`unsupported Claude tool policy "${toolPolicy}"`);
|
|
@@ -4945,6 +5031,12 @@ function buildClaudeArgs({ permissionMode = DEFAULT_PERMISSION_MODE, maxTurns, m
|
|
|
4945
5031
|
"none"
|
|
4946
5032
|
);
|
|
4947
5033
|
}
|
|
5034
|
+
if (structuredOutputSchema !== void 0) {
|
|
5035
|
+
if (!restrictedSkill || !structuredOutputSchema || typeof structuredOutputSchema !== "object" || Array.isArray(structuredOutputSchema)) {
|
|
5036
|
+
throw new Error("structured output schema is allowed only for a restricted skill");
|
|
5037
|
+
}
|
|
5038
|
+
args.push("--json-schema", JSON.stringify(structuredOutputSchema));
|
|
5039
|
+
}
|
|
4948
5040
|
if (frozenInputsOnly) {
|
|
4949
5041
|
args.push("--strict-mcp-config", "--safe-mode");
|
|
4950
5042
|
}
|
|
@@ -5370,13 +5462,15 @@ function cappedRunLastMessage(evt, lastProgress) {
|
|
|
5370
5462
|
return salvage;
|
|
5371
5463
|
}
|
|
5372
5464
|
function buildResultEvent(evt) {
|
|
5373
|
-
const isError = Boolean(evt.is_error) || evt.subtype === "error_max_turns" || evt.subtype === "error_during_execution";
|
|
5465
|
+
const isError = Boolean(evt.is_error) || evt.subtype === "error_max_budget_usd" || evt.subtype === "error_max_turns" || evt.subtype === "error_max_structured_output_retries" || evt.subtype === "error_during_execution";
|
|
5374
5466
|
return {
|
|
5375
5467
|
kind: "result",
|
|
5376
5468
|
isError,
|
|
5377
5469
|
costUsd: typeof evt.total_cost_usd === "number" ? evt.total_cost_usd : null,
|
|
5378
5470
|
summary: typeof evt.result === "string" && evt.result.length > 0 ? evt.result : evt.subtype || (isError ? "error" : "completed"),
|
|
5471
|
+
terminalSubtype: typeof evt.subtype === "string" ? evt.subtype : null,
|
|
5379
5472
|
numTurns: typeof evt.num_turns === "number" ? evt.num_turns : null,
|
|
5473
|
+
structuredOutput: Object.hasOwn(evt, "structured_output") ? evt.structured_output : null,
|
|
5380
5474
|
tokenUsage: extractTokenUsage(evt),
|
|
5381
5475
|
modelUsage: extractModelUsage(evt)
|
|
5382
5476
|
};
|
|
@@ -5515,8 +5609,150 @@ var MIN_CLAUDE_CLI_VERSION, SECURITY_RATIONALE;
|
|
|
5515
5609
|
var init_cli_version_floor = __esm({
|
|
5516
5610
|
"../../scripts/virtual-office/code-runner/cli-version-floor.mjs"() {
|
|
5517
5611
|
"use strict";
|
|
5518
|
-
MIN_CLAUDE_CLI_VERSION = "2.1.218";
|
|
5519
|
-
SECURITY_RATIONALE = "Claude Code 2.1.211/2.1.213 fixed a PreToolUse-hook bypass on unsandboxed Bash (our destructive-fs/git/cloud tripwires DO NOT FIRE on older CLIs) and worktree-subagents mutating the main checkout. 2.1.218 fixed Windows paths with a lowercase-\\u segment (e.g. ...\\utils\\, ...\\ui\\) being corrupted into CJK in tool inputs, making those files silently inaccessible \u2014 the fleet is Windows and 1,376 tracked files sit under utils/ alone. Update: npm install -g @anthropic-ai/claude-code (or the native installer).";
|
|
5612
|
+
MIN_CLAUDE_CLI_VERSION = "2.1.218";
|
|
5613
|
+
SECURITY_RATIONALE = "Claude Code 2.1.211/2.1.213 fixed a PreToolUse-hook bypass on unsandboxed Bash (our destructive-fs/git/cloud tripwires DO NOT FIRE on older CLIs) and worktree-subagents mutating the main checkout. 2.1.218 fixed Windows paths with a lowercase-\\u segment (e.g. ...\\utils\\, ...\\ui\\) being corrupted into CJK in tool inputs, making those files silently inaccessible \u2014 the fleet is Windows and 1,376 tracked files sit under utils/ alone. Update: npm install -g @anthropic-ai/claude-code (or the native installer).";
|
|
5614
|
+
}
|
|
5615
|
+
});
|
|
5616
|
+
|
|
5617
|
+
// ../../scripts/virtual-office/code-runner/claude-skill-capability.mjs
|
|
5618
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
5619
|
+
import { accessSync, constants, realpathSync as realpathSync2, statSync as statSync4 } from "node:fs";
|
|
5620
|
+
import path14 from "node:path";
|
|
5621
|
+
function hasOption(help, option) {
|
|
5622
|
+
const literal = option.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
5623
|
+
return new RegExp(`(^|\\s)${literal}(?=\\s|,|=|<|$)`, "mu").test(help);
|
|
5624
|
+
}
|
|
5625
|
+
function assessClaudeSkillCapability({ versionOutput, helpOutput }) {
|
|
5626
|
+
const version = parseCliVersion(versionOutput);
|
|
5627
|
+
if (!version || !VALIDATED_CLAUDE_SKILL_VERSIONS.includes(version)) {
|
|
5628
|
+
return { compatible: false, version, reason: "claude version is not in the validated restricted-skill manifest" };
|
|
5629
|
+
}
|
|
5630
|
+
const help = String(helpOutput ?? "");
|
|
5631
|
+
const missing = REQUIRED_CLAUDE_SKILL_HELP.filter((option) => !hasOption(help, option));
|
|
5632
|
+
if (missing.length > 0) {
|
|
5633
|
+
return { compatible: false, version, reason: `claude help is missing required options: ${missing.join(", ")}` };
|
|
5634
|
+
}
|
|
5635
|
+
if (!/--permission-prompts[\s\S]{0,300}(?:"none"|\bnone\b)/mu.test(help) || !/--output-format[\s\S]{0,300}\bstream-json\b/mu.test(help)) {
|
|
5636
|
+
return { compatible: false, version, reason: "claude help does not prove required none/stream-json values" };
|
|
5637
|
+
}
|
|
5638
|
+
return { compatible: true, version, reason: "validated restricted-skill CLI contract" };
|
|
5639
|
+
}
|
|
5640
|
+
function runProbe(bin, args, env2, timeoutMs = PROBE_TIMEOUT_MS) {
|
|
5641
|
+
if (process.platform === "win32") {
|
|
5642
|
+
try {
|
|
5643
|
+
const launch = buildWindowsClaudeLaunch({ bin, args, env: env2 });
|
|
5644
|
+
return spawnSync6(launch.bin, launch.args, {
|
|
5645
|
+
...launch.spawnOptions,
|
|
5646
|
+
env: env2,
|
|
5647
|
+
encoding: "utf8",
|
|
5648
|
+
timeout: timeoutMs
|
|
5649
|
+
});
|
|
5650
|
+
} catch (error) {
|
|
5651
|
+
return { status: null, stdout: "", stderr: "", error };
|
|
5652
|
+
}
|
|
5653
|
+
}
|
|
5654
|
+
return spawnSync6(bin, args, { env: env2, encoding: "utf8", timeout: timeoutMs, windowsHide: true });
|
|
5655
|
+
}
|
|
5656
|
+
function probeText(probe) {
|
|
5657
|
+
return `${String(probe?.stdout ?? "")}
|
|
5658
|
+
${String(probe?.stderr ?? "")}`.trim();
|
|
5659
|
+
}
|
|
5660
|
+
function resolveClaudeBinaryIdentity(bin = "claude", env2 = process.env) {
|
|
5661
|
+
let resolvedBin = String(bin);
|
|
5662
|
+
try {
|
|
5663
|
+
if (process.platform === "win32") {
|
|
5664
|
+
resolvedBin = buildWindowsClaudeLaunch({ bin: resolvedBin, args: [], env: env2 }).bin;
|
|
5665
|
+
} else if (!path14.isAbsolute(resolvedBin)) {
|
|
5666
|
+
const found = String(env2?.PATH ?? "").split(path14.delimiter).find((dir) => {
|
|
5667
|
+
try {
|
|
5668
|
+
accessSync(path14.join(dir, resolvedBin), constants.X_OK);
|
|
5669
|
+
return true;
|
|
5670
|
+
} catch {
|
|
5671
|
+
return false;
|
|
5672
|
+
}
|
|
5673
|
+
});
|
|
5674
|
+
if (found) resolvedBin = path14.join(found, resolvedBin);
|
|
5675
|
+
}
|
|
5676
|
+
const canonical = realpathSync2(resolvedBin);
|
|
5677
|
+
const stat3 = statSync4(canonical);
|
|
5678
|
+
return { resolvedBin: canonical, fingerprint: `${canonical}\0${stat3.size}\0${stat3.mtimeMs}` };
|
|
5679
|
+
} catch {
|
|
5680
|
+
const pathValue2 = String(env2?.PATH ?? env2?.Path ?? "");
|
|
5681
|
+
return { resolvedBin, fingerprint: `${resolvedBin}\0${pathValue2}` };
|
|
5682
|
+
}
|
|
5683
|
+
}
|
|
5684
|
+
function probeClaudeSkillCapability({
|
|
5685
|
+
bin = "claude",
|
|
5686
|
+
env: env2 = process.env,
|
|
5687
|
+
versionOutput,
|
|
5688
|
+
spawnProbe = runProbe,
|
|
5689
|
+
now = () => Date.now(),
|
|
5690
|
+
cacheTtlMs = CACHE_TTL_MS,
|
|
5691
|
+
timeoutMs = PROBE_TIMEOUT_MS,
|
|
5692
|
+
freshIdentity = false,
|
|
5693
|
+
resolveIdentity = resolveClaudeBinaryIdentity
|
|
5694
|
+
} = {}) {
|
|
5695
|
+
const identity = resolveIdentity(bin, env2);
|
|
5696
|
+
const key = identity.fingerprint;
|
|
5697
|
+
const existing = cache.get(key);
|
|
5698
|
+
if (!freshIdentity && versionOutput === void 0 && existing && now() - existing.at < cacheTtlMs) {
|
|
5699
|
+
return existing.value;
|
|
5700
|
+
}
|
|
5701
|
+
const versionProbe = freshIdentity || versionOutput === void 0 ? spawnProbe(identity.resolvedBin, ["--version"], env2, timeoutMs) : null;
|
|
5702
|
+
if (versionProbe?.error || versionProbe && versionProbe.status !== 0) {
|
|
5703
|
+
return {
|
|
5704
|
+
compatible: false,
|
|
5705
|
+
version: null,
|
|
5706
|
+
resolvedBin: identity.resolvedBin,
|
|
5707
|
+
reason: "claude version capability probe failed"
|
|
5708
|
+
};
|
|
5709
|
+
}
|
|
5710
|
+
const effectiveVersionOutput = versionProbe ? probeText(versionProbe) : versionOutput;
|
|
5711
|
+
const suppliedVersion = parseCliVersion(effectiveVersionOutput);
|
|
5712
|
+
if (existing && now() - existing.at < cacheTtlMs && suppliedVersion === existing.value.version) {
|
|
5713
|
+
return existing.value;
|
|
5714
|
+
}
|
|
5715
|
+
const helpProbe = spawnProbe(identity.resolvedBin, ["--help"], env2, timeoutMs);
|
|
5716
|
+
if (helpProbe?.error || helpProbe?.status !== 0) {
|
|
5717
|
+
return {
|
|
5718
|
+
compatible: false,
|
|
5719
|
+
version: suppliedVersion,
|
|
5720
|
+
resolvedBin: identity.resolvedBin,
|
|
5721
|
+
reason: "claude help capability probe failed"
|
|
5722
|
+
};
|
|
5723
|
+
}
|
|
5724
|
+
const assessed = assessClaudeSkillCapability({
|
|
5725
|
+
versionOutput: effectiveVersionOutput,
|
|
5726
|
+
helpOutput: probeText(helpProbe)
|
|
5727
|
+
});
|
|
5728
|
+
const value = { ...assessed, resolvedBin: identity.resolvedBin };
|
|
5729
|
+
cache.set(key, { at: now(), value });
|
|
5730
|
+
return value;
|
|
5731
|
+
}
|
|
5732
|
+
var VALIDATED_CLAUDE_SKILL_VERSIONS, REQUIRED_CLAUDE_SKILL_HELP, PROBE_TIMEOUT_MS, CACHE_TTL_MS, cache;
|
|
5733
|
+
var init_claude_skill_capability = __esm({
|
|
5734
|
+
"../../scripts/virtual-office/code-runner/claude-skill-capability.mjs"() {
|
|
5735
|
+
"use strict";
|
|
5736
|
+
init_cli_version_floor();
|
|
5737
|
+
init_windows_claude_launch();
|
|
5738
|
+
VALIDATED_CLAUDE_SKILL_VERSIONS = Object.freeze(["2.1.263"]);
|
|
5739
|
+
REQUIRED_CLAUDE_SKILL_HELP = Object.freeze([
|
|
5740
|
+
"--allowedTools",
|
|
5741
|
+
"--disable-slash-commands",
|
|
5742
|
+
"--json-schema",
|
|
5743
|
+
"--max-budget-usd",
|
|
5744
|
+
"--no-chrome",
|
|
5745
|
+
"--no-session-persistence",
|
|
5746
|
+
"--output-format",
|
|
5747
|
+
"--permission-mode",
|
|
5748
|
+
"--permission-prompts",
|
|
5749
|
+
"--safe-mode",
|
|
5750
|
+
"--strict-mcp-config",
|
|
5751
|
+
"--tools"
|
|
5752
|
+
]);
|
|
5753
|
+
PROBE_TIMEOUT_MS = 2e3;
|
|
5754
|
+
CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
5755
|
+
cache = /* @__PURE__ */ new Map();
|
|
5520
5756
|
}
|
|
5521
5757
|
});
|
|
5522
5758
|
|
|
@@ -5546,9 +5782,12 @@ async function checkClaudeAuth({
|
|
|
5546
5782
|
spawnVersion = spawnClaudeSync,
|
|
5547
5783
|
probeLogin = probeClaudeLoginState,
|
|
5548
5784
|
getStoredKey = getAnthropicKey,
|
|
5549
|
-
|
|
5785
|
+
probeSkillCapability = probeClaudeSkillCapability,
|
|
5786
|
+
env: env2 = process.env,
|
|
5787
|
+
now = () => Date.now()
|
|
5550
5788
|
} = {}) {
|
|
5551
5789
|
try {
|
|
5790
|
+
const startedAt = now();
|
|
5552
5791
|
let probe = spawnVersion(["--version"], {
|
|
5553
5792
|
timeout: FIRST_VERSION_TIMEOUT_MS,
|
|
5554
5793
|
encoding: "utf8",
|
|
@@ -5582,23 +5821,40 @@ async function checkClaudeAuth({
|
|
|
5582
5821
|
}
|
|
5583
5822
|
const floorGate = applyCliVersionFloor({ versionOutput: probe.stdout, env: env2 });
|
|
5584
5823
|
if (floorGate.refused) {
|
|
5585
|
-
return {
|
|
5824
|
+
return {
|
|
5825
|
+
installed: true,
|
|
5826
|
+
authenticated: false,
|
|
5827
|
+
version: floorGate.check.version ?? void 0,
|
|
5828
|
+
skillCapable: false,
|
|
5829
|
+
message: floorGate.message
|
|
5830
|
+
};
|
|
5586
5831
|
}
|
|
5587
5832
|
const loggedIn = retriedAfterTimeout ? null : probeLogin();
|
|
5588
5833
|
if (loggedIn === false) {
|
|
5589
5834
|
return {
|
|
5590
5835
|
installed: true,
|
|
5591
5836
|
authenticated: false,
|
|
5837
|
+
version: floorGate.check.version ?? void 0,
|
|
5838
|
+
skillCapable: false,
|
|
5592
5839
|
message: "claude CLI is installed but NOT logged in \u2014 its login is SEPARATE from the Claude Desktop app and the Claude Code IDE extension. Run: claude auth login (Claude subscription), then restart the runner."
|
|
5593
5840
|
};
|
|
5594
5841
|
}
|
|
5842
|
+
const authTier = resolveClaudeAuthTier({ env: env2, loggedIn, getStoredKey });
|
|
5843
|
+
const remainingMs = AUTH_PROBE_BUDGET_MS - (now() - startedAt);
|
|
5844
|
+
const skillCapability = loggedIn === true && remainingMs >= MIN_SKILL_PROBE_MS ? probeSkillCapability({
|
|
5845
|
+
versionOutput: probe.stdout,
|
|
5846
|
+
env: env2,
|
|
5847
|
+
timeoutMs: Math.min(2e3, remainingMs)
|
|
5848
|
+
}) : { compatible: false };
|
|
5595
5849
|
return {
|
|
5596
5850
|
installed: true,
|
|
5597
5851
|
authenticated: true,
|
|
5852
|
+
version: floorGate.check.version ?? void 0,
|
|
5853
|
+
skillCapable: skillCapability.compatible === true,
|
|
5598
5854
|
// Dispatch-time billing signal, carried on the same probe that already
|
|
5599
5855
|
// paid for the login read. Never sent for a non-authenticated result:
|
|
5600
5856
|
// there is no tier without a working credential.
|
|
5601
|
-
authTier
|
|
5857
|
+
authTier,
|
|
5602
5858
|
message: loggedIn === true ? "claude CLI installed and logged in (claude auth status)" : "claude binary found (login state unknown \u2014 auth check is best-effort)"
|
|
5603
5859
|
};
|
|
5604
5860
|
} catch (error) {
|
|
@@ -5609,7 +5865,7 @@ async function checkClaudeAuth({
|
|
|
5609
5865
|
};
|
|
5610
5866
|
}
|
|
5611
5867
|
}
|
|
5612
|
-
var FIRST_VERSION_TIMEOUT_MS, RETRY_VERSION_TIMEOUT_MS;
|
|
5868
|
+
var FIRST_VERSION_TIMEOUT_MS, RETRY_VERSION_TIMEOUT_MS, AUTH_PROBE_BUDGET_MS, MIN_SKILL_PROBE_MS;
|
|
5613
5869
|
var init_claude_auth_check = __esm({
|
|
5614
5870
|
"../../scripts/virtual-office/code-runner/claude-auth-check.mjs"() {
|
|
5615
5871
|
"use strict";
|
|
@@ -5617,8 +5873,11 @@ var init_claude_auth_check = __esm({
|
|
|
5617
5873
|
init_agent_auth_tier();
|
|
5618
5874
|
init_cli_version_floor();
|
|
5619
5875
|
init_windows_claude_launch();
|
|
5876
|
+
init_claude_skill_capability();
|
|
5620
5877
|
FIRST_VERSION_TIMEOUT_MS = 4500;
|
|
5621
5878
|
RETRY_VERSION_TIMEOUT_MS = 2e3;
|
|
5879
|
+
AUTH_PROBE_BUDGET_MS = 9500;
|
|
5880
|
+
MIN_SKILL_PROBE_MS = 250;
|
|
5622
5881
|
}
|
|
5623
5882
|
});
|
|
5624
5883
|
|
|
@@ -5639,6 +5898,7 @@ function runAgentTask({
|
|
|
5639
5898
|
maxBudgetUsd = null,
|
|
5640
5899
|
researchHarness = false,
|
|
5641
5900
|
toolPolicy = "default",
|
|
5901
|
+
structuredOutputSchema,
|
|
5642
5902
|
env: env2 = process.env,
|
|
5643
5903
|
onProgress = () => {
|
|
5644
5904
|
},
|
|
@@ -5653,11 +5913,14 @@ function runAgentTask({
|
|
|
5653
5913
|
exitDrainGraceMs = 300,
|
|
5654
5914
|
armTerminalCleanup = armTerminalProcessCleanup,
|
|
5655
5915
|
spawnImpl = spawn2,
|
|
5656
|
-
sandbox = null
|
|
5916
|
+
sandbox = null,
|
|
5917
|
+
allowApiBilling = false
|
|
5657
5918
|
}) {
|
|
5658
5919
|
return new Promise((resolve3) => {
|
|
5659
|
-
const args = runner.buildArgs({ permissionMode, maxTurns, model, effort, maxBudgetUsd, researchHarness, toolPolicy, prompt });
|
|
5660
|
-
const
|
|
5920
|
+
const args = runner.buildArgs({ permissionMode, maxTurns, model, effort, maxBudgetUsd, researchHarness, toolPolicy, structuredOutputSchema, prompt });
|
|
5921
|
+
const authedEnv = typeof runner.applyAuthEnv === "function" ? runner.applyAuthEnv(env2) : env2;
|
|
5922
|
+
const billing = applyApiBillingPolicy(authedEnv, { runnerEnv: env2, allowApiBilling });
|
|
5923
|
+
const spawnEnv = billing.env;
|
|
5661
5924
|
const costBasis = typeof runner.costBasis === "function" ? runner.costBasis(spawnEnv) : "unknown";
|
|
5662
5925
|
if (costBasis === "vendor_billed" && runner.enforcesBudgetCap !== true && env2.VO_CODE_RUNNER_ALLOW_UNCAPPED_VENDOR_BILLED !== "1") {
|
|
5663
5926
|
throw new Error(
|
|
@@ -5701,7 +5964,7 @@ function runAgentTask({
|
|
|
5701
5964
|
} catch {
|
|
5702
5965
|
}
|
|
5703
5966
|
let buffer = "";
|
|
5704
|
-
let result = { ok: false, costUsd: null, costBasis, summary: "", lastAgentMessage: 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 };
|
|
5705
5968
|
child.once("spawn", () => {
|
|
5706
5969
|
result = { ...result, executionStarted: true };
|
|
5707
5970
|
Promise.resolve(onSpawn()).catch(() => {
|
|
@@ -5739,6 +6002,8 @@ function runAgentTask({
|
|
|
5739
6002
|
// A budget/turn-capped run's honest last message, kept OUT of summary (see
|
|
5740
6003
|
// claude-result-event.cappedRunLastMessage) and surfaced in the PR body.
|
|
5741
6004
|
lastAgentMessage: cappedRunLastMessage(evt, lastProgress),
|
|
6005
|
+
structuredOutput: Object.hasOwn(evt, "structuredOutput") ? evt.structuredOutput : result.structuredOutput,
|
|
6006
|
+
terminalSubtype: Object.hasOwn(evt, "terminalSubtype") ? evt.terminalSubtype : result.terminalSubtype,
|
|
5742
6007
|
numTurns: evt.numTurns,
|
|
5743
6008
|
// MUST be listed explicitly. This assignment spreads the PREVIOUS
|
|
5744
6009
|
// result and then names each field it carries forward, so anything
|
|
@@ -5820,7 +6085,9 @@ function runAgentTask({
|
|
|
5820
6085
|
hardKill();
|
|
5821
6086
|
return;
|
|
5822
6087
|
}
|
|
5823
|
-
if (decision.delayMs
|
|
6088
|
+
if (decision.delayMs !== null && decision.delayMs !== void 0) {
|
|
6089
|
+
wallTimer = setTimeout(armDeadline, decision.delayMs);
|
|
6090
|
+
}
|
|
5824
6091
|
};
|
|
5825
6092
|
armDeadline();
|
|
5826
6093
|
child.stdout.on("data", (chunk) => {
|
|
@@ -5881,6 +6148,8 @@ var init_claude_runner = __esm({
|
|
|
5881
6148
|
init_claude_stream_event();
|
|
5882
6149
|
init_claude_result_event();
|
|
5883
6150
|
init_claude_auth_check();
|
|
6151
|
+
init_claude_skill_capability();
|
|
6152
|
+
init_agent_auth_attestation();
|
|
5884
6153
|
ClaudeRunner = class {
|
|
5885
6154
|
get enforcesBudgetCap() {
|
|
5886
6155
|
return true;
|
|
@@ -5888,8 +6157,8 @@ var init_claude_runner = __esm({
|
|
|
5888
6157
|
get binary() {
|
|
5889
6158
|
return "claude";
|
|
5890
6159
|
}
|
|
5891
|
-
buildArgs({ permissionMode, maxTurns, model, effort, maxBudgetUsd, researchHarness, toolPolicy } = {}) {
|
|
5892
|
-
return buildClaudeArgs({ permissionMode, maxTurns, model, effort, maxBudgetUsd, researchHarness, toolPolicy });
|
|
6160
|
+
buildArgs({ permissionMode, maxTurns, model, effort, maxBudgetUsd, researchHarness, toolPolicy, structuredOutputSchema } = {}) {
|
|
6161
|
+
return buildClaudeArgs({ permissionMode, maxTurns, model, effort, maxBudgetUsd, researchHarness, toolPolicy, structuredOutputSchema });
|
|
5893
6162
|
}
|
|
5894
6163
|
parseEvent(line) {
|
|
5895
6164
|
return parseStreamEvent(line);
|
|
@@ -5913,6 +6182,9 @@ var init_claude_runner = __esm({
|
|
|
5913
6182
|
async checkAuth() {
|
|
5914
6183
|
return checkClaudeAuth();
|
|
5915
6184
|
}
|
|
6185
|
+
checkSkillCapability({ bin = this.binary, env: env2 = process.env } = {}) {
|
|
6186
|
+
return probeClaudeSkillCapability({ bin, env: env2, freshIdentity: true });
|
|
6187
|
+
}
|
|
5916
6188
|
};
|
|
5917
6189
|
claudeRunner = new ClaudeRunner();
|
|
5918
6190
|
}
|
|
@@ -6096,10 +6368,10 @@ var init_error_message = __esm({
|
|
|
6096
6368
|
});
|
|
6097
6369
|
|
|
6098
6370
|
// ../../scripts/virtual-office/code-runner/codex-runner.mjs
|
|
6099
|
-
import { spawnSync as
|
|
6371
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
6100
6372
|
import { existsSync as existsSync10 } from "node:fs";
|
|
6101
6373
|
import { win32 as win322 } from "node:path";
|
|
6102
|
-
function
|
|
6374
|
+
function isTruthyFlag3(value) {
|
|
6103
6375
|
return ["1", "true", "yes", "on"].includes(String(value ?? "").trim().toLowerCase());
|
|
6104
6376
|
}
|
|
6105
6377
|
function resolveCodexBinary({
|
|
@@ -6211,7 +6483,7 @@ var init_codex_runner = __esm({
|
|
|
6211
6483
|
CODEX_PREFER_LOGIN_ENV = "VO_RUNNER_CODEX_PREFER_LOGIN";
|
|
6212
6484
|
LEGACY_PREFER_LOGIN_ENV = "VO_RUNNER_PREFER_LOGIN";
|
|
6213
6485
|
CodexRunner = class {
|
|
6214
|
-
constructor({ spawn: spawn5 =
|
|
6486
|
+
constructor({ spawn: spawn5 = spawnSync7, resolveBinary = resolveCodexBinary, env: env2 = process.env } = {}) {
|
|
6215
6487
|
this.spawn = spawn5;
|
|
6216
6488
|
this.resolveBinary = resolveBinary;
|
|
6217
6489
|
this.env = env2;
|
|
@@ -6250,7 +6522,7 @@ var init_codex_runner = __esm({
|
|
|
6250
6522
|
};
|
|
6251
6523
|
}
|
|
6252
6524
|
applyAuthEnv(env2 = process.env) {
|
|
6253
|
-
if (
|
|
6525
|
+
if (isTruthyFlag3(env2[CODEX_PREFER_LOGIN_ENV]) || isTruthyFlag3(env2[LEGACY_PREFER_LOGIN_ENV])) {
|
|
6254
6526
|
const out = { ...env2 };
|
|
6255
6527
|
delete out.OPENAI_API_KEY;
|
|
6256
6528
|
delete out.CODEX_API_KEY;
|
|
@@ -6324,7 +6596,7 @@ ${login.stderr || ""}`.trim();
|
|
|
6324
6596
|
});
|
|
6325
6597
|
|
|
6326
6598
|
// ../../scripts/virtual-office/code-runner/cursor-runner.mjs
|
|
6327
|
-
import { spawnSync as
|
|
6599
|
+
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
6328
6600
|
function buildCursorArgs({ model, prompt } = {}) {
|
|
6329
6601
|
const args = ["-p", "--output-format", "stream-json", "--force"];
|
|
6330
6602
|
if (model) {
|
|
@@ -6436,7 +6708,7 @@ var init_cursor_runner = __esm({
|
|
|
6436
6708
|
/** Best-effort: is `cursor-agent` on PATH? Never throws. */
|
|
6437
6709
|
async checkAuth() {
|
|
6438
6710
|
try {
|
|
6439
|
-
const { status, error } =
|
|
6711
|
+
const { status, error } = spawnSync8("cursor-agent", ["--version"], {
|
|
6440
6712
|
shell: false,
|
|
6441
6713
|
windowsHide: true,
|
|
6442
6714
|
timeout: 3e3,
|
|
@@ -7155,9 +7427,9 @@ var init_rate_limit_detector_core = __esm({
|
|
|
7155
7427
|
|
|
7156
7428
|
// ../../scripts/virtual-office/code-runner/rate-limit-resume-state.mjs
|
|
7157
7429
|
import fsp10 from "node:fs/promises";
|
|
7158
|
-
import
|
|
7430
|
+
import path15 from "node:path";
|
|
7159
7431
|
async function atomicWrite(file, content) {
|
|
7160
|
-
await fsp10.mkdir(
|
|
7432
|
+
await fsp10.mkdir(path15.dirname(file), { recursive: true });
|
|
7161
7433
|
const temp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
7162
7434
|
const handle = await fsp10.open(temp, "wx");
|
|
7163
7435
|
try {
|
|
@@ -7218,7 +7490,7 @@ function writeResumeAttempts(file, store) {
|
|
|
7218
7490
|
}
|
|
7219
7491
|
async function acquireLock(lockFile, { now = Date.now, sleep: sleep3 = delay } = {}) {
|
|
7220
7492
|
const deadline = now() + LOCK_WAIT_MS;
|
|
7221
|
-
await fsp10.mkdir(
|
|
7493
|
+
await fsp10.mkdir(path15.dirname(lockFile), { recursive: true });
|
|
7222
7494
|
for (; ; ) {
|
|
7223
7495
|
let handle;
|
|
7224
7496
|
try {
|
|
@@ -7522,7 +7794,7 @@ var init_auto_merge = __esm({
|
|
|
7522
7794
|
});
|
|
7523
7795
|
|
|
7524
7796
|
// ../../scripts/virtual-office/code-runner/pr-overlap-gate.mjs
|
|
7525
|
-
import { spawnSync as
|
|
7797
|
+
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
7526
7798
|
import { existsSync as existsSync11 } from "node:fs";
|
|
7527
7799
|
import { dirname as dirname6, join as join9 } from "node:path";
|
|
7528
7800
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
@@ -7688,14 +7960,14 @@ function parsePorcelainZ(out) {
|
|
|
7688
7960
|
for (let i = 0; i < tokens.length; i += 1) {
|
|
7689
7961
|
const token2 = tokens[i];
|
|
7690
7962
|
if (!token2) continue;
|
|
7691
|
-
const
|
|
7692
|
-
if (
|
|
7963
|
+
const path24 = token2.slice(3);
|
|
7964
|
+
if (path24) files.push(path24);
|
|
7693
7965
|
if (token2[0] === "R" || token2[0] === "C") i += 1;
|
|
7694
7966
|
}
|
|
7695
7967
|
return files;
|
|
7696
7968
|
}
|
|
7697
|
-
function isAgentScratch(
|
|
7698
|
-
const normalized = String(
|
|
7969
|
+
function isAgentScratch(path24) {
|
|
7970
|
+
const normalized = String(path24 || "");
|
|
7699
7971
|
return SCRATCH_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
7700
7972
|
}
|
|
7701
7973
|
var SCRATCH_PATTERNS;
|
|
@@ -7713,7 +7985,7 @@ var init_publish_file_state = __esm({
|
|
|
7713
7985
|
});
|
|
7714
7986
|
|
|
7715
7987
|
// ../../scripts/virtual-office/code-runner/publish.mjs
|
|
7716
|
-
import { spawnSync as
|
|
7988
|
+
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
7717
7989
|
function isMaxTurnsResult(summary) {
|
|
7718
7990
|
return /(^|[^a-z])error[-_ ]?max[-_ ]?turns([^a-z]|$)|max[-_ ]?turns/i.test(String(summary || ""));
|
|
7719
7991
|
}
|
|
@@ -7952,7 +8224,7 @@ var init_executor = __esm({
|
|
|
7952
8224
|
|
|
7953
8225
|
// ../../scripts/virtual-office/code-runner/test-gen-gate.mjs
|
|
7954
8226
|
import fs7 from "node:fs";
|
|
7955
|
-
import
|
|
8227
|
+
import path16 from "node:path";
|
|
7956
8228
|
async function postFailed(client, id, message, result) {
|
|
7957
8229
|
try {
|
|
7958
8230
|
await client.postProgress(id, {
|
|
@@ -7988,7 +8260,7 @@ async function gateTestGenTaskOrFail({ client, id, task, files, worktreeDir, env
|
|
|
7988
8260
|
}
|
|
7989
8261
|
let testSource = "";
|
|
7990
8262
|
try {
|
|
7991
|
-
testSource = fs7.readFileSync(
|
|
8263
|
+
testSource = fs7.readFileSync(path16.join(worktreeDir, testFile), "utf8");
|
|
7992
8264
|
} catch (err) {
|
|
7993
8265
|
await postFailed(client, id, `could not read generated test ${testFile}: ${err.message}`, "gate_test_unreadable");
|
|
7994
8266
|
return true;
|
|
@@ -8038,7 +8310,7 @@ var init_test_gen_gate = __esm({
|
|
|
8038
8310
|
// ../../scripts/virtual-office/code-runner/completion-gate.mjs
|
|
8039
8311
|
import { execFile } from "node:child_process";
|
|
8040
8312
|
import fs8 from "node:fs";
|
|
8041
|
-
import
|
|
8313
|
+
import path17 from "node:path";
|
|
8042
8314
|
function resolveCompletionGate(task) {
|
|
8043
8315
|
const raw = task?.completion_gate;
|
|
8044
8316
|
if (raw === void 0 || raw === null) return null;
|
|
@@ -8076,14 +8348,14 @@ function workspaceFingerprint(worktreeDir, execFileImpl = execFile) {
|
|
|
8076
8348
|
}
|
|
8077
8349
|
function readState(worktreeDir) {
|
|
8078
8350
|
try {
|
|
8079
|
-
return JSON.parse(fs8.readFileSync(
|
|
8351
|
+
return JSON.parse(fs8.readFileSync(path17.join(worktreeDir, COMPLETION_GATE_STATE_FILE), "utf8"));
|
|
8080
8352
|
} catch {
|
|
8081
8353
|
return null;
|
|
8082
8354
|
}
|
|
8083
8355
|
}
|
|
8084
8356
|
function writeState(worktreeDir, state) {
|
|
8085
8357
|
try {
|
|
8086
|
-
fs8.writeFileSync(
|
|
8358
|
+
fs8.writeFileSync(path17.join(worktreeDir, COMPLETION_GATE_STATE_FILE), `${JSON.stringify(state)}
|
|
8087
8359
|
`, "utf8");
|
|
8088
8360
|
} catch {
|
|
8089
8361
|
}
|
|
@@ -9221,7 +9493,7 @@ var init_headless_execution_contract = __esm({
|
|
|
9221
9493
|
});
|
|
9222
9494
|
|
|
9223
9495
|
// ../../scripts/virtual-office/code-runner/skill-catalog.mjs
|
|
9224
|
-
import { readdirSync as readdirSync3, readFileSync as readFileSync8, statSync as
|
|
9496
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync8, statSync as statSync5 } from "node:fs";
|
|
9225
9497
|
import { dirname as dirname7, join as join10 } from "node:path";
|
|
9226
9498
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
9227
9499
|
function parseFrontmatterNameDescription(raw) {
|
|
@@ -9243,12 +9515,12 @@ function parseFrontmatterNameDescription(raw) {
|
|
|
9243
9515
|
}
|
|
9244
9516
|
function isRepoCheckout(dir) {
|
|
9245
9517
|
try {
|
|
9246
|
-
if (!
|
|
9518
|
+
if (!statSync5(join10(dir, ".claude", "skills")).isDirectory()) return false;
|
|
9247
9519
|
} catch {
|
|
9248
9520
|
return false;
|
|
9249
9521
|
}
|
|
9250
9522
|
try {
|
|
9251
|
-
|
|
9523
|
+
statSync5(join10(dir, ".git"));
|
|
9252
9524
|
return true;
|
|
9253
9525
|
} catch {
|
|
9254
9526
|
return false;
|
|
@@ -9274,7 +9546,7 @@ function loadSkillCatalog({ repoRoot: repoRoot2 = resolveDefaultRepoRoot() } = {
|
|
|
9274
9546
|
for (const entry of readdirSync3(skillsDir)) {
|
|
9275
9547
|
const dir = join10(skillsDir, entry);
|
|
9276
9548
|
try {
|
|
9277
|
-
if (!
|
|
9549
|
+
if (!statSync5(dir).isDirectory()) continue;
|
|
9278
9550
|
const parsed = parseFrontmatterNameDescription(
|
|
9279
9551
|
readFileSync8(join10(dir, "SKILL.md"), "utf8")
|
|
9280
9552
|
);
|
|
@@ -9804,7 +10076,7 @@ var init_task_prompt = __esm({
|
|
|
9804
10076
|
import { createHash as createHash5, randomUUID as randomUUID4 } from "node:crypto";
|
|
9805
10077
|
import { chmod, mkdir, mkdtemp, readFile, readdir, realpath, rm, stat, writeFile } from "node:fs/promises";
|
|
9806
10078
|
import os2 from "node:os";
|
|
9807
|
-
import
|
|
10079
|
+
import path18 from "node:path";
|
|
9808
10080
|
function safeTaskToken(taskId) {
|
|
9809
10081
|
return String(taskId || "task").replace(/[^0-9A-Za-z_-]/gu, "_").slice(0, 48) || "task";
|
|
9810
10082
|
}
|
|
@@ -9817,9 +10089,9 @@ function hasGeneratedPrefix(name) {
|
|
|
9817
10089
|
return name.startsWith(DIRECTORY_PREFIX) || name.startsWith(LEGACY_DIRECTORY_PREFIX);
|
|
9818
10090
|
}
|
|
9819
10091
|
function assertGeneratedDirectory(directory, containmentRoot) {
|
|
9820
|
-
const resolvedDirectory =
|
|
9821
|
-
const resolvedRoot =
|
|
9822
|
-
if (
|
|
10092
|
+
const resolvedDirectory = path18.resolve(directory);
|
|
10093
|
+
const resolvedRoot = path18.resolve(containmentRoot);
|
|
10094
|
+
if (path18.dirname(resolvedDirectory) !== resolvedRoot || !hasGeneratedPrefix(path18.basename(resolvedDirectory))) {
|
|
9823
10095
|
throw new Error("refusing to clean an unverified task-attachment directory");
|
|
9824
10096
|
}
|
|
9825
10097
|
return resolvedDirectory;
|
|
@@ -9828,7 +10100,7 @@ async function resolveContainmentRoot(worktreeDir) {
|
|
|
9828
10100
|
if (typeof worktreeDir !== "string" || !worktreeDir.trim()) {
|
|
9829
10101
|
throw new Error("refusing to materialize task attachments outside an agent-readable worktree: no worktreeDir given");
|
|
9830
10102
|
}
|
|
9831
|
-
const root =
|
|
10103
|
+
const root = path18.resolve(worktreeDir);
|
|
9832
10104
|
const stats = await stat(root).catch(() => null);
|
|
9833
10105
|
if (!stats?.isDirectory()) {
|
|
9834
10106
|
throw new Error(`refusing to materialize task attachments: agent worktree root is not a directory (${root})`);
|
|
@@ -9837,15 +10109,15 @@ async function resolveContainmentRoot(worktreeDir) {
|
|
|
9837
10109
|
}
|
|
9838
10110
|
async function createAttachmentDirectory(taskId, containmentRoot) {
|
|
9839
10111
|
const root = await resolveContainmentRoot(containmentRoot);
|
|
9840
|
-
const directory = await mkdtemp(
|
|
10112
|
+
const directory = await mkdtemp(path18.join(root, `${DIRECTORY_PREFIX}${safeTaskToken(taskId)}-`));
|
|
9841
10113
|
const [realRoot, realDirectory] = await Promise.all([realpath(root), realpath(directory)]);
|
|
9842
|
-
if (
|
|
10114
|
+
if (path18.dirname(realDirectory) !== realRoot) {
|
|
9843
10115
|
await rm(directory, { recursive: true, force: true }).catch(() => void 0);
|
|
9844
10116
|
throw new Error("task-attachment directory escaped the agent worktree root");
|
|
9845
10117
|
}
|
|
9846
|
-
await writeFile(
|
|
9847
|
-
const marker = JSON.stringify({ owner: MARKER_OWNER, token: randomUUID4(), directory:
|
|
9848
|
-
await writeFile(
|
|
10118
|
+
await writeFile(path18.join(directory, GITIGNORE_FILE), GITIGNORE_BODY, { encoding: "utf8", mode: 384 });
|
|
10119
|
+
const marker = JSON.stringify({ owner: MARKER_OWNER, token: randomUUID4(), directory: path18.basename(directory), created_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
10120
|
+
await writeFile(path18.join(directory, MARKER_FILE), marker, { encoding: "utf8", mode: 384 });
|
|
9849
10121
|
return { directory, marker, root, cleaned: false };
|
|
9850
10122
|
}
|
|
9851
10123
|
async function cleanupGeneratedDirectory(state) {
|
|
@@ -9859,7 +10131,7 @@ async function cleanupGeneratedDirectory(state) {
|
|
|
9859
10131
|
state.cleaned = true;
|
|
9860
10132
|
return;
|
|
9861
10133
|
}
|
|
9862
|
-
const marker = await readFile(
|
|
10134
|
+
const marker = await readFile(path18.join(directory, MARKER_FILE), "utf8").catch(() => "");
|
|
9863
10135
|
if (marker !== state.marker) throw new Error("refusing to clean a task-attachment directory without its exact marker");
|
|
9864
10136
|
await rm(directory, { recursive: true, force: true });
|
|
9865
10137
|
state.cleaned = true;
|
|
@@ -9878,7 +10150,7 @@ async function sweepStaleTaskAttachmentDirectories({
|
|
|
9878
10150
|
now = Date.now(),
|
|
9879
10151
|
maxAgeMs = DEFAULT_STALE_AGE_MS
|
|
9880
10152
|
} = {}) {
|
|
9881
|
-
const root =
|
|
10153
|
+
const root = path18.resolve(tempRoot);
|
|
9882
10154
|
if (!Number.isFinite(maxAgeMs) || maxAgeMs <= 0) throw new Error("stale attachment age must be positive");
|
|
9883
10155
|
const entries = await readdir(root, { withFileTypes: true }).catch((error) => {
|
|
9884
10156
|
if (error?.code === "ENOENT") return [];
|
|
@@ -9887,8 +10159,8 @@ async function sweepStaleTaskAttachmentDirectories({
|
|
|
9887
10159
|
let removed = 0;
|
|
9888
10160
|
for (const entry of entries) {
|
|
9889
10161
|
if (!entry.isDirectory() || !hasGeneratedPrefix(entry.name)) continue;
|
|
9890
|
-
const directory = assertGeneratedDirectory(
|
|
9891
|
-
const markerRaw = await readFile(
|
|
10162
|
+
const directory = assertGeneratedDirectory(path18.join(root, entry.name), root);
|
|
10163
|
+
const markerRaw = await readFile(path18.join(directory, MARKER_FILE), "utf8").catch(() => "");
|
|
9892
10164
|
const marker = parseOwnedMarker(markerRaw, entry.name);
|
|
9893
10165
|
if (!marker) continue;
|
|
9894
10166
|
const directoryStat = await stat(directory);
|
|
@@ -9945,8 +10217,8 @@ async function materializeTaskAttachments(client, task, { worktreeDir } = {}) {
|
|
|
9945
10217
|
const sha2562 = createHash5("sha256").update(content).digest("hex");
|
|
9946
10218
|
if (sha2562 !== ref.sha256) throw new Error(`attachment ${ref.attachment_id} sha256 mismatch`);
|
|
9947
10219
|
const name = sanitizeTaskAttachmentName(ref.name, index);
|
|
9948
|
-
const filePath =
|
|
9949
|
-
if (
|
|
10220
|
+
const filePath = path18.resolve(state.directory, name);
|
|
10221
|
+
if (path18.dirname(filePath) !== state.directory) throw new Error(`attachment ${ref.attachment_id} resolved outside its task directory`);
|
|
9950
10222
|
await writeFile(filePath, content, { flag: "wx", mode: 384 });
|
|
9951
10223
|
await chmod(filePath, 384);
|
|
9952
10224
|
files.push({ attachmentId: ref.attachment_id, name, mime: ref.mime, sizeBytes: ref.size_bytes, sha256: sha2562, path: filePath });
|
|
@@ -10031,9 +10303,9 @@ async function readSpool(spoolDir = SPOOL_DIR) {
|
|
|
10031
10303
|
}
|
|
10032
10304
|
return out;
|
|
10033
10305
|
}
|
|
10034
|
-
async function readCloudMap(
|
|
10306
|
+
async function readCloudMap(path24) {
|
|
10035
10307
|
try {
|
|
10036
|
-
return JSON.parse(await readFile2(
|
|
10308
|
+
return JSON.parse(await readFile2(path24, "utf8"));
|
|
10037
10309
|
} catch {
|
|
10038
10310
|
return {};
|
|
10039
10311
|
}
|
|
@@ -10334,9 +10606,9 @@ function backoffMs(streak, baseMs) {
|
|
|
10334
10606
|
if (streak <= 0) return 0;
|
|
10335
10607
|
return Math.min(baseMs * 2 ** Math.min(streak - 1, 20), MAX_BACKOFF_MS);
|
|
10336
10608
|
}
|
|
10337
|
-
async function loadState(
|
|
10609
|
+
async function loadState(path24) {
|
|
10338
10610
|
try {
|
|
10339
|
-
const parsed = JSON.parse(await readFile3(
|
|
10611
|
+
const parsed = JSON.parse(await readFile3(path24, "utf8"));
|
|
10340
10612
|
if (parsed && typeof parsed === "object" && Number.isInteger(parsed.byte_offset) && parsed.byte_offset >= 0) {
|
|
10341
10613
|
return { ...parsed, byte_offset: parsed.byte_offset };
|
|
10342
10614
|
}
|
|
@@ -10344,15 +10616,15 @@ async function loadState(path23) {
|
|
|
10344
10616
|
}
|
|
10345
10617
|
return { byte_offset: 0, last_event_id: null, forwarded_total: 0, rejected_total: 0, rejected_event_ids: [] };
|
|
10346
10618
|
}
|
|
10347
|
-
async function saveState(
|
|
10348
|
-
await mkdir2(dirname9(
|
|
10349
|
-
await writeFile3(
|
|
10619
|
+
async function saveState(path24, state) {
|
|
10620
|
+
await mkdir2(dirname9(path24), { recursive: true });
|
|
10621
|
+
await writeFile3(path24, JSON.stringify(state, null, 2), "utf8");
|
|
10350
10622
|
}
|
|
10351
|
-
async function readNewBytes(
|
|
10352
|
-
const st = await stat2(
|
|
10623
|
+
async function readNewBytes(path24, offset, max) {
|
|
10624
|
+
const st = await stat2(path24);
|
|
10353
10625
|
if (st.size <= offset) return { buf: Buffer.alloc(0), size: st.size };
|
|
10354
10626
|
const length = Math.min(st.size - offset, max);
|
|
10355
|
-
const fh = await open(
|
|
10627
|
+
const fh = await open(path24, "r");
|
|
10356
10628
|
try {
|
|
10357
10629
|
const buf = Buffer.alloc(length);
|
|
10358
10630
|
const { bytesRead } = await fh.read(buf, 0, length, offset);
|
|
@@ -10603,6 +10875,10 @@ var init_telemetry_forwarder = __esm({
|
|
|
10603
10875
|
});
|
|
10604
10876
|
|
|
10605
10877
|
// ../../scripts/virtual-office/code-runner/loop-ticks.mjs
|
|
10878
|
+
function supportedTaskKindsFor(availableAgents) {
|
|
10879
|
+
const claude = Array.isArray(availableAgents) ? availableAgents.find((row) => row?.agent === "claude") : null;
|
|
10880
|
+
return claude?.installed === true && claude.authenticated === true && claude.skill_capable === true ? RUNNER_SUPPORTED_TASK_KINDS : RUNNER_SUPPORTED_TASK_KINDS.filter((kind) => kind !== "skill");
|
|
10881
|
+
}
|
|
10606
10882
|
function makeLoopTicks({
|
|
10607
10883
|
client,
|
|
10608
10884
|
cfg,
|
|
@@ -10636,6 +10912,16 @@ function makeLoopTicks({
|
|
|
10636
10912
|
getAgentAvailability = () => null,
|
|
10637
10913
|
// Cached account-usage provider (account-usage.mjs); [] omits the field.
|
|
10638
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,
|
|
10639
10925
|
// Injectable for tests; default to the real scheduler + wall clock.
|
|
10640
10926
|
runResumeScheduler = runScheduler,
|
|
10641
10927
|
now: nowFn = () => Date.now(),
|
|
@@ -10736,10 +11022,17 @@ function makeLoopTicks({
|
|
|
10736
11022
|
...servedOperators.length > 0 ? { servedOperators } : {},
|
|
10737
11023
|
...Array.isArray(availableAgents) && availableAgents.length > 0 ? { availableAgents } : {},
|
|
10738
11024
|
...Array.isArray(accountUsage) && accountUsage.length > 0 ? { accountUsage } : {},
|
|
11025
|
+
...env2.VO_HOST_HEALTH_HEARTBEAT === "1" && getHostHealth() ? { hostHealth: getHostHealth() } : {},
|
|
10739
11026
|
uptimeSec: Math.floor(process.uptime()),
|
|
10740
11027
|
activeTasks: getActive(),
|
|
10741
11028
|
maxConcurrency: cfg.maxConcurrency,
|
|
10742
|
-
supportedTaskKinds:
|
|
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),
|
|
10743
11036
|
...capacityFields,
|
|
10744
11037
|
...localModelFields,
|
|
10745
11038
|
...preparedJobFields
|
|
@@ -10771,6 +11064,7 @@ var init_loop_ticks = __esm({
|
|
|
10771
11064
|
init_session_spool_forwarder();
|
|
10772
11065
|
init_rate_limit_resume_scheduler();
|
|
10773
11066
|
init_telemetry_forwarder();
|
|
11067
|
+
init_agent_auth_attestation();
|
|
10774
11068
|
HEARTBEAT_MS = 6e4;
|
|
10775
11069
|
DEFAULT_RESUME_SCHEDULE_SEC = 300;
|
|
10776
11070
|
RUNNER_SUPPORTED_TASK_KINDS = Object.freeze(["code", "inference", "skill"]);
|
|
@@ -10899,7 +11193,7 @@ function resolveAgentClaimContext(provider, defaultAgent) {
|
|
|
10899
11193
|
async function collectAgentAvailability({
|
|
10900
11194
|
agents = listAgents(),
|
|
10901
11195
|
runnerFor,
|
|
10902
|
-
probeTimeoutMs =
|
|
11196
|
+
probeTimeoutMs = PROBE_TIMEOUT_MS2
|
|
10903
11197
|
} = {}) {
|
|
10904
11198
|
const probes = agents.map(async (agent) => {
|
|
10905
11199
|
const degraded = { agent, installed: false, authenticated: false };
|
|
@@ -10917,6 +11211,7 @@ async function collectAgentAvailability({
|
|
|
10917
11211
|
installed,
|
|
10918
11212
|
authenticated,
|
|
10919
11213
|
...typeof r?.version === "string" && r.version ? { version: r.version } : {},
|
|
11214
|
+
...typeof r?.skillCapable === "boolean" ? { skill_capable: r.skillCapable } : {},
|
|
10920
11215
|
// Omitted when unknown, which is what an older daemon's silence already
|
|
10921
11216
|
// means — the control-plane schema resolves BOTH to 'unknown'. Never
|
|
10922
11217
|
// invent a tier to fill the gap.
|
|
@@ -10977,7 +11272,7 @@ function makeAgentAvailabilityProvider({
|
|
|
10977
11272
|
}
|
|
10978
11273
|
};
|
|
10979
11274
|
}
|
|
10980
|
-
var DEFAULT_TTL_MS,
|
|
11275
|
+
var DEFAULT_TTL_MS, PROBE_TIMEOUT_MS2;
|
|
10981
11276
|
var init_agent_availability = __esm({
|
|
10982
11277
|
"../../scripts/virtual-office/code-runner/agent-availability.mjs"() {
|
|
10983
11278
|
"use strict";
|
|
@@ -10985,7 +11280,7 @@ var init_agent_availability = __esm({
|
|
|
10985
11280
|
init_agent_auth_probe_process();
|
|
10986
11281
|
init_agent_auth_tier();
|
|
10987
11282
|
DEFAULT_TTL_MS = 5 * 60 * 1e3;
|
|
10988
|
-
|
|
11283
|
+
PROBE_TIMEOUT_MS2 = 1e4;
|
|
10989
11284
|
}
|
|
10990
11285
|
});
|
|
10991
11286
|
|
|
@@ -11447,10 +11742,10 @@ function formatShadowLogLine(record) {
|
|
|
11447
11742
|
const loud = record.unexplained_fields?.length ? "!! " : "";
|
|
11448
11743
|
return `${loud}[prepared-job-shadow] ${parts.join(" ")}`;
|
|
11449
11744
|
}
|
|
11450
|
-
function appendShadowRecord(record, { path:
|
|
11745
|
+
function appendShadowRecord(record, { path: path24 = PREPARED_JOB_SHADOW_SINK, append = appendFileSync, mkdir: mkdir5 = mkdirSync8 } = {}) {
|
|
11451
11746
|
try {
|
|
11452
|
-
mkdir5(dirname10(
|
|
11453
|
-
append(
|
|
11747
|
+
mkdir5(dirname10(path24), { recursive: true });
|
|
11748
|
+
append(path24, `${JSON.stringify(record)}
|
|
11454
11749
|
`, "utf8");
|
|
11455
11750
|
return true;
|
|
11456
11751
|
} catch {
|
|
@@ -11770,7 +12065,7 @@ var init_shared = __esm({
|
|
|
11770
12065
|
// ../../scripts/virtual-office/code-runner/account-usage/claude.mjs
|
|
11771
12066
|
import fs10 from "node:fs";
|
|
11772
12067
|
import os3 from "node:os";
|
|
11773
|
-
import
|
|
12068
|
+
import path19 from "node:path";
|
|
11774
12069
|
function fileCaptureTime(filePath, explicit, statFn) {
|
|
11775
12070
|
if (typeof explicit === "string" && explicit) return explicit;
|
|
11776
12071
|
try {
|
|
@@ -11784,7 +12079,7 @@ function usageBaseUrl(env2 = process.env) {
|
|
|
11784
12079
|
return String(raw).replace(/\/+$/, "");
|
|
11785
12080
|
}
|
|
11786
12081
|
function readOAuthToken({ homeDir = os3.homedir(), read = readJson2, now = Date.now() } = {}) {
|
|
11787
|
-
const creds = read(
|
|
12082
|
+
const creds = read(path19.join(homeDir, ".claude", ".credentials.json"));
|
|
11788
12083
|
const oauth = creds && typeof creds === "object" ? creds.claudeAiOauth : null;
|
|
11789
12084
|
if (!oauth || typeof oauth !== "object") return null;
|
|
11790
12085
|
const token2 = typeof oauth.accessToken === "string" ? oauth.accessToken.trim() : "";
|
|
@@ -11794,7 +12089,7 @@ function readOAuthToken({ homeDir = os3.homedir(), read = readJson2, now = Date.
|
|
|
11794
12089
|
return token2;
|
|
11795
12090
|
}
|
|
11796
12091
|
function readAccountId({ homeDir = os3.homedir(), read = readJson2 } = {}) {
|
|
11797
|
-
const cfg = read(
|
|
12092
|
+
const cfg = read(path19.join(homeDir, ".claude.json"));
|
|
11798
12093
|
const account = cfg && typeof cfg === "object" ? cfg.oauthAccount : null;
|
|
11799
12094
|
return account && typeof account.accountUuid === "string" ? account.accountUuid : null;
|
|
11800
12095
|
}
|
|
@@ -11904,7 +12199,7 @@ function readClaudeFileUsage({
|
|
|
11904
12199
|
if (age === null || age > MAX_FILE_AGE_MS) return null;
|
|
11905
12200
|
return row;
|
|
11906
12201
|
};
|
|
11907
|
-
const statusPath =
|
|
12202
|
+
const statusPath = path19.join(homeDir, ".claude", "claude-usage.json");
|
|
11908
12203
|
const status = read(statusPath);
|
|
11909
12204
|
if (status && (status.seven_day || status.five_hour)) {
|
|
11910
12205
|
const row = fresh(makeUsageRow({
|
|
@@ -11919,7 +12214,7 @@ function readClaudeFileUsage({
|
|
|
11919
12214
|
}));
|
|
11920
12215
|
if (row) return row;
|
|
11921
12216
|
}
|
|
11922
|
-
const weeklyPath =
|
|
12217
|
+
const weeklyPath = path19.join(homeDir, ".claude", "claude-weekly-usage.json");
|
|
11923
12218
|
const weekly = read(weeklyPath);
|
|
11924
12219
|
if (weekly) {
|
|
11925
12220
|
const row = fresh(makeUsageRow({
|
|
@@ -12733,11 +13028,11 @@ var init_watcher_adoption = __esm({
|
|
|
12733
13028
|
// ../../scripts/virtual-office/code-runner/watcher-github-token.mjs
|
|
12734
13029
|
function makeWatcherTokenProvider(client, { required = true, allowAmbient = false, now = () => Date.now(), log: log2 = () => {
|
|
12735
13030
|
} } = {}) {
|
|
12736
|
-
const
|
|
13031
|
+
const cache2 = /* @__PURE__ */ new Map();
|
|
12737
13032
|
let ciUnreadableLoggedAt = null;
|
|
12738
13033
|
return async (repo) => {
|
|
12739
13034
|
const key = String(repo).toLowerCase();
|
|
12740
|
-
const prior =
|
|
13035
|
+
const prior = cache2.get(key);
|
|
12741
13036
|
if (prior && now() - prior.at < 45 * 60 * 1e3) return prior.token;
|
|
12742
13037
|
const result = await client.getInstallationToken({ required, readOnly: true, repo });
|
|
12743
13038
|
if (!result?.token) {
|
|
@@ -12748,7 +13043,7 @@ function makeWatcherTokenProvider(client, { required = true, allowAmbient = fals
|
|
|
12748
13043
|
ciUnreadableLoggedAt = now();
|
|
12749
13044
|
log2(`watch: the plane minted a read token for ${repo} WITHOUT CI read (ci_readable=false \u2014 the GitHub App installation has not accepted checks:read/statuses:read); PR CI stays unreadable until the operator accepts the App permission update`);
|
|
12750
13045
|
}
|
|
12751
|
-
|
|
13046
|
+
cache2.set(key, { token: result.token, at: now() });
|
|
12752
13047
|
return result.token;
|
|
12753
13048
|
};
|
|
12754
13049
|
}
|
|
@@ -12914,7 +13209,7 @@ function noteCiViaRest(log2) {
|
|
|
12914
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)");
|
|
12915
13210
|
}
|
|
12916
13211
|
async function readCommitCiViaRest(repo, sha, { run, env: env2 }) {
|
|
12917
|
-
const api = async (
|
|
13212
|
+
const api = async (path24) => JSON.parse(await run("gh", ["api", path24], { timeout: 3e4, env: env2 }) || "{}");
|
|
12918
13213
|
const rollup = [];
|
|
12919
13214
|
let total = null;
|
|
12920
13215
|
for (let page = 1; page <= REST_MAX_PAGES && (total === null || rollup.length < total); page += 1) {
|
|
@@ -13578,9 +13873,9 @@ function buildControlHandler({ getStatus, requestStop, allowedOrigin }) {
|
|
|
13578
13873
|
res.end();
|
|
13579
13874
|
return;
|
|
13580
13875
|
}
|
|
13581
|
-
const
|
|
13876
|
+
const path24 = String(req.url || "").split("?")[0];
|
|
13582
13877
|
res.setHeader("content-type", "application/json");
|
|
13583
|
-
if (req.method === "GET" &&
|
|
13878
|
+
if (req.method === "GET" && path24 === "/status") {
|
|
13584
13879
|
let status;
|
|
13585
13880
|
try {
|
|
13586
13881
|
status = getStatus();
|
|
@@ -13591,7 +13886,7 @@ function buildControlHandler({ getStatus, requestStop, allowedOrigin }) {
|
|
|
13591
13886
|
res.end(JSON.stringify({ ok: true, ...status }));
|
|
13592
13887
|
return;
|
|
13593
13888
|
}
|
|
13594
|
-
if (req.method === "POST" &&
|
|
13889
|
+
if (req.method === "POST" && path24 === "/stop") {
|
|
13595
13890
|
if (!isControlOriginAllowed(req.headers.origin, allowedOrigin) || !req.headers["x-vo-control"]) {
|
|
13596
13891
|
res.statusCode = 403;
|
|
13597
13892
|
res.end(JSON.stringify({ ok: false, error: "forbidden" }));
|
|
@@ -13785,22 +14080,22 @@ var init_effort_mode_config = __esm({
|
|
|
13785
14080
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
13786
14081
|
import fs11 from "node:fs";
|
|
13787
14082
|
import os4 from "node:os";
|
|
13788
|
-
import
|
|
14083
|
+
import path20 from "node:path";
|
|
13789
14084
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
13790
14085
|
function userCacheRoot() {
|
|
13791
14086
|
try {
|
|
13792
14087
|
const home = os4.homedir();
|
|
13793
|
-
if (home) return
|
|
14088
|
+
if (home) return path20.join(home, ".claude");
|
|
13794
14089
|
} catch {
|
|
13795
14090
|
}
|
|
13796
|
-
return
|
|
14091
|
+
return path20.join(os4.tmpdir(), `vo-model-registry-${randomUUID7()}`);
|
|
13797
14092
|
}
|
|
13798
14093
|
function resolveCacheBaseDir(env2 = process.env, moduleDir = __dirname) {
|
|
13799
14094
|
if (env2.VO_MODEL_REGISTRY_CACHE_DIR) return env2.VO_MODEL_REGISTRY_CACHE_DIR;
|
|
13800
14095
|
if (env2.VO_RUNNER_RUNTIME_ROOT) return env2.VO_RUNNER_RUNTIME_ROOT;
|
|
13801
|
-
const segments = moduleDir.split(
|
|
14096
|
+
const segments = moduleDir.split(path20.sep);
|
|
13802
14097
|
const isRepoCheckout2 = segments.at(-1) === "virtual-office" && segments.at(-2) === "scripts";
|
|
13803
|
-
return isRepoCheckout2 ?
|
|
14098
|
+
return isRepoCheckout2 ? path20.resolve(moduleDir, "..", "..") : userCacheRoot();
|
|
13804
14099
|
}
|
|
13805
14100
|
function uniqueModels(models = []) {
|
|
13806
14101
|
return [...new Set(models.map((model) => String(model || "").trim()).filter(Boolean))];
|
|
@@ -13923,7 +14218,7 @@ function readCache(cacheFile = DEFAULT_CACHE_FILE, nowMs = Date.now(), ttlMs = D
|
|
|
13923
14218
|
}
|
|
13924
14219
|
}
|
|
13925
14220
|
function writeCache(cacheFile = DEFAULT_CACHE_FILE, payload) {
|
|
13926
|
-
fs11.mkdirSync(
|
|
14221
|
+
fs11.mkdirSync(path20.dirname(cacheFile), { recursive: true });
|
|
13927
14222
|
fs11.writeFileSync(cacheFile, JSON.stringify(payload, null, 2));
|
|
13928
14223
|
}
|
|
13929
14224
|
async function fetchRegistryCatalog({
|
|
@@ -13981,13 +14276,13 @@ var __dirname, DEFAULT_CACHE_DIR, DEFAULT_CACHE_FILE, DEFAULT_TTL_MS3, ANTHROPIC
|
|
|
13981
14276
|
var init_model_registry = __esm({
|
|
13982
14277
|
"../../scripts/virtual-office/model-registry.mjs"() {
|
|
13983
14278
|
"use strict";
|
|
13984
|
-
__dirname =
|
|
13985
|
-
DEFAULT_CACHE_DIR =
|
|
14279
|
+
__dirname = path20.dirname(fileURLToPath6(import.meta.url));
|
|
14280
|
+
DEFAULT_CACHE_DIR = path20.join(
|
|
13986
14281
|
resolveCacheBaseDir(),
|
|
13987
14282
|
".virtual-office-cache",
|
|
13988
14283
|
"model-registry"
|
|
13989
14284
|
);
|
|
13990
|
-
DEFAULT_CACHE_FILE =
|
|
14285
|
+
DEFAULT_CACHE_FILE = path20.join(DEFAULT_CACHE_DIR, "catalog.json");
|
|
13991
14286
|
DEFAULT_TTL_MS3 = 60 * 60 * 1e3;
|
|
13992
14287
|
ANTHROPIC_API_VERSION = "2023-06-01";
|
|
13993
14288
|
FAMILY_DEFINITIONS = {
|
|
@@ -14626,18 +14921,18 @@ function operatorTierToRung(tier, difficulty, thresholds) {
|
|
|
14626
14921
|
if (tier === "best" && difficulty >= thresholds.rungBounds.R5) return "R5";
|
|
14627
14922
|
return base;
|
|
14628
14923
|
}
|
|
14629
|
-
function readCodexModelsCache({ path:
|
|
14924
|
+
function readCodexModelsCache({ path: path24 = DEFAULT_CODEX_MODELS_CACHE, read = readFileSync9 } = {}) {
|
|
14630
14925
|
try {
|
|
14631
|
-
const parsed = JSON.parse(read(
|
|
14926
|
+
const parsed = JSON.parse(read(path24, "utf8"));
|
|
14632
14927
|
return Array.isArray(parsed?.models) ? parsed : null;
|
|
14633
14928
|
} catch {
|
|
14634
14929
|
return null;
|
|
14635
14930
|
}
|
|
14636
14931
|
}
|
|
14637
|
-
function clampCodexEffort(effort,
|
|
14932
|
+
function clampCodexEffort(effort, cache2) {
|
|
14638
14933
|
if (!effort) return { effort: null, degraded: false };
|
|
14639
14934
|
const supported = /* @__PURE__ */ new Set();
|
|
14640
|
-
for (const model of
|
|
14935
|
+
for (const model of cache2?.models || []) {
|
|
14641
14936
|
if (model?.visibility === "hide") continue;
|
|
14642
14937
|
for (const lvl of model?.supported_reasoning_levels || []) {
|
|
14643
14938
|
if (lvl?.effort) supported.add(lvl.effort);
|
|
@@ -14885,15 +15180,15 @@ function formatDecisionReason(decision, maxLen = 480) {
|
|
|
14885
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("; ")}`;
|
|
14886
15181
|
return s.length > maxLen ? `${s.slice(0, maxLen - 1)}\u2026` : s;
|
|
14887
15182
|
}
|
|
14888
|
-
function appendDecisionFallback(decision, { path:
|
|
15183
|
+
function appendDecisionFallback(decision, { path: path24 = DECISION_FALLBACK_PATH, append = appendFileSync2, mkdir: mkdir5 = mkdirSync9, task, thresholds, roleCostInputs } = {}) {
|
|
14889
15184
|
try {
|
|
14890
|
-
mkdir5(dirname12(
|
|
14891
|
-
append(
|
|
15185
|
+
mkdir5(dirname12(path24), { recursive: true });
|
|
15186
|
+
append(path24, `${JSON.stringify(decision)}
|
|
14892
15187
|
`, "utf8");
|
|
14893
15188
|
if (isRouterDecision(decision)) {
|
|
14894
15189
|
try {
|
|
14895
15190
|
const records = buildShadowRecords({ decision, task, thresholds: thresholds || loadThresholds(), roleCostInputs });
|
|
14896
|
-
for (const record of records) append(
|
|
15191
|
+
for (const record of records) append(path24, `${JSON.stringify(record)}
|
|
14897
15192
|
`, "utf8");
|
|
14898
15193
|
} catch {
|
|
14899
15194
|
}
|
|
@@ -15425,6 +15720,17 @@ var init_agent_process_env = __esm({
|
|
|
15425
15720
|
// escape hatch inert — an operator who set it still got the subscription.
|
|
15426
15721
|
"VO_RUNNER_PREFER_KEY",
|
|
15427
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",
|
|
15428
15734
|
// The swarm tier binding (SWARM_TIER_BINDING_ENV in
|
|
15429
15735
|
// packages/vo-mcp/src/swarm/tier-binding.ts). A fan-out resolves its billing
|
|
15430
15736
|
// tier ONCE at admission and exports the binding so every subagent inherits
|
|
@@ -16215,6 +16521,53 @@ var init_terminal_delivery = __esm({
|
|
|
16215
16521
|
}
|
|
16216
16522
|
});
|
|
16217
16523
|
|
|
16524
|
+
// ../../scripts/virtual-office/code-runner/skill-result-json-schema.mjs
|
|
16525
|
+
function buildSkillResultJsonSchema({ expectedSkill, producedByAgent }) {
|
|
16526
|
+
return {
|
|
16527
|
+
type: "object",
|
|
16528
|
+
additionalProperties: false,
|
|
16529
|
+
properties: {
|
|
16530
|
+
schema_version: { const: 1 },
|
|
16531
|
+
skill: { const: expectedSkill },
|
|
16532
|
+
outcome: { enum: ["findings", "no_findings", "refused"] },
|
|
16533
|
+
findings: {
|
|
16534
|
+
type: "array",
|
|
16535
|
+
maxItems: MAX_FINDINGS,
|
|
16536
|
+
items: {
|
|
16537
|
+
type: "object",
|
|
16538
|
+
additionalProperties: false,
|
|
16539
|
+
properties: {
|
|
16540
|
+
claim: { type: "string", minLength: 1, maxLength: 500 },
|
|
16541
|
+
evidence: { type: "string", minLength: 1, maxLength: 2e3 },
|
|
16542
|
+
source: { type: "string", maxLength: 500 },
|
|
16543
|
+
confidence: { enum: ["high", "medium", "low"] }
|
|
16544
|
+
},
|
|
16545
|
+
required: ["claim", "evidence", "confidence"]
|
|
16546
|
+
}
|
|
16547
|
+
},
|
|
16548
|
+
findings_truncated: { type: "boolean" },
|
|
16549
|
+
summary: { type: "string", minLength: 1, maxLength: 2e3 },
|
|
16550
|
+
produced_by_agent: { const: producedByAgent }
|
|
16551
|
+
},
|
|
16552
|
+
required: [
|
|
16553
|
+
"schema_version",
|
|
16554
|
+
"skill",
|
|
16555
|
+
"outcome",
|
|
16556
|
+
"findings",
|
|
16557
|
+
"findings_truncated",
|
|
16558
|
+
"summary",
|
|
16559
|
+
"produced_by_agent"
|
|
16560
|
+
]
|
|
16561
|
+
};
|
|
16562
|
+
}
|
|
16563
|
+
var MAX_FINDINGS;
|
|
16564
|
+
var init_skill_result_json_schema = __esm({
|
|
16565
|
+
"../../scripts/virtual-office/code-runner/skill-result-json-schema.mjs"() {
|
|
16566
|
+
"use strict";
|
|
16567
|
+
MAX_FINDINGS = 50;
|
|
16568
|
+
}
|
|
16569
|
+
});
|
|
16570
|
+
|
|
16218
16571
|
// ../../scripts/virtual-office/code-runner/skill-task-runner.mjs
|
|
16219
16572
|
import { createHash as createHash9 } from "node:crypto";
|
|
16220
16573
|
import { mkdtemp as mkdtemp2, rm as rm2 } from "node:fs/promises";
|
|
@@ -16275,13 +16628,7 @@ function skillResultPayloadSha256(result) {
|
|
|
16275
16628
|
};
|
|
16276
16629
|
return createHash9("sha256").update(JSON.stringify(payload), "utf8").digest("hex");
|
|
16277
16630
|
}
|
|
16278
|
-
function
|
|
16279
|
-
let value;
|
|
16280
|
-
try {
|
|
16281
|
-
value = JSON.parse(String(text ?? "").trim());
|
|
16282
|
-
} catch {
|
|
16283
|
-
throw new Error("skill agent did not return one strict JSON result");
|
|
16284
|
-
}
|
|
16631
|
+
function parseSkillResultValue(value, { expectedSkill, producedByAgent }) {
|
|
16285
16632
|
if (!exactKeys(value, ["schema_version", "skill", "outcome", "findings", "findings_truncated", "summary", "produced_by_agent"])) {
|
|
16286
16633
|
throw new Error("skill result shape is invalid");
|
|
16287
16634
|
}
|
|
@@ -16290,7 +16637,7 @@ function parseSkillResult(text, { expectedSkill, producedByAgent }) {
|
|
|
16290
16637
|
if (value.produced_by_agent !== producedByAgent) throw new Error("skill result agent attribution is invalid");
|
|
16291
16638
|
if (typeof value.findings_truncated !== "boolean") throw new Error("skill result truncation flag is invalid");
|
|
16292
16639
|
boundedString(value.summary, 1, 2e3, "skill result summary");
|
|
16293
|
-
if (!Array.isArray(value.findings) || value.findings.length >
|
|
16640
|
+
if (!Array.isArray(value.findings) || value.findings.length > MAX_FINDINGS2) throw new Error("skill result findings are invalid");
|
|
16294
16641
|
if (value.outcome === "findings" ? value.findings.length === 0 : value.findings.length > 0) {
|
|
16295
16642
|
throw new Error("skill result findings do not match its outcome");
|
|
16296
16643
|
}
|
|
@@ -16307,6 +16654,27 @@ function parseSkillResult(text, { expectedSkill, producedByAgent }) {
|
|
|
16307
16654
|
}
|
|
16308
16655
|
return value;
|
|
16309
16656
|
}
|
|
16657
|
+
function assertSkillRunWithinDispatch(run, { maxTurns, maxBudgetUsd } = {}) {
|
|
16658
|
+
if (run?.terminalSubtype !== "success") {
|
|
16659
|
+
throw new Error(`skill agent terminal subtype was not success (${run?.terminalSubtype || "unknown"})`);
|
|
16660
|
+
}
|
|
16661
|
+
if (Number.isInteger(maxTurns) && maxTurns > 0) {
|
|
16662
|
+
if (!Number.isInteger(run?.numTurns) || run.numTurns < 0) {
|
|
16663
|
+
throw new Error("skill agent did not report valid turn usage for the active ceiling");
|
|
16664
|
+
}
|
|
16665
|
+
if (run.numTurns > maxTurns) {
|
|
16666
|
+
throw new Error(`skill agent exceeded the turn ceiling (${run.numTurns} > ${maxTurns})`);
|
|
16667
|
+
}
|
|
16668
|
+
}
|
|
16669
|
+
if (typeof maxBudgetUsd === "number" && Number.isFinite(maxBudgetUsd) && maxBudgetUsd > 0) {
|
|
16670
|
+
if (typeof run?.costUsd !== "number" || !Number.isFinite(run.costUsd) || run.costUsd < 0) {
|
|
16671
|
+
throw new Error("skill agent did not report valid cost usage for the active ceiling");
|
|
16672
|
+
}
|
|
16673
|
+
if (run.costUsd > maxBudgetUsd) {
|
|
16674
|
+
throw new Error(`skill agent exceeded the cost ceiling (${run.costUsd} > ${maxBudgetUsd})`);
|
|
16675
|
+
}
|
|
16676
|
+
}
|
|
16677
|
+
}
|
|
16310
16678
|
function composeSkillTaskPrompt({ skillBody, invocation, taskPrompt, producedByAgent }) {
|
|
16311
16679
|
const body = boundedString(skillBody, 1, MAX_SKILL_BODY_CHARS, "skill body");
|
|
16312
16680
|
const request = boundedString(taskPrompt, 1, 810201, "skill task request");
|
|
@@ -16341,8 +16709,9 @@ async function processSkillTask(client, task, cfg, {
|
|
|
16341
16709
|
swarmAdmission = null,
|
|
16342
16710
|
runTask = runAgentTask,
|
|
16343
16711
|
resolveDispatch = resolveEffortDispatch,
|
|
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" },
|
|
16344
16713
|
createScratch = () => mkdtemp2(join19(tmpdir(), "algohq-skill-task-")),
|
|
16345
|
-
removeScratch = (
|
|
16714
|
+
removeScratch = (path24) => rm2(path24, { recursive: true, force: true })
|
|
16346
16715
|
} = {}) {
|
|
16347
16716
|
const id = task.code_task_id;
|
|
16348
16717
|
let run = null;
|
|
@@ -16389,12 +16758,25 @@ async function processSkillTask(client, task, cfg, {
|
|
|
16389
16758
|
if (selected.agent !== "claude") {
|
|
16390
16759
|
throw new Error("skill execution requires the policy-restricted Claude runner");
|
|
16391
16760
|
}
|
|
16761
|
+
const capability = await checkSkillCapability({
|
|
16762
|
+
runner: selected.runner,
|
|
16763
|
+
bin: selected.runnerBin,
|
|
16764
|
+
env: env2
|
|
16765
|
+
});
|
|
16766
|
+
if (!capability?.compatible) {
|
|
16767
|
+
throw new Error(`resolved Claude CLI is incompatible with restricted skill execution: ${capability?.reason || "unknown capability"}`);
|
|
16768
|
+
}
|
|
16769
|
+
const executionBin = capability.resolvedBin || selected.runnerBin;
|
|
16392
16770
|
const basePrompt = composeSkillTaskPrompt({
|
|
16393
16771
|
skillBody: skill.body,
|
|
16394
16772
|
invocation,
|
|
16395
16773
|
taskPrompt: task.prompt,
|
|
16396
16774
|
producedByAgent: selected.agent
|
|
16397
16775
|
});
|
|
16776
|
+
const structuredOutputSchema = buildSkillResultJsonSchema({
|
|
16777
|
+
expectedSkill: invocation.skill,
|
|
16778
|
+
producedByAgent: selected.agent
|
|
16779
|
+
});
|
|
16398
16780
|
const dispatch = await resolveDispatch({ client, task, agent: selected.agent, env: env2, basePrompt });
|
|
16399
16781
|
assertRunnerGovernors({ agent: selected.agent, task });
|
|
16400
16782
|
scratch = await createScratch();
|
|
@@ -16412,10 +16794,11 @@ async function processSkillTask(client, task, cfg, {
|
|
|
16412
16794
|
});
|
|
16413
16795
|
run = await runTask({
|
|
16414
16796
|
runner: selected.runner,
|
|
16415
|
-
bin:
|
|
16797
|
+
bin: executionBin,
|
|
16416
16798
|
prompt: dispatch.prompt,
|
|
16417
16799
|
cwd: scratch,
|
|
16418
16800
|
toolPolicy: frozenInputsOnly ? "frozen_inputs_only" : "skill_readonly",
|
|
16801
|
+
structuredOutputSchema,
|
|
16419
16802
|
permissionMode: dispatch.permissionMode,
|
|
16420
16803
|
maxTurns: selected.agent === "claude" ? dispatch.maxTurns : void 0,
|
|
16421
16804
|
model: dispatch.model,
|
|
@@ -16466,7 +16849,11 @@ async function processSkillTask(client, task, cfg, {
|
|
|
16466
16849
|
} });
|
|
16467
16850
|
return;
|
|
16468
16851
|
}
|
|
16469
|
-
|
|
16852
|
+
assertSkillRunWithinDispatch(run, dispatch);
|
|
16853
|
+
if (!run.structuredOutput || typeof run.structuredOutput !== "object" || Array.isArray(run.structuredOutput)) {
|
|
16854
|
+
throw new Error("skill agent did not return provider-validated structured output");
|
|
16855
|
+
}
|
|
16856
|
+
const parsedResult = parseSkillResultValue(run.structuredOutput, {
|
|
16470
16857
|
expectedSkill: invocation.skill,
|
|
16471
16858
|
producedByAgent: selected.agent
|
|
16472
16859
|
});
|
|
@@ -16509,7 +16896,7 @@ async function processSkillTask(client, task, cfg, {
|
|
|
16509
16896
|
} });
|
|
16510
16897
|
}
|
|
16511
16898
|
}
|
|
16512
|
-
var SKILL_SUCCESS_STATUS, SKILL_NAME_RE, INPUT_KEY_RE, MAX_SKILL_BODY_CHARS, MAX_INPUTS,
|
|
16899
|
+
var SKILL_SUCCESS_STATUS, SKILL_NAME_RE, INPUT_KEY_RE, MAX_SKILL_BODY_CHARS, MAX_INPUTS, MAX_FINDINGS2, SHA256_RE;
|
|
16513
16900
|
var init_skill_task_runner = __esm({
|
|
16514
16901
|
"../../scripts/virtual-office/code-runner/skill-task-runner.mjs"() {
|
|
16515
16902
|
"use strict";
|
|
@@ -16523,12 +16910,13 @@ var init_skill_task_runner = __esm({
|
|
|
16523
16910
|
init_killed_run_outcome();
|
|
16524
16911
|
init_terminal_delivery();
|
|
16525
16912
|
init_cancelled_run_report();
|
|
16913
|
+
init_skill_result_json_schema();
|
|
16526
16914
|
SKILL_SUCCESS_STATUS = "no_changes_needed";
|
|
16527
16915
|
SKILL_NAME_RE = /^[a-z0-9][a-z0-9-]{0,62}(?::[a-z0-9][a-z0-9-]{0,62})?$/u;
|
|
16528
16916
|
INPUT_KEY_RE = /^[a-z][a-z0-9_]{0,39}$/u;
|
|
16529
16917
|
MAX_SKILL_BODY_CHARS = 256e3;
|
|
16530
16918
|
MAX_INPUTS = 12;
|
|
16531
|
-
|
|
16919
|
+
MAX_FINDINGS2 = 50;
|
|
16532
16920
|
SHA256_RE = /^[a-f0-9]{64}$/u;
|
|
16533
16921
|
}
|
|
16534
16922
|
});
|
|
@@ -16536,7 +16924,7 @@ var init_skill_task_runner = __esm({
|
|
|
16536
16924
|
// ../../scripts/virtual-office/code-runner/isolation-audit.mjs
|
|
16537
16925
|
import fs12 from "node:fs";
|
|
16538
16926
|
import fsp11 from "node:fs/promises";
|
|
16539
|
-
import
|
|
16927
|
+
import path21 from "node:path";
|
|
16540
16928
|
async function defaultRun3(command, args, cwd, options = {}) {
|
|
16541
16929
|
return runProcess2(command, args, { cwd, timeout: 6e4, ...options });
|
|
16542
16930
|
}
|
|
@@ -16549,7 +16937,7 @@ async function canonicalRootForWorktree(worktreeDir, run) {
|
|
|
16549
16937
|
"--path-format=absolute",
|
|
16550
16938
|
"--git-common-dir"
|
|
16551
16939
|
])).trim();
|
|
16552
|
-
const root =
|
|
16940
|
+
const root = path21.dirname(commonDir);
|
|
16553
16941
|
return samePath3(root, worktreeDir) ? null : root;
|
|
16554
16942
|
}
|
|
16555
16943
|
async function snapshot(root, run) {
|
|
@@ -16591,21 +16979,21 @@ async function changedPaths(root, run) {
|
|
|
16591
16979
|
}
|
|
16592
16980
|
async function quarantineCanonicalWrites({ baseline, worktreeDir, taskId, run, now }) {
|
|
16593
16981
|
const paths = await changedPaths(baseline.root, run);
|
|
16594
|
-
const quarantineDir =
|
|
16595
|
-
|
|
16982
|
+
const quarantineDir = path21.join(
|
|
16983
|
+
path21.dirname(worktreeDir),
|
|
16596
16984
|
".canonical-recovery",
|
|
16597
16985
|
`${String(taskId || "unknown").replace(/[^a-z0-9-]/gi, "-")}-${now().toISOString().replace(/[:.]/g, "-")}`
|
|
16598
16986
|
);
|
|
16599
16987
|
await fsp11.mkdir(quarantineDir, { recursive: true });
|
|
16600
16988
|
const patch = await git2(run, baseline.root, ["diff", "--binary", "HEAD"], { raw: true });
|
|
16601
|
-
await fsp11.writeFile(
|
|
16989
|
+
await fsp11.writeFile(path21.join(quarantineDir, "tracked.patch"), patch, "utf8");
|
|
16602
16990
|
for (const relative of paths.untracked) {
|
|
16603
|
-
const source =
|
|
16604
|
-
const target =
|
|
16605
|
-
await fsp11.mkdir(
|
|
16991
|
+
const source = path21.join(baseline.root, relative);
|
|
16992
|
+
const target = path21.join(quarantineDir, "untracked", relative);
|
|
16993
|
+
await fsp11.mkdir(path21.dirname(target), { recursive: true });
|
|
16606
16994
|
await fsp11.copyFile(source, target);
|
|
16607
16995
|
}
|
|
16608
|
-
await fsp11.writeFile(
|
|
16996
|
+
await fsp11.writeFile(path21.join(quarantineDir, "manifest.json"), `${JSON.stringify({
|
|
16609
16997
|
taskId,
|
|
16610
16998
|
canonicalRoot: baseline.root,
|
|
16611
16999
|
canonicalHead: baseline.head,
|
|
@@ -16627,8 +17015,8 @@ async function restoreExactCanonicalPaths(baseline, evidence, run) {
|
|
|
16627
17015
|
]);
|
|
16628
17016
|
}
|
|
16629
17017
|
for (const relative of evidence.untracked) {
|
|
16630
|
-
const target =
|
|
16631
|
-
const prefix = `${
|
|
17018
|
+
const target = path21.resolve(baseline.root, relative);
|
|
17019
|
+
const prefix = `${path21.resolve(baseline.root)}${path21.sep}`;
|
|
16632
17020
|
if (!target.startsWith(prefix) || !fs12.existsSync(target)) continue;
|
|
16633
17021
|
await fsp11.rm(target, { force: true });
|
|
16634
17022
|
}
|
|
@@ -16665,7 +17053,7 @@ var init_isolation_audit = __esm({
|
|
|
16665
17053
|
init_process_runner2();
|
|
16666
17054
|
splitZ2 = (value) => String(value || "").split("\0").map((item) => item.trim()).filter(Boolean);
|
|
16667
17055
|
samePath3 = (left, right) => {
|
|
16668
|
-
const [a, b] = [left, right].map((value) =>
|
|
17056
|
+
const [a, b] = [left, right].map((value) => path21.resolve(value));
|
|
16669
17057
|
return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
16670
17058
|
};
|
|
16671
17059
|
}
|
|
@@ -16830,6 +17218,174 @@ var init_outcome_commit = __esm({
|
|
|
16830
17218
|
}
|
|
16831
17219
|
});
|
|
16832
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
|
+
|
|
16833
17389
|
// ../../scripts/virtual-office/code-runner/publication-outcome.mjs
|
|
16834
17390
|
async function closeCancelledReplacementPr({
|
|
16835
17391
|
pr,
|
|
@@ -16956,6 +17512,20 @@ async function finalizePublishedPr({
|
|
|
16956
17512
|
const runnerFailureRecord = serializedRunnerFailure ? JSON.parse(serializedRunnerFailure) : null;
|
|
16957
17513
|
const runnerFailureMessage = runnerFailureRecord ? `blocked by ${runnerFailureRecord.code}; operator review required: ${runnerFailureRecord.operator_next_action}` : "";
|
|
16958
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
|
+
}
|
|
16959
17529
|
let resumeQueued = false;
|
|
16960
17530
|
if (cfg.watchEnabled) {
|
|
16961
17531
|
try {
|
|
@@ -16966,8 +17536,8 @@ async function finalizePublishedPr({
|
|
|
16966
17536
|
taskId: id,
|
|
16967
17537
|
operatorId: task.operator_id,
|
|
16968
17538
|
tenantId: task.tenant_id,
|
|
16969
|
-
needsContinuation:
|
|
16970
|
-
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),
|
|
16971
17541
|
repairChain: task.repair_chain ?? {
|
|
16972
17542
|
root_pr_number: pr.prNumber,
|
|
16973
17543
|
attempt: 0,
|
|
@@ -17024,14 +17594,19 @@ async function finalizePublishedPr({
|
|
|
17024
17594
|
log: log2,
|
|
17025
17595
|
patch: {
|
|
17026
17596
|
status: runnerFailure ? "failed" : "pr_opened",
|
|
17027
|
-
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)" : ""}`,
|
|
17028
17598
|
pr_url: pr.prUrl,
|
|
17029
17599
|
pr_number: pr.prNumber,
|
|
17030
17600
|
pr_branch: pr.branch,
|
|
17031
17601
|
result: (() => {
|
|
17032
17602
|
const prefix = overlapBlocked ? `[VO-PUBLISH-OVERLAP-BLOCKED: ${blockedRefs}] ` : "";
|
|
17033
17603
|
const room = 2e3 - prefix.length;
|
|
17034
|
-
|
|
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)}`;
|
|
17035
17610
|
})(),
|
|
17036
17611
|
...runOutcomePatch(run),
|
|
17037
17612
|
...terminalLedgerPatch(run)
|
|
@@ -17059,6 +17634,16 @@ async function finalizePublishedPr({
|
|
|
17059
17634
|
});
|
|
17060
17635
|
return posted.cancelled;
|
|
17061
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
|
+
}
|
|
17062
17647
|
log2(`task ${id} \u2192 PR ${pr.prUrl}`);
|
|
17063
17648
|
return false;
|
|
17064
17649
|
}
|
|
@@ -17076,13 +17661,15 @@ var init_publication_outcome = __esm({
|
|
|
17076
17661
|
init_terminal_delivery();
|
|
17077
17662
|
init_rate_limit_resume();
|
|
17078
17663
|
init_error_message();
|
|
17664
|
+
init_capped_run_classification();
|
|
17665
|
+
init_capped_run_publication_repair();
|
|
17079
17666
|
defaultRunCommand4 = (cmd, args, cwd, opts = {}) => runProcess2(cmd, args, { cwd, ...opts });
|
|
17080
17667
|
}
|
|
17081
17668
|
});
|
|
17082
17669
|
|
|
17083
17670
|
// ../../scripts/virtual-office/code-runner/committed-scratch-cleanup.mjs
|
|
17084
17671
|
import fsp12 from "node:fs/promises";
|
|
17085
|
-
import
|
|
17672
|
+
import path22 from "node:path";
|
|
17086
17673
|
function defaultRun4(command, args, cwd, options = {}) {
|
|
17087
17674
|
return runProcess2(command, args, { cwd, ...options });
|
|
17088
17675
|
}
|
|
@@ -17090,13 +17677,13 @@ async function resolveSafeScratchTarget(worktreeDir, file) {
|
|
|
17090
17677
|
if (!isAgentScratch(file)) {
|
|
17091
17678
|
throw new Error(`refusing to remove non-scratch publication path: ${file}`);
|
|
17092
17679
|
}
|
|
17093
|
-
const root =
|
|
17094
|
-
const target =
|
|
17095
|
-
const relative =
|
|
17096
|
-
if (!relative || relative.startsWith(`..${
|
|
17680
|
+
const root = path22.resolve(worktreeDir);
|
|
17681
|
+
const target = path22.resolve(root, file);
|
|
17682
|
+
const relative = path22.relative(root, target);
|
|
17683
|
+
if (!relative || relative.startsWith(`..${path22.sep}`) || path22.isAbsolute(relative)) {
|
|
17097
17684
|
throw new Error(`refusing to remove publication scratch outside worktree: ${file}`);
|
|
17098
17685
|
}
|
|
17099
|
-
for (let cursor = target; cursor !== root; cursor =
|
|
17686
|
+
for (let cursor = target; cursor !== root; cursor = path22.dirname(cursor)) {
|
|
17100
17687
|
try {
|
|
17101
17688
|
if ((await fsp12.lstat(cursor)).isSymbolicLink()) {
|
|
17102
17689
|
throw new Error(`refusing to follow symlink while removing publication scratch: ${file}`);
|
|
@@ -17218,7 +17805,7 @@ var init_publication_scope = __esm({
|
|
|
17218
17805
|
// ../../scripts/virtual-office/code-runner/recovery-ledger.mjs
|
|
17219
17806
|
import fs13 from "node:fs";
|
|
17220
17807
|
import fsp13 from "node:fs/promises";
|
|
17221
|
-
import
|
|
17808
|
+
import path23 from "node:path";
|
|
17222
17809
|
function recoveryTaskId(prompt) {
|
|
17223
17810
|
const match = String(prompt || "").match(/VO_RECOVERY_FROM_CODE_TASK:\s*([0-9a-f-]{36})/i);
|
|
17224
17811
|
return match ? match[1].toLowerCase() : null;
|
|
@@ -17232,10 +17819,10 @@ function cloneLeaf(repo) {
|
|
|
17232
17819
|
function recoveryLedgerCandidates(repo, clonesRoot2) {
|
|
17233
17820
|
const leaf = cloneLeaf(repo);
|
|
17234
17821
|
if (!leaf || !clonesRoot2) return [];
|
|
17235
|
-
const canonical =
|
|
17822
|
+
const canonical = path23.join(clonesRoot2, leaf);
|
|
17236
17823
|
return [
|
|
17237
|
-
|
|
17238
|
-
|
|
17824
|
+
path23.join(clonesRoot2, ".agent-worktrees", leaf, "recovery-ledger.jsonl"),
|
|
17825
|
+
path23.join(canonical, ".agent-worktrees", "recovery-ledger.jsonl")
|
|
17239
17826
|
];
|
|
17240
17827
|
}
|
|
17241
17828
|
async function readLedger(file, readFile6) {
|
|
@@ -17393,6 +17980,290 @@ var init_recovery_ledger = __esm({
|
|
|
17393
17980
|
}
|
|
17394
17981
|
});
|
|
17395
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
|
+
|
|
17396
18267
|
// ../../scripts/virtual-office/code-runner/no-changes-terminal-status.mjs
|
|
17397
18268
|
function defaultRunCommand5(cmd, args, cwd, opts = {}) {
|
|
17398
18269
|
return runProcess2(cmd, args, { cwd, ...opts });
|
|
@@ -17414,6 +18285,14 @@ function isMaxTurnExhaustion(run = {}, maxTurns) {
|
|
|
17414
18285
|
}
|
|
17415
18286
|
function decideNoChangesTerminalStatus({ partial, run = {}, maxTurns } = {}) {
|
|
17416
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
|
+
}
|
|
17417
18296
|
if (!partial) {
|
|
17418
18297
|
if (structuredFailure) {
|
|
17419
18298
|
const failure = JSON.parse(structuredFailure);
|
|
@@ -17582,6 +18461,7 @@ var init_no_changes_terminal_status = __esm({
|
|
|
17582
18461
|
init_terminal_delivery();
|
|
17583
18462
|
init_error_message();
|
|
17584
18463
|
init_partial_pr_continuation();
|
|
18464
|
+
init_host_preflight();
|
|
17585
18465
|
RESULT_LIMIT = 2e3;
|
|
17586
18466
|
}
|
|
17587
18467
|
});
|
|
@@ -17873,6 +18753,8 @@ async function processOneTask(client, task, cfg, runnerInstanceId, swarmAdmissio
|
|
|
17873
18753
|
env: buildAgentProcessEnv(process.env, { agent: sel.agent, runnerId: cfg.runnerId, taskId: id, repo: task.repo, githubReadToken: agentGithubReadToken, swarmAdmission }),
|
|
17874
18754
|
// swarmAdmission mints VO_SWARM_TIER_BINDING: ONE tier decision for this task's whole agent tree
|
|
17875
18755
|
sandbox,
|
|
18756
|
+
allowApiBilling: task.allow_api_billing === true,
|
|
18757
|
+
// per-TASK grant; AND-ed with the per-machine VO_RUNNER_ALLOW_API_BILLING inside applyApiBillingPolicy
|
|
17876
18758
|
onProgress: (text, checkpoint) => {
|
|
17877
18759
|
const usage = checkpoint?.tokenUsage ? { token_usage: checkpoint.tokenUsage } : {};
|
|
17878
18760
|
const patch = text ? runnerStagePatch("agent_working", text, usage) : { stage: "agent_working", ...usage };
|
|
@@ -18103,7 +18985,8 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
|
|
|
18103
18985
|
const agentAvailability = makeAgentAvailabilityProvider({ onError: (e) => log(`agent probe failed: ${e.message}`) });
|
|
18104
18986
|
await agentAvailability.ready();
|
|
18105
18987
|
const accountUsage = makeAccountUsageProvider();
|
|
18106
|
-
const
|
|
18988
|
+
const hostGate = makeHostPreflightGate({ cwd: process.cwd(), runGit: runProcess2, runAgent: runProcess2, bin: cfg.runnerBin, agent: cfg.agent, env: env2, log });
|
|
18989
|
+
const loopTick = makeLoopTicks({ client, cfg, env: env2, log, getActive: () => active, runnerInstanceId, capacityController, localModelController: createLocalModelRemoteController({ env: env2, log }), preparedJobController: createPreparedJobRemoteController({ log }), getAgentAvailability: () => agentAvailability.get(), getAccountUsage: () => accountUsage.get(), getHostHealth: () => hostGate.get() });
|
|
18107
18990
|
const backoff = makeReconnectBackoff({ baseMs: cfg.pollSec * 1e3, log });
|
|
18108
18991
|
let detachedFlushRunning = false;
|
|
18109
18992
|
while (!stopping) {
|
|
@@ -18113,9 +18996,10 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
|
|
|
18113
18996
|
detachedFlushRunning = false;
|
|
18114
18997
|
});
|
|
18115
18998
|
}
|
|
18999
|
+
await hostGate.ensure();
|
|
18116
19000
|
const heartbeatCompletion = loopTick();
|
|
18117
19001
|
if (watchCyclesEnabled) watchCoordinator.start();
|
|
18118
|
-
const claimAgents = resolveAgentClaimContext(agentAvailability, cfg.agent);
|
|
19002
|
+
const claimAgents = hostGate.allowClaim() ? resolveAgentClaimContext(agentAvailability, cfg.agent) : null;
|
|
18119
19003
|
if (!claimAgents) {
|
|
18120
19004
|
await heartbeatCompletion;
|
|
18121
19005
|
await sleep2(cfg.pollSec * 1e3);
|
|
@@ -18132,7 +19016,7 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
|
|
|
18132
19016
|
}
|
|
18133
19017
|
let task;
|
|
18134
19018
|
try {
|
|
18135
|
-
task = await client.claim(cfg.runnerId, cfg.servedRepos, cfg.servedOperators, { runnerInstanceId, reconcileStale, ...claimAgents });
|
|
19019
|
+
task = await client.claim(cfg.runnerId, cfg.servedRepos, cfg.servedOperators, { runnerInstanceId, reconcileStale, ...claimAgents, agentAuthSource: resolveRunnerAttestedAuthSource(process.env) });
|
|
18136
19020
|
reconcileStale = false;
|
|
18137
19021
|
backoff.onSuccess();
|
|
18138
19022
|
} catch (err) {
|
|
@@ -18152,6 +19036,7 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
|
|
|
18152
19036
|
continue;
|
|
18153
19037
|
}
|
|
18154
19038
|
log(`claimed task ${task.code_task_id} (${task.repo})`);
|
|
19039
|
+
log(`task ${task.code_task_id} ${describeTaskAuthSource(process.env, task)}`);
|
|
18155
19040
|
active += 1;
|
|
18156
19041
|
activeTaskIds.add(task.code_task_id);
|
|
18157
19042
|
const runTask = selectTaskProcessor(task, {
|
|
@@ -18224,6 +19109,7 @@ var init_code_runner_daemon = __esm({
|
|
|
18224
19109
|
init_task_helpers();
|
|
18225
19110
|
init_prepared_job_shadow();
|
|
18226
19111
|
init_agent_process_env();
|
|
19112
|
+
init_agent_auth_attestation();
|
|
18227
19113
|
init_sandbox_config();
|
|
18228
19114
|
init_inference_task_runner();
|
|
18229
19115
|
init_skill_task_runner();
|
|
@@ -18241,6 +19127,8 @@ var init_code_runner_daemon = __esm({
|
|
|
18241
19127
|
init_daemon_config();
|
|
18242
19128
|
init_publication_scope();
|
|
18243
19129
|
init_task_worktree_preparation();
|
|
19130
|
+
init_process_runner2();
|
|
19131
|
+
init_host_preflight();
|
|
18244
19132
|
init_detached_economics_spool();
|
|
18245
19133
|
RATE_LIMIT_RESUME_ENABLED = process.env.VO_RATE_LIMIT_RESUME !== "0";
|
|
18246
19134
|
sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|