@algosuite/vo-mcp 0.2.0-beta.10 → 0.2.0-beta.12
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 +177 -19
- package/dist/runner-cli.js.map +3 -3
- package/dist/runner-supervisor.js +945 -32
- package/dist/runner-supervisor.js.map +4 -4
- package/dist/update-cli.js +75 -12
- package/dist/update-cli.js.map +4 -4
- package/package.json +1 -1
package/dist/runner-cli.js
CHANGED
|
@@ -2125,22 +2125,41 @@ async function resolveBearer(env2) {
|
|
|
2125
2125
|
function createControlPlaneClient({
|
|
2126
2126
|
baseUrl = process.env.VO_CONTROL_PLANE_URL || "",
|
|
2127
2127
|
env: env2 = process.env,
|
|
2128
|
-
fetchImpl = fetch
|
|
2128
|
+
fetchImpl = fetch,
|
|
2129
|
+
heartbeatTimeoutMs = Math.min(
|
|
2130
|
+
Math.max(Number(env2.VO_CODE_RUNNER_HEARTBEAT_TIMEOUT_MS) || 15e3, 1e3),
|
|
2131
|
+
6e4
|
|
2132
|
+
)
|
|
2129
2133
|
} = {}) {
|
|
2130
2134
|
if (!baseUrl) {
|
|
2131
2135
|
throw new Error("VO_CONTROL_PLANE_URL is required for the code-runner daemon");
|
|
2132
2136
|
}
|
|
2133
2137
|
const root = baseUrl.replace(/\/+$/, "");
|
|
2134
|
-
async function req(method, path16, body) {
|
|
2138
|
+
async function req(method, path16, body, { timeoutMs } = {}) {
|
|
2135
2139
|
const bearer = await resolveBearer(env2);
|
|
2136
|
-
|
|
2140
|
+
const controller = timeoutMs ? new AbortController() : null;
|
|
2141
|
+
let timeoutId;
|
|
2142
|
+
const request = Promise.resolve(fetchImpl(`${root}${path16}`, {
|
|
2137
2143
|
method,
|
|
2138
2144
|
headers: {
|
|
2139
2145
|
"content-type": "application/json",
|
|
2140
2146
|
authorization: `Bearer ${bearer}`
|
|
2141
2147
|
},
|
|
2142
|
-
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
2148
|
+
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
2149
|
+
...controller ? { signal: controller.signal } : {}
|
|
2150
|
+
}));
|
|
2151
|
+
if (!timeoutMs) return request;
|
|
2152
|
+
const timeout = new Promise((_, reject) => {
|
|
2153
|
+
timeoutId = setTimeout(() => {
|
|
2154
|
+
controller.abort();
|
|
2155
|
+
reject(new Error(`control-plane ${path16} timed out after ${timeoutMs}ms`));
|
|
2156
|
+
}, timeoutMs);
|
|
2143
2157
|
});
|
|
2158
|
+
try {
|
|
2159
|
+
return await Promise.race([request, timeout]);
|
|
2160
|
+
} finally {
|
|
2161
|
+
clearTimeout(timeoutId);
|
|
2162
|
+
}
|
|
2144
2163
|
}
|
|
2145
2164
|
return {
|
|
2146
2165
|
/**
|
|
@@ -2156,6 +2175,10 @@ function createControlPlaneClient({
|
|
|
2156
2175
|
if (Array.isArray(operatorIds) && operatorIds.length > 0) body.operator_ids = operatorIds;
|
|
2157
2176
|
if (session.runnerInstanceId) body.runner_instance_id = session.runnerInstanceId;
|
|
2158
2177
|
if (session.runnerInstanceId && session.reconcileStale) body.reconcile_stale = true;
|
|
2178
|
+
if (session.defaultAgent) body.default_agent = session.defaultAgent;
|
|
2179
|
+
if (Array.isArray(session.availableAgents)) {
|
|
2180
|
+
body.available_agents = session.availableAgents.filter((entry) => entry?.installed === true && entry?.authenticated === true).map((entry) => entry.agent);
|
|
2181
|
+
}
|
|
2159
2182
|
const res = await req("POST", "/api/v1/code-task/claim", body);
|
|
2160
2183
|
if (res.status === 401) {
|
|
2161
2184
|
cachedFirebaseToken = null;
|
|
@@ -2299,13 +2322,20 @@ function createControlPlaneClient({
|
|
|
2299
2322
|
* authenticated operator so the web shows a TRUE "runner online" signal.
|
|
2300
2323
|
* Best-effort caller; throws on 401/non-ok so the daemon can log + retry.
|
|
2301
2324
|
*/
|
|
2302
|
-
async postHeartbeat({ runnerId, operatorId, uptimeSec, activeTasks, version, daemonVersion, servedRepos, servedOperators, availableAgents, accountUsage }) {
|
|
2325
|
+
async postHeartbeat({ runnerId, runnerInstanceId, operatorId, uptimeSec, activeTasks, version, daemonVersion, defaultAgent, supervisorInstanceId, supervisorVersion, supervisorCapabilities, servedRepos, servedOperators, availableAgents, accountUsage }) {
|
|
2303
2326
|
const body = { runner_id: runnerId };
|
|
2327
|
+
if (runnerInstanceId) body.runner_instance_id = runnerInstanceId;
|
|
2304
2328
|
if (operatorId) body.operator_id = operatorId;
|
|
2305
2329
|
if (typeof uptimeSec === "number") body.uptime_sec = uptimeSec;
|
|
2306
2330
|
if (typeof activeTasks === "number") body.active_tasks = activeTasks;
|
|
2307
2331
|
if (version) body.version = version;
|
|
2308
2332
|
if (daemonVersion) body.daemon_version = daemonVersion;
|
|
2333
|
+
if (defaultAgent) body.default_agent = defaultAgent;
|
|
2334
|
+
if (supervisorInstanceId) body.supervisor_instance_id = supervisorInstanceId;
|
|
2335
|
+
if (supervisorVersion) body.supervisor_version = supervisorVersion;
|
|
2336
|
+
if (Array.isArray(supervisorCapabilities) && supervisorCapabilities.length > 0) {
|
|
2337
|
+
body.supervisor_capabilities = supervisorCapabilities;
|
|
2338
|
+
}
|
|
2309
2339
|
if (Array.isArray(servedRepos) && servedRepos.length > 0) body.served_repos = servedRepos;
|
|
2310
2340
|
if (Array.isArray(servedOperators) && servedOperators.length > 0) {
|
|
2311
2341
|
body.served_operator_ids = servedOperators;
|
|
@@ -2316,7 +2346,9 @@ function createControlPlaneClient({
|
|
|
2316
2346
|
if (Array.isArray(accountUsage) && accountUsage.length > 0) {
|
|
2317
2347
|
body.account_usage = accountUsage;
|
|
2318
2348
|
}
|
|
2319
|
-
const res = await req("POST", "/api/v1/runner/heartbeat", body
|
|
2349
|
+
const res = await req("POST", "/api/v1/runner/heartbeat", body, {
|
|
2350
|
+
timeoutMs: heartbeatTimeoutMs
|
|
2351
|
+
});
|
|
2320
2352
|
if (res.status === 401) {
|
|
2321
2353
|
cachedFirebaseToken = null;
|
|
2322
2354
|
throw new Error("heartbeat unauthorized (401)");
|
|
@@ -2324,10 +2356,27 @@ function createControlPlaneClient({
|
|
|
2324
2356
|
if (!res.ok) throw new Error(`heartbeat failed: HTTP ${res.status}`);
|
|
2325
2357
|
return true;
|
|
2326
2358
|
},
|
|
2359
|
+
/** Read the server-authoritative heartbeat ledger without mutating it. */
|
|
2360
|
+
async getRunnerStatus({ operatorId } = {}) {
|
|
2361
|
+
const query = operatorId ? `?operator_id=${encodeURIComponent(operatorId)}` : "";
|
|
2362
|
+
const res = await req("GET", `/api/v1/runner/status${query}`, void 0, {
|
|
2363
|
+
timeoutMs: heartbeatTimeoutMs
|
|
2364
|
+
});
|
|
2365
|
+
if (res.status === 401) {
|
|
2366
|
+
cachedFirebaseToken = null;
|
|
2367
|
+
throw new Error("runner status unauthorized (401)");
|
|
2368
|
+
}
|
|
2369
|
+
if (!res.ok) throw new Error(`runner status failed: HTTP ${res.status}`);
|
|
2370
|
+
const body = await res.json();
|
|
2371
|
+
return Array.isArray(body?.runners) ? body.runners : [];
|
|
2372
|
+
},
|
|
2327
2373
|
/** Poll one authenticated runner's durable Mission Control action queue. */
|
|
2328
|
-
async pollRunnerControl({ runnerId, operatorId }) {
|
|
2374
|
+
async pollRunnerControl({ runnerId, operatorId, supervisorInstanceId, supervisorVersion, capabilities }) {
|
|
2329
2375
|
const body = { runner_id: runnerId };
|
|
2330
2376
|
if (operatorId) body.operator_id = operatorId;
|
|
2377
|
+
if (supervisorInstanceId) body.supervisor_instance_id = supervisorInstanceId;
|
|
2378
|
+
if (supervisorVersion) body.supervisor_version = supervisorVersion;
|
|
2379
|
+
if (Array.isArray(capabilities) && capabilities.length > 0) body.capabilities = capabilities;
|
|
2331
2380
|
const res = await req("POST", "/api/v1/runner/control/poll", body);
|
|
2332
2381
|
if (res.status === 401) {
|
|
2333
2382
|
cachedFirebaseToken = null;
|
|
@@ -2339,9 +2388,12 @@ function createControlPlaneClient({
|
|
|
2339
2388
|
return action && typeof action.action_id === "string" && action.action_id ? { ...action, actionId: action.action_id } : null;
|
|
2340
2389
|
},
|
|
2341
2390
|
/** Acknowledge a maintenance action after the host has restarted the child. */
|
|
2342
|
-
async completeRunnerControl(actionId, { runnerId, operatorId, status, detail }) {
|
|
2391
|
+
async completeRunnerControl(actionId, { runnerId, operatorId, supervisorInstanceId, supervisorVersion, capabilities, status, detail }) {
|
|
2343
2392
|
const body = { runner_id: runnerId, status };
|
|
2344
2393
|
if (operatorId) body.operator_id = operatorId;
|
|
2394
|
+
if (supervisorInstanceId) body.supervisor_instance_id = supervisorInstanceId;
|
|
2395
|
+
if (supervisorVersion) body.supervisor_version = supervisorVersion;
|
|
2396
|
+
if (Array.isArray(capabilities) && capabilities.length > 0) body.capabilities = capabilities;
|
|
2345
2397
|
if (detail) body.detail = detail;
|
|
2346
2398
|
const res = await req("POST", `/api/v1/runner/control/${encodeURIComponent(actionId)}/complete`, body);
|
|
2347
2399
|
if (res.status === 401) {
|
|
@@ -2423,17 +2475,55 @@ function cleanPathSegment(value) {
|
|
|
2423
2475
|
const trimmed = String(value || "").trim();
|
|
2424
2476
|
return trimmed.startsWith('"') && trimmed.endsWith('"') ? trimmed.slice(1, -1) : trimmed;
|
|
2425
2477
|
}
|
|
2478
|
+
function envValue(env2, name) {
|
|
2479
|
+
const exact = env2?.[name];
|
|
2480
|
+
if (typeof exact === "string") return exact.trim();
|
|
2481
|
+
const key = Object.keys(env2 || {}).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
|
|
2482
|
+
return typeof env2?.[key] === "string" ? env2[key].trim() : "";
|
|
2483
|
+
}
|
|
2484
|
+
function userClaudeCandidates(bin, env2) {
|
|
2485
|
+
if (!/^claude(?:\.(?:exe|cmd|ps1))?$/iu.test(bin)) return [];
|
|
2486
|
+
const userProfile = envValue(env2, "USERPROFILE");
|
|
2487
|
+
const appData = envValue(env2, "APPDATA") || (userProfile ? path9.join(userProfile, "AppData", "Roaming") : "");
|
|
2488
|
+
const localAppData = envValue(env2, "LOCALAPPDATA") || (userProfile ? path9.join(userProfile, "AppData", "Local") : "");
|
|
2489
|
+
const candidates = [];
|
|
2490
|
+
if (appData) {
|
|
2491
|
+
const npmBin = path9.join(appData, "npm");
|
|
2492
|
+
candidates.push(
|
|
2493
|
+
path9.join(npmBin, "claude.exe"),
|
|
2494
|
+
path9.join(npmBin, "claude.cmd"),
|
|
2495
|
+
path9.join(npmBin, "claude.ps1"),
|
|
2496
|
+
path9.join(npmBin, "claude"),
|
|
2497
|
+
path9.join(npmBin, ...NATIVE_CLAUDE_PARTS)
|
|
2498
|
+
);
|
|
2499
|
+
}
|
|
2500
|
+
if (userProfile) candidates.push(path9.join(userProfile, ".local", "bin", "claude.exe"));
|
|
2501
|
+
if (localAppData) {
|
|
2502
|
+
candidates.push(
|
|
2503
|
+
path9.join(localAppData, "Microsoft", "WinGet", "Links", "claude.exe"),
|
|
2504
|
+
path9.join(localAppData, "Microsoft", "WindowsApps", "claude.exe")
|
|
2505
|
+
);
|
|
2506
|
+
}
|
|
2507
|
+
return candidates;
|
|
2508
|
+
}
|
|
2426
2509
|
function pathCandidates(bin, env2) {
|
|
2427
2510
|
if (path9.isAbsolute(bin) || /[\\/]/u.test(bin)) {
|
|
2428
2511
|
return [path9.resolve(bin)];
|
|
2429
2512
|
}
|
|
2430
2513
|
const extension = path9.extname(bin);
|
|
2431
|
-
|
|
2514
|
+
const fromPath = pathValue(env2).split(";").map(cleanPathSegment).filter(Boolean).flatMap((directory) => extension ? [path9.join(directory, bin)] : [
|
|
2432
2515
|
path9.join(directory, `${bin}.exe`),
|
|
2433
2516
|
path9.join(directory, `${bin}.cmd`),
|
|
2434
2517
|
path9.join(directory, `${bin}.ps1`),
|
|
2435
2518
|
path9.join(directory, bin)
|
|
2436
2519
|
]);
|
|
2520
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2521
|
+
return [...fromPath, ...userClaudeCandidates(bin, env2)].filter((candidate) => {
|
|
2522
|
+
const key = candidate.toLowerCase();
|
|
2523
|
+
if (seen.has(key)) return false;
|
|
2524
|
+
seen.add(key);
|
|
2525
|
+
return true;
|
|
2526
|
+
});
|
|
2437
2527
|
}
|
|
2438
2528
|
function canonicalExistingPath(candidate, exists, canonicalize) {
|
|
2439
2529
|
if (!exists(candidate)) return null;
|
|
@@ -2462,7 +2552,7 @@ function resolveWindowsClaudeExecutable({
|
|
|
2462
2552
|
if (resolvedNative) return resolvedNative;
|
|
2463
2553
|
}
|
|
2464
2554
|
const error = new Error(
|
|
2465
|
-
`Could not resolve a native claude.exe for "${requested}".
|
|
2555
|
+
`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.`
|
|
2466
2556
|
);
|
|
2467
2557
|
error.code = "ENOENT";
|
|
2468
2558
|
throw error;
|
|
@@ -4779,7 +4869,7 @@ var init_dispatch_onboarding = __esm({
|
|
|
4779
4869
|
"docs/current/virtual-office-operating-model.md",
|
|
4780
4870
|
"docs/current/virtual-office-test-architect.md",
|
|
4781
4871
|
"docs/current/evidence-grounded-consensus-testing.md",
|
|
4782
|
-
"docs/vo/ADR-001-verification-oracle-not-orchestrator-2026-05-29.md (
|
|
4872
|
+
"docs/vo/ADR-001-verification-oracle-not-orchestrator-2026-05-29.md (AlgoHQ verifies + signs; human approves merges; NO autonomous bot-merge / headless triggers)",
|
|
4783
4873
|
"docs/vo/vo-adr-002-two-plane-moat.md (fat secret server / thin dumb client)",
|
|
4784
4874
|
"docs/vo/vo-roadmap-2026-05-26.md (the live roadmap \u2014 read its Change log tail for current state)",
|
|
4785
4875
|
"the nearest scoped CLAUDE.md for any directory you edit",
|
|
@@ -5240,6 +5330,7 @@ function makeLoopTicks({
|
|
|
5240
5330
|
env: env2,
|
|
5241
5331
|
log: log3,
|
|
5242
5332
|
getActive,
|
|
5333
|
+
runnerInstanceId,
|
|
5243
5334
|
// Cached agent-availability provider (agent-availability.mjs); returns null
|
|
5244
5335
|
// until the first probe completes — the heartbeat simply omits the field.
|
|
5245
5336
|
getAgentAvailability = () => null,
|
|
@@ -5251,9 +5342,49 @@ function makeLoopTicks({
|
|
|
5251
5342
|
}) {
|
|
5252
5343
|
let lastSessionForward = 0;
|
|
5253
5344
|
let lastHeartbeat = 0;
|
|
5345
|
+
let availabilityWasReady = false;
|
|
5346
|
+
const heartbeatState = /* @__PURE__ */ new Map();
|
|
5254
5347
|
let lastResumeSchedule = 0;
|
|
5255
5348
|
let resumeRunning = false;
|
|
5349
|
+
function enqueueHeartbeat(payload) {
|
|
5350
|
+
const key = payload.operatorId || "";
|
|
5351
|
+
let state = heartbeatState.get(key);
|
|
5352
|
+
if (!state) {
|
|
5353
|
+
state = { running: false, pending: null };
|
|
5354
|
+
heartbeatState.set(key, state);
|
|
5355
|
+
}
|
|
5356
|
+
return new Promise((resolve2) => {
|
|
5357
|
+
if (state.running) {
|
|
5358
|
+
if (state.pending) state.pending.waiters.push(resolve2);
|
|
5359
|
+
else state.pending = { payload, waiters: [resolve2] };
|
|
5360
|
+
state.pending.payload = payload;
|
|
5361
|
+
return;
|
|
5362
|
+
}
|
|
5363
|
+
const launch = (nextPayload, waiters) => {
|
|
5364
|
+
state.running = true;
|
|
5365
|
+
let request;
|
|
5366
|
+
try {
|
|
5367
|
+
request = Promise.resolve(client.postHeartbeat(nextPayload));
|
|
5368
|
+
} catch (error) {
|
|
5369
|
+
request = Promise.reject(error);
|
|
5370
|
+
}
|
|
5371
|
+
request.catch((e) => log3(`heartbeat failed: ${e.message}`)).finally(() => {
|
|
5372
|
+
for (const done of waiters) done();
|
|
5373
|
+
if (state.pending) {
|
|
5374
|
+
const pending = state.pending;
|
|
5375
|
+
state.pending = null;
|
|
5376
|
+
launch(pending.payload, pending.waiters);
|
|
5377
|
+
} else {
|
|
5378
|
+
state.running = false;
|
|
5379
|
+
heartbeatState.delete(key);
|
|
5380
|
+
}
|
|
5381
|
+
});
|
|
5382
|
+
};
|
|
5383
|
+
launch(payload, [resolve2]);
|
|
5384
|
+
});
|
|
5385
|
+
}
|
|
5256
5386
|
return function tick() {
|
|
5387
|
+
const heartbeatCompletions = [];
|
|
5257
5388
|
const now = nowFn();
|
|
5258
5389
|
if (cfg.sessionForwardSec > 0 && now - lastSessionForward >= cfg.sessionForwardSec * 1e3) {
|
|
5259
5390
|
lastSessionForward = now;
|
|
@@ -5264,18 +5395,29 @@ function makeLoopTicks({
|
|
|
5264
5395
|
}).catch(() => {
|
|
5265
5396
|
});
|
|
5266
5397
|
}
|
|
5267
|
-
|
|
5398
|
+
const availableAgents = getAgentAvailability();
|
|
5399
|
+
const availabilityReady = Array.isArray(availableAgents);
|
|
5400
|
+
const availabilityJustBecameReady = availabilityReady && !availabilityWasReady;
|
|
5401
|
+
availabilityWasReady = availabilityReady;
|
|
5402
|
+
if (now - lastHeartbeat >= HEARTBEAT_MS || availabilityJustBecameReady) {
|
|
5268
5403
|
lastHeartbeat = now;
|
|
5269
5404
|
const servedRepos = Array.isArray(cfg.servedRepos) ? cfg.servedRepos.slice(0, 100) : [];
|
|
5270
5405
|
const servedOperators = Array.isArray(cfg.servedOperators) ? cfg.servedOperators.slice(0, 100) : [];
|
|
5271
|
-
const availableAgents = getAgentAvailability();
|
|
5272
5406
|
const accountUsage = getAccountUsage();
|
|
5273
5407
|
const version = String(env2.VO_CODE_RUNNER_VERSION || "").trim().slice(0, 40);
|
|
5274
5408
|
const daemonVersion = String(env2.VO_CODE_RUNNER_DAEMON_VERSION || "").trim().slice(0, 40);
|
|
5409
|
+
const supervisorInstanceId = String(env2.VO_RUNNER_SUPERVISOR_INSTANCE_ID || "").trim();
|
|
5410
|
+
const supervisorVersion = String(env2.VO_RUNNER_SUPERVISOR_VERSION || "").trim().slice(0, 40);
|
|
5411
|
+
const supervisorCapabilities = String(env2.VO_RUNNER_SUPERVISOR_CAPABILITIES || "").split(",").map((value) => value.trim()).filter(Boolean).slice(0, 8);
|
|
5275
5412
|
const baseHeartbeat = {
|
|
5276
5413
|
runnerId: cfg.runnerId,
|
|
5414
|
+
...runnerInstanceId ? { runnerInstanceId } : {},
|
|
5277
5415
|
...version ? { version } : {},
|
|
5278
5416
|
...daemonVersion ? { daemonVersion } : {},
|
|
5417
|
+
...cfg.agent ? { defaultAgent: cfg.agent } : {},
|
|
5418
|
+
...supervisorInstanceId ? { supervisorInstanceId } : {},
|
|
5419
|
+
...supervisorVersion ? { supervisorVersion } : {},
|
|
5420
|
+
...supervisorCapabilities.length > 0 ? { supervisorCapabilities } : {},
|
|
5279
5421
|
...servedRepos.length > 0 ? { servedRepos } : {},
|
|
5280
5422
|
...servedOperators.length > 0 ? { servedOperators } : {},
|
|
5281
5423
|
...Array.isArray(availableAgents) && availableAgents.length > 0 ? { availableAgents } : {},
|
|
@@ -5285,7 +5427,10 @@ function makeLoopTicks({
|
|
|
5285
5427
|
};
|
|
5286
5428
|
const operatorIds = servedOperators.length > 0 ? servedOperators : [void 0];
|
|
5287
5429
|
for (const operatorId of operatorIds) {
|
|
5288
|
-
|
|
5430
|
+
heartbeatCompletions.push(enqueueHeartbeat({
|
|
5431
|
+
...baseHeartbeat,
|
|
5432
|
+
...operatorId ? { operatorId } : {}
|
|
5433
|
+
}));
|
|
5289
5434
|
}
|
|
5290
5435
|
}
|
|
5291
5436
|
const resumeSec = Number(env2.VO_RESUME_SCHEDULE_SEC) > 0 ? Number(env2.VO_RESUME_SCHEDULE_SEC) : DEFAULT_RESUME_SCHEDULE_SEC;
|
|
@@ -5296,6 +5441,7 @@ function makeLoopTicks({
|
|
|
5296
5441
|
resumeRunning = false;
|
|
5297
5442
|
});
|
|
5298
5443
|
}
|
|
5444
|
+
return Promise.all(heartbeatCompletions).then(() => void 0);
|
|
5299
5445
|
};
|
|
5300
5446
|
}
|
|
5301
5447
|
var HEARTBEAT_MS, DEFAULT_RESUME_SCHEDULE_SEC;
|
|
@@ -5310,6 +5456,10 @@ var init_loop_ticks = __esm({
|
|
|
5310
5456
|
});
|
|
5311
5457
|
|
|
5312
5458
|
// ../../scripts/virtual-office/code-runner/agent-availability.mjs
|
|
5459
|
+
function resolveAgentClaimContext(provider, defaultAgent) {
|
|
5460
|
+
const availableAgents = provider.get();
|
|
5461
|
+
return Array.isArray(availableAgents) ? { availableAgents, defaultAgent } : null;
|
|
5462
|
+
}
|
|
5313
5463
|
async function collectAgentAvailability({
|
|
5314
5464
|
agents = listAgents(),
|
|
5315
5465
|
runnerFor = (agent) => resolveRunner({ VO_CODE_RUNNER_AGENT: agent }).runner
|
|
@@ -6085,7 +6235,7 @@ function startControlServer({ port, getStatus, requestStop, allowedOrigin, log:
|
|
|
6085
6235
|
server.unref?.();
|
|
6086
6236
|
return server;
|
|
6087
6237
|
}
|
|
6088
|
-
function startDaemonControl({ cfg, requestStop, getActiveCount, isRunning, startedAt, log: log3 = () => {
|
|
6238
|
+
function startDaemonControl({ cfg, runnerInstanceId, requestStop, getActiveCount, isRunning, startedAt, log: log3 = () => {
|
|
6089
6239
|
}, onDuplicate = null }) {
|
|
6090
6240
|
if (!cfg.controlEnabled) return null;
|
|
6091
6241
|
return startControlServer({
|
|
@@ -6097,6 +6247,7 @@ function startDaemonControl({ cfg, requestStop, getActiveCount, isRunning, start
|
|
|
6097
6247
|
running: isRunning(),
|
|
6098
6248
|
pid: process.pid,
|
|
6099
6249
|
runnerId: cfg.runnerId,
|
|
6250
|
+
runnerInstanceId,
|
|
6100
6251
|
servedRepos: cfg.servedRepos,
|
|
6101
6252
|
servedOperators: cfg.servedOperators,
|
|
6102
6253
|
watchEnabled: cfg.watchEnabled,
|
|
@@ -7626,7 +7777,7 @@ function reportsBlocker(summary) {
|
|
|
7626
7777
|
const text = String(summary || "");
|
|
7627
7778
|
const explicit = explicitTaskOutcome(text);
|
|
7628
7779
|
if (explicit) return explicit === "BLOCKED" || explicit === "FAILED";
|
|
7629
|
-
return /\btask remains\s+(?:\*\*)?BLOCKED\b/i.test(text) || /(?:^|\n)\s*(?:#{1,6}\s*)?(?:host-recovery\s+)?(?:result|outcome)\s*:\s*(?:\*\*)?BLOCKED\b/im.test(text);
|
|
7780
|
+
return /\btask remains\s+(?:\*\*)?BLOCKED\b/i.test(text) || /(?:^|\n)\s*(?:#{1,6}\s*)?(?:\*\*)?(?:host-recovery\s+)?(?:result|outcome|status)\s*:\s*(?:\*\*)?BLOCKED\b/im.test(text);
|
|
7630
7781
|
}
|
|
7631
7782
|
function isMaxTurnExhaustion(run = {}, maxTurns) {
|
|
7632
7783
|
const summary = String(run.summary || "").trim().toLowerCase();
|
|
@@ -7902,6 +8053,7 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
|
|
|
7902
8053
|
const startedAt = Date.now();
|
|
7903
8054
|
const controlServer = startDaemonControl({
|
|
7904
8055
|
cfg,
|
|
8056
|
+
runnerInstanceId,
|
|
7905
8057
|
requestStop: () => stop("web-control"),
|
|
7906
8058
|
getActiveCount: () => active,
|
|
7907
8059
|
isRunning: () => !stopping,
|
|
@@ -7929,18 +8081,24 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
|
|
|
7929
8081
|
const watchCoordinator = makeWatchCycleCoordinator({ runWatch, log: log2, intervalMs: cfg.watchIntervalSec * 1e3 });
|
|
7930
8082
|
const agentAvailability = makeAgentAvailabilityProvider({ onError: (e) => log2(`agent probe failed: ${e.message}`) });
|
|
7931
8083
|
const accountUsage = makeAccountUsageProvider();
|
|
7932
|
-
const loopTick = makeLoopTicks({ client, cfg, env: env2, log: log2, getActive: () => active, getAgentAvailability: () => agentAvailability.get(), getAccountUsage: () => accountUsage.get() });
|
|
8084
|
+
const loopTick = makeLoopTicks({ client, cfg, env: env2, log: log2, getActive: () => active, runnerInstanceId, getAgentAvailability: () => agentAvailability.get(), getAccountUsage: () => accountUsage.get() });
|
|
7933
8085
|
const backoff = makeReconnectBackoff({ baseMs: cfg.pollSec * 1e3, log: log2 });
|
|
7934
8086
|
while (!stopping) {
|
|
7935
|
-
loopTick();
|
|
8087
|
+
const heartbeatCompletion = loopTick();
|
|
7936
8088
|
if (cfg.watchEnabled) watchCoordinator.start();
|
|
7937
8089
|
if (active >= cfg.maxConcurrency) {
|
|
7938
8090
|
await sleep2(cfg.pollSec * 1e3);
|
|
7939
8091
|
continue;
|
|
7940
8092
|
}
|
|
8093
|
+
const claimAgents = resolveAgentClaimContext(agentAvailability, cfg.agent);
|
|
8094
|
+
if (!claimAgents) {
|
|
8095
|
+
await sleep2(cfg.pollSec * 1e3);
|
|
8096
|
+
continue;
|
|
8097
|
+
}
|
|
8098
|
+
await heartbeatCompletion;
|
|
7941
8099
|
let task;
|
|
7942
8100
|
try {
|
|
7943
|
-
task = await client.claim(cfg.runnerId, cfg.servedRepos, cfg.servedOperators, { runnerInstanceId, reconcileStale });
|
|
8101
|
+
task = await client.claim(cfg.runnerId, cfg.servedRepos, cfg.servedOperators, { runnerInstanceId, reconcileStale, ...claimAgents });
|
|
7944
8102
|
reconcileStale = false;
|
|
7945
8103
|
backoff.onSuccess();
|
|
7946
8104
|
} catch (err) {
|