@volter/supercode-orchestrator 0.4.136 → 0.4.138
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 +6 -5
- package/board/tools.mjs +3 -3
- package/board/workflow.mjs +18 -0
- package/package.json +1 -1
package/board/cli.mjs
CHANGED
|
@@ -15,7 +15,7 @@ import { openZtrackBoard } from './ztrack.mjs';
|
|
|
15
15
|
import * as engine from './engine.mjs';
|
|
16
16
|
import { adoptSession, boardAddress, boardLabel, boardSettings, callerSession, commandEnv, reconcileCallerLaunch, closeRequested, lanesOf, localMachine, observeFleet, paused, plan, startCard, supercodeBin, tick } from './dispatch.mjs';
|
|
17
17
|
import { watchBoard } from './wake.mjs';
|
|
18
|
-
import {
|
|
18
|
+
import { completionOwnerOf, loadWorkflow, requestSessionClose, scopeOf, statusOf, useWorkflow, workflowEntry, workflowFor } from './workflow.mjs';
|
|
19
19
|
import { evaluate } from './expr.mjs';
|
|
20
20
|
|
|
21
21
|
const HELP = `supercode workflow <verb> [--root <home>] [--board <slug>] [--json]
|
|
@@ -316,9 +316,10 @@ export async function runWorkflow(argv) {
|
|
|
316
316
|
const address = args['no-notify'] ? null : args.notify ?? callerAddress();
|
|
317
317
|
if (address) withBoard((b) => engine.subscribe(b, created.id, address, 'creator'));
|
|
318
318
|
const task = withBoard((b) => view(b, (db) => engine.getTask(db, created.id)));
|
|
319
|
-
|
|
319
|
+
const recipients = withBoard(b => engine.subscriptions(b.db, created.id)).filter(s => s.platform === 'supercode').map(s => s.chat_id);
|
|
320
|
+
if (args.json) { say(args, '', { ...cardDict(task), ...answer, notify: address ?? null, recipients }); return answer.failed ? 1 : 0; }
|
|
320
321
|
const said = answer.started ? `; started session ${answer.session} (${answer.mode})` : answer.failed ? `; did not start: ${answer.failed}` : answer.waits ? `; waits: ${answer.waits}` : '';
|
|
321
|
-
say(args, `${created.existing ? 'Existing' : 'Created'} ${created.id} (${task.status}, assignee=${task.assignee || '-'})${said}${
|
|
322
|
+
say(args, `${created.existing ? 'Existing' : 'Created'} ${created.id} (${task.status}, assignee=${task.assignee || '-'})${said}${recipients.length ? `; notices go to ${recipients.join(', ')}` : ''}`, null);
|
|
322
323
|
return answer.failed ? 1 : 0;
|
|
323
324
|
}
|
|
324
325
|
|
|
@@ -1089,7 +1090,7 @@ function rolesFor(db, cardId, sessionId, params = {}) {
|
|
|
1089
1090
|
const task = engine.getTask(db, cardId);
|
|
1090
1091
|
if (!task) return [];
|
|
1091
1092
|
const roles = [];
|
|
1092
|
-
if (
|
|
1093
|
+
if (completionOwnerOf(db, cardId)?.endsWith(`:${sessionId}`)) roles.push('creator');
|
|
1093
1094
|
const current = task.current_run_id ? json(db.prepare('SELECT metadata FROM task_runs WHERE id = ?').get(task.current_run_id)?.metadata, {})?.supercode : null;
|
|
1094
1095
|
if (current?.session_id === sessionId) roles.push(current.role ?? 'implementer');
|
|
1095
1096
|
else if (engine.lastSession(db, cardId)?.session_id === sessionId) roles.push('implementer');
|
|
@@ -1110,7 +1111,7 @@ function authorize(db, verb, rest, args, own) {
|
|
|
1110
1111
|
if (args.all) refuse(`does not ${verb} every card; only its own`);
|
|
1111
1112
|
const card = engine.getTask(db, rest[0]);
|
|
1112
1113
|
if (!card) return; // the verb itself answers "no card"
|
|
1113
|
-
if (
|
|
1114
|
+
if (completionOwnerOf(db, card.id)?.endsWith(`:${callerSession()}`)) return; // its declared creator doors still govern the act
|
|
1114
1115
|
// a card's session writes its own card and its subtasks; what it may do with them is the workflow's (`by`)
|
|
1115
1116
|
if (card.id !== own.id && card.subtask_of !== own.id) refuse(`writes only its own card and its subtasks, not ${card.id}`);
|
|
1116
1117
|
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/tools.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { mkdirSync, statSync, writeFileSync } from 'node:fs';
|
|
|
7
7
|
import { basename, join } from 'node:path';
|
|
8
8
|
import { DEFAULT_BOARD, actorOf, event, json, openBoard, setActor, tx } from './store.mjs';
|
|
9
9
|
import * as engine from './engine.mjs';
|
|
10
|
-
import {
|
|
10
|
+
import { completionOwnerOf, loadWorkflow } from './workflow.mjs';
|
|
11
11
|
|
|
12
12
|
const ok = (fields = {}) => JSON.stringify({ ok: true, ...fields });
|
|
13
13
|
const error = (message) => JSON.stringify({ error: message });
|
|
@@ -44,7 +44,7 @@ export async function boardTool(root, name, args = {}, env = {}) {
|
|
|
44
44
|
if (!own || !runId) return own ? [] : ['manager'];
|
|
45
45
|
const meta = json(db.prepare('SELECT metadata FROM task_runs WHERE id = ?').get(runId)?.metadata, {})?.supercode ?? {};
|
|
46
46
|
const roles = cardId === own ? [meta.role ?? 'implementer'] : [];
|
|
47
|
-
if (actorOf() &&
|
|
47
|
+
if (actorOf() && completionOwnerOf(db, cardId) === actorOf()) roles.push('creator');
|
|
48
48
|
return roles;
|
|
49
49
|
});
|
|
50
50
|
let board;
|
|
@@ -52,7 +52,7 @@ export async function boardTool(root, name, args = {}, env = {}) {
|
|
|
52
52
|
board.label = slug;
|
|
53
53
|
const callingRun = runId ? json(board.db.prepare('SELECT metadata FROM task_runs WHERE id=?').get(runId)?.metadata, {})?.supercode : null;
|
|
54
54
|
setActor(own && runId ? callingRun?.address ?? `hermes-run:${slug}:${runId}` : null);
|
|
55
|
-
if (own && LIFECYCLE.has(name) && tid !== own &&
|
|
55
|
+
if (own && LIFECYCLE.has(name) && tid !== own && completionOwnerOf(board.db, tid) !== actorOf()) {
|
|
56
56
|
board.close(); return error(`worker is scoped to task ${own}; refusing to change ${tid}. Use kanban_comment to hand off to other tasks.`);
|
|
57
57
|
}
|
|
58
58
|
const current = () => tx(board, (db) => engine.getTask(db, tid));
|
package/board/workflow.mjs
CHANGED
|
@@ -66,6 +66,23 @@ 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
|
+
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
|
+
return creatorOf(db, id);
|
|
84
|
+
}
|
|
85
|
+
|
|
69
86
|
/** What kanban.db stores for `status`. */
|
|
70
87
|
export function storedAs(workflow, status) {
|
|
71
88
|
return workflow.statuses[status]?.stored_as ?? status;
|
|
@@ -223,6 +240,7 @@ export function scopeOf(db, board, task, { params, event = {}, extra = {} } = {}
|
|
|
223
240
|
const card = {
|
|
224
241
|
...task,
|
|
225
242
|
creator_address: creatorOf(db, task.id),
|
|
243
|
+
completion_owner_address: completionOwnerOf(db, task.id),
|
|
226
244
|
ci: completionEvidence(board, task.id),
|
|
227
245
|
dispatch_hold: one(db, "SELECT kind FROM task_events WHERE task_id=? AND kind IN ('dispatch_held','dispatch_released','session_adopted') ORDER BY id DESC LIMIT 1", task.id)?.kind === 'dispatch_held',
|
|
228
246
|
status,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-orchestrator",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.138",
|
|
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": {
|