@volter/supercode-orchestrator 0.5.170 → 0.5.172
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/args.mjs +1 -1
- package/board/cli.mjs +38 -12
- package/board/side-sessions.mjs +73 -0
- package/board/workflow.mjs +3 -0
- package/package.json +2 -2
package/board/args.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { resolve } from 'node:path';
|
|
|
3
3
|
|
|
4
4
|
export const MULTI = new Set(['parent', 'skill', 'worker', 'child', 'ids', 'rm', 'tag', 'pr', 'pr-head', 'reviewed-sha', 'finding']);
|
|
5
5
|
export const FLAGS = new Set(['json', 'triage', 'no-notify', 'no-start', 'all', 'force', 'help', 'mine', 'archived', 'reclaim', 'dry-run',
|
|
6
|
-
'once', 'goal', 'no-goal', 'off', 'switch', 'delete', 'no-attachments', 'include-logs', 'verbose', 'commit']);
|
|
6
|
+
'once', 'goal', 'no-goal', 'off', 'switch', 'delete', 'no-attachments', 'include-logs', 'verbose', 'commit', 'side']);
|
|
7
7
|
// flags that take every following word up to the next flag (Hermes's nargs='+')
|
|
8
8
|
export const GREEDY = new Set(['ids', 'rm']);
|
|
9
9
|
|
package/board/cli.mjs
CHANGED
|
@@ -66,6 +66,7 @@ async function boardPublishing(root) {
|
|
|
66
66
|
}
|
|
67
67
|
import { dispatchingOf, knowLocalMachine, requestAdoption, alertManagers, boardAddress, boardLabel, boardSettings, boardCaller, callerSession, commandEnv, reconcileCallerLaunch, closeRequested, lanesOf, localMachine, observeFleet, paused, plan, startCard, stopDispatching, supercodeBin, tick, deliver, startDelivery } from './dispatch.mjs';
|
|
68
68
|
import { watchBoard } from './wake.mjs';
|
|
69
|
+
import { attachSideSession, releaseSideSession, sideArcs, sideSessions } from './side-sessions.mjs';
|
|
69
70
|
import { onPanesChanged } from './machine-fleet.mjs';
|
|
70
71
|
import { streamBoardPublications } from './event-stream.mjs';
|
|
71
72
|
import { completionOwnerOf, followHome, isLatestReviewer, loadWorkflow, readHome, requestSessionClose, scopeOf, statusOf, useWorkflow, workflowEntry, workflowFor } from './workflow.mjs';
|
|
@@ -127,7 +128,7 @@ And:
|
|
|
127
128
|
create … --machine M --subtask-of ARC --notify ADDRESS|--no-notify --no-start
|
|
128
129
|
With a pane lane and room, a card starts at once and answers "started" with its session; else it answers why it
|
|
129
130
|
waits, and its creator is told when it starts. A subtask is worked by its arc's session and never starts alone.
|
|
130
|
-
adopt <id> <session> [--assignee P] [--role ROLE] close <id> serve (= daemon) list --machine M
|
|
131
|
+
adopt <id> <session> [--assignee P] [--role ROLE] adopt <arc> <session> --side · release <arc> <session> close <id> serve (= daemon) list --machine M
|
|
131
132
|
notify-subscribe <id> --address sc:<machine>:<harness>:<id> [--role creator|worker|dependency]
|
|
132
133
|
notify-subscribe <dependency> --for-card <your-arc>: subscribe only your arc's session to dependency notices.
|
|
133
134
|
notify-unsubscribe <dependency> --for-card <your-arc>: remove that arc's subscription only.
|
|
@@ -145,7 +146,8 @@ And:
|
|
|
145
146
|
request-state <id>... --state STATE --reason REASON: request several decisions in one retained manager mail.
|
|
146
147
|
complete <id>: creator/manager accepts the independently reviewed arc; deployment CI is not a board gate.
|
|
147
148
|
specify/decompose with no spec start a session on the triage card to write it. A card's own session may comment on,
|
|
148
|
-
heartbeat and request review of its card, specify or decompose it in triage, and work its subtasks.
|
|
149
|
+
heartbeat and request review of its card, specify or decompose it in triage, and work its subtasks. A side session
|
|
150
|
+
(adopt --side) works the arc's subtasks and comments on the arc; the arc's run and state stay its main session's. The home's
|
|
149
151
|
workflow declares who may block, pause or finish a card; request-state mails its manager without changing state.
|
|
150
152
|
`;
|
|
151
153
|
|
|
@@ -365,13 +367,15 @@ export async function runVerb(argv) {
|
|
|
365
367
|
if (sid && !READS.has(verb) && boardExists(root, slug)) {
|
|
366
368
|
// binding the caller's launch is a write: only the owner, answering the caller's own verb, makes it (a local read
|
|
367
369
|
// never writes)
|
|
368
|
-
await withBoardAsync(async (b) => { if (isCallersCall()) await reconcileCallerLaunch(b, sid); const own = ownCards(b.db, sid); if (own.worked.size) authorize(b.db, verb, rest, args, own); });
|
|
370
|
+
await withBoardAsync(async (b) => { if (isCallersCall()) await reconcileCallerLaunch(b, sid); const own = { ...ownCards(b.db, sid), side: sideArcs(b.db, sid) }; if (own.worked.size || own.side.size) authorize(b.db, verb, rest, args, own); });
|
|
369
371
|
}
|
|
370
372
|
// the board's workflow (the IR's): what each verb does, and who may send it, is its
|
|
371
373
|
// every verb reads the board through its workflow (a list settles cards too), so every verb loads it
|
|
372
374
|
engine.setCaller((db, cardId) => {
|
|
373
375
|
const roles = rolesFor(db, cardId, sid, workflowFor(root).params);
|
|
374
376
|
if (verb === 'comment' && isLatestReviewer(db, cardId, actorOf()) && !roles.includes('reviewer')) roles.push('reviewer');
|
|
377
|
+
// a side session comments on its arc as the arc's (its subtasks it works as `arc` through rolesFor)
|
|
378
|
+
if (verb === 'comment' && sid && !roles.includes('arc') && sideSessions(db, cardId).some((s) => s.session === sid)) roles.push('arc');
|
|
375
379
|
return roles;
|
|
376
380
|
});
|
|
377
381
|
// A plain terminal acts as its signed-in person; an offline OS label grants no creator authority.
|
|
@@ -542,7 +546,7 @@ export async function runVerb(argv) {
|
|
|
542
546
|
const t = card.task;
|
|
543
547
|
const latest = [...card.runs].reverse().find((r) => r.summary)?.summary ?? null;
|
|
544
548
|
if (args.json) {
|
|
545
|
-
say(args, '', { task: cardDict(t), latest_summary: latest, parents: card.parents.map((p) => p.id), children: card.children, subtasks: card.subtasks, comments: card.comments, events: card.events, runs: card.runs, time: card.time, subscriptions: card.subscriptions, diagnostics: card.diagnostics });
|
|
549
|
+
say(args, '', { task: cardDict(t), side_sessions: withBoard((b) => sideSessions(b.db, t.id)), latest_summary: latest, parents: card.parents.map((p) => p.id), children: card.children, subtasks: card.subtasks, comments: card.comments, events: card.events, runs: card.runs, time: card.time, subscriptions: card.subscriptions, diagnostics: card.diagnostics });
|
|
546
550
|
return 0;
|
|
547
551
|
}
|
|
548
552
|
const out = [`Task ${t.id}: ${t.title}`, ` status: ${taskStatusOf(card.stage, t.completed_at)} (lane ${card.stage}${card.stage !== t.status ? `, ${t.status} in kanban.db` : ''})`, ` assignee: ${t.assignee || '-'}`];
|
|
@@ -572,6 +576,8 @@ export async function runVerb(argv) {
|
|
|
572
576
|
if (card.parents.length) out.push(` parents: ${card.parents.map((p) => p.id).join(', ')}`);
|
|
573
577
|
if (card.children.length) out.push(` children: ${card.children.join(', ')}`);
|
|
574
578
|
if (card.subtasks.length) out.push(` subtasks: ${card.subtasks.map((s) => `${s.id} (${s.status})`).join(', ')}`);
|
|
579
|
+
const side = withBoard((b) => sideSessions(b.db, t.id));
|
|
580
|
+
if (side.length) out.push(` side sessions: ${side.map((s) => s.address).join(', ')}`);
|
|
575
581
|
if (t.body) out.push('', 'Body:', t.body);
|
|
576
582
|
if (t.result) out.push('', 'Result:', t.result);
|
|
577
583
|
else if (latest) out.push('', 'Latest summary:', latest);
|
|
@@ -847,7 +853,7 @@ export async function runVerb(argv) {
|
|
|
847
853
|
const children = args.child.map((c) => { const [title, assignee] = c.split(/@(?=[^@]*$)/); return { title: title.trim(), assignee: assignee?.trim() || undefined }; });
|
|
848
854
|
results.push({ id: t, children: await withBoardAsync((b) => engine.decompose(b, t, children, { author: args.author ?? callerAddress() ?? callerName() })) });
|
|
849
855
|
} else {
|
|
850
|
-
const session = await withBoardAsync((b) => startCard(b, t, { lanes, settings }));
|
|
856
|
+
const session = await withBoardAsync((b) => startCard(b, t, { lanes, settings, roles: rolesFor(b.db, t, sid, workflowFor(root).params) }));
|
|
851
857
|
results.push({ id: t, session: session.address, mode: session.mode });
|
|
852
858
|
}
|
|
853
859
|
}
|
|
@@ -1114,10 +1120,22 @@ export async function runVerb(argv) {
|
|
|
1114
1120
|
case 'adopt': {
|
|
1115
1121
|
need(2, 'a card id and a session');
|
|
1116
1122
|
withBoard(b => { if (!rolesFor(b.db, id, sid, workflowFor(root).params).includes('manager')) throw new Error('Only the manager adopts an existing session.'); });
|
|
1123
|
+
if (args.side) {
|
|
1124
|
+
withBoard((b) => attachSideSession(b, id, rest[1]));
|
|
1125
|
+
say(args, `${rest[1]} is a side session of ${id}: it works ${id}'s subtasks and comments on ${id}; ${id}'s run and state stay its main session's`, { ok: true, side: rest[1] });
|
|
1126
|
+
return 0;
|
|
1127
|
+
}
|
|
1117
1128
|
withBoard((b) => requestAdoption(b, id, rest[1], { workspace: args.workspace ?? null, assignee: args.assignee ?? null, role: args.role ?? null }));
|
|
1118
1129
|
say(args, `${id} is held for ${rest[1]}; the dispatcher verifies and binds it on its next tick (session_adopted, or adoption_failed with the reason)`, { ok: true, requested: rest[1] });
|
|
1119
1130
|
return 0;
|
|
1120
1131
|
}
|
|
1132
|
+
case 'release': {
|
|
1133
|
+
need(2, 'a card id and its side session');
|
|
1134
|
+
withBoard(b => { if (!rolesFor(b.db, id, sid, workflowFor(root).params).includes('manager')) throw new Error('Only the manager releases a side session.'); });
|
|
1135
|
+
withBoard((b) => releaseSideSession(b, id, rest[1]));
|
|
1136
|
+
say(args, `${rest[1]} is no longer a side session of ${id}`, { ok: true, released: rest[1] });
|
|
1137
|
+
return 0;
|
|
1138
|
+
}
|
|
1121
1139
|
case 'goto': {
|
|
1122
1140
|
need(2, 'a card id and the status to take it to');
|
|
1123
1141
|
const { _: _words, root: _r, board: _b, json: _j, at: _a, ...fields } = args;
|
|
@@ -1429,7 +1447,8 @@ function agentMain(address) { return current().agentMains.get(address) ?? null;
|
|
|
1429
1447
|
/**
|
|
1430
1448
|
* Who a caller is on card `cardId`, in the workflow's roles: a signed-in terminal person retains their creator role;
|
|
1431
1449
|
* the manager acts from its named session; the session of the card's current run has that run's role; the card's working session between runs
|
|
1432
|
-
* is its `implementer`; the session working a subtask's arc is `arc`; its recorded creator also has the
|
|
1450
|
+
* is its `implementer`; the session working a subtask's arc, and each side session attached to that arc, is `arc`; its recorded creator also has the
|
|
1451
|
+
* creator role; any other session is nobody on it.
|
|
1433
1452
|
*/
|
|
1434
1453
|
function rolesFor(db, cardId, sessionId, params = {}) {
|
|
1435
1454
|
// a session the workflow names as the board's manager (params.managers: its addresses) is the manager on every card
|
|
@@ -1447,27 +1466,32 @@ function rolesFor(db, cardId, sessionId, params = {}) {
|
|
|
1447
1466
|
if (task.subtask_of) {
|
|
1448
1467
|
const parent = engine.getTask(db, task.subtask_of);
|
|
1449
1468
|
const worker = parent?.current_run_id ? engine.currentRun(db, parent.id)?.metadata?.supercode : engine.lastSession(db, task.subtask_of);
|
|
1450
|
-
if (worker?.session_id === sessionId) roles.push('arc');
|
|
1469
|
+
if (worker?.session_id === sessionId || sideSessions(db, task.subtask_of).some((s) => s.session === sessionId)) roles.push('arc');
|
|
1451
1470
|
}
|
|
1452
1471
|
return roles;
|
|
1453
1472
|
}
|
|
1454
1473
|
|
|
1474
|
+
/**
|
|
1475
|
+
* What a session that works a card, or is a side session of an arc (`own.side`), may name: its own cards and their
|
|
1476
|
+
* subtasks, and the subtasks of the arcs it is a side session of, and those arcs themselves only to comment on. What it
|
|
1477
|
+
* may do to them is the workflow's (rolesFor).
|
|
1478
|
+
*/
|
|
1455
1479
|
function authorize(db, verb, rest, args, own) {
|
|
1456
|
-
const cards = [...own.worked];
|
|
1480
|
+
const cards = [...new Set([...own.worked, ...own.side])];
|
|
1457
1481
|
const named = cards.join(', ');
|
|
1458
1482
|
const refuse = (why) => { throw new Error(`the session working ${named} ${why}`); };
|
|
1459
1483
|
if (verb === 'create') {
|
|
1460
|
-
if (!own.worked.has(args['subtask-of'])) refuse(`files subtasks of its own card only (--subtask-of ${cards.join(' or ')})`);
|
|
1484
|
+
if (!own.worked.has(args['subtask-of']) && !own.side.has(args['subtask-of'])) refuse(`files subtasks of its own card only (--subtask-of ${cards.join(' or ')})`);
|
|
1461
1485
|
return;
|
|
1462
1486
|
}
|
|
1463
1487
|
if (verb === 'boards') { if (!['list', 'ls', 'show', 'current', undefined].includes(rest[0])) refuse('does not change the home\'s boards'); return; }
|
|
1464
1488
|
// verbs that act on the board rather than on a card are the manager's and the board's, never a card's session's
|
|
1465
1489
|
// (what a session may do to a card is its workflow's: each transition's `by`)
|
|
1466
|
-
if ((['pause', 'resume'].includes(verb) && !rest[0]) || ['swarm', 'dispatch', 'daemon', 'serve', 'gc', 'repair', 'adopt', 'close', 'claim', 'attach-rm', 'migrate'].includes(verb)) refuse(`does not ${verb}; the fleet's manager does`);
|
|
1490
|
+
if ((['pause', 'resume'].includes(verb) && !rest[0]) || ['swarm', 'dispatch', 'daemon', 'serve', 'gc', 'repair', 'adopt', 'release', 'close', 'claim', 'attach-rm', 'migrate'].includes(verb)) refuse(`does not ${verb}; the fleet's manager does`);
|
|
1467
1491
|
if (verb === 'set' && !(own.worked.has(rest[0]) && rest[1] === 'every')) refuse("sets only Every on its own auditor card; other properties are the manager's");
|
|
1468
1492
|
if (args.all) refuse(`does not ${verb} every card; only its own`);
|
|
1469
1493
|
if (['notify-subscribe', 'notify-unsubscribe'].includes(verb) && args['for-card']) {
|
|
1470
|
-
if (!own.worked.has(args['for-card'])) refuse(`subscribes only its own arc (--for-card ${
|
|
1494
|
+
if (!own.worked.has(args['for-card'])) refuse(`subscribes only its own arc (--for-card ${[...own.worked].join(' or ')})`);
|
|
1471
1495
|
return;
|
|
1472
1496
|
}
|
|
1473
1497
|
const targets = verb === 'request-state' ? [...rest, ...(args.ids ?? [])] : [rest[0], ...(args.ids ?? [])];
|
|
@@ -1477,7 +1501,9 @@ function authorize(db, verb, rest, args, own) {
|
|
|
1477
1501
|
if (isCaller(completionOwnerOf(db, card.id))) continue;
|
|
1478
1502
|
if (verb === 'comment' && isLatestReviewer(db, card.id, actorOf())) continue;
|
|
1479
1503
|
// the card it names is the one it addresses: any card it holds, or a subtask of one it works
|
|
1480
|
-
|
|
1504
|
+
// a side session names its arc only to comment on it; the arc's other verbs are its main session's
|
|
1505
|
+
if (own.side.has(card.id) && !own.held.has(card.id) && verb !== 'comment') refuse(`only comments on ${card.id}, which it is a side session of; the arc's other verbs are its main session's and the manager's`);
|
|
1506
|
+
if (!own.held.has(card.id) && !own.worked.has(card.subtask_of) && !own.side.has(card.id) && !own.side.has(card.subtask_of)) refuse(`writes only its own card${cards.length > 1 ? 's' : ''} and ${cards.length > 1 ? 'their' : 'its'} subtasks, not ${card.id}`);
|
|
1481
1507
|
}
|
|
1482
1508
|
}
|
|
1483
1509
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Side sessions: an arc keeps its one main session (its run, its driver), and the manager may attach other sessions
|
|
2
|
+
// beside it for parts of the arc (`adopt <arc> <session> --side`, `release <arc> <session>`). A side session is bound
|
|
3
|
+
// to no run and to no machine (the card's machine is its main session's); it holds the `arc` role on the arc's
|
|
4
|
+
// subtasks (file, comment on, complete or drop them) and may comment on the arc card, and nothing else of the arc's
|
|
5
|
+
// own: its state, its run and its review stay the main session's and the manager's. An arc that ends (done or
|
|
6
|
+
// cancelled) releases its side sessions in the same transaction.
|
|
7
|
+
//
|
|
8
|
+
// The live attachments are rows of `supercode_side_sessions`, keyed by arc and session and indexed by session, so the
|
|
9
|
+
// question every verb a session runs asks ("which arcs is this session a side session of?") reads only its own rows.
|
|
10
|
+
// Each attach and release is also recorded on the arc as an event (`side_session_attached`, `side_session_released`).
|
|
11
|
+
import { boardCopyOf, copyRefusal, event, tx } from './store.mjs';
|
|
12
|
+
import { currentRun, getTask, lastSession } from './engine.mjs';
|
|
13
|
+
|
|
14
|
+
const TABLE = 'supercode_side_sessions';
|
|
15
|
+
const SCHEMA = `CREATE TABLE IF NOT EXISTS ${TABLE} (task_id TEXT NOT NULL, session TEXT NOT NULL, address TEXT NOT NULL, PRIMARY KEY (task_id, session));
|
|
16
|
+
CREATE INDEX IF NOT EXISTS idx_side_sessions_session ON ${TABLE}(session);`;
|
|
17
|
+
// a read never writes: a board with no attachment yet has no table, and reads as none
|
|
18
|
+
const present = (db) => Boolean(db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(TABLE));
|
|
19
|
+
|
|
20
|
+
/** A harness session's address, `sc:<machine>:<harness>:<id>`, as `{ session, address }`; refused for anything else. */
|
|
21
|
+
function sessionAddress(who) {
|
|
22
|
+
const address = String(who ?? '').trim();
|
|
23
|
+
const m = /^sc:([^:\s]+):([^:\s]+):([^:\s]+)$/.exec(address);
|
|
24
|
+
if (!m || ['agent', 'board', 'operator'].includes(m[2])) throw new Error(`a side session is named by its harness session address, sc:<machine>:<harness>:<id>, not ${address || 'nothing'}`);
|
|
25
|
+
return { session: m[3], address };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function attachSideSession(board, id, who) {
|
|
29
|
+
const { session, address } = sessionAddress(who);
|
|
30
|
+
if (boardCopyOf(board)) throw copyRefusal(board, 'attaches no session');
|
|
31
|
+
tx(board, (db) => {
|
|
32
|
+
const card = getTask(db, id);
|
|
33
|
+
if (!card) throw new Error(`no card ${id}`);
|
|
34
|
+
if (card.subtask_of) throw new Error(`${id} is a subtask of ${card.subtask_of}; attach the side session to the arc, ${card.subtask_of}`);
|
|
35
|
+
// its main session: the current run's (any slot) and the card's last worker (its implementer while a review runs)
|
|
36
|
+
const main = [card.current_run_id ? currentRun(db, id)?.metadata?.supercode?.session_id : null, lastSession(db, id)?.session_id];
|
|
37
|
+
if (main.includes(session)) throw new Error(`${address} is ${id}'s main session; a side session is another session beside it`);
|
|
38
|
+
if (sideSessions(db, id).some((s) => s.session === session)) throw new Error(`${address} is already a side session of ${id}`);
|
|
39
|
+
db.exec(SCHEMA);
|
|
40
|
+
db.prepare(`INSERT INTO ${TABLE} (task_id, session, address) VALUES (?, ?, ?)`).run(id, session, address);
|
|
41
|
+
event(db, id, 'side_session_attached', { session, address });
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function releaseSideSession(board, id, who) {
|
|
46
|
+
const { session, address } = sessionAddress(who);
|
|
47
|
+
if (boardCopyOf(board)) throw copyRefusal(board, 'releases no session');
|
|
48
|
+
tx(board, (db) => {
|
|
49
|
+
if (!getTask(db, id)) throw new Error(`no card ${id}`);
|
|
50
|
+
if (!sideSessions(db, id).some((s) => s.session === session)) throw new Error(`${address} is no side session of ${id}`);
|
|
51
|
+
db.prepare(`DELETE FROM ${TABLE} WHERE task_id = ? AND session = ?`).run(id, session);
|
|
52
|
+
event(db, id, 'side_session_released', { session, address });
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Release every side session of arc `id`, in the caller's transaction (the arc ended: `reason`). */
|
|
57
|
+
export function releaseAllSideSessions(db, id, reason) {
|
|
58
|
+
for (const s of sideSessions(db, id)) {
|
|
59
|
+
db.prepare(`DELETE FROM ${TABLE} WHERE task_id = ? AND session = ?`).run(id, s.session);
|
|
60
|
+
event(db, id, 'side_session_released', { session: s.session, address: s.address, reason });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The side sessions attached to arc `id` now: `{ session, address }` each. */
|
|
65
|
+
export function sideSessions(db, id) {
|
|
66
|
+
return present(db) ? db.prepare(`SELECT session, address FROM ${TABLE} WHERE task_id = ? ORDER BY rowid`).all(id).map((r) => ({ session: r.session, address: r.address })) : [];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** The arcs session `sessionId` is a side session of now. */
|
|
70
|
+
export function sideArcs(db, sessionId) {
|
|
71
|
+
if (!sessionId || !present(db)) return new Set();
|
|
72
|
+
return new Set(db.prepare(`SELECT task_id FROM ${TABLE} WHERE session = ?`).all(sessionId).map((r) => r.task_id));
|
|
73
|
+
}
|
package/board/workflow.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import { existsSync, readdirSync, realpathSync, watch, readFileSync } from 'node
|
|
|
13
13
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
14
14
|
import { randomBytes } from 'node:crypto';
|
|
15
15
|
import { actorOf, appendRunObservation, event as record, json, now } from './store.mjs';
|
|
16
|
+
import { releaseAllSideSessions } from './side-sessions.mjs';
|
|
16
17
|
import { evaluate, holds, render } from './expr.mjs';
|
|
17
18
|
import { completionEvidence, declaredWorkBranches, namedWorkBranch, reviewCandidate } from './ci.mjs';
|
|
18
19
|
import { cardCommitsOn, isLocalWorkspace, isWorkspacePath } from './workspace.mjs';
|
|
@@ -682,6 +683,8 @@ export function setStatus(db, workflow, id, status) {
|
|
|
682
683
|
run(db, 'UPDATE tasks SET status = ? WHERE id = ?', stored, id);
|
|
683
684
|
run(db, `INSERT INTO supercode_cards (task_id, stage, since, after_fired) VALUES (?, ?, ?, NULL)
|
|
684
685
|
ON CONFLICT(task_id) DO UPDATE SET stage = excluded.stage, since = excluded.since, after_fired = NULL`, id, stored === status ? null : status, now());
|
|
686
|
+
// an arc that ends releases its side sessions with it (side-sessions.mjs)
|
|
687
|
+
if (['done', 'archived'].includes(stored) && !db.prepare('SELECT subtask_of FROM supercode_cards WHERE task_id = ?').get(id)?.subtask_of) releaseAllSideSessions(db, id, `${id} is ${status}`);
|
|
685
688
|
}
|
|
686
689
|
|
|
687
690
|
/** Take every `always` transition that holds, from wherever the card is, until none does. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-orchestrator",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.172",
|
|
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": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@volter/supercode-harness-sdk": "^0.3.100",
|
|
28
|
-
"@volter/teams": "0.2.
|
|
28
|
+
"@volter/teams": "0.2.73",
|
|
29
29
|
"@volter/ztrack": "^2.4.23",
|
|
30
30
|
"yaml": "^2.9.0"
|
|
31
31
|
},
|