@volter/supercode-orchestrator 0.4.36 → 0.4.38
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/board/cli.mjs +19 -4
- package/board/dispatch.mjs +26 -8
- package/package.json +1 -1
package/board/cli.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import { read } from '../orchestration.mjs';
|
|
|
12
12
|
import { holdDispatcherLock } from '../hermes-locks.mjs';
|
|
13
13
|
import { boardPath, DEFAULT_BOARD, event, json, listBoards, now, openBoard, tx } from './store.mjs';
|
|
14
14
|
import * as engine from './engine.mjs';
|
|
15
|
-
import { adoptSession, boardLabel, boardSettings, lanesOf, localMachine, observeFleet, paused, startCard, tick } from './dispatch.mjs';
|
|
15
|
+
import { adoptSession, boardLabel, boardSettings, lanesOf, localMachine, observeFleet, paused, plan, startCard, tick } from './dispatch.mjs';
|
|
16
16
|
import { loadWorkflow, statusOf, useWorkflow, workflowEntry, workflowFor } from './workflow.mjs';
|
|
17
17
|
import { evaluate } from './expr.mjs';
|
|
18
18
|
|
|
@@ -316,7 +316,21 @@ export async function runWorkflow(argv) {
|
|
|
316
316
|
updated: (a, b) => (b.completed_at ?? b.started_at ?? b.created_at) - (a.completed_at ?? a.started_at ?? a.created_at),
|
|
317
317
|
};
|
|
318
318
|
if (args.sort) { if (!sorters[args.sort]) throw new Error(`--sort is one of ${Object.keys(sorters).join(', ')}`); cards.sort(sorters[args.sort]); }
|
|
319
|
-
if (args.json) {
|
|
319
|
+
if (args.json) {
|
|
320
|
+
// beyond Hermes's fields: the workflow's own status, the cards it waits on and its open run, so a reader of the
|
|
321
|
+
// whole board (a board file's sync) needs no `show` per card
|
|
322
|
+
const extra = withBoard((b) => new Map(cards.map((t) => {
|
|
323
|
+
const run = t.current_run_id ? b.db.prepare('SELECT id, status, started_at, metadata FROM task_runs WHERE id = ?').get(t.current_run_id) : null;
|
|
324
|
+
const session = run ? json(run.metadata, {})?.supercode ?? {} : {};
|
|
325
|
+
return [t.id, {
|
|
326
|
+
workflow_status: t.stage ?? t.status,
|
|
327
|
+
parents: b.db.prepare('SELECT parent_id FROM task_links WHERE child_id = ? ORDER BY parent_id').all(t.id).map((r) => r.parent_id),
|
|
328
|
+
run: run ? { id: run.id, status: run.status, started_at: run.started_at, session: session.address ?? session.session_id ?? null } : null,
|
|
329
|
+
}];
|
|
330
|
+
})));
|
|
331
|
+
say(args, '', cards.map((t) => ({ ...cardDict(t), ...extra.get(t.id) })));
|
|
332
|
+
return 0;
|
|
333
|
+
}
|
|
320
334
|
const others = boardsOf(root, { includeArchived: false }).length - 1;
|
|
321
335
|
if (others > 0) process.stdout.write(`Board: ${slug} (${others} other board${others === 1 ? '' : 's'} — \`supercode workflow boards list\`)\n\n`);
|
|
322
336
|
process.stdout.write(cards.length ? `${cards.map(line).join('\n')}\n` : '(no matching tasks)\n');
|
|
@@ -667,8 +681,9 @@ export async function runWorkflow(argv) {
|
|
|
667
681
|
const { lanes, settings } = await laneState(root);
|
|
668
682
|
const params = { ...(args['failure-limit'] ? { failure_limit: Number(args['failure-limit']) } : {}), ...(args.max ? { max_spawn: Number(args.max) } : {}) };
|
|
669
683
|
if (args['dry-run']) {
|
|
670
|
-
|
|
671
|
-
|
|
684
|
+
// planned through the workflow's own dispatch (its start event, eligible, order and limits), writing nothing
|
|
685
|
+
const would = (await plan({ root, lanes, settings, params, slugs: [slug] })).flatMap((r) => r.would);
|
|
686
|
+
say(args, would.length ? would.map((w) => `would start ${w.id} (${w.status}${w.role ? `, its ${w.role}` : ''}) as ${w.surface} for ${w.assignee}${w.machine ? ` on ${w.machine}` : ''}`).join('\n') : 'nothing would start', would);
|
|
672
687
|
return 0;
|
|
673
688
|
}
|
|
674
689
|
const reports = await tick({ root, lanes, settings, params });
|
package/board/dispatch.mjs
CHANGED
|
@@ -266,7 +266,7 @@ const hostOf = (pressure) => ({ memory_pressure: pressure, max_in_progress: deri
|
|
|
266
266
|
* the cards a reserve names while any waits). Each is sent the start event (its validators are Hermes's respawn
|
|
267
267
|
* guard) and what its new status runs is launched.
|
|
268
268
|
*/
|
|
269
|
-
function startCards(board, fleet, lanes, settings, report, entry, effects, { only = null, hostRunning = 0 } = {}) {
|
|
269
|
+
function startCards(board, fleet, lanes, settings, report, entry, effects, { only = null, hostRunning = 0, dry = false } = {}) {
|
|
270
270
|
const stop = paused(board.root);
|
|
271
271
|
if (stop) { report.paused = stop; return; }
|
|
272
272
|
const { workflow, params } = entry;
|
|
@@ -321,10 +321,12 @@ function startCards(board, fleet, lanes, settings, report, entry, effects, { onl
|
|
|
321
321
|
if (evaluate(l.group, { card: {} }) === 'home') l.tally.set(JSON.stringify('home'), (l.tally.get(JSON.stringify('home')) ?? 0) + Math.max(0, hostRunning - here));
|
|
322
322
|
}
|
|
323
323
|
const reserveWaits = (l) => l.reserve && candidates.some((c) => holds(l.reserve.for, c.scope));
|
|
324
|
+
// a dry run (dispatch --dry-run) plans the same way and writes nothing: no notes, no moves, no launches
|
|
325
|
+
const note = (id, why) => { if (!dry) tx(board, (db) => noteGuard(db, id, why)); };
|
|
324
326
|
for (const c of candidates) {
|
|
325
327
|
const { task, plan, scope } = c;
|
|
326
|
-
if (!plan.profile) { report.unassigned.push(task.id);
|
|
327
|
-
if (!plan.lane) { report.nonspawnable.push(task.id);
|
|
328
|
+
if (!plan.profile) { report.unassigned.push(task.id); note(task.id, 'no profile runs it (no assignee, and the workflow names none)'); continue; }
|
|
329
|
+
if (!plan.lane) { report.nonspawnable.push(task.id); note(task.id, `${plan.profile} is no lane of this home`); continue; }
|
|
328
330
|
let why = null;
|
|
329
331
|
for (const l of limits) {
|
|
330
332
|
const max = evaluate(l.max, scope);
|
|
@@ -335,7 +337,12 @@ function startCards(board, fleet, lanes, settings, report, entry, effects, { onl
|
|
|
335
337
|
}
|
|
336
338
|
// the launcher's own conditions: a lane that can start this card where it names
|
|
337
339
|
if (!why) why = launcherCheck(board.db, task, plan.lane, fleet);
|
|
338
|
-
if (why) {
|
|
340
|
+
if (why) { note(task.id, why); report.queued.push([task.id, why]); continue; }
|
|
341
|
+
if (dry) {
|
|
342
|
+
report.would.push({ id: task.id, status: plan.target, role: plan.runSpec?.role ?? null, assignee: plan.profile, machine: task.machine ?? null, surface: plan.lane.surface });
|
|
343
|
+
for (const l of limits) if (holds(l.counts, scope)) { const g = JSON.stringify(evaluate(l.group, scope)); l.tally.set(g, (l.tally.get(g) ?? 0) + 1); }
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
339
346
|
let moved;
|
|
340
347
|
try {
|
|
341
348
|
moved = tx(board, (db) => {
|
|
@@ -1029,14 +1036,25 @@ async function pool(items, n, fn) {
|
|
|
1029
1036
|
}));
|
|
1030
1037
|
}
|
|
1031
1038
|
|
|
1032
|
-
/**
|
|
1033
|
-
* One tick over every board of the home at `root`. `lanes` from `lanesOf`, `settings` from `boardSettings`.
|
|
1034
|
-
* `only` limits starting to one card (a create that answers whether its card started). Answers one report per board.
|
|
1035
|
-
*/
|
|
1036
1039
|
/** A board file's fingerprint: a corrupt board is skipped until the file changes (Hermes retries it then). */
|
|
1037
1040
|
const fingerprint = (path) => { try { const st = statSync(path); return `${st.size}:${st.mtimeMs}`; } catch { return 'missing'; } };
|
|
1038
1041
|
const CORRUPT = /malformed|not a database|corrupt|disk image/i;
|
|
1039
1042
|
|
|
1043
|
+
/** What a tick would start on each board of the home, planned through the workflow's `dispatch`, writing nothing. */
|
|
1044
|
+
export async function plan({ root, lanes, settings, slugs = null, params = {} }) {
|
|
1045
|
+
const loaded = await loadWorkflow(root, { fresh: true });
|
|
1046
|
+
const entry = { ...loaded, params: { ...loaded.params, ...params } };
|
|
1047
|
+
const fleet = Object.values(lanes).some((l) => l.surface === 'pane') ? observeFleet() : { ok: false, alive: new Set(), online: new Set(), sessions: [], error: 'no pane lanes' };
|
|
1048
|
+
const out = [];
|
|
1049
|
+
for (const slug of slugs ?? listBoards(root)) {
|
|
1050
|
+
const report = { board: slug, would: [], queued: [], unassigned: [], nonspawnable: [] };
|
|
1051
|
+
const board = openBoard(root, slug);
|
|
1052
|
+
try { board.label = boardLabel(root, slug); startCards(board, fleet, lanes, settings, report, entry, [], { dry: true }); } finally { board.close(); }
|
|
1053
|
+
out.push(report);
|
|
1054
|
+
}
|
|
1055
|
+
return out;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1040
1058
|
/**
|
|
1041
1059
|
* One tick over every board of the home at `root`. `lanes` from `lanesOf`, `settings` from `boardSettings`.
|
|
1042
1060
|
* `only` limits starting to one card (a create that answers whether its card started). Each board is ticked under its
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-orchestrator",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.38",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The orchestrator runtime over the Volter Harness ontology: one typed operational model whose folder is its serialization, read and written through the harness orchestration doors (docs/ORCHESTRATOR-IR.md)",
|
|
6
6
|
"exports": {
|