@algosuite/vo-mcp 0.2.0-beta.21 → 0.2.0-beta.23
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/runner-cli.js +151 -52
- package/dist/runner-cli.js.map +4 -4
- package/dist/runner-supervisor.js +28 -31
- package/dist/runner-supervisor.js.map +3 -3
- package/package.json +1 -1
package/dist/runner-cli.js
CHANGED
|
@@ -2241,6 +2241,37 @@ var init_spend_cap_shim = __esm({
|
|
|
2241
2241
|
}
|
|
2242
2242
|
});
|
|
2243
2243
|
|
|
2244
|
+
// ../../scripts/virtual-office/code-runner/installation-token.mjs
|
|
2245
|
+
async function fetchInstallationToken({ req, required = false, readOnly = false, repo = null }) {
|
|
2246
|
+
const fail = (reason) => {
|
|
2247
|
+
if (required) throw new Error(`installation-token required: ${reason}`);
|
|
2248
|
+
return null;
|
|
2249
|
+
};
|
|
2250
|
+
try {
|
|
2251
|
+
const res = await req(
|
|
2252
|
+
"POST",
|
|
2253
|
+
"/api/v1/github/installation-token",
|
|
2254
|
+
readOnly ? { scope: "read", ...repo ? { repo } : {} } : {},
|
|
2255
|
+
readOnly ? { timeoutMs: READ_TOKEN_TIMEOUT_MS } : {}
|
|
2256
|
+
);
|
|
2257
|
+
if (!res.ok) return fail(`HTTP ${res.status}`);
|
|
2258
|
+
const json = await res.json();
|
|
2259
|
+
if (!json || !json.token) return fail("missing token");
|
|
2260
|
+
if (readOnly && json.scope !== "read") return fail("control plane did not confirm a read-only grant");
|
|
2261
|
+
return { token: json.token, expiresAt: json.expires_at || null };
|
|
2262
|
+
} catch (err) {
|
|
2263
|
+
if (required) throw err;
|
|
2264
|
+
return null;
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
var READ_TOKEN_TIMEOUT_MS;
|
|
2268
|
+
var init_installation_token = __esm({
|
|
2269
|
+
"../../scripts/virtual-office/code-runner/installation-token.mjs"() {
|
|
2270
|
+
"use strict";
|
|
2271
|
+
READ_TOKEN_TIMEOUT_MS = 15e3;
|
|
2272
|
+
}
|
|
2273
|
+
});
|
|
2274
|
+
|
|
2244
2275
|
// src/runner/control-plane-auth-stub.mjs
|
|
2245
2276
|
var control_plane_auth_stub_exports = {};
|
|
2246
2277
|
__export(control_plane_auth_stub_exports, {
|
|
@@ -2564,37 +2595,9 @@ function createControlPlaneClient({
|
|
|
2564
2595
|
const json = await res.json();
|
|
2565
2596
|
return json?.action || null;
|
|
2566
2597
|
},
|
|
2567
|
-
/**
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
* authenticated operator (ctx.operator_id), so the token covers only that
|
|
2571
|
-
* operator's installation.
|
|
2572
|
-
*
|
|
2573
|
-
* Returns { token, expiresAt } on success. In legacy/admin mode it returns
|
|
2574
|
-
* null on a miss so the caller may use ambient `gh`. In scoped-operator mode
|
|
2575
|
-
* callers pass `{ required: true }`, which fails closed instead of letting a
|
|
2576
|
-
* missing/mis-scoped installation fall through to the runner machine's `gh`.
|
|
2577
|
-
*
|
|
2578
|
-
* The minted token is only usable for push + PR if the GitHub App grants
|
|
2579
|
-
* BOTH `Contents: write` (git push) AND `Pull requests: write` (gh pr
|
|
2580
|
-
* create) — see docs/vo/github-app-setup-2026-06-18.md. A token missing
|
|
2581
|
-
* either scope fails at push (→ ambient fallback) or at `gh pr create`.
|
|
2582
|
-
*/
|
|
2583
|
-
async getInstallationToken({ required = false } = {}) {
|
|
2584
|
-
const fail = (reason) => {
|
|
2585
|
-
if (required) throw new Error(`installation-token required: ${reason}`);
|
|
2586
|
-
return null;
|
|
2587
|
-
};
|
|
2588
|
-
try {
|
|
2589
|
-
const res = await req("POST", "/api/v1/github/installation-token", {});
|
|
2590
|
-
if (!res.ok) return fail(`HTTP ${res.status}`);
|
|
2591
|
-
const json = await res.json();
|
|
2592
|
-
if (!json || !json.token) return fail("missing token");
|
|
2593
|
-
return { token: json.token, expiresAt: json.expires_at || null };
|
|
2594
|
-
} catch (err) {
|
|
2595
|
-
if (required) throw err;
|
|
2596
|
-
return null;
|
|
2597
|
-
}
|
|
2598
|
+
/** Mint a GitHub App installation token — see installation-token.mjs. */
|
|
2599
|
+
async getInstallationToken({ required = false, readOnly = false, repo = null } = {}) {
|
|
2600
|
+
return fetchInstallationToken({ req, required, readOnly, repo });
|
|
2598
2601
|
},
|
|
2599
2602
|
/**
|
|
2600
2603
|
* Read the operator's dispatch-mode config (Fast→Ultracode effort setting).
|
|
@@ -2617,6 +2620,7 @@ var cachedFirebaseToken;
|
|
|
2617
2620
|
var init_control_plane_client = __esm({
|
|
2618
2621
|
"../../scripts/virtual-office/code-runner/control-plane-client.mjs"() {
|
|
2619
2622
|
"use strict";
|
|
2623
|
+
init_installation_token();
|
|
2620
2624
|
cachedFirebaseToken = null;
|
|
2621
2625
|
}
|
|
2622
2626
|
});
|
|
@@ -3844,6 +3848,11 @@ function itemText(item) {
|
|
|
3844
3848
|
}
|
|
3845
3849
|
return "";
|
|
3846
3850
|
}
|
|
3851
|
+
function parseCodexVersion(stdout) {
|
|
3852
|
+
if (typeof stdout !== "string") return null;
|
|
3853
|
+
const match = stdout.match(/\b(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)\b/u);
|
|
3854
|
+
return match ? match[1] : null;
|
|
3855
|
+
}
|
|
3847
3856
|
function parseCodexEvent(line) {
|
|
3848
3857
|
const trimmed = String(line || "").trim();
|
|
3849
3858
|
if (!trimmed) return null;
|
|
@@ -3938,7 +3947,7 @@ var init_codex_runner = __esm({
|
|
|
3938
3947
|
...this.getSpawnOptions({ bin }),
|
|
3939
3948
|
windowsHide: true,
|
|
3940
3949
|
timeout: 3e3,
|
|
3941
|
-
|
|
3950
|
+
encoding: "utf8"
|
|
3942
3951
|
});
|
|
3943
3952
|
if (version.error) {
|
|
3944
3953
|
return { installed: false, authenticated: false, message: `codex not found on PATH: ${version.error.message}` };
|
|
@@ -3946,6 +3955,8 @@ var init_codex_runner = __esm({
|
|
|
3946
3955
|
if (version.status !== 0) {
|
|
3947
3956
|
return { installed: true, authenticated: false, message: "codex exists but --version failed (auth unclear)" };
|
|
3948
3957
|
}
|
|
3958
|
+
const cliVersion = parseCodexVersion(version.stdout);
|
|
3959
|
+
const versionField = cliVersion ? { version: cliVersion } : {};
|
|
3949
3960
|
const login = this.spawn(bin, ["login", "status"], {
|
|
3950
3961
|
...this.getSpawnOptions({ bin }),
|
|
3951
3962
|
windowsHide: true,
|
|
@@ -3960,18 +3971,21 @@ ${login.stderr || ""}`.trim();
|
|
|
3960
3971
|
return {
|
|
3961
3972
|
installed: true,
|
|
3962
3973
|
authenticated: true,
|
|
3974
|
+
...versionField,
|
|
3963
3975
|
message: "codex API key available (no persisted ChatGPT login)"
|
|
3964
3976
|
};
|
|
3965
3977
|
}
|
|
3966
3978
|
return {
|
|
3967
3979
|
installed: true,
|
|
3968
3980
|
authenticated: false,
|
|
3981
|
+
...versionField,
|
|
3969
3982
|
message: output || login.error?.message || "codex is installed but not logged in"
|
|
3970
3983
|
};
|
|
3971
3984
|
}
|
|
3972
3985
|
return {
|
|
3973
3986
|
installed: true,
|
|
3974
3987
|
authenticated: true,
|
|
3988
|
+
...versionField,
|
|
3975
3989
|
message: output || "codex login status succeeded"
|
|
3976
3990
|
};
|
|
3977
3991
|
} catch (err) {
|
|
@@ -6417,12 +6431,23 @@ function resolveAgentClaimContext(provider, defaultAgent) {
|
|
|
6417
6431
|
}
|
|
6418
6432
|
async function collectAgentAvailability({
|
|
6419
6433
|
agents = listAgents(),
|
|
6420
|
-
runnerFor = (agent) => resolveRunner({ VO_CODE_RUNNER_AGENT: agent }).runner
|
|
6434
|
+
runnerFor = (agent) => resolveRunner({ VO_CODE_RUNNER_AGENT: agent }).runner,
|
|
6435
|
+
probeTimeoutMs = PROBE_TIMEOUT_MS
|
|
6421
6436
|
} = {}) {
|
|
6422
6437
|
const probes = agents.map(async (agent) => {
|
|
6438
|
+
const degraded = { agent, installed: false, authenticated: false };
|
|
6423
6439
|
try {
|
|
6424
|
-
const r = await
|
|
6425
|
-
|
|
6440
|
+
const r = await Promise.race([
|
|
6441
|
+
Promise.resolve().then(() => runnerFor(agent).checkAuth()),
|
|
6442
|
+
new Promise((resolve2) => setTimeout(() => resolve2(null), probeTimeoutMs))
|
|
6443
|
+
]);
|
|
6444
|
+
if (!r) return degraded;
|
|
6445
|
+
return {
|
|
6446
|
+
agent,
|
|
6447
|
+
installed: Boolean(r?.installed),
|
|
6448
|
+
authenticated: Boolean(r?.authenticated),
|
|
6449
|
+
...typeof r?.version === "string" && r.version ? { version: r.version } : {}
|
|
6450
|
+
};
|
|
6426
6451
|
} catch {
|
|
6427
6452
|
return { agent, installed: false, authenticated: false };
|
|
6428
6453
|
}
|
|
@@ -6438,28 +6463,53 @@ function makeAgentAvailabilityProvider({
|
|
|
6438
6463
|
} = {}) {
|
|
6439
6464
|
let cached2 = null;
|
|
6440
6465
|
let fetchedAt = 0;
|
|
6441
|
-
let inFlight =
|
|
6466
|
+
let inFlight = null;
|
|
6467
|
+
const refresh = () => {
|
|
6468
|
+
if (inFlight) return inFlight;
|
|
6469
|
+
try {
|
|
6470
|
+
inFlight = Promise.resolve(collect()).then((list) => {
|
|
6471
|
+
cached2 = list;
|
|
6472
|
+
fetchedAt = now();
|
|
6473
|
+
}).catch((e) => onError(e)).finally(() => {
|
|
6474
|
+
inFlight = null;
|
|
6475
|
+
});
|
|
6476
|
+
} catch (e) {
|
|
6477
|
+
inFlight = null;
|
|
6478
|
+
onError(e);
|
|
6479
|
+
return Promise.resolve();
|
|
6480
|
+
}
|
|
6481
|
+
return inFlight;
|
|
6482
|
+
};
|
|
6442
6483
|
return {
|
|
6443
6484
|
get() {
|
|
6444
|
-
if (!inFlight && now() - fetchedAt >= ttlMs)
|
|
6445
|
-
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
|
|
6485
|
+
if (!inFlight && now() - fetchedAt >= ttlMs) refresh();
|
|
6486
|
+
return cached2;
|
|
6487
|
+
},
|
|
6488
|
+
/**
|
|
6489
|
+
* Resolve once a probe has actually completed, so the FIRST heartbeat can
|
|
6490
|
+
* report real agents. Without this the daemon heartbeats immediately with
|
|
6491
|
+
* `available_agents: []` (the cache is null until the first probe lands),
|
|
6492
|
+
* and a supervisor activation attestation — which requires the default
|
|
6493
|
+
* agent present, installed AND authenticated — can never be satisfied. The
|
|
6494
|
+
* supervisor then kills and restarts the child forever: observed on
|
|
6495
|
+
* JacksPC 2026-07-25, uptime stuck at 1s across every heartbeat.
|
|
6496
|
+
*
|
|
6497
|
+
* Bounded on purpose: a wedged probe must delay startup, never prevent it.
|
|
6498
|
+
*/
|
|
6499
|
+
async ready(timeoutMs = 2e4) {
|
|
6500
|
+
if (Array.isArray(cached2)) return cached2;
|
|
6501
|
+
await Promise.race([refresh(), new Promise((r) => setTimeout(r, timeoutMs))]);
|
|
6453
6502
|
return cached2;
|
|
6454
6503
|
}
|
|
6455
6504
|
};
|
|
6456
6505
|
}
|
|
6457
|
-
var DEFAULT_TTL_MS;
|
|
6506
|
+
var DEFAULT_TTL_MS, PROBE_TIMEOUT_MS;
|
|
6458
6507
|
var init_agent_availability = __esm({
|
|
6459
6508
|
"../../scripts/virtual-office/code-runner/agent-availability.mjs"() {
|
|
6460
6509
|
"use strict";
|
|
6461
6510
|
init_resolve_runner();
|
|
6462
6511
|
DEFAULT_TTL_MS = 5 * 60 * 1e3;
|
|
6512
|
+
PROBE_TIMEOUT_MS = 1e4;
|
|
6463
6513
|
}
|
|
6464
6514
|
});
|
|
6465
6515
|
|
|
@@ -8823,11 +8873,41 @@ var init_reconnect_backoff = __esm({
|
|
|
8823
8873
|
}
|
|
8824
8874
|
});
|
|
8825
8875
|
|
|
8876
|
+
// ../../scripts/virtual-office/code-runner/redact-tokens.mjs
|
|
8877
|
+
function redactSecrets(text) {
|
|
8878
|
+
if (typeof text !== "string" || text.length === 0) return text;
|
|
8879
|
+
let out = text;
|
|
8880
|
+
for (const [re, mask] of TOKEN_PATTERNS) out = out.replace(re, mask);
|
|
8881
|
+
return out;
|
|
8882
|
+
}
|
|
8883
|
+
function redactPatch(patch) {
|
|
8884
|
+
if (!patch || typeof patch !== "object") return patch;
|
|
8885
|
+
const out = Array.isArray(patch) ? [...patch] : { ...patch };
|
|
8886
|
+
for (const [k, v] of Object.entries(out)) {
|
|
8887
|
+
if (typeof v === "string") out[k] = redactSecrets(v);
|
|
8888
|
+
else if (v && typeof v === "object") out[k] = redactPatch(v);
|
|
8889
|
+
}
|
|
8890
|
+
return out;
|
|
8891
|
+
}
|
|
8892
|
+
var TOKEN_PATTERNS;
|
|
8893
|
+
var init_redact_tokens = __esm({
|
|
8894
|
+
"../../scripts/virtual-office/code-runner/redact-tokens.mjs"() {
|
|
8895
|
+
"use strict";
|
|
8896
|
+
TOKEN_PATTERNS = [
|
|
8897
|
+
[/\b(?:gh[oprsu]|vocred|npm)_[A-Za-z0-9._-]{10,}\b/gu, "[REDACTED]"],
|
|
8898
|
+
[/\bgithub_pat_[A-Za-z0-9_]{10,}\b/gu, "[REDACTED]"],
|
|
8899
|
+
// Keep the scheme so the line still reads as an auth header, matching
|
|
8900
|
+
// sanitizeMaintenanceDiagnostic's behaviour.
|
|
8901
|
+
[/\bBearer\s+[A-Za-z0-9._~+/-]{10,}=*/giu, "Bearer [REDACTED]"]
|
|
8902
|
+
];
|
|
8903
|
+
}
|
|
8904
|
+
});
|
|
8905
|
+
|
|
8826
8906
|
// ../../scripts/virtual-office/code-runner/task-helpers.mjs
|
|
8827
8907
|
function makeSafeProgress(log3) {
|
|
8828
8908
|
return async (client, id, patch) => {
|
|
8829
8909
|
try {
|
|
8830
|
-
const r = await client.postProgress(id, patch);
|
|
8910
|
+
const r = await client.postProgress(id, redactPatch(patch));
|
|
8831
8911
|
if (r && r.terminal) log3(`task ${id} is terminal server-side; stopping updates`);
|
|
8832
8912
|
return r;
|
|
8833
8913
|
} catch (err) {
|
|
@@ -8853,20 +8933,34 @@ function buildPrBody(task, run, files, { armAutoMerge = false } = {}) {
|
|
|
8853
8933
|
"### Prompt",
|
|
8854
8934
|
"",
|
|
8855
8935
|
"```",
|
|
8856
|
-
String(task.prompt).slice(0, 2e3),
|
|
8936
|
+
redactSecrets(String(task.prompt)).slice(0, 2e3),
|
|
8857
8937
|
"```",
|
|
8858
8938
|
"",
|
|
8859
8939
|
"### Agent summary",
|
|
8860
8940
|
"",
|
|
8861
|
-
String(run.summary || "").slice(0, 2e3),
|
|
8941
|
+
redactSecrets(String(run.summary || "")).slice(0, 2e3),
|
|
8862
8942
|
"",
|
|
8863
8943
|
"---",
|
|
8864
8944
|
armAutoMerge ? "_Opened by the AlgoHQ code-runner daemon. After CI passes, the watcher must obtain a durable consensus receipt and merge the exact verified SHA._" : "_Opened by the AlgoHQ code-runner daemon. This PR awaits the verify-before-act gate / operator review \u2014 it is NOT auto-merged._"
|
|
8865
8945
|
].filter((l) => l !== "").join("\n");
|
|
8866
8946
|
}
|
|
8947
|
+
async function mintRunnerGithubTokens({ client, taskId, log: log3, repo = null, requirePublish = false }) {
|
|
8948
|
+
const publishToken = (await client.getInstallationToken({ required: requirePublish }))?.token ?? null;
|
|
8949
|
+
let agentReadToken = null;
|
|
8950
|
+
let reason = null;
|
|
8951
|
+
try {
|
|
8952
|
+
agentReadToken = (await client.getInstallationToken({ readOnly: true, repo }))?.token ?? null;
|
|
8953
|
+
if (!agentReadToken) reason = "control plane returned no confirmed read-only grant";
|
|
8954
|
+
} catch (err) {
|
|
8955
|
+
reason = err?.message ?? String(err);
|
|
8956
|
+
}
|
|
8957
|
+
if (!agentReadToken) log3(`task ${taskId}: no GitHub read access for the agent (${reason}); it cannot read a private repo`);
|
|
8958
|
+
return { publishToken, agentReadToken };
|
|
8959
|
+
}
|
|
8867
8960
|
var init_task_helpers = __esm({
|
|
8868
8961
|
"../../scripts/virtual-office/code-runner/task-helpers.mjs"() {
|
|
8869
8962
|
"use strict";
|
|
8963
|
+
init_redact_tokens();
|
|
8870
8964
|
}
|
|
8871
8965
|
});
|
|
8872
8966
|
|
|
@@ -8882,8 +8976,12 @@ function safeBaseEnv(env2 = {}) {
|
|
|
8882
8976
|
}
|
|
8883
8977
|
return result;
|
|
8884
8978
|
}
|
|
8885
|
-
function buildAgentProcessEnv(env2, { agent = "agent", runnerId = "vo-runner", taskId = "task" } = {}) {
|
|
8979
|
+
function buildAgentProcessEnv(env2, { agent = "agent", runnerId = "vo-runner", taskId = "task", githubReadToken = null } = {}) {
|
|
8886
8980
|
const base = safeBaseEnv(env2);
|
|
8981
|
+
if (typeof githubReadToken === "string" && githubReadToken) {
|
|
8982
|
+
base.GH_TOKEN = githubReadToken;
|
|
8983
|
+
base.GITHUB_TOKEN = githubReadToken;
|
|
8984
|
+
}
|
|
8887
8985
|
if (String(env2?.AGENT_ID || "").trim()) return { ...base, AGENT_ID: env2.AGENT_ID };
|
|
8888
8986
|
const generated = [
|
|
8889
8987
|
"vo",
|
|
@@ -9574,7 +9672,7 @@ async function processOneTask(client, task, cfg) {
|
|
|
9574
9672
|
const wt = await Promise.resolve(createFixWorktree("code-task", { source: id.slice(0, 8), repo: task.repo }));
|
|
9575
9673
|
worktreeName = wt.worktreeName;
|
|
9576
9674
|
if (!worktreeName || !wt.worktreeDir) throw new Error("worktree isolation failure \u2014 refusing to run in the main tree");
|
|
9577
|
-
const githubToken =
|
|
9675
|
+
const { publishToken: githubToken, agentReadToken: agentGithubReadToken } = await mintRunnerGithubTokens({ client, taskId: id, log: log2, repo: task.repo, requirePublish: cfg.requireGithubAppAuth });
|
|
9578
9676
|
const parentTask = task.resumed_from ? await client.getTask(task.resumed_from).catch(() => null) : null;
|
|
9579
9677
|
const continuationRestore = await prepareContinuationBranch(wt.worktreeDir, {
|
|
9580
9678
|
task,
|
|
@@ -9609,7 +9707,7 @@ async function processOneTask(client, task, cfg) {
|
|
|
9609
9707
|
model,
|
|
9610
9708
|
effort: effectiveEffort,
|
|
9611
9709
|
maxBudgetUsd: effectiveMaxBudgetUsd,
|
|
9612
|
-
env: buildAgentProcessEnv(process.env, { agent: cfg.agent, runnerId: cfg.runnerId, taskId: id }),
|
|
9710
|
+
env: buildAgentProcessEnv(process.env, { agent: cfg.agent, runnerId: cfg.runnerId, taskId: id, githubReadToken: agentGithubReadToken }),
|
|
9613
9711
|
onProgress: (text) => {
|
|
9614
9712
|
void safeProgress(client, id, runnerStagePatch("agent_working", text));
|
|
9615
9713
|
},
|
|
@@ -9794,6 +9892,7 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
|
|
|
9794
9892
|
const runWatch = makeWatchRunner({ client, log: log2, maxFixAttempts: cfg.watchMaxFix, autoMergeEnabled: cfg.armAutoMerge });
|
|
9795
9893
|
const watchCoordinator = makeWatchCycleCoordinator({ runWatch, log: log2, intervalMs: cfg.watchIntervalSec * 1e3 });
|
|
9796
9894
|
const agentAvailability = makeAgentAvailabilityProvider({ onError: (e) => log2(`agent probe failed: ${e.message}`) });
|
|
9895
|
+
await agentAvailability.ready();
|
|
9797
9896
|
const accountUsage = makeAccountUsageProvider();
|
|
9798
9897
|
const loopTick = makeLoopTicks({ client, cfg, env: env2, log: log2, getActive: () => active, runnerInstanceId, capacityController, localModelController: createLocalModelRemoteController({ env: env2, log: log2 }), getAgentAvailability: () => agentAvailability.get(), getAccountUsage: () => accountUsage.get() });
|
|
9799
9898
|
const backoff = makeReconnectBackoff({ baseMs: cfg.pollSec * 1e3, log: log2 });
|