@volter/supercode-orchestrator 0.4.138 → 0.4.140
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 +5 -3
- package/board/dispatch.mjs +5 -4
- package/board/engine.mjs +1 -1
- package/board/workflow.mjs +6 -14
- package/package.json +1 -1
package/board/cli.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
9
9
|
import { basename, join, resolve } from 'node:path';
|
|
10
10
|
import { tmpdir } from 'node:os';
|
|
11
11
|
import { read } from '../orchestration.mjs';
|
|
12
|
-
import { view, backingPaths, layOut, backingOf, boardExists, boardPath, DEFAULT_BOARD, event, json, listBoards, logsDir, now, openBoard, setActor, tx } from './store.mjs';
|
|
12
|
+
import { actorOf, view, backingPaths, layOut, backingOf, boardExists, boardPath, DEFAULT_BOARD, event, json, listBoards, logsDir, now, openBoard, setActor, tx } from './store.mjs';
|
|
13
13
|
import { isLocalWorkspace } from './workspace.mjs';
|
|
14
14
|
import { openZtrackBoard } from './ztrack.mjs';
|
|
15
15
|
import * as engine from './engine.mjs';
|
|
@@ -1090,7 +1090,8 @@ function rolesFor(db, cardId, sessionId, params = {}) {
|
|
|
1090
1090
|
const task = engine.getTask(db, cardId);
|
|
1091
1091
|
if (!task) return [];
|
|
1092
1092
|
const roles = [];
|
|
1093
|
-
|
|
1093
|
+
const creator = completionOwnerOf(db, cardId);
|
|
1094
|
+
if (creator && creator === actorOf()) roles.push('creator');
|
|
1094
1095
|
const current = task.current_run_id ? json(db.prepare('SELECT metadata FROM task_runs WHERE id = ?').get(task.current_run_id)?.metadata, {})?.supercode : null;
|
|
1095
1096
|
if (current?.session_id === sessionId) roles.push(current.role ?? 'implementer');
|
|
1096
1097
|
else if (engine.lastSession(db, cardId)?.session_id === sessionId) roles.push('implementer');
|
|
@@ -1111,7 +1112,8 @@ function authorize(db, verb, rest, args, own) {
|
|
|
1111
1112
|
if (args.all) refuse(`does not ${verb} every card; only its own`);
|
|
1112
1113
|
const card = engine.getTask(db, rest[0]);
|
|
1113
1114
|
if (!card) return; // the verb itself answers "no card"
|
|
1114
|
-
|
|
1115
|
+
const creator = completionOwnerOf(db, card.id);
|
|
1116
|
+
if (creator && creator === actorOf()) return;
|
|
1115
1117
|
// a card's session writes its own card and its subtasks; what it may do with them is the workflow's (`by`)
|
|
1116
1118
|
if (card.id !== own.id && card.subtask_of !== own.id) refuse(`writes only its own card and its subtasks, not ${card.id}`);
|
|
1117
1119
|
for (const other of args.ids ?? []) if (other !== own.id && engine.getTask(db, other)?.subtask_of !== own.id) refuse(`writes only its own card and its subtasks, not ${other}`);
|
package/board/dispatch.mjs
CHANGED
|
@@ -1307,9 +1307,9 @@ function deliverNow(board, to, body, key) {
|
|
|
1307
1307
|
if (!answer.receipt?.delivered) throw new Error(answer.receipt?.reason ?? 'native delivery confirmation pending');
|
|
1308
1308
|
}
|
|
1309
1309
|
|
|
1310
|
-
async function send(from, label, to, body, key, { queue = false, ask = false } = {}) {
|
|
1310
|
+
async function send(from, label, to, body, key, { queue = false, ask = false, subject } = {}) {
|
|
1311
1311
|
const request = {
|
|
1312
|
-
op: 'send', from, from_name: `${label} board`, to, queue, id: key, subject: String(body).split('\n')[0].slice(0, 160),
|
|
1312
|
+
op: 'send', from, from_name: `${label} board`, to, queue, id: key, subject: String(subject ?? String(body).split('\n')[0]).slice(0, 160),
|
|
1313
1313
|
// a question the board waits on is answered by a reply; a notice is not
|
|
1314
1314
|
body: ask ? `[A question from the ${label} board] ${body}` : `[Board notice from the ${label} board] ${body}\n\n(An automated notice from the board's dispatcher; replies to this address go nowhere.)`,
|
|
1315
1315
|
};
|
|
@@ -1333,7 +1333,8 @@ export async function sendBoardMessages(board, report, taskId = null) {
|
|
|
1333
1333
|
if (taskId && row.task_id !== taskId) continue;
|
|
1334
1334
|
const m = json(row.payload, {}) ?? {};
|
|
1335
1335
|
try {
|
|
1336
|
-
const
|
|
1336
|
+
const task = view(board, db => getTask(db, row.task_id));
|
|
1337
|
+
const receipt = await send(boardAddress(board.label), board.label, m.to, m.text, m.message_id ?? `board-message-${row.id}`, { ask: Boolean(m.message_id), subject: `${task?.title || row.task_id} · ${m.message_id ? 'Question' : 'Update'}` });
|
|
1337
1338
|
if (!receipt.delivered) throw new Error(receipt.reason ?? 'queued; native transcript confirmation pending');
|
|
1338
1339
|
tx(board, (db) => event(db, row.task_id, 'board_message_sent', { event: row.id, to: m.to, receipt, ...(m.message_id ? { message_id: m.message_id } : {}) }));
|
|
1339
1340
|
} catch (error) {
|
|
@@ -1420,7 +1421,7 @@ async function notify(board, fleet, report, entry) {
|
|
|
1420
1421
|
text = c ? noticeOf(entry, role, ev.kind, task, p, c) : null;
|
|
1421
1422
|
} else text = noticeOf(entry, role, ev.kind, task, p);
|
|
1422
1423
|
if (text) {
|
|
1423
|
-
const receipt = await send(from, board.label, sub.chat_id, text, `board-${sub.task_id}-${ev.id}-${sub.chat_id.slice(-12)}`, { queue: !entry.workflow.run && sub.delivery_mode === 'notify' });
|
|
1424
|
+
const receipt = await send(from, board.label, sub.chat_id, text, `board-${sub.task_id}-${ev.id}-${sub.chat_id.slice(-12)}`, { queue: !entry.workflow.run && sub.delivery_mode === 'notify', subject: `${task?.title || sub.task_id} · ${ev.kind.replaceAll('_', ' ')}` });
|
|
1424
1425
|
if (!receipt.delivered) { pending = Boolean(receipt.queued); throw new Error(receipt.reason ?? 'native delivery confirmation pending'); }
|
|
1425
1426
|
tx(board, (db) => event(db, sub.task_id, 'notice_delivered', { source_event: ev.id, kind: ev.kind, address: sub.chat_id, receipt }));
|
|
1426
1427
|
report.notified.push([sub.task_id, ev.kind, sub.chat_id]);
|
package/board/engine.mjs
CHANGED
|
@@ -205,7 +205,7 @@ export function createCard(board, spec) {
|
|
|
205
205
|
tenant, idempotency_key, max_runtime_seconds, skills, max_retries, model_override, provider_override, reasoning_effort, session_id,
|
|
206
206
|
project_id, goal_mode, goal_max_turns)
|
|
207
207
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
208
|
-
id, title, spec.body ?? null, spec.assignee || null, 'todo', Number(spec.priority) || 0, spec.createdBy ?? 'user', t, ws.kind, ws.path,
|
|
208
|
+
id, title, spec.body ?? null, spec.assignee || null, 'todo', Number(spec.priority) || 0, actorOf() ?? spec.createdBy ?? 'user', t, ws.kind, ws.path,
|
|
209
209
|
spec.branch ?? null, spec.tenant ?? null, spec.idempotencyKey ?? null, spec.maxRuntime ?? null,
|
|
210
210
|
spec.skills?.length ? JSON.stringify(spec.skills) : null, spec.maxRetries ?? null, spec.model ?? null, spec.provider ?? null,
|
|
211
211
|
spec.reasoning ?? null, spec.sessionId ?? null, spec.project ?? null, spec.goal ? 1 : 0, spec.goal?.maxTurns ?? null);
|
package/board/workflow.mjs
CHANGED
|
@@ -66,20 +66,7 @@ export function creatorOf(db, id) {
|
|
|
66
66
|
return payload.source !== 'document' && /^(?:sc:[^:]+:[^:]+:|hermes-run:)/.test(address ?? '') ? address : null;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
/** Open subtasks are the current parent worker's plan. Keep creation history
|
|
70
|
-
* immutable while transferring its creator doors when that worker is replaced. */
|
|
71
69
|
export function completionOwnerOf(db, id) {
|
|
72
|
-
const task = one(db, 'SELECT t.status,c.subtask_of FROM tasks t LEFT JOIN supercode_cards c ON c.task_id=t.id WHERE t.id=?', id);
|
|
73
|
-
if (task?.subtask_of && !['done', 'archived'].includes(task.status)) {
|
|
74
|
-
for (const row of all(db, 'SELECT metadata FROM task_runs WHERE task_id=? ORDER BY id DESC', task.subtask_of)) {
|
|
75
|
-
const session = json(row.metadata, {})?.supercode;
|
|
76
|
-
if (!session || (session.slot ?? 'worker') !== 'worker') continue;
|
|
77
|
-
// An unbound replacement leaves authority with the manager; it never
|
|
78
|
-
// revives the previous worker's ability to finish the new worker's plan.
|
|
79
|
-
return session.address ?? (session.machine && session.harness && session.session_id
|
|
80
|
-
? `sc:${session.machine}:${session.harness}:${session.session_id}` : null);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
70
|
return creatorOf(db, id);
|
|
84
71
|
}
|
|
85
72
|
|
|
@@ -377,7 +364,12 @@ export function fire(db, board, entry, id, name, { roles = [], payload = {}, ext
|
|
|
377
364
|
// `sender`: the roles the event was sent with (a validator can ask who is asking)
|
|
378
365
|
const scope = scopeOf(db, board, task, { params, event: payload, extra: { lanes: lanesIn(entry), ...extra, sender: roles } });
|
|
379
366
|
const allowed = candidates.filter((t) => !t.by?.length || t.by.some((r) => roles.includes(r)));
|
|
380
|
-
if (!allowed.length)
|
|
367
|
+
if (!allowed.length) {
|
|
368
|
+
const state = name === 'complete' ? 'done' : name === 'cancel' ? 'cancelled' : null;
|
|
369
|
+
const request = state && candidates.some(transition => transition.by?.includes('creator'))
|
|
370
|
+
? ` Ask the manager with workflow request-state ${id} --state ${state} --reason <why> --evidence <verdict-and-pointers>.` : '';
|
|
371
|
+
throw new Error(`${id} is ${from}; ${name.replace(/_/g, '-')} there is for ${[...new Set(candidates.flatMap((t) => t.by ?? []))].join(' or ')}. ${candidates.map((t) => t.description).filter(Boolean).join(' ')}${request}`);
|
|
372
|
+
}
|
|
381
373
|
const taken = allowed.find((t) => holds(t.guard ?? null, scope));
|
|
382
374
|
if (!taken) {
|
|
383
375
|
const state = scope.card;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-orchestrator",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.140",
|
|
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": {
|