@volter/supercode-orchestrator 0.5.159 → 0.5.161

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
@@ -26,7 +26,7 @@ import * as slowLog from '@volter/supercode-harness-sdk/slow-log';
26
26
  import { basename, dirname, join, resolve } from 'node:path';
27
27
  import { fileURLToPath } from 'node:url';
28
28
  import { homedir, tmpdir, userInfo } from 'node:os';
29
- import { actorOf, CALLER_RUN_ADDRESS, heldConfigWatched, heldDocumentsChanged, heldDocumentsWatched, holdBoards, retireHeldBoards, releaseHeldBoards, publishMarked, view, backingPaths, layOut, backingOf, boardExists, boardPath, briefPath, DEFAULT_BOARD, event, json, listBoards, logsDir, now, openBoard, setActor, setPrincipal, principalOf, isCaller, tx } from './store.mjs';
29
+ import { actorOf, CALLER_RUN_ADDRESS, SESSION_CURRENT_RUNS, SESSION_RUNS, sessionIndexed, heldConfigWatched, heldDocumentsChanged, heldDocumentsWatched, holdBoards, retireHeldBoards, releaseHeldBoards, publishMarked, view, backingPaths, layOut, backingOf, boardExists, boardPath, briefPath, DEFAULT_BOARD, event, json, listBoards, logsDir, now, openBoard, setActor, setPrincipal, principalOf, isCaller, tx } from './store.mjs';
30
30
  import { isLocalWorkspace } from './workspace.mjs';
31
31
  import { openZtrackBoard } from './ztrack.mjs';
32
32
  import * as engine from './engine.mjs';
