@volter/supercode-orchestrator 0.4.85 → 0.4.87
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 +0 -1
- package/board/dispatch.mjs +2 -1
- package/board/store.mjs +10 -1
- package/board/workflow.mjs +28 -12
- package/board/ztrack.mjs +7 -5
- package/ontology.d.ts +2 -0
- package/package.json +1 -1
package/board/cli.mjs
CHANGED
|
@@ -327,7 +327,6 @@ export async function runWorkflow(argv) {
|
|
|
327
327
|
case 'list': case 'ls': {
|
|
328
328
|
const assignee = args.assignee ?? (args.mine ? callerName() : null);
|
|
329
329
|
const cards = withBoard((b) => {
|
|
330
|
-
engine.recomputeReady(b);
|
|
331
330
|
let rows = engine.listCards(b.db, { status: args.status, assignee, machine: args.machine, includeArchived: args.archived || args.all });
|
|
332
331
|
if (args.tenant) rows = rows.filter((t) => t.tenant === args.tenant);
|
|
333
332
|
if (args.session) rows = rows.filter((t) => t.session_id === args.session);
|
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/board/ztrack.mjs
CHANGED
|
@@ -39,7 +39,7 @@ class ZtrackBoard {
|
|
|
39
39
|
this.codec = require('@volter/ztrack/board-document');
|
|
40
40
|
this.preset = require('@volter/ztrack/kanban-preset');
|
|
41
41
|
this.runtime = new DatabaseSync(`${marker}.runtime.db`);
|
|
42
|
-
this.runtime.
|
|
42
|
+
if (!this.runtime.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='state'").get()) this.runtime.exec('CREATE TABLE state (key TEXT PRIMARY KEY, value TEXT NOT NULL)');
|
|
43
43
|
this.mem = null; this.active = false;
|
|
44
44
|
mkdirSync(dirname(this.path), { recursive: true });
|
|
45
45
|
const self = this;
|
|
@@ -48,7 +48,8 @@ class ZtrackBoard {
|
|
|
48
48
|
exec(sql) { return self.access(() => self.mem.exec(sql)); },
|
|
49
49
|
close() { self.close(); },
|
|
50
50
|
};
|
|
51
|
-
this.tx(() => {});
|
|
51
|
+
if (seed) this.tx(() => {});
|
|
52
|
+
else { this.load(); this.loadedStamp = this.stamp(); }
|
|
52
53
|
}
|
|
53
54
|
get(key, fallback = null) { return this.computing && this.pendingValues.has(key) ? this.pendingValues.get(key) : json(this.runtime.prepare('SELECT value FROM state WHERE key = ?').get(key)?.value, fallback); }
|
|
54
55
|
put(key, value) { if (this.computing) { this.pendingValues.set(key, value); return; } this.runtime.prepare('INSERT INTO state VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value').run(key, JSON.stringify(value)); }
|
|
@@ -58,6 +59,7 @@ class ZtrackBoard {
|
|
|
58
59
|
}
|
|
59
60
|
access(fn, reading = false) {
|
|
60
61
|
if (this.active || (reading && this.mem && this.loadedStamp === this.stamp())) return fn();
|
|
62
|
+
if (reading) { this.load(); this.loadedStamp = this.stamp(); return fn(); }
|
|
61
63
|
return this.tx(fn);
|
|
62
64
|
}
|
|
63
65
|
close() { this.mem?.close(); this.runtime.close(); }
|
|
@@ -92,7 +94,7 @@ class ZtrackBoard {
|
|
|
92
94
|
this.runtime.exec('COMMIT');
|
|
93
95
|
// Parsing, rules and rendering run against a snapshot, without a runtime writer transaction.
|
|
94
96
|
this.pendingValues = new Map(); this.computing = true;
|
|
95
|
-
this.load();
|
|
97
|
+
this.load({ observe: true });
|
|
96
98
|
if (this.seed) this.seedIntoMemory();
|
|
97
99
|
else this.lift();
|
|
98
100
|
const result = fn(this.db);
|
|
@@ -178,7 +180,7 @@ class ZtrackBoard {
|
|
|
178
180
|
if (!keys.length) return;
|
|
179
181
|
this.mem.prepare(`INSERT OR REPLACE INTO ${quote(table)} (${keys.map(quote).join(',')}) VALUES (${keys.map(() => '?').join(',')})`).run(...keys.map((k) => row[k] ?? null));
|
|
180
182
|
}
|
|
181
|
-
load() {
|
|
183
|
+
load({ observe = false } = {}) {
|
|
182
184
|
this.text = read(this.path); this.archiveText = read(this.archive);
|
|
183
185
|
this.mem?.close(); this.mem = new DatabaseSync(':memory:'); this.init(this.mem, { fresh: true });
|
|
184
186
|
this.saved = this.get('snapshot', { tables: {}, runtime: {}, clocks: {}, fingerprints: {} });
|
|
@@ -214,7 +216,7 @@ class ZtrackBoard {
|
|
|
214
216
|
this.mem.prepare('UPDATE supercode_cards SET blocked_by = ? WHERE task_id = ?').run(JSON.stringify(blocks), row.task_id);
|
|
215
217
|
for (const b of blocks.filter((b) => b.kind === 'card')) this.insert('task_links', { parent_id: b.id, child_id: row.task_id });
|
|
216
218
|
}
|
|
217
|
-
this.documentEvents();
|
|
219
|
+
if (observe) this.documentEvents();
|
|
218
220
|
}
|
|
219
221
|
loadCard(id, card, parent = null) {
|
|
220
222
|
const meta = card.metadata ?? {};
|
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.87",
|
|
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": {
|