@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 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);
@@ -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
- meta.events = [...(meta.events ?? []), { kind, payload, by: actor, at: now() }];
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
+ }
@@ -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
- /** The first answer to message `id` in its sender's mailbox (`supercode message show --replies`), as its id; or null. */
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
- if (!id) return null;
113
- const r = spawnSync(process.env.SUPERCODE_BIN || 'supercode', ['message', 'show', '--replies', id, '--json'], { encoding: 'utf8', timeout: 20_000 });
114
- let rows = null;
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
- meta.events = [...(meta.events ?? []), { kind, payload, by: actorOf(), at: now() }];
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.exec('PRAGMA busy_timeout = 10000; CREATE TABLE IF NOT EXISTS state (key TEXT PRIMARY KEY, value TEXT NOT NULL)');
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.85",
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": {