@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 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 = tx(b, (db) => engine.getTask(db, id));
453
- if (task?.status === 'running' && args.reclaim) await engine.reclaim(b, id, args.reason ?? 'reassign');
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 { task, rows } = tx(board, (db) => ({ task: engine.getTask(db, id), rows: db.prepare('SELECT id, metadata FROM task_runs WHERE task_id = ? AND metadata IS NOT NULL ORDER BY id DESC').all(id) }));
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.status === 'running') throw new Error(`${id} is running; block, complete or reclaim it before closing its session`);
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) {
@@ -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/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.86",
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": {