@algosuite/vo-mcp 0.2.0-beta.57 → 0.2.0-beta.58
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 +58 -8
- package/dist/runner-cli.js.map +3 -3
- package/dist/runner-supervisor.js +165 -13
- package/dist/runner-supervisor.js.map +4 -4
- package/package.json +1 -1
|
@@ -2318,6 +2318,157 @@ function resolveSupervisorChildEntry({
|
|
|
2318
2318
|
};
|
|
2319
2319
|
}
|
|
2320
2320
|
|
|
2321
|
+
// src/runner/update-drain-gate.mjs
|
|
2322
|
+
var DEFAULT_DRAIN_CAP_MS = 45 * 60 * 1e3;
|
|
2323
|
+
var DEFAULT_DRAIN_CHECK_MS = 3e4;
|
|
2324
|
+
var MAX_DRAIN_CAP_MS = 6 * 60 * 60 * 1e3;
|
|
2325
|
+
var MIN_DRAIN_CHECK_MS = 1e3;
|
|
2326
|
+
var MAX_DRAIN_CHECK_MS = 5 * 60 * 1e3;
|
|
2327
|
+
var UPDATE_RESTART_ERROR_MESSAGE = "runner update restart after drain timeout";
|
|
2328
|
+
var UPDATE_RESTART_RESULT = "runner_update_restart";
|
|
2329
|
+
var sleepMs = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
2330
|
+
function positiveNumber(raw) {
|
|
2331
|
+
if (raw === void 0 || raw === null || String(raw).trim() === "") return null;
|
|
2332
|
+
const value = Number(raw);
|
|
2333
|
+
return Number.isFinite(value) && value >= 0 ? value : null;
|
|
2334
|
+
}
|
|
2335
|
+
function resolveDrainCapMs(env = {}) {
|
|
2336
|
+
const ms = positiveNumber(env.VO_RUNNER_UPDATE_DRAIN_CAP_MS);
|
|
2337
|
+
if (ms !== null) return Math.min(ms, MAX_DRAIN_CAP_MS);
|
|
2338
|
+
const minutes = positiveNumber(env.VO_RUNNER_UPDATE_DRAIN_CAP_MIN);
|
|
2339
|
+
if (minutes !== null) return Math.min(minutes * 6e4, MAX_DRAIN_CAP_MS);
|
|
2340
|
+
return DEFAULT_DRAIN_CAP_MS;
|
|
2341
|
+
}
|
|
2342
|
+
function resolveDrainCheckMs(env = {}) {
|
|
2343
|
+
const ms = positiveNumber(env.VO_RUNNER_UPDATE_DRAIN_CHECK_MS);
|
|
2344
|
+
if (ms === null) return DEFAULT_DRAIN_CHECK_MS;
|
|
2345
|
+
return Math.min(Math.max(ms, MIN_DRAIN_CHECK_MS), MAX_DRAIN_CHECK_MS);
|
|
2346
|
+
}
|
|
2347
|
+
function readActiveTasks(status, { childRunning = true } = {}) {
|
|
2348
|
+
if (!childRunning) {
|
|
2349
|
+
return { count: 0, ids: [], known: true, runnerId: null, runnerInstanceId: null };
|
|
2350
|
+
}
|
|
2351
|
+
if (!status || status.ok === false) {
|
|
2352
|
+
return { count: 0, ids: [], known: false, runnerId: null, runnerInstanceId: null };
|
|
2353
|
+
}
|
|
2354
|
+
const ids = Array.isArray(status.activeTaskIds) ? status.activeTaskIds.map((id) => String(id)).filter(Boolean) : [];
|
|
2355
|
+
const reported = Number(status.activeTasks);
|
|
2356
|
+
const count = Number.isFinite(reported) && reported >= 0 ? reported : ids.length;
|
|
2357
|
+
return {
|
|
2358
|
+
count: Math.max(count, ids.length),
|
|
2359
|
+
ids,
|
|
2360
|
+
known: true,
|
|
2361
|
+
runnerId: status.runnerId ? String(status.runnerId) : null,
|
|
2362
|
+
runnerInstanceId: status.runnerInstanceId ? String(status.runnerInstanceId) : null
|
|
2363
|
+
};
|
|
2364
|
+
}
|
|
2365
|
+
function decideDrainStep({ active, elapsedMs, capMs }) {
|
|
2366
|
+
const busy = active.known ? active.count : "unknown";
|
|
2367
|
+
if (active.known && active.count === 0) return { action: "proceed", busy };
|
|
2368
|
+
if (elapsedMs >= capMs) return { action: "cap", busy };
|
|
2369
|
+
return { action: "wait", busy };
|
|
2370
|
+
}
|
|
2371
|
+
function describeBusy(active) {
|
|
2372
|
+
if (!active.known) return "status unreadable from a running child (assumed busy)";
|
|
2373
|
+
const ids = active.ids.length > 0 ? ` [${active.ids.join(", ")}]` : " [ids unavailable]";
|
|
2374
|
+
return `${active.count} active task(s)${ids}`;
|
|
2375
|
+
}
|
|
2376
|
+
async function markCappedTasks({ active, failInFlightTask, log }) {
|
|
2377
|
+
if (active.ids.length === 0) {
|
|
2378
|
+
if (active.count > 0 || !active.known) {
|
|
2379
|
+
log(`update drain cap reached with ${describeBusy(active)} \u2014 cannot annotate tasks the runner did not name`);
|
|
2380
|
+
}
|
|
2381
|
+
return [];
|
|
2382
|
+
}
|
|
2383
|
+
const marked = [];
|
|
2384
|
+
for (const taskId of active.ids) {
|
|
2385
|
+
try {
|
|
2386
|
+
await failInFlightTask(taskId, {
|
|
2387
|
+
message: UPDATE_RESTART_ERROR_MESSAGE,
|
|
2388
|
+
result: UPDATE_RESTART_RESULT,
|
|
2389
|
+
runnerId: active.runnerId,
|
|
2390
|
+
runnerInstanceId: active.runnerInstanceId
|
|
2391
|
+
});
|
|
2392
|
+
marked.push(taskId);
|
|
2393
|
+
} catch (error) {
|
|
2394
|
+
log(`could not mark task ${taskId} before update restart: ${error instanceof Error ? error.message : String(error)}`);
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
return marked;
|
|
2398
|
+
}
|
|
2399
|
+
async function awaitRunnerUpdateDrain({
|
|
2400
|
+
readStatus,
|
|
2401
|
+
isChildRunning = () => true,
|
|
2402
|
+
failInFlightTask = async () => {
|
|
2403
|
+
},
|
|
2404
|
+
drainable = false,
|
|
2405
|
+
env = {},
|
|
2406
|
+
now = Date.now,
|
|
2407
|
+
sleep: sleep2 = sleepMs,
|
|
2408
|
+
log = () => {
|
|
2409
|
+
}
|
|
2410
|
+
} = {}) {
|
|
2411
|
+
const capMs = resolveDrainCapMs(env);
|
|
2412
|
+
const checkMs = resolveDrainCheckMs(env);
|
|
2413
|
+
const startedAt = now();
|
|
2414
|
+
let checks = 0;
|
|
2415
|
+
let lastReported = null;
|
|
2416
|
+
const snapshot = async () => {
|
|
2417
|
+
checks += 1;
|
|
2418
|
+
const childRunning = Boolean(isChildRunning());
|
|
2419
|
+
let status;
|
|
2420
|
+
try {
|
|
2421
|
+
status = await readStatus();
|
|
2422
|
+
} catch {
|
|
2423
|
+
status = null;
|
|
2424
|
+
}
|
|
2425
|
+
return readActiveTasks(status, { childRunning });
|
|
2426
|
+
};
|
|
2427
|
+
if (!drainable) {
|
|
2428
|
+
const active = await snapshot();
|
|
2429
|
+
if (active.known && active.count > 0) {
|
|
2430
|
+
return {
|
|
2431
|
+
proceed: false,
|
|
2432
|
+
detail: `deferred safely: ${active.count} active task(s); retry when the runner is idle`,
|
|
2433
|
+
capped: false,
|
|
2434
|
+
waitedMs: 0,
|
|
2435
|
+
checks,
|
|
2436
|
+
markedTaskIds: []
|
|
2437
|
+
};
|
|
2438
|
+
}
|
|
2439
|
+
return { proceed: true, detail: "runner idle", capped: false, waitedMs: 0, checks, markedTaskIds: [] };
|
|
2440
|
+
}
|
|
2441
|
+
for (; ; ) {
|
|
2442
|
+
const active = await snapshot();
|
|
2443
|
+
const elapsedMs = now() - startedAt;
|
|
2444
|
+
const step = decideDrainStep({ active, elapsedMs, capMs });
|
|
2445
|
+
if (step.action === "proceed") {
|
|
2446
|
+
if (lastReported !== null) {
|
|
2447
|
+
log(`update drain complete after ${Math.round(elapsedMs / 1e3)}s \u2014 runner idle, applying staged update restart`);
|
|
2448
|
+
}
|
|
2449
|
+
return { proceed: true, detail: "runner idle", capped: false, waitedMs: elapsedMs, checks, markedTaskIds: [] };
|
|
2450
|
+
}
|
|
2451
|
+
if (step.action === "cap") {
|
|
2452
|
+
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`);
|
|
2453
|
+
const markedTaskIds = await markCappedTasks({ active, failInFlightTask, log });
|
|
2454
|
+
return {
|
|
2455
|
+
proceed: true,
|
|
2456
|
+
capped: true,
|
|
2457
|
+
detail: `update restart applied after drain cap; marked ${markedTaskIds.length} task(s) as "${UPDATE_RESTART_ERROR_MESSAGE}"`,
|
|
2458
|
+
waitedMs: elapsedMs,
|
|
2459
|
+
checks,
|
|
2460
|
+
markedTaskIds
|
|
2461
|
+
};
|
|
2462
|
+
}
|
|
2463
|
+
const fingerprint = `${step.busy}:${active.ids.join(",")}`;
|
|
2464
|
+
if (fingerprint !== lastReported) {
|
|
2465
|
+
lastReported = fingerprint;
|
|
2466
|
+
log(`deferring staged update restart \u2014 ${describeBusy(active)}; re-checking every ${Math.round(checkMs / 1e3)}s until idle (cap ${Math.round(capMs / 1e3)}s)`);
|
|
2467
|
+
}
|
|
2468
|
+
await sleep2(Math.max(1, Math.min(checkMs, capMs - elapsedMs)));
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2321
2472
|
// src/runner/supervisor-child-env.mjs
|
|
2322
2473
|
import { hostname as systemHostname } from "node:os";
|
|
2323
2474
|
|
|
@@ -3408,22 +3559,24 @@ async function main() {
|
|
|
3408
3559
|
}
|
|
3409
3560
|
deferredControlRecoveryGeneration = null;
|
|
3410
3561
|
handling = true;
|
|
3411
|
-
const
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
})
|
|
3562
|
+
const controlIdentity = { runnerId, ...operatorId ? { operatorId } : {}, ...supervisorControlIdentity };
|
|
3563
|
+
const bundledAction = action.kind === "update" || action.kind === "reinstall";
|
|
3564
|
+
const drain = await awaitRunnerUpdateDrain({
|
|
3565
|
+
readStatus: localStatus,
|
|
3566
|
+
drainable: bundledAction,
|
|
3567
|
+
env: process.env,
|
|
3568
|
+
isChildRunning: () => supervisorChildIsRunning(child),
|
|
3569
|
+
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 } : {} }),
|
|
3570
|
+
log: (message) => console.warn(`[vo-runner supervisor] ${message}`)
|
|
3571
|
+
});
|
|
3572
|
+
if (!drain.proceed) {
|
|
3573
|
+
await client.completeRunnerControl(action.actionId, { ...controlIdentity, status: "failed", detail: drain.detail });
|
|
3420
3574
|
handling = false;
|
|
3421
3575
|
respawn();
|
|
3422
3576
|
continue;
|
|
3423
3577
|
}
|
|
3424
3578
|
controlRecoveryFence.markBeforeStop(child);
|
|
3425
3579
|
await stopChildExpectedly(child);
|
|
3426
|
-
const bundledAction = action.kind === "update" || action.kind === "reinstall";
|
|
3427
3580
|
const result = bundledAction ? stageAndActivateBundledUpdate({
|
|
3428
3581
|
runtimeRoot,
|
|
3429
3582
|
packageSpec: `@algosuite/vo-mcp@${action.desired_package_version}`,
|
|
@@ -3445,6 +3598,7 @@ async function main() {
|
|
|
3445
3598
|
log: (message) => console.warn(`[vo-runner supervisor] ${message}`)
|
|
3446
3599
|
});
|
|
3447
3600
|
if (result.ok && result.handoff) {
|
|
3601
|
+
if (drain.capped) console.warn(`[vo-runner supervisor] ${drain.detail}`);
|
|
3448
3602
|
console.warn(`[vo-runner supervisor] activated ${result.active.version}; exiting for new-process attestation`);
|
|
3449
3603
|
return;
|
|
3450
3604
|
}
|
|
@@ -3456,9 +3610,7 @@ async function main() {
|
|
|
3456
3610
|
const relaunchedHealthy = recoveryAuthorized ? await ensureHealthyChildAfterControl() : false;
|
|
3457
3611
|
if (!relaunchedHealthy) result.ok = false;
|
|
3458
3612
|
await client.completeRunnerControl(action.actionId, {
|
|
3459
|
-
|
|
3460
|
-
...operatorId ? { operatorId } : {},
|
|
3461
|
-
...supervisorControlIdentity,
|
|
3613
|
+
...controlIdentity,
|
|
3462
3614
|
status: result.ok ? "succeeded" : "failed",
|
|
3463
3615
|
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
3616
|
});
|