@algosuite/vo-mcp 0.2.0-beta.56 → 0.2.0-beta.57
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/autostart-cli.js +6 -3
- package/dist/autostart-cli.js.map +2 -2
- package/dist/install-cli.js +6 -3
- package/dist/install-cli.js.map +2 -2
- package/dist/runner-cli.js +286 -55
- package/dist/runner-cli.js.map +3 -3
- package/dist/runner-supervisor.js +805 -75
- package/dist/runner-supervisor.js.map +4 -4
- package/package.json +1 -1
package/dist/runner-cli.js
CHANGED
|
@@ -675,12 +675,15 @@ function installWindowsAutostart(runnerCommand, log2, env2) {
|
|
|
675
675
|
log2(`\u26A0 Could not remove legacy launcher ${legacyCmdPath} (${error instanceof Error ? error.message : String(error)}); remove it manually to avoid a double start`);
|
|
676
676
|
}
|
|
677
677
|
}
|
|
678
|
-
const
|
|
678
|
+
const runnerConsoleCommand = `cmd /c ${runnerCommand} >> "%USERPROFILE%\\.claude\\vo-runner.log" 2>&1`.replace(/"/g, '""');
|
|
679
679
|
const launcherContent = `' Auto-start launcher for vo-mcp runner
|
|
680
680
|
' Created by vo-mcp autostart installer
|
|
681
681
|
' Keepalive supervisor: restarts the runner if it exits (parity with launchd
|
|
682
682
|
' KeepAlive on macOS and systemd Restart=on-failure on Linux).
|
|
683
|
-
'
|
|
683
|
+
' Runs the runner in a MINIMIZED (style 7), never a hidden (style 0) window:
|
|
684
|
+
' hidden script-host exec trips Defender's PowhidSubExec.B heuristic and gets
|
|
685
|
+
' blocked at logon. To stop: create %USERPROFILE%\\.claude\\vo-runner.stop, or
|
|
686
|
+
' end wscript.exe.
|
|
684
687
|
Dim sh, fso, stopFile, backoff, startedAt, ranMs
|
|
685
688
|
Set sh = CreateObject("WScript.Shell")
|
|
686
689
|
Set fso = CreateObject("Scripting.FileSystemObject")
|
|
@@ -694,7 +697,7 @@ Do
|
|
|
694
697
|
WScript.Quit 0
|
|
695
698
|
End If
|
|
696
699
|
startedAt = Timer
|
|
697
|
-
sh.Run "${
|
|
700
|
+
sh.Run "${runnerConsoleCommand}", 7, True
|
|
698
701
|
ranMs = (Timer - startedAt) * 1000
|
|
699
702
|
If ranMs < 0 Then ranMs = ${WINDOWS_HEALTHY_RUN_MS}
|
|
700
703
|
If ranMs >= ${WINDOWS_HEALTHY_RUN_MS} Then
|
|
@@ -1999,12 +2002,12 @@ var init_pnpm_materialize = __esm({
|
|
|
1999
2002
|
});
|
|
2000
2003
|
|
|
2001
2004
|
// src/runner/pnpm-hydration.mjs
|
|
2002
|
-
import { createHash } from "node:crypto";
|
|
2005
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
2003
2006
|
import fs3 from "node:fs";
|
|
2004
2007
|
import fsp5 from "node:fs/promises";
|
|
2005
2008
|
import path5 from "node:path";
|
|
2006
2009
|
function hashText(text) {
|
|
2007
|
-
return
|
|
2010
|
+
return createHash2("sha256").update(String(text)).digest("hex");
|
|
2008
2011
|
}
|
|
2009
2012
|
function statePath(root) {
|
|
2010
2013
|
return path5.join(root, ".agent-worktrees", "runner-pnpm-hydration.json");
|
|
@@ -2314,7 +2317,7 @@ var init_pnpm_hydration = __esm({
|
|
|
2314
2317
|
});
|
|
2315
2318
|
|
|
2316
2319
|
// src/runner/worktree-paths.mjs
|
|
2317
|
-
import { createHash as
|
|
2320
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
2318
2321
|
import path6 from "node:path";
|
|
2319
2322
|
function samePath2(left, right) {
|
|
2320
2323
|
const a = path6.resolve(left);
|
|
@@ -2332,7 +2335,7 @@ function worktreePoolForRoot(root, { clonesRootDir = process.env.VO_CODE_RUNNER_
|
|
|
2332
2335
|
return path6.join(canonicalRoot, ".agent-worktrees");
|
|
2333
2336
|
}
|
|
2334
2337
|
function worktreeDirForName(root, worktreeName, options = {}) {
|
|
2335
|
-
const leaf =
|
|
2338
|
+
const leaf = createHash3("sha256").update(String(worktreeName)).digest("hex").slice(0, 16);
|
|
2336
2339
|
return path6.join(worktreePoolForRoot(root, options), leaf);
|
|
2337
2340
|
}
|
|
2338
2341
|
function recoveryLedgerPathForRoot(root, options = {}) {
|
|
@@ -8666,7 +8669,7 @@ var init_task_prompt = __esm({
|
|
|
8666
8669
|
});
|
|
8667
8670
|
|
|
8668
8671
|
// ../../scripts/virtual-office/code-runner/task-attachments.mjs
|
|
8669
|
-
import { createHash as
|
|
8672
|
+
import { createHash as createHash4, randomUUID as randomUUID3 } from "node:crypto";
|
|
8670
8673
|
import { chmod, mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
8671
8674
|
import os2 from "node:os";
|
|
8672
8675
|
import path16 from "node:path";
|
|
@@ -8690,7 +8693,7 @@ async function createAttachmentDirectory(taskId, tempRoot) {
|
|
|
8690
8693
|
const root = path16.resolve(tempRoot);
|
|
8691
8694
|
await mkdir(root, { recursive: true });
|
|
8692
8695
|
const directory = await mkdtemp(path16.join(root, `${DIRECTORY_PREFIX}${safeTaskToken(taskId)}-`));
|
|
8693
|
-
const marker = JSON.stringify({ owner: MARKER_OWNER, token:
|
|
8696
|
+
const marker = JSON.stringify({ owner: MARKER_OWNER, token: randomUUID3(), directory: path16.basename(directory), created_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
8694
8697
|
await writeFile(path16.join(directory, MARKER_FILE), marker, { encoding: "utf8", mode: 384 });
|
|
8695
8698
|
return { directory, marker, tempRoot: root };
|
|
8696
8699
|
}
|
|
@@ -8766,7 +8769,7 @@ async function materializeTaskAttachments(client, task, { tempRoot = os2.tmpdir(
|
|
|
8766
8769
|
const content = await client.downloadTaskAttachment(task.code_task_id, ref.attachment_id);
|
|
8767
8770
|
if (!Buffer.isBuffer(content)) throw new Error(`attachment ${ref.attachment_id} did not return binary content`);
|
|
8768
8771
|
if (content.byteLength !== ref.size_bytes) throw new Error(`attachment ${ref.attachment_id} size mismatch`);
|
|
8769
|
-
const sha256 =
|
|
8772
|
+
const sha256 = createHash4("sha256").update(content).digest("hex");
|
|
8770
8773
|
if (sha256 !== ref.sha256) throw new Error(`attachment ${ref.attachment_id} sha256 mismatch`);
|
|
8771
8774
|
const name = sanitizeTaskAttachmentName(ref.name, index);
|
|
8772
8775
|
const filePath = path16.join(state.directory, name);
|
|
@@ -8797,9 +8800,9 @@ var init_task_attachments = __esm({
|
|
|
8797
8800
|
import { homedir as homedir7 } from "node:os";
|
|
8798
8801
|
import { join as join11 } from "node:path";
|
|
8799
8802
|
import { readdir as readdir2, readFile as readFile2, unlink, writeFile as writeFile2 } from "node:fs/promises";
|
|
8800
|
-
import { createHash as
|
|
8803
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
8801
8804
|
function deriveUuid(seed) {
|
|
8802
|
-
const h =
|
|
8805
|
+
const h = createHash5("sha256").update(seed).digest("hex");
|
|
8803
8806
|
return `${h.slice(0, 8)}-${h.slice(8, 12)}-5${h.slice(13, 16)}-${(parseInt(h.slice(16, 18), 16) & 63 | 128).toString(16)}${h.slice(18, 20)}-${h.slice(20, 32)}`;
|
|
8804
8807
|
}
|
|
8805
8808
|
function spoolToCloud(record, ids) {
|
|
@@ -10521,7 +10524,7 @@ var init_error_message = __esm({
|
|
|
10521
10524
|
});
|
|
10522
10525
|
|
|
10523
10526
|
// ../../scripts/virtual-office/code-runner/watcher-coordination.mjs
|
|
10524
|
-
import { createHash as
|
|
10527
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
10525
10528
|
function ciFixOccurrenceKey({ repo, prNumber, headSha, repairAttempt }) {
|
|
10526
10529
|
const occurrence = JSON.stringify([
|
|
10527
10530
|
String(repo).toLowerCase(),
|
|
@@ -10529,7 +10532,7 @@ function ciFixOccurrenceKey({ repo, prNumber, headSha, repairAttempt }) {
|
|
|
10529
10532
|
String(headSha).toLowerCase(),
|
|
10530
10533
|
Number(repairAttempt)
|
|
10531
10534
|
]);
|
|
10532
|
-
return `ci-fix:v1:${
|
|
10535
|
+
return `ci-fix:v1:${createHash6("sha256").update(occurrence).digest("hex")}`;
|
|
10533
10536
|
}
|
|
10534
10537
|
function coordinationRetryDue(entry, nowMs) {
|
|
10535
10538
|
return !entry.nextRetryAt || nowMs >= entry.nextRetryAt;
|
|
@@ -10659,7 +10662,7 @@ var init_watcher_coordination = __esm({
|
|
|
10659
10662
|
});
|
|
10660
10663
|
|
|
10661
10664
|
// ../../scripts/virtual-office/code-runner/watcher-state.mjs
|
|
10662
|
-
import { randomUUID as
|
|
10665
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
10663
10666
|
import { mkdir as mkdir3, open as open2, readFile as readFile4, rename, unlink as unlink2 } from "node:fs/promises";
|
|
10664
10667
|
import { dirname as dirname10 } from "node:path";
|
|
10665
10668
|
async function readWatcherState(stateFile) {
|
|
@@ -10679,7 +10682,7 @@ async function readWatcherState(stateFile) {
|
|
|
10679
10682
|
async function writeWatcherState(stateFile, state) {
|
|
10680
10683
|
const directory = dirname10(stateFile);
|
|
10681
10684
|
await mkdir3(directory, { recursive: true });
|
|
10682
|
-
const temp = `${stateFile}.${process.pid}.${
|
|
10685
|
+
const temp = `${stateFile}.${process.pid}.${randomUUID4()}.tmp`;
|
|
10683
10686
|
let handle;
|
|
10684
10687
|
try {
|
|
10685
10688
|
handle = await open2(temp, "wx");
|
|
@@ -11157,7 +11160,7 @@ var init_pr_watcher_github = __esm({
|
|
|
11157
11160
|
});
|
|
11158
11161
|
|
|
11159
11162
|
// ../../scripts/virtual-office/code-runner/enqueue-autonomous-code-task.mjs
|
|
11160
|
-
import { randomUUID as
|
|
11163
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
11161
11164
|
function isDefiniteRefusal(err) {
|
|
11162
11165
|
const status = Number(err?.status);
|
|
11163
11166
|
if (Number.isFinite(status) && status >= 400 && status < 500) return true;
|
|
@@ -11176,7 +11179,7 @@ async function enqueueAutonomousCodeTask(client, task, log2 = () => {
|
|
|
11176
11179
|
if (typeof client?.reserveAutonomousDispatchBudget !== "function" || typeof client?.releaseAutonomousDispatchBudget !== "function") {
|
|
11177
11180
|
throw new Error("autonomous dispatch admission client unavailable");
|
|
11178
11181
|
}
|
|
11179
|
-
const reservationId =
|
|
11182
|
+
const reservationId = randomUUID5();
|
|
11180
11183
|
const admission = await client.reserveAutonomousDispatchBudget({
|
|
11181
11184
|
requestedBudgetUsd,
|
|
11182
11185
|
reservationId,
|
|
@@ -12010,7 +12013,7 @@ var init_effort_mode_config = __esm({
|
|
|
12010
12013
|
});
|
|
12011
12014
|
|
|
12012
12015
|
// ../../scripts/virtual-office/model-registry.mjs
|
|
12013
|
-
import { randomUUID as
|
|
12016
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
12014
12017
|
import fs11 from "node:fs";
|
|
12015
12018
|
import os4 from "node:os";
|
|
12016
12019
|
import path18 from "node:path";
|
|
@@ -12021,7 +12024,7 @@ function userCacheRoot() {
|
|
|
12021
12024
|
if (home) return path18.join(home, ".claude");
|
|
12022
12025
|
} catch {
|
|
12023
12026
|
}
|
|
12024
|
-
return path18.join(os4.tmpdir(), `vo-model-registry-${
|
|
12027
|
+
return path18.join(os4.tmpdir(), `vo-model-registry-${randomUUID6()}`);
|
|
12025
12028
|
}
|
|
12026
12029
|
function resolveCacheBaseDir(env2 = process.env, moduleDir = __dirname) {
|
|
12027
12030
|
if (env2.VO_MODEL_REGISTRY_CACHE_DIR) return env2.VO_MODEL_REGISTRY_CACHE_DIR;
|
|
@@ -15313,7 +15316,7 @@ var init_detached_economics_spool = __esm({
|
|
|
15313
15316
|
});
|
|
15314
15317
|
|
|
15315
15318
|
// ../../scripts/virtual-office/code-runner/killed-run-outcome.mjs
|
|
15316
|
-
import { randomUUID as
|
|
15319
|
+
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
15317
15320
|
async function handleKilledRun({
|
|
15318
15321
|
client,
|
|
15319
15322
|
id,
|
|
@@ -15350,7 +15353,7 @@ async function handleKilledRun({
|
|
|
15350
15353
|
};
|
|
15351
15354
|
}
|
|
15352
15355
|
if (reason === "claim_authority_changed") {
|
|
15353
|
-
const occurrenceId =
|
|
15356
|
+
const occurrenceId = randomUUID7();
|
|
15354
15357
|
const economics = {
|
|
15355
15358
|
occurrence_id: occurrenceId,
|
|
15356
15359
|
runner_id: runnerId,
|
|
@@ -15650,7 +15653,7 @@ var code_runner_daemon_exports = {};
|
|
|
15650
15653
|
__export(code_runner_daemon_exports, {
|
|
15651
15654
|
main: () => main
|
|
15652
15655
|
});
|
|
15653
|
-
import { randomUUID as
|
|
15656
|
+
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
15654
15657
|
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
15655
15658
|
function log(msg) {
|
|
15656
15659
|
console.log(`[code-runner ${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}`);
|
|
@@ -15883,7 +15886,7 @@ Closes #${publicationTarget.supersedesPrNumber}` : "";
|
|
|
15883
15886
|
async function main({ env: env2 = process.env, once: once2 = false } = {}) {
|
|
15884
15887
|
const cfg = loadCodeRunnerConfig(env2, { log });
|
|
15885
15888
|
await sweepStaleTaskAttachmentDirectories().catch((error) => log(`stale attachment cleanup failed: ${error.message}`));
|
|
15886
|
-
const runnerInstanceId =
|
|
15889
|
+
const runnerInstanceId = randomUUID8();
|
|
15887
15890
|
const client = createControlPlaneClient({
|
|
15888
15891
|
env: env2,
|
|
15889
15892
|
runnerId: cfg.runnerId,
|
|
@@ -16095,8 +16098,163 @@ var init_code_runner_daemon = __esm({
|
|
|
16095
16098
|
import { createRequire as createRequire4 } from "node:module";
|
|
16096
16099
|
|
|
16097
16100
|
// src/runner-readiness.mjs
|
|
16098
|
-
|
|
16099
|
-
|
|
16101
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
16102
|
+
var RUNNER_GITHUB_READINESS_TIMEOUT_MS = 5e4;
|
|
16103
|
+
var RUNNER_IDENTITY_READINESS_TIMEOUT_MS = 1e4;
|
|
16104
|
+
var RUNNER_READINESS_MAX_REPOSITORIES = 4;
|
|
16105
|
+
var RUNNER_READINESS_MAX_TIMER_DELAY_MS = 2147e6;
|
|
16106
|
+
var RUNNER_READINESS_TRANSIENT_MAX_RETRY_AFTER_MS = 3e5;
|
|
16107
|
+
var RUNNER_READINESS_IPC_ACK_TIMEOUT_MS = 5e3;
|
|
16108
|
+
var RUNNER_READINESS_DEFERRAL_MESSAGE_TYPE = "vo-runner-readiness-deferred-v1";
|
|
16109
|
+
var RUNNER_READINESS_DEFERRAL_ACK_TYPE = "vo-runner-readiness-deferred-ack-v1";
|
|
16110
|
+
var RUNNER_REPOSITORY = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u;
|
|
16111
|
+
function runnerRepositoryScopeFromEnv(env2 = {}) {
|
|
16112
|
+
return String(env2.VO_CODE_RUNNER_REPOS || "").split(/[\s,]+/u).map((repo) => repo.trim()).filter(Boolean);
|
|
16113
|
+
}
|
|
16114
|
+
function normalizeRunnerRepositoryScope(repositories) {
|
|
16115
|
+
if (!Array.isArray(repositories) || repositories.length < 1) {
|
|
16116
|
+
throw new Error("VO_CODE_RUNNER_REPOS must name at least one owner/name repository");
|
|
16117
|
+
}
|
|
16118
|
+
if (repositories.length > RUNNER_READINESS_MAX_REPOSITORIES) {
|
|
16119
|
+
throw new Error(`VO_CODE_RUNNER_REPOS supports at most ${RUNNER_READINESS_MAX_REPOSITORIES} repositories`);
|
|
16120
|
+
}
|
|
16121
|
+
const normalized = repositories.map((repo) => {
|
|
16122
|
+
const [owner, name] = typeof repo === "string" ? repo.split("/") : [];
|
|
16123
|
+
if (typeof repo !== "string" || repo.length > 140 || !RUNNER_REPOSITORY.test(repo) || owner === "." || owner === ".." || name === "." || name === "..") {
|
|
16124
|
+
throw new Error("VO_CODE_RUNNER_REPOS entries must be canonical owner/name repositories");
|
|
16125
|
+
}
|
|
16126
|
+
return repo.toLowerCase();
|
|
16127
|
+
});
|
|
16128
|
+
if (new Set(normalized).size !== normalized.length) {
|
|
16129
|
+
throw new Error("VO_CODE_RUNNER_REPOS entries must be unique");
|
|
16130
|
+
}
|
|
16131
|
+
const owners = new Set(normalized.map((repo) => repo.split("/")[0]));
|
|
16132
|
+
if (owners.size !== 1) {
|
|
16133
|
+
throw new Error("VO_CODE_RUNNER_REPOS entries must share one owner");
|
|
16134
|
+
}
|
|
16135
|
+
return Object.freeze(normalized.sort());
|
|
16136
|
+
}
|
|
16137
|
+
function runnerRepositoryScopeDigest(repositories) {
|
|
16138
|
+
const scope = normalizeRunnerRepositoryScope(repositories);
|
|
16139
|
+
return createHash("sha256").update(JSON.stringify({
|
|
16140
|
+
version: 1,
|
|
16141
|
+
repositories: scope
|
|
16142
|
+
}), "utf8").digest("hex");
|
|
16143
|
+
}
|
|
16144
|
+
function runnerReadinessRetryDelayMs(readiness) {
|
|
16145
|
+
if (readiness?.paired !== true || readiness?.githubReady !== false) return null;
|
|
16146
|
+
if (Number.isFinite(readiness.retryAfterMs) && readiness.retryAfterMs > 0) {
|
|
16147
|
+
return Math.ceil(readiness.retryAfterMs);
|
|
16148
|
+
}
|
|
16149
|
+
return null;
|
|
16150
|
+
}
|
|
16151
|
+
function runnerReadinessFailureDisposition(readiness, delivery = null) {
|
|
16152
|
+
const retryAfterMs = runnerReadinessRetryDelayMs(readiness);
|
|
16153
|
+
if (retryAfterMs === null) {
|
|
16154
|
+
return Object.freeze({ action: "exit", exitCode: 1, retryAfterMs: null });
|
|
16155
|
+
}
|
|
16156
|
+
if (delivery?.attempted === true) {
|
|
16157
|
+
return Object.freeze({
|
|
16158
|
+
action: "exit",
|
|
16159
|
+
exitCode: delivery.delivered === true ? 75 : 1,
|
|
16160
|
+
retryAfterMs
|
|
16161
|
+
});
|
|
16162
|
+
}
|
|
16163
|
+
return Object.freeze({ action: "wait", exitCode: null, retryAfterMs });
|
|
16164
|
+
}
|
|
16165
|
+
async function notifySupervisorReadinessDeferral({
|
|
16166
|
+
processLike,
|
|
16167
|
+
retryAfterMs,
|
|
16168
|
+
error = null,
|
|
16169
|
+
ackTimeoutMs = RUNNER_READINESS_IPC_ACK_TIMEOUT_MS
|
|
16170
|
+
}) {
|
|
16171
|
+
if (processLike?.connected !== true || typeof processLike.send !== "function") {
|
|
16172
|
+
return Object.freeze({ attempted: false, delivered: false });
|
|
16173
|
+
}
|
|
16174
|
+
if (typeof processLike.on !== "function" || typeof processLike.off !== "function") {
|
|
16175
|
+
return Object.freeze({ attempted: true, delivered: false });
|
|
16176
|
+
}
|
|
16177
|
+
const nonce = randomUUID();
|
|
16178
|
+
const delivered = await new Promise((resolve3) => {
|
|
16179
|
+
let settled = false;
|
|
16180
|
+
const onMessage = (message) => {
|
|
16181
|
+
if (message?.type === RUNNER_READINESS_DEFERRAL_ACK_TYPE && message.nonce === nonce) {
|
|
16182
|
+
finish(true);
|
|
16183
|
+
}
|
|
16184
|
+
};
|
|
16185
|
+
const onDisconnect = () => finish(false);
|
|
16186
|
+
const finish = (value) => {
|
|
16187
|
+
if (settled) return;
|
|
16188
|
+
settled = true;
|
|
16189
|
+
clearTimeout(timer);
|
|
16190
|
+
processLike.off("message", onMessage);
|
|
16191
|
+
processLike.off("disconnect", onDisconnect);
|
|
16192
|
+
resolve3(value);
|
|
16193
|
+
};
|
|
16194
|
+
const timer = setTimeout(() => finish(false), ackTimeoutMs);
|
|
16195
|
+
processLike.on("message", onMessage);
|
|
16196
|
+
processLike.on("disconnect", onDisconnect);
|
|
16197
|
+
try {
|
|
16198
|
+
processLike.send({
|
|
16199
|
+
type: RUNNER_READINESS_DEFERRAL_MESSAGE_TYPE,
|
|
16200
|
+
nonce,
|
|
16201
|
+
retryAfterMs,
|
|
16202
|
+
error
|
|
16203
|
+
}, (sendError) => {
|
|
16204
|
+
if (sendError) finish(false);
|
|
16205
|
+
});
|
|
16206
|
+
} catch {
|
|
16207
|
+
finish(false);
|
|
16208
|
+
}
|
|
16209
|
+
});
|
|
16210
|
+
return Object.freeze({ attempted: true, delivered });
|
|
16211
|
+
}
|
|
16212
|
+
async function waitForRunnerReadinessRetry(delayMs, {
|
|
16213
|
+
nowMs = () => Date.now(),
|
|
16214
|
+
wait: wait3 = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms))
|
|
16215
|
+
} = {}) {
|
|
16216
|
+
if (!Number.isFinite(delayMs) || delayMs <= 0) {
|
|
16217
|
+
throw new TypeError("runner readiness retry delay is invalid");
|
|
16218
|
+
}
|
|
16219
|
+
const deadline = nowMs() + Math.ceil(delayMs);
|
|
16220
|
+
while (nowMs() < deadline) {
|
|
16221
|
+
await wait3(Math.min(RUNNER_READINESS_MAX_TIMER_DELAY_MS, deadline - nowMs()));
|
|
16222
|
+
}
|
|
16223
|
+
}
|
|
16224
|
+
function failed({
|
|
16225
|
+
paired = false,
|
|
16226
|
+
operatorId = null,
|
|
16227
|
+
tenantId = null,
|
|
16228
|
+
githubReady = null,
|
|
16229
|
+
retryAfterMs = null,
|
|
16230
|
+
error,
|
|
16231
|
+
message
|
|
16232
|
+
}) {
|
|
16233
|
+
return {
|
|
16234
|
+
ok: false,
|
|
16235
|
+
paired,
|
|
16236
|
+
operatorId,
|
|
16237
|
+
tenantId,
|
|
16238
|
+
githubReady,
|
|
16239
|
+
...Number.isFinite(retryAfterMs) && retryAfterMs > 0 ? { retryAfterMs } : {},
|
|
16240
|
+
error,
|
|
16241
|
+
message
|
|
16242
|
+
};
|
|
16243
|
+
}
|
|
16244
|
+
function responseRetryAfterMs(response, body) {
|
|
16245
|
+
const transientReadinessFailure = response.status === 503 && body?.error === "github_installation_readiness_retryable";
|
|
16246
|
+
if (response.status !== 429 && !transientReadinessFailure) return null;
|
|
16247
|
+
const bound = (retryAfterMs) => transientReadinessFailure ? Math.min(RUNNER_READINESS_TRANSIENT_MAX_RETRY_AFTER_MS, retryAfterMs) : retryAfterMs;
|
|
16248
|
+
const value = response.headers?.get?.("retry-after")?.trim() ?? "";
|
|
16249
|
+
const seconds = Number(value);
|
|
16250
|
+
if (value && Number.isFinite(seconds) && seconds >= 0) {
|
|
16251
|
+
return bound(Math.max(1e3, Math.ceil(seconds * 1e3)));
|
|
16252
|
+
}
|
|
16253
|
+
const at = value ? Date.parse(value) : Number.NaN;
|
|
16254
|
+
if (Number.isFinite(at)) {
|
|
16255
|
+
return bound(Math.max(1e3, Math.ceil(at - Date.now())));
|
|
16256
|
+
}
|
|
16257
|
+
return bound(6e4);
|
|
16100
16258
|
}
|
|
16101
16259
|
async function responseBody(response) {
|
|
16102
16260
|
try {
|
|
@@ -16109,11 +16267,21 @@ async function responseBody(response) {
|
|
|
16109
16267
|
function serverMessage(body, fallback) {
|
|
16110
16268
|
return typeof body.message === "string" && body.message.trim() ? body.message.trim() : fallback;
|
|
16111
16269
|
}
|
|
16112
|
-
async function
|
|
16270
|
+
async function fetchJsonWithTimeout(fetchImpl, url, init, timeoutMs) {
|
|
16113
16271
|
const controller = new AbortController();
|
|
16114
|
-
|
|
16272
|
+
let timer;
|
|
16273
|
+
const timeout = new Promise((_, reject) => {
|
|
16274
|
+
timer = setTimeout(() => {
|
|
16275
|
+
controller.abort();
|
|
16276
|
+
reject(new Error(`request aborted after ${timeoutMs}ms`));
|
|
16277
|
+
}, timeoutMs);
|
|
16278
|
+
});
|
|
16115
16279
|
try {
|
|
16116
|
-
|
|
16280
|
+
const requestAndBody = (async () => {
|
|
16281
|
+
const response = await fetchImpl(url, { ...init, signal: controller.signal });
|
|
16282
|
+
return { response, body: await responseBody(response) };
|
|
16283
|
+
})();
|
|
16284
|
+
return await Promise.race([requestAndBody, timeout]);
|
|
16117
16285
|
} finally {
|
|
16118
16286
|
clearTimeout(timer);
|
|
16119
16287
|
}
|
|
@@ -16123,18 +16291,24 @@ async function probeRunnerReadiness({
|
|
|
16123
16291
|
token: token2,
|
|
16124
16292
|
fetchImpl = fetch,
|
|
16125
16293
|
requireGithub = false,
|
|
16126
|
-
|
|
16294
|
+
repositories = [],
|
|
16295
|
+
timeoutMs = RUNNER_IDENTITY_READINESS_TIMEOUT_MS,
|
|
16296
|
+
// The server performs at most five sequential App-JWT GETs (installation plus
|
|
16297
|
+
// every one of at most four configured repositories), each bounded at 8s.
|
|
16298
|
+
// Keep a transport margin while preventing a stuck proof from hanging startup.
|
|
16299
|
+
githubTimeoutMs = RUNNER_GITHUB_READINESS_TIMEOUT_MS
|
|
16127
16300
|
}) {
|
|
16128
16301
|
const base = controlPlaneUrl2.replace(/\/+$/u, "");
|
|
16129
16302
|
const headers = { authorization: `Bearer ${token2}` };
|
|
16130
16303
|
let identityResponse;
|
|
16304
|
+
let identity;
|
|
16131
16305
|
try {
|
|
16132
|
-
identityResponse = await
|
|
16306
|
+
({ response: identityResponse, body: identity } = await fetchJsonWithTimeout(
|
|
16133
16307
|
fetchImpl,
|
|
16134
16308
|
`${base}/api/v1/auth/me`,
|
|
16135
16309
|
{ headers },
|
|
16136
16310
|
timeoutMs
|
|
16137
|
-
);
|
|
16311
|
+
));
|
|
16138
16312
|
} catch (error) {
|
|
16139
16313
|
const detail = error instanceof Error ? error.message : String(error);
|
|
16140
16314
|
return failed({
|
|
@@ -16142,7 +16316,6 @@ async function probeRunnerReadiness({
|
|
|
16142
16316
|
message: `AlgoHQ could not be reached: ${detail}`
|
|
16143
16317
|
});
|
|
16144
16318
|
}
|
|
16145
|
-
const identity = await responseBody(identityResponse);
|
|
16146
16319
|
if (!identityResponse.ok) {
|
|
16147
16320
|
return failed({
|
|
16148
16321
|
error: "credential_rejected",
|
|
@@ -16168,18 +16341,33 @@ async function probeRunnerReadiness({
|
|
|
16168
16341
|
message: "Paired to AlgoHQ."
|
|
16169
16342
|
};
|
|
16170
16343
|
}
|
|
16344
|
+
let repositoryScope = null;
|
|
16345
|
+
try {
|
|
16346
|
+
if (!Array.isArray(repositories)) {
|
|
16347
|
+
throw new Error("VO_CODE_RUNNER_REPOS must be a repository array");
|
|
16348
|
+
}
|
|
16349
|
+
if (repositories.length > 0) repositoryScope = normalizeRunnerRepositoryScope(repositories);
|
|
16350
|
+
} catch (error) {
|
|
16351
|
+
return failed({
|
|
16352
|
+
paired: true,
|
|
16353
|
+
operatorId,
|
|
16354
|
+
tenantId,
|
|
16355
|
+
githubReady: false,
|
|
16356
|
+
error: "github_repository_scope_invalid",
|
|
16357
|
+
message: error instanceof Error ? error.message : String(error)
|
|
16358
|
+
});
|
|
16359
|
+
}
|
|
16171
16360
|
let githubResponse;
|
|
16361
|
+
let github;
|
|
16172
16362
|
try {
|
|
16173
|
-
|
|
16363
|
+
const readinessUrl = new URL(`${base}/api/v1/github/installation-readiness`);
|
|
16364
|
+
for (const repo of repositoryScope ?? []) readinessUrl.searchParams.append("repo", repo);
|
|
16365
|
+
({ response: githubResponse, body: github } = await fetchJsonWithTimeout(
|
|
16174
16366
|
fetchImpl,
|
|
16175
|
-
|
|
16176
|
-
{
|
|
16177
|
-
|
|
16178
|
-
|
|
16179
|
-
body: "{}"
|
|
16180
|
-
},
|
|
16181
|
-
timeoutMs
|
|
16182
|
-
);
|
|
16367
|
+
readinessUrl.toString(),
|
|
16368
|
+
{ headers },
|
|
16369
|
+
githubTimeoutMs
|
|
16370
|
+
));
|
|
16183
16371
|
} catch (error) {
|
|
16184
16372
|
const detail = error instanceof Error ? error.message : String(error);
|
|
16185
16373
|
return failed({
|
|
@@ -16191,14 +16379,25 @@ async function probeRunnerReadiness({
|
|
|
16191
16379
|
message: `GitHub publication readiness could not be checked: ${detail}`
|
|
16192
16380
|
});
|
|
16193
16381
|
}
|
|
16194
|
-
const
|
|
16195
|
-
|
|
16382
|
+
const repositorySelection = github.repository_selection;
|
|
16383
|
+
const repositoriesVerified = github.repositories_verified;
|
|
16384
|
+
const returnedScope = github.repository_scope;
|
|
16385
|
+
let normalizedReturnedScope = null;
|
|
16386
|
+
try {
|
|
16387
|
+
normalizedReturnedScope = normalizeRunnerRepositoryScope(returnedScope);
|
|
16388
|
+
} catch {
|
|
16389
|
+
}
|
|
16390
|
+
const effectiveScope = repositoryScope ?? normalizedReturnedScope;
|
|
16391
|
+
const sourceVerified = repositoryScope === null ? github.repository_scope_source === "persisted_installation_singleton" && normalizedReturnedScope?.length === 1 : github.repository_scope_source === "runner_config";
|
|
16392
|
+
const repositoryScopeVerified = effectiveScope !== null && normalizedReturnedScope !== null && Array.isArray(returnedScope) && returnedScope.length === normalizedReturnedScope.length && returnedScope.every((repo, index) => repo === normalizedReturnedScope[index]) && normalizedReturnedScope.length === effectiveScope.length && normalizedReturnedScope.every((repo, index) => repo === effectiveScope[index]) && github.repository_scope_sha256 === runnerRepositoryScopeDigest(effectiveScope) && repositoriesVerified === effectiveScope.length && sourceVerified && (repositorySelection === "all" || repositorySelection === "selected");
|
|
16393
|
+
if (!githubResponse.ok || github.configured !== true || github.verified !== true || github.publication_ready !== true || !repositoryScopeVerified) {
|
|
16196
16394
|
const error = typeof github.error === "string" && github.error ? github.error : "github_not_ready";
|
|
16197
16395
|
return failed({
|
|
16198
16396
|
paired: true,
|
|
16199
16397
|
operatorId,
|
|
16200
16398
|
tenantId,
|
|
16201
16399
|
githubReady: false,
|
|
16400
|
+
retryAfterMs: responseRetryAfterMs(githubResponse, github),
|
|
16202
16401
|
error,
|
|
16203
16402
|
message: serverMessage(github, `GitHub publication preflight failed (HTTP ${githubResponse.status}).`)
|
|
16204
16403
|
});
|
|
@@ -16209,8 +16408,10 @@ async function probeRunnerReadiness({
|
|
|
16209
16408
|
operatorId,
|
|
16210
16409
|
tenantId,
|
|
16211
16410
|
githubReady: true,
|
|
16411
|
+
repositoryScope: effectiveScope,
|
|
16412
|
+
repositoryScopeSource: github.repository_scope_source,
|
|
16212
16413
|
error: null,
|
|
16213
|
-
message:
|
|
16414
|
+
message: `Paired with a verified Algosuite GitHub App installation (${repositoriesVerified} repos).`
|
|
16214
16415
|
};
|
|
16215
16416
|
}
|
|
16216
16417
|
function pairedOperatorScope(readiness) {
|
|
@@ -16221,7 +16422,7 @@ function pairedOperatorScope(readiness) {
|
|
|
16221
16422
|
import { closeSync, existsSync, mkdirSync, openSync, unlinkSync } from "node:fs";
|
|
16222
16423
|
import { homedir } from "node:os";
|
|
16223
16424
|
import { posix, win32 } from "node:path";
|
|
16224
|
-
import { randomUUID } from "node:crypto";
|
|
16425
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
16225
16426
|
var APP_IDENTIFIER = "ai.algosuite.vo-runner";
|
|
16226
16427
|
var CLONES_DIR = "clones";
|
|
16227
16428
|
function pathsFor(platform4) {
|
|
@@ -16291,7 +16492,7 @@ function runnerWorkingDirectory({ repoRoot: repoRoot2, clonesRoot: clonesRoot2 }
|
|
|
16291
16492
|
}
|
|
16292
16493
|
function assertWritableRunnerDirectory(root) {
|
|
16293
16494
|
mkdirSync(root, { recursive: true });
|
|
16294
|
-
const probe = pathsFor(process.platform).join(root, `.vo-runner-write-probe-${process.pid}-${
|
|
16495
|
+
const probe = pathsFor(process.platform).join(root, `.vo-runner-write-probe-${process.pid}-${randomUUID2()}`);
|
|
16295
16496
|
let handle;
|
|
16296
16497
|
try {
|
|
16297
16498
|
handle = openSync(probe, "wx", 384);
|
|
@@ -16411,21 +16612,51 @@ if (!token) {
|
|
|
16411
16612
|
}
|
|
16412
16613
|
var controlPlaneUrl = process.env.VO_CONTROL_PLANE_URL || DEFAULT_CONTROL_PLANE_URL3;
|
|
16413
16614
|
var pairedOperatorId = null;
|
|
16414
|
-
|
|
16415
|
-
const
|
|
16416
|
-
|
|
16417
|
-
|
|
16418
|
-
|
|
16615
|
+
async function deferRunnerReadinessRetry(readiness) {
|
|
16616
|
+
const initial = runnerReadinessFailureDisposition(readiness);
|
|
16617
|
+
if (initial.action === "exit") return initial;
|
|
16618
|
+
const { retryAfterMs } = initial;
|
|
16619
|
+
console.error(
|
|
16620
|
+
`[vo-mcp runner] GitHub readiness is temporarily unavailable; retrying after ${retryAfterMs}ms: ${readiness.message}`
|
|
16621
|
+
);
|
|
16622
|
+
const delivery = await notifySupervisorReadinessDeferral({
|
|
16623
|
+
processLike: process,
|
|
16624
|
+
retryAfterMs,
|
|
16625
|
+
error: readiness.error
|
|
16419
16626
|
});
|
|
16420
|
-
if (
|
|
16421
|
-
|
|
16422
|
-
|
|
16627
|
+
if (delivery.attempted) {
|
|
16628
|
+
const disposition = runnerReadinessFailureDisposition(readiness, delivery);
|
|
16629
|
+
if (disposition.exitCode === 1) {
|
|
16630
|
+
console.error("[vo-mcp runner] Supervisor readiness deferral could not be delivered; failing closed.");
|
|
16631
|
+
}
|
|
16632
|
+
return disposition;
|
|
16633
|
+
}
|
|
16634
|
+
await waitForRunnerReadinessRetry(retryAfterMs);
|
|
16635
|
+
return Object.freeze({ action: "retry", exitCode: null, retryAfterMs });
|
|
16636
|
+
}
|
|
16637
|
+
if (!explicitAdminToken) {
|
|
16638
|
+
let readiness;
|
|
16639
|
+
while (true) {
|
|
16640
|
+
readiness = await probeRunnerReadiness({
|
|
16641
|
+
controlPlaneUrl,
|
|
16642
|
+
token,
|
|
16643
|
+
requireGithub: true,
|
|
16644
|
+
repositories: runnerRepositoryScopeFromEnv(process.env)
|
|
16645
|
+
});
|
|
16646
|
+
if (readiness.ok) break;
|
|
16647
|
+
const disposition = await deferRunnerReadinessRetry(readiness);
|
|
16648
|
+
if (disposition.action === "retry") continue;
|
|
16649
|
+
if (disposition.retryAfterMs === null) {
|
|
16650
|
+
console.error(`[vo-mcp runner] Readiness check failed: ${readiness.message}`);
|
|
16651
|
+
}
|
|
16652
|
+
process.exit(disposition.exitCode);
|
|
16423
16653
|
}
|
|
16424
16654
|
pairedOperatorId = pairedOperatorScope(readiness);
|
|
16425
16655
|
if (!pairedOperatorId) {
|
|
16426
16656
|
console.error("[vo-mcp runner] Readiness check failed: paired operator scope is missing. Pair this computer again.");
|
|
16427
16657
|
process.exit(1);
|
|
16428
16658
|
}
|
|
16659
|
+
process.env.VO_CODE_RUNNER_REPOS = readiness.repositoryScope.join(",");
|
|
16429
16660
|
}
|
|
16430
16661
|
var rootConfig;
|
|
16431
16662
|
try {
|