@@ -1406,18 +1406,38 @@ function workerRun(db, id, sessionId) {
1406
1406
  * worked card is held to nothing here. The newest run alone named one card, and a session adopted into a second card
1407
1407
  * was refused every write on its first (volter-org t_9a9c2a9c: no comment, heartbeat or closed subtask there, read
1408
1408
  * by the board as no progress).
1409
+ *
1410
+ * Asked on every verb a session runs, so it reads the session's own runs and no others where it can (store.mjs). The
1411
+ * newest run naming the session is the newest whose text holds its id anywhere, an observation of another run's
1412
+ * included, which no index answers: it is looked for only when its answer can add a card (one of the session's own
1413
+ * runs is on a card it does not already work and is still the last worker of), and then from the newest run back to
1414
+ * the session's own newest, which its text names too. A session whose runs are on more cards than OWN_CARDS_ASKED is
1415
+ * not asked card by card first: the newest run is looked for and its card asked alone, which answers the same.
1416
+ * A board without the session's index (store.mjs sessionIndexed) is asked from its cards and by text alone, which
1417
+ * reads no run it cannot read.
1409
1418
  */
1419
+ const OWN_CARDS_ASKED = 8;
1410
1420
  function ownCards(db, sessionId) {
1411
1421
  const worked = new Set(), held = new Set();
1412
1422
  const like = `%"session_id":"${sessionId}"%`;
1413
- for (const row of db.prepare('SELECT t.id, r.metadata FROM tasks t JOIN task_runs r ON r.id = t.current_run_id WHERE r.metadata LIKE ?').all(like)) {
1423
+ const byIndex = sessionIndexed(db);
1424
+ for (const row of db.prepare(byIndex ? SESSION_CURRENT_RUNS : 'SELECT t.id, r.metadata FROM tasks t JOIN task_runs r ON r.id = t.current_run_id WHERE r.metadata LIKE ?').all(...(byIndex ? [sessionId, like] : [like]))) {
1414
1425
  const session = json(row.metadata, {})?.supercode;
1415
1426
  if (session?.session_id !== sessionId) continue;
1416
1427
  held.add(row.id);
1417
1428
  if ((session.slot ?? 'worker') === 'worker') worked.add(row.id);
1418
1429
  }
1419
- const newest = db.prepare('SELECT task_id FROM task_runs WHERE metadata LIKE ? ORDER BY id DESC LIMIT 1').get(like);
1420
- if (newest && engine.lastSession(db, newest.task_id)?.session_id === sessionId) { worked.add(newest.task_id); held.add(newest.task_id); }
1430
+ const anywhere = 'SELECT task_id FROM task_runs WHERE metadata LIKE ? ORDER BY id DESC LIMIT 1';
1431
+ const asked = new Map();
1432
+ const lastWorker = (id) => { if (!asked.has(id)) asked.set(id, engine.lastSession(db, id)?.session_id === sessionId); return asked.get(id); };
1433
+ let newest;
1434
+ if (byIndex) {
1435
+ const runs = db.prepare(SESSION_RUNS).all(sessionId);
1436
+ const between = [...new Set(runs.map((run) => run.task_id))].filter((id) => !worked.has(id));
1437
+ if (!between.length || (between.length <= OWN_CARDS_ASKED && !between.some(lastWorker))) return { worked, held };
1438
+ newest = db.prepare('SELECT task_id FROM task_runs WHERE id >= ? AND metadata LIKE ? ORDER BY id DESC LIMIT 1').get(runs.at(-1).id, like) ?? db.prepare(anywhere).get(like);
1439
+ } else newest = db.prepare(anywhere).get(like);
1440
+ if (newest && lastWorker(newest.task_id)) { worked.add(newest.task_id); held.add(newest.task_id); }
1421
1441
  return { worked, held };
1422
1442
  }
1423
1443
 
@@ -40,7 +40,7 @@ import { homedir, hostname, tmpdir, totalmem } from 'node:os';
40
40
  import { basename, dirname, join } from 'node:path';
41
41
  import { createHash, randomUUID } from 'node:crypto';
42
42
  import { fileURLToPath } from 'node:url';
43
- import { view, boardCopyOf, boardPath, briefPath, claimer, claimHolds, copyRefusal, event, json, listBoards, logsDir, newClaim, now, openBoard, setActor, storageFault, tx } from './store.mjs';
43
+ import { view, boardCopyOf, boardPath, briefPath, claimer, claimHolds, copyRefusal, event, json, listBoards, logsDir, newClaim, now, openBoard, pendingLaunches, sessionBound, setActor, storageFault, tx } from './store.mjs';
44
44
  import { doors, fire, loadWorkflow, resumeOf, runSpec, runWorkingSeconds, requestSessionClose, retireSupersededRuns, refreshCardInputs, refreshRelationAnswers, transitionsFor, scopeOf, statusOf, projectStatus, storedStatus, templateValue, workflowFor } from './workflow.mjs';
45
45
  import { checkWorkspace, assertWorkspaceOwner, notePresence, resolveWorkspace } from './workspace.mjs';
46
46
  import { capacity, harnessDefaults, holdings } from './capacity.mjs';
@@ -1822,11 +1822,8 @@ function bindLaunch(board, row, session, pane, sid) {
1822
1822
  /** A fast worker can reach its first board door before open's reply. Bind only the daemon's keyed pane. */
1823
1823
  export async function reconcileCallerLaunch(board, sid) {
1824
1824
  fleetRoot = board.root;
1825
- if (view(board, (db) => db.prepare(`SELECT 1 FROM task_runs r JOIN tasks t ON t.current_run_id=r.id
1826
- WHERE json_extract(r.metadata, '$.supercode.session_id')=? AND COALESCE(json_extract(r.metadata, '$.supercode.launch_pending'),0)=0`).get(sid))) return;
1827
- const rows = view(board, (db) => db.prepare(`SELECT r.id, r.task_id, r.metadata FROM task_runs r
1828
- JOIN tasks t ON t.current_run_id=r.id WHERE r.ended_at IS NULL
1829
- AND json_extract(r.metadata, '$.supercode.launch_pending') = 1`).all());
1825
+ if (view(board, (db) => db.prepare(sessionBound(db)).get(sid))) return;
1826
+ const rows = view(board, (db) => db.prepare(pendingLaunches(db)).all());
1830
1827
  const machine = localMachine();
1831
1828
  const harness = boardCaller()?.harness ?? (sid === process.env.CODEX_THREAD_ID ? 'codex' : 'claude-code');
1832
1829
  const candidates = rows.filter((row) => {
package/board/store.mjs CHANGED
@@ -14,7 +14,7 @@ import { fenced } from './fence.mjs';
14
14
  import { noteStep } from '@volter/supercode-harness-sdk/slow-log';
15
15
  import { redactValue } from '@volter/teams/redact';
16
16
  import { randomBytes } from 'node:crypto';
17
- import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
17
+ import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, writeFileSync, writeSync } from 'node:fs';
18
18
  import { hostname } from 'node:os';
19
19
  import { dirname, join } from 'node:path';
20
20
  import { DatabaseSync } from 'node:sqlite';
@@ -148,8 +148,79 @@ export function listBoards(root) {
148
148
  // SQLite uses an expression index only for a statement that writes the expression the same way.
149
149
  const RUN_SESSION = "json_extract(metadata, '$.supercode.session_id')";
150
150
  const RUN_ADDRESS = "json_extract(metadata, '$.supercode.address')";
151
+ const RUN_LAUNCH_PENDING = "json_extract(metadata, '$.supercode.launch_pending')";
151
152
  export const CALLER_RUN_ADDRESS = `SELECT ${RUN_ADDRESS} AS address FROM task_runs WHERE ${RUN_SESSION} = ? ORDER BY id DESC LIMIT 1`;
152
153
  const RUN_SESSION_INDEX = 'idx_runs_supercode_session';
154
+ const RUN_LAUNCH_PENDING_INDEX = 'idx_runs_supercode_launch_pending';
155
+ // The indexes on a run's metadata. The second holds only the runs whose launch is pending (7 of 1,387 on the store
156
+ // these were measured for), in the runs' own order.
157
+ const RUN_INDEXES = [
158
+ [RUN_SESSION_INDEX, `(${RUN_SESSION}, id, ${RUN_ADDRESS})`],
159
+ [RUN_LAUNCH_PENDING_INDEX, `(id) WHERE ${RUN_LAUNCH_PENDING} = 1`],
160
+ ];
161
+
162
+ // What every verb a session runs asks about its caller (cli.mjs ownCards, dispatch.mjs reconcileCallerLaunch). Each
163
+ // starts from the runs an index names (the session's, or the pending launches) and reaches the card through the run's
164
+ // own `task_id`, the card's key: a card's current run is one of its own runs, and the join says both. Starting from the
165
+ // cards instead read every card's row to find the few that have a current run (810 of the 843 pages each of these
166
+ // statements read, whatever the session, on a store of 2,636 cards with 5 of them running). `r` is read from the session's
167
+ // index alone and the run's metadata only as `m`, after the card is known to name the run as current, so a session's
168
+ // earlier runs cost a page each and none of their metadata. The rows come in the cards' own order, as they did.
169
+ //
170
+ // Each of these is asked only of a board that has the index it starts from (sessionIndexed, sessionBound,
171
+ // pendingLaunches). Without the index it would read every run, and a board is without it exactly when one of its runs
172
+ // holds metadata SQLite cannot read as JSON (layOut), which `json_extract` refuses: the statement would fail on a run
173
+ // it had no reason to read. Such a board is asked the earlier statements instead, which start from the cards and read
174
+ // only their current runs. The board is asked which it has at each call, not told at its open: the owner holds a board
175
+ // open for hours, and the index may be made meanwhile by another process's open.
176
+ const ofRun = (expression, run) => expression.replace('(metadata,', `(${run}.metadata,`);
177
+ const CURRENT_RUN_OF = 'JOIN tasks t ON t.id = r.task_id AND t.current_run_id = r.id';
178
+ /** The cards whose current run names the session (`?`), with that run's metadata; `LIKE ?` as the run's text says it. */
179
+ export const SESSION_CURRENT_RUNS = `SELECT t.id, m.metadata FROM task_runs r ${CURRENT_RUN_OF} CROSS JOIN task_runs m ON m.id = r.id
180
+ WHERE ${ofRun(RUN_SESSION, 'r')} = ? AND m.metadata LIKE ? ORDER BY t.rowid`;
181
+ /** Every run that names the session (`?`) as its own, oldest first, with its card. */
182
+ export const SESSION_RUNS = `SELECT id, task_id FROM task_runs WHERE ${RUN_SESSION} = ? ORDER BY id`;
183
+ const SESSION_BOUND = `SELECT 1 FROM task_runs r ${CURRENT_RUN_OF} CROSS JOIN task_runs m ON m.id = r.id
184
+ WHERE ${ofRun(RUN_SESSION, 'r')} = ? AND COALESCE(${ofRun(RUN_LAUNCH_PENDING, 'm')},0)=0`;
185
+ // The runs come first (CROSS JOIN): the planner cannot know how few the index holds, and reads every card otherwise.
186
+ const PENDING_LAUNCHES = `SELECT r.id, r.task_id, r.metadata FROM task_runs r CROSS ${CURRENT_RUN_OF}
187
+ WHERE r.ended_at IS NULL AND ${ofRun(RUN_LAUNCH_PENDING, 'r')} = 1 ORDER BY t.rowid`;
188
+ // the earlier statements, from the cards: they read every card and the current runs alone
189
+ const CURRENT_RUN = 'FROM task_runs r JOIN tasks t ON t.current_run_id=r.id';
190
+ const SESSION_BOUND_BY_CARDS = `SELECT 1 ${CURRENT_RUN} WHERE ${ofRun(RUN_SESSION, 'r')}=? AND COALESCE(${ofRun(RUN_LAUNCH_PENDING, 'r')},0)=0`;
191
+ const PENDING_LAUNCHES_BY_CARDS = `SELECT r.id, r.task_id, r.metadata ${CURRENT_RUN} WHERE r.ended_at IS NULL AND ${ofRun(RUN_LAUNCH_PENDING, 'r')} = 1`;
192
+ const indexed = (db, index) => Boolean(db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'index' AND name = ?").get(index));
193
+ /** Whether the board has the index SESSION_CURRENT_RUNS and SESSION_RUNS start from. */
194
+ export const sessionIndexed = (db) => indexed(db, RUN_SESSION_INDEX);
195
+ /** The statement that says whether a card's current run names the session (`?`) with its launch already bound. */
196
+ export const sessionBound = (db) => (sessionIndexed(db) ? SESSION_BOUND : SESSION_BOUND_BY_CARDS);
197
+ /** The statement that lists the open current runs whose launch is still pending. */
198
+ export const pendingLaunches = (db) => (indexed(db, RUN_LAUNCH_PENDING_INDEX) ? PENDING_LAUNCHES : PENDING_LAUNCHES_BY_CARDS);
199
+
200
+ const EVENT_KIND_INDEX = 'idx_events_task_kind';
201
+ // creatorOf's first `created` event and the latest review events are point lookups in this order. The latest event
202
+ // of each kind can group from the index alone; IN-kind lookups still sort, but read only the selected kinds. Keep
203
+ // idx_events_task: statements asking for events since a time need its (task_id, created_at) order.
204
+ // Installed here on every backing's open, including an existing store, before any engine statement is prepared.
205
+ // Its first creation scans task_events once, sorts the keys and writes an entry per event (no payload in the index).
206
+ // SQLite can reuse free pages; this does not vacuum the store. It is synchronous and can hold the owner's loop for
207
+ // seconds, so say so BEFORE starting and report its time afterward. Write the owner's stderr synchronously even
208
+ // inside a caller's call: that call's err is buffered until its answer, after the migration has held the loop.
209
+ function layOutEventIndex(db) {
210
+ if (!db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'task_events'").get() || indexed(db, EVENT_KIND_INDEX)) return;
211
+ const report = (text) => { writeSync(2, text); if (current().kind === 'caller') current().err(text); };
212
+ report(`board: creating index ${EVENT_KIND_INDEX} on task_events: one-time table scan, key sort and index write; this synchronous migration holds the owner event loop until it finishes and can take seconds\n`);
213
+ const began = performance.now();
214
+ let made = false;
215
+ try {
216
+ db.exec(`CREATE INDEX IF NOT EXISTS ${EVENT_KIND_INDEX} ON task_events(task_id, kind, id)`);
217
+ made = true;
218
+ } finally {
219
+ const ms = performance.now() - began;
220
+ noteStep(`board open index ${EVENT_KIND_INDEX}`, ms);
221
+ report(`board: index ${EVENT_KIND_INDEX} ${made ? 'created' : 'creation failed'} after ${Math.round(ms)} ms\n`);
222
+ }
223
+ }
153
224
 
154
225
  /** Hermes's tables on a fresh board, supercode's own table and columns on every one. */
155
226
  export function layOut(db, { fresh }) {
@@ -162,22 +233,28 @@ export function layOut(db, { fresh }) {
162
233
  // each read walked every card's row. Laid out here, after the column it names exists on a store of any age and before
163
234
  // any statement is prepared; no statement names it, so a store without it is only slower.
164
235
  db.exec('CREATE INDEX IF NOT EXISTS idx_supercode_cards_subtask_of ON supercode_cards(subtask_of)');
165
- // The calling session's run (CALLER_RUN_ADDRESS): without this index each lookup read every run's whole metadata,
166
- // overflow pages included. `id` keeps a session's runs in the order the lookup asks for, and the address makes the
167
- // index answer the lookup without reading the run. No statement names it, so a store without it is only slower, and a
168
- // release that does not know it keeps it current like any other index. Its first creation reads `task_runs` once.
169
- // A run whose metadata SQLite cannot read as JSON refuses the creation: the board opens without the index, says which
170
- // rows to look for, and tries again on its next open. Once the index is there, SQLite refuses a write of such
171
- // metadata to a run.
236
+ layOutEventIndex(db);
237
+ // The calling session's run (CALLER_RUN_ADDRESS) and its cards (SESSION_CURRENT_RUNS, SESSION_BOUND, SESSION_RUNS):
238
+ // without the session's index each lookup read every run's whole metadata, overflow pages included. `id` keeps a
239
+ // session's runs in the order the lookups ask for, and the address makes the index answer the caller's lookup without
240
+ // reading the run. The pending launches (PENDING_LAUNCHES) have an index of their own, which holds only them. No
241
+ // statement names either, and a release that does not know one keeps it current like any other index. An index's
242
+ // first creation reads `task_runs` once.
243
+ // A run whose metadata SQLite cannot read as JSON refuses the creation: the board opens without the indexes, says so
244
+ // once with the rows to look for, and tries again on its next open. Without them the caller's lookup reads every run
245
+ // and fails at the run that refused them once it reaches it, and a session's cards are found the earlier way, from the
246
+ // cards (sessionIndexed). Once an index is there, SQLite refuses a write of such metadata to a run.
172
247
  if (db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'task_runs'").get()) {
173
- try { db.exec(`CREATE INDEX IF NOT EXISTS ${RUN_SESSION_INDEX} ON task_runs(${RUN_SESSION}, id, ${RUN_ADDRESS})`); }
174
- catch (error) {
175
- if (contention(error)) throw error;
176
- const said = current().said;
177
- if (!said.has(RUN_SESSION_INDEX)) {
178
- said.add(RUN_SESSION_INDEX);
179
- current().err(`board: warning: index ${RUN_SESSION_INDEX} on task_runs was not created (${error.message}); the board is open without it and each session's verb reads every run. The runs that refuse it: SELECT id FROM task_runs WHERE metadata IS NOT NULL AND NOT json_valid(metadata, 6)\n`);
180
- }
248
+ const refused = [];
249
+ for (const [index, on] of RUN_INDEXES) {
250
+ try { db.exec(`CREATE INDEX IF NOT EXISTS ${index} ON task_runs${on}`); }
251
+ catch (error) { if (contention(error)) throw error; refused.push([index, error.message]); }
252
+ }
253
+ const said = current().said;
254
+ if (refused.length && !said.has(RUN_SESSION_INDEX)) {
255
+ said.add(RUN_SESSION_INDEX);
256
+ const names = refused.map(([index]) => index).join(' and ');
257
+ current().err(`board: warning: ${refused.length > 1 ? 'indexes' : 'index'} ${names} on task_runs ${refused.length > 1 ? 'were' : 'was'} not created (${refused[0][1]}); the board is open without ${refused.length > 1 ? 'them' : 'it'}: each session's verb reads every run for its caller, and finds its cards from the cards. The runs that refuse ${refused.length > 1 ? 'them' : 'it'}: SELECT id FROM task_runs WHERE metadata IS NOT NULL AND NOT json_valid(metadata, 6)\n`);
181
258
  }
182
259
  }
183
260
  const effectColumns = new Set(db.prepare('PRAGMA table_info(supercode_effects)').all().map((c) => c.name));
package/board/ztrack.mjs CHANGED
@@ -29,10 +29,43 @@ const timedStep = (step, fn) => { const started = performance.now(); try { retur
29
29
  // the reads of `board_state` and `board_errors` that come before each of the engine's statements, and a statement's
30
30
  // compilation, which reads the schema.
31
31
  const SLOW_STATEMENT_MS = 50;
32
+ // A statement that is a step also says what the whole process used while it ran (`used` on its step, named as a slow
33
+ // line's `whole_process` is): processor time, page faults, and context switches given up and taken away. The statement
34
+ // runs on the loop's thread and holds it, so over a statement these are nearly the statement's own. The store is read
35
+ // with pread (no memory map: `PRAGMA mmap_size` is 0), so a page SQLite does not hold and the system has to read is a
36
+ // switch given up, never a page fault and, on macOS, never an `fs_read`. Measured on macOS 26 (Darwin 25.4), a 741 MB
37
+ // store of 181,000 pages evicted from the system's cache before each of 20 runs: the creator's lookup of a card with
38
+ // 1,893 events read 1,116 pages in 80 to 88 ms with 7 to 11 ms of processor time, voluntary_switches 758 every time,
39
+ // major_page_faults 0 and fs_read 0; the same lookup with the file cached took 2.6 ms with voluntary_switches 0. So:
40
+ // voluntary_switches in the hundreds with little processor time is what waiting for file pages reads as (`ms` over
41
+ // that count is the wait for each read); few of them with little processor time is a wait that is not page reads.
42
+ // They are the whole process's, so a hold long enough for the loop's watcher to read the held stack (slow-log.mjs
43
+ // watchEventLoop) has that thread's use in them too. Measured over a 2 s sleep on a watched loop, two runs: 24 to 26
44
+ // ms of processor time, 149 to 234 minor page faults, 110 to 140 involuntary switches, no voluntary switch, and 85
45
+ // major page faults in one run against 0 in the next; unwatched, 0.6 ms, 17, 1, 0 and 0. So major page faults under a
46
+ // hundred in a long hold do not say the statement faulted, and the watcher adds no voluntary switch.
47
+ // The counters are read once before every statement (process.resourceUsage, 0.3 µs) and once more only for a step:
48
+ // a fast statement went from 491 to 917 ns (100,000 a round, five rounds, median).
49
+ const statementCounters = () => { try { return process.resourceUsage(); } catch { return null; } };
50
+ const statementUse = (before, after) => {
51
+ if (!before || !after) return null;
52
+ const ms = (micros) => Math.round(micros / 1000);
53
+ return { cpu_user_ms: ms(after.userCPUTime - before.userCPUTime), cpu_system_ms: ms(after.systemCPUTime - before.systemCPUTime),
54
+ major_page_faults: after.majorPageFault - before.majorPageFault, minor_page_faults: after.minorPageFault - before.minorPageFault, fs_read: after.fsRead - before.fsRead,
55
+ voluntary_switches: after.voluntaryContextSwitches - before.voluntaryContextSwitches, involuntary_switches: after.involuntaryContextSwitches - before.involuntaryContextSwitches };
56
+ };
57
+ // Called from `finally`: it answers nothing and never throws, so it can replace neither a statement's value nor its error.
58
+ const noteStatement = (sql, started, before) => {
59
+ try {
60
+ const ms = performance.now() - started;
61
+ if (ms >= SLOW_STATEMENT_MS) noteStep(`board statement: ${String(sql).replace(/\s+/g, ' ').trim().slice(0, 120)}`, ms, statementUse(before, statementCounters()));
62
+ } catch { /* the step is not noted */ }
63
+ };
32
64
  const timedStatement = (sql, fn) => {
65
+ const before = statementCounters();
33
66
  const started = performance.now();
34
67
  try { return fn(); }
35
- finally { const ms = performance.now() - started; if (ms >= SLOW_STATEMENT_MS) noteStep(`board statement: ${String(sql).replace(/\s+/g, ' ').trim().slice(0, 120)}`, ms); }
68
+ finally { noteStatement(sql, started, before); }
36
69
  };
37
70
  import { fire, workflowFor } from './workflow.mjs';
38
71
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-orchestrator",
3
- "version": "0.5.159",
3
+ "version": "0.5.161",
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": {
@@ -24,7 +24,7 @@
24
24
  "check-types": "node scripts/generate-types.mjs --check"
25
25
  },
26
26
  "dependencies": {
27
- "@volter/supercode-harness-sdk": "^0.3.97",
27
+ "@volter/supercode-harness-sdk": "^0.3.98",
28
28
  "@volter/teams": "0.2.65",
29
29
  "@volter/ztrack": "^2.4.23",
30
30
  "yaml": "^2.9.0"