@volter/supercode-orchestrator 0.4.44 → 0.4.46
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 +3 -2
- package/board/dispatch.mjs +14 -10
- package/board/workflow.mjs +6 -5
- package/package.json +1 -1
package/board/cli.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import { read } from '../orchestration.mjs';
|
|
|
12
12
|
import { holdDispatcherLock } from '../hermes-locks.mjs';
|
|
13
13
|
import { backingOf, boardExists, boardPath, DEFAULT_BOARD, event, json, listBoards, logsDir, now, openBoard, tx } from './store.mjs';
|
|
14
14
|
import * as engine from './engine.mjs';
|
|
15
|
-
import { adoptSession, boardLabel, boardSettings, lanesOf, localMachine, observeFleet, paused, plan, startCard, tick } from './dispatch.mjs';
|
|
15
|
+
import { adoptSession, boardAddress, boardLabel, boardSettings, lanesOf, localMachine, observeFleet, paused, plan, startCard, tick } from './dispatch.mjs';
|
|
16
16
|
import { loadWorkflow, statusOf, useWorkflow, workflowEntry, workflowFor } from './workflow.mjs';
|
|
17
17
|
import { evaluate } from './expr.mjs';
|
|
18
18
|
|
|
@@ -1050,7 +1050,8 @@ function closeSession(board, id) {
|
|
|
1050
1050
|
if (!task) throw new Error(`no card ${id}`);
|
|
1051
1051
|
if (task.status === 'running') throw new Error(`${id} is running; block, complete or reclaim it before closing its session`);
|
|
1052
1052
|
if (!session?.pane) return null;
|
|
1053
|
-
|
|
1053
|
+
// the board closes its card's pane, whichever session ran `workflow close`
|
|
1054
|
+
const r = spawnSync(process.env.SUPERCODE_BIN || 'supercode', ['teams', 'panes', 'close', session.pane, '--machine', session.machine], { cwd: tmpdir(), encoding: 'utf8', timeout: 60_000, env: { ...process.env, SUPERCODE_CALLER: boardAddress(board.label) } });
|
|
1054
1055
|
if (r.status !== 0) throw new Error(`closing pane ${session.pane} on ${session.machine}: ${(r.stderr || r.stdout).trim().slice(-300)}`);
|
|
1055
1056
|
tx(board, (db) => event(db, id, 'session_closed', { session_id: session.session_id, machine: session.machine, pane: session.pane }));
|
|
1056
1057
|
return session;
|
package/board/dispatch.mjs
CHANGED
|
@@ -54,9 +54,11 @@ export function localMachine() {
|
|
|
54
54
|
/** The supercode command the dispatcher drives. */
|
|
55
55
|
export const supercodeBin = () => process.env.SUPERCODE_BIN || 'supercode';
|
|
56
56
|
|
|
57
|
-
function sc(args, { input = null, timeout = 120_000 } = {}) {
|
|
58
|
-
// a neutral directory: `supercode teams …` resolves its entry from a checkout at the cwd
|
|
59
|
-
|
|
57
|
+
function sc(args, { input = null, timeout = 120_000, as = null } = {}) {
|
|
58
|
+
// a neutral directory: `supercode teams …` resolves its entry from a checkout at the cwd. `as`: the board this
|
|
59
|
+
// command acts for, so a card's pane is started and closed by its board, whichever session ran the board's verb
|
|
60
|
+
const env = as ? { ...process.env, SUPERCODE_CALLER: as } : process.env;
|
|
61
|
+
const r = spawnSync(supercodeBin(), args, { cwd: tmpdir(), input: input ?? undefined, encoding: 'utf8', timeout, env, maxBuffer: 512 * 1024 * 1024 });
|
|
60
62
|
return { code: r.status, stdout: r.stdout ?? '', stderr: (r.stderr ?? '') + (r.error ? r.error.message : '') };
|
|
61
63
|
}
|
|
62
64
|
|
|
@@ -236,7 +238,7 @@ function limitsOf(db, board, entry, task, session) {
|
|
|
236
238
|
return out;
|
|
237
239
|
}
|
|
238
240
|
|
|
239
|
-
function stopSession(session) {
|
|
241
|
+
function stopSession(board, session) {
|
|
240
242
|
if (!session) return;
|
|
241
243
|
if (session.surface === 'headless' && pidAlive(session.pid)) {
|
|
242
244
|
// SIGTERM to the worker's process group, then SIGKILL if it is still there after a grace, as Hermes stops its own
|
|
@@ -246,7 +248,7 @@ function stopSession(session) {
|
|
|
246
248
|
if (pidAlive(session.pid)) signal('SIGKILL');
|
|
247
249
|
return;
|
|
248
250
|
}
|
|
249
|
-
if (session.pane) sc(['teams', 'panes', 'close', session.pane, ...(session.machine ? ['--machine', session.machine] : [])], { timeout: 60_000 });
|
|
251
|
+
if (session.pane) sc(['teams', 'panes', 'close', session.pane, ...(session.machine ? ['--machine', session.machine] : [])], { timeout: 60_000, as: boardAddress(board.label) });
|
|
250
252
|
}
|
|
251
253
|
|
|
252
254
|
// ------------------------------------------------------------------ starting, as the workflow's dispatch says
|
|
@@ -416,7 +418,7 @@ function closeRequested(board, report) {
|
|
|
416
418
|
for (const row of rows) {
|
|
417
419
|
const session = json(row.metadata, {})?.supercode;
|
|
418
420
|
if (!session?.pane) continue;
|
|
419
|
-
try { stopSession(session); } catch (error) { report.undelivered.push([row.task_id, session.address, `closing it: ${String(error.message).slice(-200)}`]); continue; }
|
|
421
|
+
try { stopSession(board, session); } catch (error) { report.undelivered.push([row.task_id, session.address, `closing it: ${String(error.message).slice(-200)}`]); continue; }
|
|
420
422
|
tx(board, (db) => setRunSession(db, row.id, { ...session, closed_at: now() }));
|
|
421
423
|
}
|
|
422
424
|
}
|
|
@@ -470,7 +472,7 @@ export async function sessionEffect(board, e) {
|
|
|
470
472
|
// the session that sent the event is not closed under its own command: the dispatcher closes it next tick
|
|
471
473
|
const caller = process.env.CLAUDE_CODE_SESSION_ID || process.env.CODEX_THREAD_ID;
|
|
472
474
|
if (caller && caller === session.session_id) { tx(board, (db) => setRunSession(db, row.id, { ...session, close_requested: now() })); return; }
|
|
473
|
-
stopSession(session);
|
|
475
|
+
stopSession(board, session);
|
|
474
476
|
tx(board, (db) => setRunSession(db, row.id, { ...session, closed_at: now() }));
|
|
475
477
|
return;
|
|
476
478
|
}
|
|
@@ -575,8 +577,10 @@ function launchPane(board, task, runId, lane, fleet, { input = null, slot = 'wor
|
|
|
575
577
|
// a kept session is told what this run is for (its card came back to it, with what happened since)
|
|
576
578
|
if (input) deliverNow(board, prev.address ?? `sc:${prev.machine}:claude-code:${prev.session_id}`, input, `board-run-${task.id}-r${runId}`);
|
|
577
579
|
} else if (prev.resumable !== false) {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
+
// a resumed session is told what this run is for, as a kept one is: the run's own prompt (a reviewer resumed for
|
|
581
|
+
// its next round gets that round's prompt, its arc's context with it), not only that it was resumed
|
|
582
|
+
const told = input ? `The board resumed this session for this run of card ${task.id}.\n\n${input}` : `The board resumed this session after it was lost; you are still on card ${task.id}: ${task.title}. Carry on.`;
|
|
583
|
+
const r = sc([...base, '--key', `board-${task.id}-r${runId}-resume`, '--input', told, '--', '--resume', prev.session_id, ...tail], { timeout: 180_000, as: boardAddress(board.label) });
|
|
580
584
|
const pane = r.code === 0 ? lastJson(r.stdout)?.pane ?? null : null;
|
|
581
585
|
// a resume the harness refuses (its transcript is gone) ends the pane at once; another machine's launch
|
|
582
586
|
// answers before that shows, so the pane is looked for again a moment later
|
|
@@ -586,7 +590,7 @@ function launchPane(board, task, runId, lane, fleet, { input = null, slot = 'wor
|
|
|
586
590
|
} else if (prev) replaced = { session_id: prev.session_id, why: `the card moved from ${prev.machine} to ${machine}` };
|
|
587
591
|
if (!session) {
|
|
588
592
|
const sid = randomUUID();
|
|
589
|
-
const r = sc([...base, '--key', `board-${task.id}-r${runId}-${sid.slice(0, 8)}`, '--input', input ?? `Card ${task.id}: ${task.title}`, '--', '--session-id', sid, ...tail], { timeout: 180_000 });
|
|
593
|
+
const r = sc([...base, '--key', `board-${task.id}-r${runId}-${sid.slice(0, 8)}`, '--input', input ?? `Card ${task.id}: ${task.title}`, '--', '--session-id', sid, ...tail], { timeout: 180_000, as: boardAddress(board.label) });
|
|
590
594
|
if (r.code !== 0) throw new Error(`supercode open --on ${task.machine}: ${(r.stderr || r.stdout).trim().slice(-500)}`);
|
|
591
595
|
session = { surface: 'pane', session_id: sid, machine, pane: lastJson(r.stdout)?.pane ?? null, mode: 'fresh', launched_at: now(), ...(replaced ? { replaces: replaced.session_id } : {}) };
|
|
592
596
|
}
|
package/board/workflow.mjs
CHANGED
|
@@ -70,10 +70,11 @@ function pattern(p) {
|
|
|
70
70
|
return text.startsWith('(?i)') ? new RegExp(text.slice(4), 'i') : new RegExp(text);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
// the time of the latest commit
|
|
74
|
-
|
|
73
|
+
// the time of the latest commit that names `id` in its message, on any ref of the repository at `path` (a worktree's
|
|
74
|
+
// pushes land on the shared remote-tracking refs), or null (asked only when an expression does)
|
|
75
|
+
function lastCommitAt(path, id) {
|
|
75
76
|
if (!path) return null;
|
|
76
|
-
const r = spawnSync('git', ['-C', path, 'log', '-1', '--format=%ct', '
|
|
77
|
+
const r = spawnSync('git', ['-C', path, 'log', '--all', '-1', '--format=%ct', '--fixed-strings', `--grep=${id}`], { encoding: 'utf8', timeout: 10_000 });
|
|
77
78
|
const t = Number(r.stdout?.trim());
|
|
78
79
|
return r.status === 0 && Number.isFinite(t) && t > 0 ? t : null;
|
|
79
80
|
}
|
|
@@ -120,11 +121,11 @@ export function scopeOf(db, board, task, { params, event = {}, extra = {} } = {}
|
|
|
120
121
|
last_events: Object.fromEntries(all(db, 'SELECT e.kind, e.created_at, e.payload FROM task_events e JOIN (SELECT kind, MAX(id) AS id FROM task_events WHERE task_id = ? GROUP BY kind) l ON l.id = e.id', task.id)
|
|
121
122
|
.map((r) => [r.kind, { created_at: r.created_at, payload: json(r.payload, {}) ?? {} }])),
|
|
122
123
|
comments_since: (t) => all(db, 'SELECT author, body, created_at FROM task_comments WHERE task_id = ? AND created_at >= ?', task.id, t),
|
|
123
|
-
// the latest commit in
|
|
124
|
+
// the latest commit naming this card, in its workspace's repository (a subtask's: its arc's), as epoch seconds
|
|
124
125
|
last_commit_at: () => {
|
|
125
126
|
const own = task.workspace_kind !== 'scratch' ? task.workspace_path : null;
|
|
126
127
|
const arcWs = !own && task.subtask_of ? one(db, "SELECT workspace_path FROM tasks WHERE id = ? AND workspace_kind != 'scratch'", task.subtask_of)?.workspace_path : null;
|
|
127
|
-
return lastCommitAt(own ?? arcWs ?? null);
|
|
128
|
+
return lastCommitAt(own ?? arcWs ?? null, task.id);
|
|
128
129
|
},
|
|
129
130
|
};
|
|
130
131
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-orchestrator",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.46",
|
|
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": {
|