@algosuite/vo-mcp 0.2.0-beta.57 → 0.2.0-beta.59
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 +634 -39
- package/dist/runner-cli.js.map +4 -4
- package/dist/runner-supervisor.js +252 -15
- package/dist/runner-supervisor.js.map +4 -4
- package/package.json +1 -1
|
@@ -340,6 +340,87 @@ async function getTaskKnowledgeContextRequest(req, taskId, { query } = {}, {
|
|
|
340
340
|
throw new Error("knowledge-context retry loop exited unexpectedly");
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
+
// ../../scripts/virtual-office/code-runner/control-plane-prepared-job.mjs
|
|
344
|
+
var PREPARED_JOB_ENV_QUERY_KEYS = [
|
|
345
|
+
"VO_CODE_RUNNER_NO_WEB",
|
|
346
|
+
"VO_CODE_RUNNER_NO_WORKFLOW",
|
|
347
|
+
"VO_CODE_RUNNER_NO_CONSENSUS",
|
|
348
|
+
"VO_CODE_RUNNER_PERMISSION_MODE",
|
|
349
|
+
"VO_CODE_RUNNER_DEFAULT_BUDGET_USD",
|
|
350
|
+
"VO_CODE_RUNNER_META_REASONING_EFFORT",
|
|
351
|
+
"VO_CODE_RUNNER_ALLOW_MISSING_KNOWLEDGE_CONTEXT",
|
|
352
|
+
"VO_ENABLE_CONTEXT7"
|
|
353
|
+
];
|
|
354
|
+
var PREPARED_JOB_ENV_VALUE_MAX = 64;
|
|
355
|
+
function preparedJobQuery({ agent = "claude", env = {} } = {}) {
|
|
356
|
+
const params = new URLSearchParams();
|
|
357
|
+
params.set("agent", String(agent));
|
|
358
|
+
const sent = [];
|
|
359
|
+
const dropped = [];
|
|
360
|
+
for (const key of PREPARED_JOB_ENV_QUERY_KEYS) {
|
|
361
|
+
const raw = env?.[key];
|
|
362
|
+
if (typeof raw !== "string" || raw.length === 0) continue;
|
|
363
|
+
if (raw.length > PREPARED_JOB_ENV_VALUE_MAX) {
|
|
364
|
+
dropped.push(key);
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
params.set(key, raw);
|
|
368
|
+
sent.push(key);
|
|
369
|
+
}
|
|
370
|
+
return { query: params.toString(), sent, dropped };
|
|
371
|
+
}
|
|
372
|
+
async function refusalCode(res) {
|
|
373
|
+
try {
|
|
374
|
+
const body = await res.json();
|
|
375
|
+
return typeof body?.error === "string" && body.error ? body.error : null;
|
|
376
|
+
} catch {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
async function getPreparedJobRequest(req, taskId, options = {}, invalidateToken = () => {
|
|
381
|
+
}) {
|
|
382
|
+
const { agent = "claude", env = {}, timeoutMs = 15e3 } = options;
|
|
383
|
+
const { query, sent, dropped } = preparedJobQuery({ agent, env });
|
|
384
|
+
const envMeta = { envSent: sent, envDropped: dropped };
|
|
385
|
+
if (typeof taskId !== "string" || taskId.length === 0) {
|
|
386
|
+
return { ok: false, reason: "missing_task_id", status: 0, ...envMeta };
|
|
387
|
+
}
|
|
388
|
+
const path2 = `/api/v1/code-task/${encodeURIComponent(taskId)}/prepared-job?${query}`;
|
|
389
|
+
let res;
|
|
390
|
+
try {
|
|
391
|
+
res = await req("GET", path2, void 0, { timeoutMs });
|
|
392
|
+
} catch (err) {
|
|
393
|
+
return { ok: false, reason: `transport: ${err?.message || String(err)}`, status: 0, ...envMeta };
|
|
394
|
+
}
|
|
395
|
+
if (res?.status === 401) {
|
|
396
|
+
try {
|
|
397
|
+
invalidateToken();
|
|
398
|
+
} catch {
|
|
399
|
+
}
|
|
400
|
+
return { ok: false, reason: "unauthorized", status: 401, ...envMeta };
|
|
401
|
+
}
|
|
402
|
+
if (!res?.ok) {
|
|
403
|
+
const code = await refusalCode(res);
|
|
404
|
+
return { ok: false, reason: code || `http_${res?.status ?? "unknown"}`, status: res?.status ?? 0, ...envMeta };
|
|
405
|
+
}
|
|
406
|
+
let body;
|
|
407
|
+
try {
|
|
408
|
+
body = await res.json();
|
|
409
|
+
} catch (err) {
|
|
410
|
+
return { ok: false, reason: `unreadable_body: ${err?.message || String(err)}`, status: res.status, ...envMeta };
|
|
411
|
+
}
|
|
412
|
+
const job = body?.prepared_job;
|
|
413
|
+
if (!job || typeof job !== "object") {
|
|
414
|
+
return { ok: false, reason: "no_prepared_job_in_body", status: res.status, ...envMeta };
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
ok: true,
|
|
418
|
+
job,
|
|
419
|
+
composition: body?.composition && typeof body.composition === "object" ? body.composition : {},
|
|
420
|
+
...envMeta
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
343
424
|
// ../../scripts/virtual-office/code-runner/control-plane-client.mjs
|
|
344
425
|
var cachedFirebaseToken = null;
|
|
345
426
|
var ClaimAuthorityChangedError = class extends Error {
|
|
@@ -569,6 +650,10 @@ function createControlPlaneClient({
|
|
|
569
650
|
log: (m) => console.warn(`[code-runner ${(/* @__PURE__ */ new Date()).toISOString()}] ${m}`)
|
|
570
651
|
});
|
|
571
652
|
},
|
|
653
|
+
/** ADR-004 § 11.1b: the plane's prepared job, for SHADOW comparison. Never throws. */
|
|
654
|
+
getPreparedJob: (taskId, options) => getPreparedJobRequest(req, taskId, options, () => {
|
|
655
|
+
cachedFirebaseToken = null;
|
|
656
|
+
}),
|
|
572
657
|
/** Weekly Claude token usage report — see control-plane-weekly-tokens.mjs. */
|
|
573
658
|
async postWeeklyTokens(report) {
|
|
574
659
|
return postWeeklyTokensRequest(taskReq, report, () => {
|
|
@@ -590,8 +675,8 @@ function createControlPlaneClient({
|
|
|
590
675
|
* authenticated operator so the web shows a TRUE "runner online" signal.
|
|
591
676
|
* Best-effort caller; throws on 401/non-ok so the daemon can log + retry.
|
|
592
677
|
*/
|
|
593
|
-
async postHeartbeat({ runnerId: runnerId3, runnerInstanceId: runnerInstanceId2, operatorId, uptimeSec, activeTasks, maxConcurrency, effectiveConcurrency, measuredTaskSlots, measuredCpuSlots, measuredMemorySlots, version, daemonVersion, defaultAgent, supervisorInstanceId: supervisorInstanceId2, supervisorVersion: supervisorVersion2, supervisorCapabilities, servedRepos, servedOperators, availableAgents, accountUsage, availableLocalModels }) {
|
|
594
|
-
const body = { runner_id: runnerId3 };
|
|
678
|
+
async postHeartbeat({ runnerId: runnerId3, runnerInstanceId: runnerInstanceId2, operatorId, uptimeSec, activeTasks, maxConcurrency, effectiveConcurrency, measuredTaskSlots, measuredCpuSlots, measuredMemorySlots, version, daemonVersion, defaultAgent, supervisorInstanceId: supervisorInstanceId2, supervisorVersion: supervisorVersion2, supervisorCapabilities, servedRepos, servedOperators, availableAgents, accountUsage, availableLocalModels, prepared_job_shadow: preparedJobShadow }) {
|
|
679
|
+
const body = { runner_id: runnerId3, ...preparedJobShadow ? { prepared_job_shadow: preparedJobShadow } : {} };
|
|
595
680
|
if (runnerInstanceId2) body.runner_instance_id = runnerInstanceId2;
|
|
596
681
|
if (operatorId) body.operator_id = operatorId;
|
|
597
682
|
if (typeof uptimeSec === "number") body.uptime_sec = uptimeSec;
|
|
@@ -2318,6 +2403,157 @@ function resolveSupervisorChildEntry({
|
|
|
2318
2403
|
};
|
|
2319
2404
|
}
|
|
2320
2405
|
|
|
2406
|
+
// src/runner/update-drain-gate.mjs
|
|
2407
|
+
var DEFAULT_DRAIN_CAP_MS = 45 * 60 * 1e3;
|
|
2408
|
+
var DEFAULT_DRAIN_CHECK_MS = 3e4;
|
|
2409
|
+
var MAX_DRAIN_CAP_MS = 6 * 60 * 60 * 1e3;
|
|
2410
|
+
var MIN_DRAIN_CHECK_MS = 1e3;
|
|
2411
|
+
var MAX_DRAIN_CHECK_MS = 5 * 60 * 1e3;
|
|
2412
|
+
var UPDATE_RESTART_ERROR_MESSAGE = "runner update restart after drain timeout";
|
|
2413
|
+
var UPDATE_RESTART_RESULT = "runner_update_restart";
|
|
2414
|
+
var sleepMs = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
2415
|
+
function positiveNumber(raw) {
|
|
2416
|
+
if (raw === void 0 || raw === null || String(raw).trim() === "") return null;
|
|
2417
|
+
const value = Number(raw);
|
|
2418
|
+
return Number.isFinite(value) && value >= 0 ? value : null;
|
|
2419
|
+
}
|
|
2420
|
+
function resolveDrainCapMs(env = {}) {
|
|
2421
|
+
const ms = positiveNumber(env.VO_RUNNER_UPDATE_DRAIN_CAP_MS);
|
|
2422
|
+
if (ms !== null) return Math.min(ms, MAX_DRAIN_CAP_MS);
|
|
2423
|
+
const minutes = positiveNumber(env.VO_RUNNER_UPDATE_DRAIN_CAP_MIN);
|
|
2424
|
+
if (minutes !== null) return Math.min(minutes * 6e4, MAX_DRAIN_CAP_MS);
|
|
2425
|
+
return DEFAULT_DRAIN_CAP_MS;
|
|
2426
|
+
}
|
|
2427
|
+
function resolveDrainCheckMs(env = {}) {
|
|
2428
|
+
const ms = positiveNumber(env.VO_RUNNER_UPDATE_DRAIN_CHECK_MS);
|
|
2429
|
+
if (ms === null) return DEFAULT_DRAIN_CHECK_MS;
|
|
2430
|
+
return Math.min(Math.max(ms, MIN_DRAIN_CHECK_MS), MAX_DRAIN_CHECK_MS);
|
|
2431
|
+
}
|
|
2432
|
+
function readActiveTasks(status, { childRunning = true } = {}) {
|
|
2433
|
+
if (!childRunning) {
|
|
2434
|
+
return { count: 0, ids: [], known: true, runnerId: null, runnerInstanceId: null };
|
|
2435
|
+
}
|
|
2436
|
+
if (!status || status.ok === false) {
|
|
2437
|
+
return { count: 0, ids: [], known: false, runnerId: null, runnerInstanceId: null };
|
|
2438
|
+
}
|
|
2439
|
+
const ids = Array.isArray(status.activeTaskIds) ? status.activeTaskIds.map((id) => String(id)).filter(Boolean) : [];
|
|
2440
|
+
const reported = Number(status.activeTasks);
|
|
2441
|
+
const count = Number.isFinite(reported) && reported >= 0 ? reported : ids.length;
|
|
2442
|
+
return {
|
|
2443
|
+
count: Math.max(count, ids.length),
|
|
2444
|
+
ids,
|
|
2445
|
+
known: true,
|
|
2446
|
+
runnerId: status.runnerId ? String(status.runnerId) : null,
|
|
2447
|
+
runnerInstanceId: status.runnerInstanceId ? String(status.runnerInstanceId) : null
|
|
2448
|
+
};
|
|
2449
|
+
}
|
|
2450
|
+
function decideDrainStep({ active, elapsedMs, capMs }) {
|
|
2451
|
+
const busy = active.known ? active.count : "unknown";
|
|
2452
|
+
if (active.known && active.count === 0) return { action: "proceed", busy };
|
|
2453
|
+
if (elapsedMs >= capMs) return { action: "cap", busy };
|
|
2454
|
+
return { action: "wait", busy };
|
|
2455
|
+
}
|
|
2456
|
+
function describeBusy(active) {
|
|
2457
|
+
if (!active.known) return "status unreadable from a running child (assumed busy)";
|
|
2458
|
+
const ids = active.ids.length > 0 ? ` [${active.ids.join(", ")}]` : " [ids unavailable]";
|
|
2459
|
+
return `${active.count} active task(s)${ids}`;
|
|
2460
|
+
}
|
|
2461
|
+
async function markCappedTasks({ active, failInFlightTask, log }) {
|
|
2462
|
+
if (active.ids.length === 0) {
|
|
2463
|
+
if (active.count > 0 || !active.known) {
|
|
2464
|
+
log(`update drain cap reached with ${describeBusy(active)} \u2014 cannot annotate tasks the runner did not name`);
|
|
2465
|
+
}
|
|
2466
|
+
return [];
|
|
2467
|
+
}
|
|
2468
|
+
const marked = [];
|
|
2469
|
+
for (const taskId of active.ids) {
|
|
2470
|
+
try {
|
|
2471
|
+
await failInFlightTask(taskId, {
|
|
2472
|
+
message: UPDATE_RESTART_ERROR_MESSAGE,
|
|
2473
|
+
result: UPDATE_RESTART_RESULT,
|
|
2474
|
+
runnerId: active.runnerId,
|
|
2475
|
+
runnerInstanceId: active.runnerInstanceId
|
|
2476
|
+
});
|
|
2477
|
+
marked.push(taskId);
|
|
2478
|
+
} catch (error) {
|
|
2479
|
+
log(`could not mark task ${taskId} before update restart: ${error instanceof Error ? error.message : String(error)}`);
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
return marked;
|
|
2483
|
+
}
|
|
2484
|
+
async function awaitRunnerUpdateDrain({
|
|
2485
|
+
readStatus,
|
|
2486
|
+
isChildRunning = () => true,
|
|
2487
|
+
failInFlightTask = async () => {
|
|
2488
|
+
},
|
|
2489
|
+
drainable = false,
|
|
2490
|
+
env = {},
|
|
2491
|
+
now = Date.now,
|
|
2492
|
+
sleep: sleep2 = sleepMs,
|
|
2493
|
+
log = () => {
|
|
2494
|
+
}
|
|
2495
|
+
} = {}) {
|
|
2496
|
+
const capMs = resolveDrainCapMs(env);
|
|
2497
|
+
const checkMs = resolveDrainCheckMs(env);
|
|
2498
|
+
const startedAt = now();
|
|
2499
|
+
let checks = 0;
|
|
2500
|
+
let lastReported = null;
|
|
2501
|
+
const snapshot = async () => {
|
|
2502
|
+
checks += 1;
|
|
2503
|
+
const childRunning = Boolean(isChildRunning());
|
|
2504
|
+
let status;
|
|
2505
|
+
try {
|
|
2506
|
+
status = await readStatus();
|
|
2507
|
+
} catch {
|
|
2508
|
+
status = null;
|
|
2509
|
+
}
|
|
2510
|
+
return readActiveTasks(status, { childRunning });
|
|
2511
|
+
};
|
|
2512
|
+
if (!drainable) {
|
|
2513
|
+
const active = await snapshot();
|
|
2514
|
+
if (active.known && active.count > 0) {
|
|
2515
|
+
return {
|
|
2516
|
+
proceed: false,
|
|
2517
|
+
detail: `deferred safely: ${active.count} active task(s); retry when the runner is idle`,
|
|
2518
|
+
capped: false,
|
|
2519
|
+
waitedMs: 0,
|
|
2520
|
+
checks,
|
|
2521
|
+
markedTaskIds: []
|
|
2522
|
+
};
|
|
2523
|
+
}
|
|
2524
|
+
return { proceed: true, detail: "runner idle", capped: false, waitedMs: 0, checks, markedTaskIds: [] };
|
|
2525
|
+
}
|
|
2526
|
+
for (; ; ) {
|
|
2527
|
+
const active = await snapshot();
|
|
2528
|
+
const elapsedMs = now() - startedAt;
|
|
2529
|
+
const step = decideDrainStep({ active, elapsedMs, capMs });
|
|
2530
|
+
if (step.action === "proceed") {
|
|
2531
|
+
if (lastReported !== null) {
|
|
2532
|
+
log(`update drain complete after ${Math.round(elapsedMs / 1e3)}s \u2014 runner idle, applying staged update restart`);
|
|
2533
|
+
}
|
|
2534
|
+
return { proceed: true, detail: "runner idle", capped: false, waitedMs: elapsedMs, checks, markedTaskIds: [] };
|
|
2535
|
+
}
|
|
2536
|
+
if (step.action === "cap") {
|
|
2537
|
+
log(`update drain cap reached after ${Math.round(elapsedMs / 1e3)}s (cap ${Math.round(capMs / 1e3)}s) \u2014 ${describeBusy(active)}; marking task(s) before restart`);
|
|
2538
|
+
const markedTaskIds = await markCappedTasks({ active, failInFlightTask, log });
|
|
2539
|
+
return {
|
|
2540
|
+
proceed: true,
|
|
2541
|
+
capped: true,
|
|
2542
|
+
detail: `update restart applied after drain cap; marked ${markedTaskIds.length} task(s) as "${UPDATE_RESTART_ERROR_MESSAGE}"`,
|
|
2543
|
+
waitedMs: elapsedMs,
|
|
2544
|
+
checks,
|
|
2545
|
+
markedTaskIds
|
|
2546
|
+
};
|
|
2547
|
+
}
|
|
2548
|
+
const fingerprint = `${step.busy}:${active.ids.join(",")}`;
|
|
2549
|
+
if (fingerprint !== lastReported) {
|
|
2550
|
+
lastReported = fingerprint;
|
|
2551
|
+
log(`deferring staged update restart \u2014 ${describeBusy(active)}; re-checking every ${Math.round(checkMs / 1e3)}s until idle (cap ${Math.round(capMs / 1e3)}s)`);
|
|
2552
|
+
}
|
|
2553
|
+
await sleep2(Math.max(1, Math.min(checkMs, capMs - elapsedMs)));
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2321
2557
|
// src/runner/supervisor-child-env.mjs
|
|
2322
2558
|
import { hostname as systemHostname } from "node:os";
|
|
2323
2559
|
|
|
@@ -3408,22 +3644,24 @@ async function main() {
|
|
|
3408
3644
|
}
|
|
3409
3645
|
deferredControlRecoveryGeneration = null;
|
|
3410
3646
|
handling = true;
|
|
3411
|
-
const
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
})
|
|
3647
|
+
const controlIdentity = { runnerId, ...operatorId ? { operatorId } : {}, ...supervisorControlIdentity };
|
|
3648
|
+
const bundledAction = action.kind === "update" || action.kind === "reinstall";
|
|
3649
|
+
const drain = await awaitRunnerUpdateDrain({
|
|
3650
|
+
readStatus: localStatus,
|
|
3651
|
+
drainable: bundledAction,
|
|
3652
|
+
env: process.env,
|
|
3653
|
+
isChildRunning: () => supervisorChildIsRunning(child),
|
|
3654
|
+
failInFlightTask: (taskId, patch) => client.postProgress(taskId, { status: "failed", message: patch.message, result: patch.result, ...patch.runnerId ? { runner_id: patch.runnerId } : {}, ...patch.runnerInstanceId ? { runner_instance_id: patch.runnerInstanceId } : {} }),
|
|
3655
|
+
log: (message) => console.warn(`[vo-runner supervisor] ${message}`)
|
|
3656
|
+
});
|
|
3657
|
+
if (!drain.proceed) {
|
|
3658
|
+
await client.completeRunnerControl(action.actionId, { ...controlIdentity, status: "failed", detail: drain.detail });
|
|
3420
3659
|
handling = false;
|
|
3421
3660
|
respawn();
|
|
3422
3661
|
continue;
|
|
3423
3662
|
}
|
|
3424
3663
|
controlRecoveryFence.markBeforeStop(child);
|
|
3425
3664
|
await stopChildExpectedly(child);
|
|
3426
|
-
const bundledAction = action.kind === "update" || action.kind === "reinstall";
|
|
3427
3665
|
const result = bundledAction ? stageAndActivateBundledUpdate({
|
|
3428
3666
|
runtimeRoot,
|
|
3429
3667
|
packageSpec: `@algosuite/vo-mcp@${action.desired_package_version}`,
|
|
@@ -3445,6 +3683,7 @@ async function main() {
|
|
|
3445
3683
|
log: (message) => console.warn(`[vo-runner supervisor] ${message}`)
|
|
3446
3684
|
});
|
|
3447
3685
|
if (result.ok && result.handoff) {
|
|
3686
|
+
if (drain.capped) console.warn(`[vo-runner supervisor] ${drain.detail}`);
|
|
3448
3687
|
console.warn(`[vo-runner supervisor] activated ${result.active.version}; exiting for new-process attestation`);
|
|
3449
3688
|
return;
|
|
3450
3689
|
}
|
|
@@ -3456,9 +3695,7 @@ async function main() {
|
|
|
3456
3695
|
const relaunchedHealthy = recoveryAuthorized ? await ensureHealthyChildAfterControl() : false;
|
|
3457
3696
|
if (!relaunchedHealthy) result.ok = false;
|
|
3458
3697
|
await client.completeRunnerControl(action.actionId, {
|
|
3459
|
-
|
|
3460
|
-
...operatorId ? { operatorId } : {},
|
|
3461
|
-
...supervisorControlIdentity,
|
|
3698
|
+
...controlIdentity,
|
|
3462
3699
|
status: result.ok ? "succeeded" : "failed",
|
|
3463
3700
|
detail: result.ok ? result.detail ? `${result.detail}; runner ${packageVersion()} reconnected`.slice(0, 1e3) : `runner ${packageVersion()} reconnected` : `maintenance exited ${result.status}${result.detail ? `: ${result.detail}` : ""}${relaunchedHealthy ? "" : "; runner relaunch did not become healthy"}`
|
|
3464
3701
|
});
|