@volter/supercode-orchestrator 0.4.39 → 0.4.41
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/bin/orchestrator.mjs +9 -4
- package/board/dispatch.mjs +8 -9
- package/board/engine.mjs +3 -3
- package/board/workflow.mjs +9 -4
- package/package.json +1 -1
package/bin/orchestrator.mjs
CHANGED
|
@@ -539,13 +539,18 @@ async function runApply(argv) {
|
|
|
539
539
|
return 0;
|
|
540
540
|
}
|
|
541
541
|
|
|
542
|
+
// exit once what was written reached the pipe: a write to a pipe is asynchronous, and process.exit() right after one
|
|
543
|
+
// cuts the output at the pipe's buffer (a board's `list --json` past 64 KiB arrived truncated)
|
|
544
|
+
const flushed = (stream) => new Promise((done) => stream.write('', done));
|
|
545
|
+
const exitAfterFlush = async (code) => { await Promise.all([flushed(process.stdout), flushed(process.stderr)]); process.exit(code); };
|
|
546
|
+
|
|
542
547
|
async function main() {
|
|
543
548
|
const argv = process.argv.slice(2);
|
|
544
549
|
if (OPERATOR_OPS.includes(argv[0])) {
|
|
545
550
|
try {
|
|
546
551
|
const code = await runOperator(argv[0], argv.slice(1));
|
|
547
552
|
await closeWorld();
|
|
548
|
-
|
|
553
|
+
await exitAfterFlush(code);
|
|
549
554
|
} catch (error) {
|
|
550
555
|
process.stdout.write(`${JSON.stringify({ ok: false, error: String(error.message ?? error) })}\n`);
|
|
551
556
|
process.exit(1);
|
|
@@ -555,7 +560,7 @@ async function main() {
|
|
|
555
560
|
try {
|
|
556
561
|
const code = await (argv[0] === 'import' ? runImport(argv.slice(1)) : runExport(argv.slice(1)));
|
|
557
562
|
await closeWorld();
|
|
558
|
-
|
|
563
|
+
await exitAfterFlush(code);
|
|
559
564
|
} catch (error) {
|
|
560
565
|
say('error', { verb: argv[0], message: String(error.message ?? error), name: error.name ?? 'Error' });
|
|
561
566
|
await closeWorld();
|
|
@@ -568,11 +573,11 @@ async function main() {
|
|
|
568
573
|
try {
|
|
569
574
|
const code = await runWorkflow(argv.slice(1));
|
|
570
575
|
await closeWorld();
|
|
571
|
-
|
|
576
|
+
await exitAfterFlush(code);
|
|
572
577
|
} catch (error) {
|
|
573
578
|
process.stderr.write(`supercode workflow: ${error.message ?? error}\n`);
|
|
574
579
|
await closeWorld();
|
|
575
|
-
|
|
580
|
+
await exitAfterFlush(1);
|
|
576
581
|
}
|
|
577
582
|
}
|
|
578
583
|
if (argv[0] === 'access') {
|
package/board/dispatch.mjs
CHANGED
|
@@ -595,10 +595,10 @@ function launchPane(board, task, runId, lane, fleet, { input = null, slot = 'wor
|
|
|
595
595
|
tx(board, (db) => {
|
|
596
596
|
setRunSession(db, runId, session);
|
|
597
597
|
event(db, task.id, kind, { session_id: session.session_id, machine, pane: session.pane, address: session.address, mode: session.mode, ...(replaced ? { replaced } : {}) }, runId);
|
|
598
|
-
// the working session hears updates to its card;
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
598
|
+
// the working session hears updates to its card; the slot's previous session no longer does (a reviewer starting
|
|
599
|
+
// leaves the implementer's, and a subtask's arc session's, subscription alone)
|
|
600
|
+
const before = prev ? prev.address ?? `sc:${prev.machine}:claude-code:${prev.session_id}` : null;
|
|
601
|
+
if (before && before !== session.address) db.prepare("DELETE FROM kanban_notify_subs WHERE task_id = ? AND platform = 'supercode' AND chat_id = ?").run(task.id, before);
|
|
602
602
|
subscribeIn(db, task.id, session.address, 'worker');
|
|
603
603
|
// it sees the cards its own depends on change, and works its subtasks
|
|
604
604
|
for (const p of db.prepare('SELECT parent_id FROM task_links WHERE child_id = ?').all(task.id)) subscribeIn(db, p.parent_id, session.address, 'dependency');
|
|
@@ -783,11 +783,10 @@ async function notify(board, fleet, report, entry) {
|
|
|
783
783
|
// each subscription's notices go in order; different subscribers are told side by side
|
|
784
784
|
await pool(subscriptions(board.db).filter((sub) => sub.platform === 'supercode'), SEND_CONCURRENCY, async (sub) => {
|
|
785
785
|
const role = ['worker', 'dependency'].includes(sub.delivery_metadata?.role) ? sub.delivery_metadata.role : 'creator';
|
|
786
|
-
// a session
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
}
|
|
786
|
+
// a session the fleet does not list this tick is told next tick: one reading is not a loss (a loaded box drops
|
|
787
|
+
// sessions from a read, and a dropped subscription never came back), and a session that is replaced on its card
|
|
788
|
+
// has its subscription moved when the new one starts
|
|
789
|
+
if (role !== 'creator' && fleet.ok && !fleet.alive.has(sub.chat_id)) return;
|
|
791
790
|
const kinds = heardBy(entry, role);
|
|
792
791
|
if (!kinds.length) return;
|
|
793
792
|
const events = board.db.prepare(`SELECT id, kind, payload, created_at FROM task_events WHERE task_id = ? AND id > ? AND kind IN (${kinds.map(() => '?').join(', ')}) ORDER BY id`)
|
package/board/engine.mjs
CHANGED
|
@@ -337,12 +337,12 @@ export async function act(board, id, name, { roles = null, payload = {}, extra =
|
|
|
337
337
|
/** Take card `id` to `status` by the event the workflow says goes there for this caller (`goto`). */
|
|
338
338
|
export async function goto(board, id, status, { roles = null, payload = {} } = {}) {
|
|
339
339
|
const entry = await loadWorkflow(board.root);
|
|
340
|
-
const { name, from } = tx(board, (db) => {
|
|
340
|
+
const { name, refusal, from } = tx(board, (db) => {
|
|
341
341
|
roles ??= callerRoles(db, id);
|
|
342
|
-
return {
|
|
342
|
+
return { ...eventTo(db, board, entry, id, status, { roles, payload }), from: statusOf(db, getTask(db, id)) };
|
|
343
343
|
});
|
|
344
344
|
if (from === status) return { from, to: status, moved: false, event: null };
|
|
345
|
-
if (!name) throw new Error(`${id} is ${from}; no event you may send takes it to ${status}`);
|
|
345
|
+
if (!name) throw new Error(refusal ?? `${id} is ${from}; no event you may send takes it to ${status}`);
|
|
346
346
|
return { ...(await act(board, id, name, { roles, payload })), event: name };
|
|
347
347
|
}
|
|
348
348
|
|
package/board/workflow.mjs
CHANGED
|
@@ -170,8 +170,9 @@ export function fire(db, board, entry, id, name, { roles = [], payload = {}, ext
|
|
|
170
170
|
|
|
171
171
|
/**
|
|
172
172
|
* The event that takes card `id` to `status` for a sender in `roles`: the first the card's status (then the workflow)
|
|
173
|
-
* declares whose transition for that sender, with `payload`, goes there
|
|
174
|
-
*
|
|
173
|
+
* declares whose transition for that sender, with `payload`, goes there and whose validators hold. Answers
|
|
174
|
+
* `{ name }`, or `{ name: null, refusal }` with the first validator that stood in the way (null when no event goes
|
|
175
|
+
* there at all).
|
|
175
176
|
*/
|
|
176
177
|
export function eventTo(db, board, entry, id, status, { roles = [], payload = {} } = {}) {
|
|
177
178
|
const { workflow, params } = entry;
|
|
@@ -179,11 +180,15 @@ export function eventTo(db, board, entry, id, status, { roles = [], payload = {}
|
|
|
179
180
|
const task = cardOf(db, id);
|
|
180
181
|
const from = statusOf(db, task);
|
|
181
182
|
const scope = scopeOf(db, board, task, { params, event: payload, extra: { lanes: lanesIn(entry), sender: roles } });
|
|
183
|
+
let refusal = null;
|
|
182
184
|
for (const name of new Set([...Object.keys(workflow.statuses[from]?.on ?? {}), ...Object.keys(workflow.on ?? {})])) {
|
|
183
185
|
const taken = transitionsFor(workflow, from, name).filter((t) => !t.by?.length || t.by.some((r) => roles.includes(r))).find((t) => holds(t.guard ?? null, scope));
|
|
184
|
-
if (taken?.target
|
|
186
|
+
if (!taken?.target || String(templateValue(taken.target, scope) ?? '') !== status) continue;
|
|
187
|
+
const failed = (taken.validate ?? []).find((v) => !holds(v.expr, scope));
|
|
188
|
+
if (!failed) return { name };
|
|
189
|
+
refusal ??= failed.message;
|
|
185
190
|
}
|
|
186
|
-
return null;
|
|
191
|
+
return { name: null, refusal };
|
|
187
192
|
}
|
|
188
193
|
|
|
189
194
|
function cardOf(db, id) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-orchestrator",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.41",
|
|
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": {
|