@volter/supercode-orchestrator 0.5.114 → 0.5.116
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/dispatch.mjs +36 -12
- package/package.json +2 -2
package/board/dispatch.mjs
CHANGED
|
@@ -172,6 +172,8 @@ const HARNESSES = {
|
|
|
172
172
|
// machine_offline; a stream that timed out, 504), says nothing about what it was asked: it is tried again next round.
|
|
173
173
|
const doorTransient = (r) => r.code === null || ['unavailable', 'machine_offline', 'rate_limited', 504, '504', 'ETIMEDOUT', 'ECONNRESET', 'EAI_AGAIN'].includes(json(String(r.stderr ?? '').trim().split('\n').pop(), null)?.code);
|
|
174
174
|
const adoptedOnly = (s) => s?.mode === 'adopt' && !s.launch_key && s.surface !== 'headless';
|
|
175
|
+
/** A close the machine refused for good (not deferred): the board will not try it again, and the session runs on. */
|
|
176
|
+
const closeRefused = (s) => Boolean(s?.close_failed_at && s.close_error && !/^close deferred/.test(s.close_error) && !s.close_confirmed_at);
|
|
175
177
|
const addressOf = (s) => s?.address ?? (s?.session_id && s.machine ? `sc:${s.machine}:${s.harness ?? 'claude-code'}:${s.session_id}` : null);
|
|
176
178
|
|
|
177
179
|
// A session in a live pane of its machine is the fleet's reading; any other run is read on its own machine: a headless
|
|
@@ -1059,7 +1061,7 @@ export async function closeRequested(board, report, { taskId = null, caller = nu
|
|
|
1059
1061
|
}
|
|
1060
1062
|
// A final refusal stops further close attempts. Only an authorized observation of this exact pane may establish
|
|
1061
1063
|
// absence: panes.list can be filtered or carry missing context keys. Unknown/invisible keeps the original failure.
|
|
1062
|
-
const finalFailure =
|
|
1064
|
+
const finalFailure = closeRefused(session);
|
|
1063
1065
|
if (finalFailure) {
|
|
1064
1066
|
if (!session.pane) continue;
|
|
1065
1067
|
const r = await doorOn(session.machine ?? localMachine(), 'harness.v1.panes.presence', { pane: session.pane }, { timeout: 10_000, as: boardAddress(board.label) });
|
|
@@ -1724,9 +1726,13 @@ async function launchPane(board, task, runId, lane, fleet, { input = null, slot
|
|
|
1724
1726
|
// session starts (never two processes on one session)
|
|
1725
1727
|
// Every old pane in the slot must be settled, including a failed launch with no session id.
|
|
1726
1728
|
await closeRequested(board, { undelivered: [] }, { taskId: task.id });
|
|
1729
|
+
// The slot's own last session whose close was refused for good is not waited on: it runs on, and is continued below
|
|
1730
|
+
// (continuationOf), never started beside.
|
|
1731
|
+
const last = view(board, (db) => lastSession(db, task.id, slot));
|
|
1727
1732
|
const pending = view(board, (db) => db.prepare('SELECT id, ended_at, metadata FROM task_runs WHERE task_id = ? AND id != ?').all(task.id, runId))
|
|
1728
1733
|
.map((r) => ({ ...r, session: json(r.metadata, {})?.supercode }))
|
|
1729
|
-
.find((r) => (r.session?.pane || r.session?.launch_key || r.session?.surface === 'native') && !r.session.close_transferred_to && (r.session.slot ?? 'worker') === slot && (r.ended_at || r.session.close_requested) && !r.session.close_confirmed_at
|
|
1734
|
+
.find((r) => (r.session?.pane || r.session?.launch_key || r.session?.surface === 'native') && !r.session.close_transferred_to && (r.session.slot ?? 'worker') === slot && (r.ended_at || r.session.close_requested) && !r.session.close_confirmed_at
|
|
1735
|
+
&& !(closeRefused(r.session) && last?.session_id && r.session.session_id === last.session_id));
|
|
1730
1736
|
if (pending) throw unavailable(`slot ${slot} still owns pane ${pending.session.pane} from run ${pending.id}; closure must be confirmed before another session starts: ${pending.session.close_error ?? 'close pending'}`);
|
|
1731
1737
|
let prev = view(board, (db) => lastSession(db, task.id, slot));
|
|
1732
1738
|
if (prev?.session_id && !prev.forgotten && (prev.harness ?? 'claude-code') === harness && prev.machine === machine) {
|
|
@@ -1740,7 +1746,7 @@ async function launchPane(board, task, runId, lane, fleet, { input = null, slot
|
|
|
1740
1746
|
if (pane && pane !== prev.pane) prev = { ...prev, pane, close_requested: null, closed_at: null, close_confirmed_at: null, close_error: null };
|
|
1741
1747
|
fleet = fresh;
|
|
1742
1748
|
}
|
|
1743
|
-
if (prev?.close_requested && !prev.closed_at) {
|
|
1749
|
+
if (prev?.close_requested && !prev.closed_at && !closeRefused(prev)) {
|
|
1744
1750
|
const report = { undelivered: [] };
|
|
1745
1751
|
await closeRequested(board, report, { taskId: task.id });
|
|
1746
1752
|
prev = view(board, (db) => lastSession(db, task.id, slot));
|
|
@@ -1786,14 +1792,13 @@ async function launchPane(board, task, runId, lane, fleet, { input = null, slot
|
|
|
1786
1792
|
let session = null;
|
|
1787
1793
|
let replaced = null;
|
|
1788
1794
|
// A session in no pane is read on its machine before anything replaces it. One the board released (an adopted
|
|
1789
|
-
// session it did not launch, left running at its run's end) is
|
|
1790
|
-
//
|
|
1795
|
+
// session it did not launch, left running at its run's end) is never resumed beside itself: while it runs it is kept,
|
|
1796
|
+
// and the card's next run is delivered into it, as into any kept session.
|
|
1791
1797
|
if (prev?.surface === 'native' && !prev.pane && (!prev.close_confirmed_at || prev.released)) {
|
|
1792
1798
|
const reading = nativeReading(await readNative(prev.machine, prev.harness, prev.session_id), prev);
|
|
1793
1799
|
if (reading.alive == null) throw unavailable(`cannot establish native continuation liveness for ${addressOf(prev)}`);
|
|
1794
1800
|
fleet.native ??= new Map(); fleet.native.set(addressOf(prev), reading);
|
|
1795
|
-
if (reading.alive && prev.released) throw unavailable(`
|
|
1796
|
-
if (reading.alive && (prev.closed_at || prev.close_requested || prev.forgotten)) throw unavailable(`native session ${addressOf(prev)} is still live; its close must finish before replacement`);
|
|
1801
|
+
if (reading.alive && !prev.released && (prev.closed_at || prev.close_requested || prev.forgotten)) throw unavailable(`native session ${addressOf(prev)} is still live; its close must finish before replacement`);
|
|
1797
1802
|
}
|
|
1798
1803
|
if (prev?.forgotten) {
|
|
1799
1804
|
if (!prev.close_confirmed_at && prev.pane && await panePresent(prev.machine, prev.pane)) throw new Error(`forgotten session ${prev.session_id} still has live pane ${prev.pane}; close it before replacement`);
|
|
@@ -1807,17 +1812,22 @@ async function launchPane(board, task, runId, lane, fleet, { input = null, slot
|
|
|
1807
1812
|
} else if (prev && prev.machine === machine) {
|
|
1808
1813
|
// a session the board closed, or one whose close its own command asked for (the next tick's sweep closes that
|
|
1809
1814
|
// run's pane), is not kept, whatever a fleet reading taken before the close still lists
|
|
1810
|
-
const
|
|
1811
|
-
if (
|
|
1812
|
-
if (
|
|
1815
|
+
const next = continuationOf(prev, prev.pane ? await panePresent(prev.machine, prev.pane) : sessionAlive(fleet, prev));
|
|
1816
|
+
if (next === 'refuse') throw new Error(`session ${prev.session_id} still has live pane ${prev.pane}; cannot resume or replace it`);
|
|
1817
|
+
if (next === 'keep') {
|
|
1813
1818
|
// the session is kept; the heartbeat's count belongs to the run that ended, so the new run starts it again
|
|
1814
|
-
const { missing_since: _m, goal_turns: _t, goal_nudged_at: _n, idle_since: _is, unknown_since: _us, unknown_notice: _un, work_progress: _wp, idle_observation: _io, idle_below_threshold: _ib, idle_eligible: _ie, close_requested: _q, closed_at: _c, close_confirmed_at: _cc, close_error: _ce, close_attempts: _ca, close_attempted_at: _ct, monitor_held: _mh, ...kept } = prev;
|
|
1819
|
+
const { missing_since: _m, goal_turns: _t, goal_nudged_at: _n, idle_since: _is, unknown_since: _us, unknown_notice: _un, work_progress: _wp, idle_observation: _io, idle_below_threshold: _ib, idle_eligible: _ie, close_requested: _q, closed_at: _c, close_confirmed_at: _cc, close_error: _ce, close_attempts: _ca, close_attempted_at: _ct, close_failed_at: _cf, close_claim: _cl, monitor_held: _mh, released: _rl, ...kept } = prev;
|
|
1815
1820
|
session = { ...kept, mode: 'adopt', launched_at: now(), seen_at: now() };
|
|
1816
1821
|
// a kept session is told what this run is for (its card came back to it, with what happened since), only while
|
|
1817
1822
|
// this run is still the card's current one (its pane and native reads were awaited)
|
|
1818
1823
|
tx(board, (db) => {
|
|
1819
1824
|
if (!runCurrent(db, task.id, runId)) throw Object.assign(new Error(`run ${runId} of ${task.id} ended or was replaced while its session was read; nothing was told to it`), { launchSuperseded: true });
|
|
1820
1825
|
setRunSession(db, runId, { ...session, ...meta });
|
|
1826
|
+
// an earlier run's close of this pane is this run's now: the pane is the kept session's, not a close to retry
|
|
1827
|
+
if (prev.pane) for (const r of db.prepare('SELECT id, metadata FROM task_runs WHERE task_id = ? AND id != ?').all(task.id, runId)) {
|
|
1828
|
+
const s = json(r.metadata, {})?.supercode;
|
|
1829
|
+
if (s?.pane === prev.pane && s.machine === prev.machine && !s.close_confirmed_at && !s.close_transferred_to) setRunSession(db, r.id, { ...s, close_transferred_to: runId, close_requested: null });
|
|
1830
|
+
}
|
|
1821
1831
|
});
|
|
1822
1832
|
// filed in its mailbox, which delivers it
|
|
1823
1833
|
if (input) { assertRunCurrent(board, task.id, runId); await fileMail(board, addressOf(prev), input, `board-run-${task.id}-r${runId}`); }
|
|
@@ -1885,6 +1895,18 @@ async function launchPane(board, task, runId, lane, fleet, { input = null, slot
|
|
|
1885
1895
|
return session;
|
|
1886
1896
|
}
|
|
1887
1897
|
|
|
1898
|
+
/**
|
|
1899
|
+
* What a slot's last session on the card's machine is to the card's next run, given whether it is live (its pane, else
|
|
1900
|
+
* its native process): an open session that is live is kept, and the run is delivered into it; a live pane holding a
|
|
1901
|
+
* session the board closed refuses the launch; anything else is resumed. A released session is open: its release only
|
|
1902
|
+
* freed the slot, and the session was left running. So is one whose close the machine refused for good.
|
|
1903
|
+
*/
|
|
1904
|
+
export function continuationOf(prev, live) {
|
|
1905
|
+
const open = Boolean(prev.released) || closeRefused(prev) || (!prev.closed_at && !prev.close_requested);
|
|
1906
|
+
if (open) return live ? 'keep' : 'resume';
|
|
1907
|
+
return live && prev.pane ? 'refuse' : 'resume';
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1888
1910
|
/** A machine reading, not a missing catalog entry, establishes whether a pane is alive. */
|
|
1889
1911
|
async function panePresent(machine, pane) {
|
|
1890
1912
|
const r = await doorOn(machine, 'harness.v1.panes.list',{}, { timeout: 60_000 });
|
|
@@ -2136,7 +2158,9 @@ async function adoptSession(board, id, who, { request = null, open = request, pr
|
|
|
2136
2158
|
if (beforeRun && assignee && assignee !== task.assignee) throw new Error('rebinding a session does not reassign its active run');
|
|
2137
2159
|
if (task.machine && normalMachine(task.machine) !== host) throw new Error(`${id} runs on ${task.machine}, and ${found.address} is on ${host}; move the card first`);
|
|
2138
2160
|
const was = lastSession(db, id);
|
|
2139
|
-
|
|
2161
|
+
// a session whose close the board confirmed is not live, whatever a fleet reading still lists (a released one was
|
|
2162
|
+
// left running, and is read)
|
|
2163
|
+
if (was && was.session_id !== sessionId && !(was.close_confirmed_at && !was.released) && sessionAlive(fleet, was)) throw new Error(`${id} already has a live session, ${was.address}`);
|
|
2140
2164
|
if (!task.machine) db.prepare('INSERT INTO supercode_cards (task_id, machine) VALUES (?, ?) ON CONFLICT(task_id) DO UPDATE SET machine = excluded.machine').run(id, host);
|
|
2141
2165
|
// the lane that resumes it if it is lost, set in the same transaction (assigning first would let a tick start another)
|
|
2142
2166
|
if (assignee && assignee !== task.assignee) { db.prepare('UPDATE tasks SET assignee = ? WHERE id = ?').run(assignee, id); event(db, id, 'assigned', { assignee }); }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-orchestrator",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.116",
|
|
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.60",
|
|
28
|
-
"@volter/teams": "0.2.
|
|
28
|
+
"@volter/teams": "0.2.47",
|
|
29
29
|
"@volter/ztrack": "^2.4.23",
|
|
30
30
|
"yaml": "^2.9.0"
|
|
31
31
|
},
|