@volter/supercode-orchestrator 0.4.86 → 0.4.88
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 +5 -4
- package/board/dispatch.mjs +2 -1
- package/board/store.mjs +10 -1
- package/board/workflow.mjs +28 -12
- package/ontology.d.ts +2 -0
- package/package.json +1 -1
package/board/cli.mjs
CHANGED
|
@@ -449,8 +449,8 @@ export async function runWorkflow(argv) {
|
|
|
449
449
|
case 'reassign': {
|
|
450
450
|
need(2, 'a card id and a profile');
|
|
451
451
|
await withBoardAsync(async (b) => {
|
|
452
|
-
const task =
|
|
453
|
-
if (task?.
|
|
452
|
+
const task = engine.getTask(b.db, id);
|
|
453
|
+
if (task?.current_run_id != null && args.reclaim) await engine.reclaim(b, id, args.reason ?? 'reassign');
|
|
454
454
|
await engine.assign(b, id, rest[1] === 'none' ? null : rest[1]);
|
|
455
455
|
});
|
|
456
456
|
say(args, `Reassigned ${id} to ${rest[1]}`, { ok: true }); return 0;
|
|
@@ -1102,9 +1102,10 @@ function authorize(db, verb, rest, args, own) {
|
|
|
1102
1102
|
|
|
1103
1103
|
/** Close the live sessions of a card that is not running (its worker's and its reviewers'), freeing its machine's place. */
|
|
1104
1104
|
function closeSession(board, id) {
|
|
1105
|
-
const
|
|
1105
|
+
const task = engine.getTask(board.db, id);
|
|
1106
|
+
const rows = board.db.prepare('SELECT id, metadata FROM task_runs WHERE task_id = ? AND metadata IS NOT NULL ORDER BY id DESC').all(id);
|
|
1106
1107
|
if (!task) throw new Error(`no card ${id}`);
|
|
1107
|
-
if (task.
|
|
1108
|
+
if (task.current_run_id != null) throw new Error(`${id} has an active run; pause, block or reclaim it before closing its session`);
|
|
1108
1109
|
const closed = [];
|
|
1109
1110
|
const seen = new Set();
|
|
1110
1111
|
for (const row of rows) {
|
package/board/dispatch.mjs
CHANGED
|
@@ -22,7 +22,7 @@ import { basename, dirname, join } from 'node:path';
|
|
|
22
22
|
import { randomUUID } from 'node:crypto';
|
|
23
23
|
import { fileURLToPath } from 'node:url';
|
|
24
24
|
import { boardPath, claimer, event, json, listBoards, logsDir, now, openBoard, setActor, tx } from './store.mjs';
|
|
25
|
-
import { doors, fire, loadWorkflow, resumeOf, runSpec, transitionsFor, scopeOf, statusOf, templateValue, workflowFor } from './workflow.mjs';
|
|
25
|
+
import { doors, fire, loadWorkflow, resumeOf, runSpec, refreshRelationAnswers, transitionsFor, scopeOf, statusOf, templateValue, workflowFor } from './workflow.mjs';
|
|
26
26
|
import { evaluate, holds, render } from './expr.mjs';
|
|
27
27
|
import {
|
|
28
28
|
cardContext, claimTtl, currentRun, DEFAULT_FAILURE_LIMIT, extendClaim, getTask, lastSession,
|
|
@@ -1240,6 +1240,7 @@ export async function tick({ root, lanes, settings, only = null, slugs = null, d
|
|
|
1240
1240
|
let mark = Date.now();
|
|
1241
1241
|
const phase = (name) => { const t = Date.now(); report.ms[name] = t - mark; mark = t; };
|
|
1242
1242
|
readMailbox(board, boardAddress(board.label), report); phase('mailbox');
|
|
1243
|
+
await refreshRelationAnswers(board); phase('answers');
|
|
1243
1244
|
const effects = [];
|
|
1244
1245
|
supervise(board, fleet, settings, report, entry, effects); closeRequested(board, report); phase('supervise');
|
|
1245
1246
|
report.unsettled = [];
|
package/board/store.mjs
CHANGED
|
@@ -214,7 +214,7 @@ export function event(db, taskId, kind, payload = null, runId = null) {
|
|
|
214
214
|
const row = db.prepare('SELECT metadata FROM task_runs WHERE id = ?').get(runId);
|
|
215
215
|
const meta = json(row?.metadata, {}) ?? {};
|
|
216
216
|
if (meta.supercode?.state) {
|
|
217
|
-
|
|
217
|
+
appendRunObservation(meta, { kind, payload, by: actor, at: now() });
|
|
218
218
|
db.prepare('UPDATE task_runs SET metadata = ? WHERE id = ?').run(JSON.stringify(meta), runId);
|
|
219
219
|
return 0;
|
|
220
220
|
}
|
|
@@ -224,3 +224,12 @@ export function event(db, taskId, kind, payload = null, runId = null) {
|
|
|
224
224
|
.run(taskId, runId, kind, said ? JSON.stringify(said) : null, now());
|
|
225
225
|
return Number(r.lastInsertRowid);
|
|
226
226
|
}
|
|
227
|
+
|
|
228
|
+
/** Bounded run observations; the latest value of each kind remains available to guards. */
|
|
229
|
+
export function appendRunObservation(meta, observation) {
|
|
230
|
+
const latest = { ...(meta.last_events ?? {}) };
|
|
231
|
+
for (const event of meta.events ?? []) latest[event.kind] = event;
|
|
232
|
+
latest[observation.kind] = observation;
|
|
233
|
+
meta.last_events = Object.fromEntries(Object.entries(latest).sort((a, b) => b[1].at - a[1].at).slice(0, 128));
|
|
234
|
+
meta.events = [...(meta.events ?? []), observation].slice(-64);
|
|
235
|
+
}
|
package/board/workflow.mjs
CHANGED
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
// must hold and what happens; this module applies it to the board file and keeps no rule of its own. Its actions are
|
|
5
5
|
// the engine's primitives (the rows Hermes's tables hold); the I/O an action asks for (stopping a worker, closing a
|
|
6
6
|
// pane, sending a session a message, cleaning a workspace) is answered as effects the caller runs after commit.
|
|
7
|
-
import { spawnSync } from 'node:child_process';
|
|
7
|
+
import { spawnSync, execFile } from 'node:child_process';
|
|
8
|
+
import { promisify } from 'node:util';
|
|
8
9
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
9
10
|
import { resolve, relative, isAbsolute } from 'node:path';
|
|
10
11
|
import { randomBytes } from 'node:crypto';
|
|
11
|
-
import { actorOf, event as record, json, now } from './store.mjs';
|
|
12
|
+
import { actorOf, appendRunObservation, event as record, json, now } from './store.mjs';
|
|
12
13
|
import { evaluate, holds, render } from './expr.mjs';
|
|
13
14
|
|
|
14
15
|
const loaded = new Map();
|
|
@@ -107,15 +108,30 @@ function lastCommitAt(path, id) {
|
|
|
107
108
|
return value;
|
|
108
109
|
}
|
|
109
110
|
|
|
110
|
-
|
|
111
|
+
// Mailbox I/O is prefetched by the dispatcher, outside every board write.
|
|
112
|
+
const mailboxAnswers = new Map();
|
|
113
|
+
const execFileAsync = promisify(execFile);
|
|
114
|
+
export async function refreshRelationAnswers(board) {
|
|
115
|
+
const ids = new Set();
|
|
116
|
+
for (const row of board.db.prepare('SELECT blocked_by FROM supercode_cards').all())
|
|
117
|
+
for (const block of json(row.blocked_by, []) ?? []) if (block.kind === 'message') ids.add(block.id);
|
|
118
|
+
for (const row of board.db.prepare("SELECT e.payload FROM task_events e JOIN (SELECT task_id, MAX(id) id FROM task_events WHERE kind IN ('blocked','unblocked') GROUP BY task_id) last ON e.id=last.id JOIN tasks t ON t.id=e.task_id WHERE e.kind='blocked' AND t.status='blocked'").all()) {
|
|
119
|
+
const id = json(row.payload, {})?.waiting_on; if (id) ids.add(id);
|
|
120
|
+
}
|
|
121
|
+
const list = [...ids];
|
|
122
|
+
for (let at = 0; at < list.length; at += 4) await Promise.all(list.slice(at, at + 4).map(async (id) => {
|
|
123
|
+
try {
|
|
124
|
+
const { stdout } = await execFileAsync(process.env.SUPERCODE_BIN || 'supercode', ['message', 'show', '--replies', id, '--json'], { encoding: 'utf8', timeout: 20_000, maxBuffer: 1024 * 1024 });
|
|
125
|
+
const rows = JSON.parse(stdout);
|
|
126
|
+
if (!Array.isArray(rows)) throw new Error('message door returned no message list');
|
|
127
|
+
mailboxAnswers.set(id, { answer: rows[0]?.replies?.[0]?.id ?? null });
|
|
128
|
+
} catch (error) { mailboxAnswers.set(id, { error: `the answer to ${id} could not be read: ${error.message.slice(0, 200)}` }); }
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
111
131
|
function answerTo(id) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
try { rows = JSON.parse(r.stdout); } catch { /* read below */ }
|
|
116
|
-
// a door that cannot answer is said, never read as "no answer yet": a wait would silently never end
|
|
117
|
-
if (!Array.isArray(rows)) throw new Error(`the answer to ${id} could not be read (supercode message show --replies): ${(r.stderr || r.stdout || r.error?.message || `exit ${r.status}`).trim().slice(-200)}`);
|
|
118
|
-
return rows[0]?.replies?.[0]?.id ?? null;
|
|
132
|
+
const observed = mailboxAnswers.get(id);
|
|
133
|
+
if (observed?.error) throw new Error(observed.error);
|
|
134
|
+
return observed?.answer ?? null;
|
|
119
135
|
}
|
|
120
136
|
|
|
121
137
|
const OPEN_OUTCOME = /^(\s*[-*] )\[ \] (.+)$/;
|
|
@@ -245,7 +261,7 @@ export function scopeOf(db, board, task, { params, event = {}, extra = {} } = {}
|
|
|
245
261
|
},
|
|
246
262
|
};
|
|
247
263
|
const runMeta = json(currentRun?.metadata, {}) ?? {};
|
|
248
|
-
for (const e of runMeta.events ?? []) card.last_events[e.kind] = { created_at: e.at, payload: e.payload ?? {} };
|
|
264
|
+
for (const e of [...Object.values(runMeta.last_events ?? {}), ...(runMeta.events ?? [])]) card.last_events[e.kind] = { created_at: e.at, payload: e.payload ?? {} };
|
|
249
265
|
for (const [key, expr] of Object.entries(loaded.get(board.root)?.workflow?.derived ?? {})) card[key] = evaluate(expr, { card, event, params, now: now() });
|
|
250
266
|
return {
|
|
251
267
|
card,
|
|
@@ -744,7 +760,7 @@ export function runEvent(db, id, kind, payload) {
|
|
|
744
760
|
if (!id) return;
|
|
745
761
|
const row = one(db, 'SELECT metadata FROM task_runs WHERE id = ?', id);
|
|
746
762
|
const meta = json(row?.metadata, {}) ?? {};
|
|
747
|
-
|
|
763
|
+
appendRunObservation(meta, { kind, payload, by: actorOf(), at: now() });
|
|
748
764
|
run(db, 'UPDATE task_runs SET metadata = ? WHERE id = ?', JSON.stringify(meta), id);
|
|
749
765
|
}
|
|
750
766
|
|
package/ontology.d.ts
CHANGED
|
@@ -745,6 +745,8 @@ export interface WorkerSpec {
|
|
|
745
745
|
harness: HarnessId;
|
|
746
746
|
/** Model, where the harness's door accepts one. */
|
|
747
747
|
model?: string | null;
|
|
748
|
+
/** Reasoning effort, passed explicitly to harnesses that support it. */
|
|
749
|
+
reasoning_effort?: string | null;
|
|
748
750
|
/** supercode preset name. */
|
|
749
751
|
preset?: string | null;
|
|
750
752
|
/** Relative to the profile dir; `.` by default. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-orchestrator",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.88",
|
|
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": {
|