@volter/supercode-orchestrator 0.5.117 → 0.5.119
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 +41 -5
- package/package.json +1 -1
package/board/dispatch.mjs
CHANGED
|
@@ -966,7 +966,10 @@ export async function startCard(board, id, { roles = ['manager'], lanes, setting
|
|
|
966
966
|
// The programs a harness pane runs in its foreground: Claude Code's, Codex's, and node (the daemon's Codex pane host,
|
|
967
967
|
// a harness installed through npm). A pane running one of these runs a harness session, read or not.
|
|
968
968
|
const HARNESS_COMMANDS = new Set(['claude', 'claude.exe', 'codex', 'codex.exe', 'node']);
|
|
969
|
-
|
|
969
|
+
/** A board launch never bound to a session (no session id read for it) whose launch window has passed. */
|
|
970
|
+
export const unboundLaunch = (session, launchWindow, at = now()) => Boolean(session?.launch_key && !session.session_id && launchWindow
|
|
971
|
+
&& Number(session.launched_at) > 0 && at - Number(session.launched_at) >= launchWindow);
|
|
972
|
+
async function turnAtClose(session, pane = null, { launchWindow = null } = {}) {
|
|
970
973
|
const machine = session.machine ?? localMachine();
|
|
971
974
|
let activity;
|
|
972
975
|
if (pane?.pid) {
|
|
@@ -977,6 +980,10 @@ async function turnAtClose(session, pane = null) {
|
|
|
977
980
|
// No session read under the pane is no absence (a registry it could not read, another config home, a failed process
|
|
978
981
|
// read): the pane's own foreground program says whether a harness runs there. Only a pane running none is closed.
|
|
979
982
|
if (!activity) {
|
|
983
|
+
// A pane the board launched whose session no reading ever found (the fleet's never bound it, and none runs under
|
|
984
|
+
// its process now) past the launch window holds a harness that never started a session: nothing is cut by its
|
|
985
|
+
// close. Waiting on it kept t_7ec85b6b's Codex pane on aarons-mac-mini, stuck before its first turn, open for 15 h.
|
|
986
|
+
if (unboundLaunch(session, launchWindow)) return null;
|
|
980
987
|
const command = String(pane.command ?? '').split(/[\\/]/).pop().toLowerCase();
|
|
981
988
|
return command && !HARNESS_COMMANDS.has(command) ? null : `pane ${session.pane} runs ${command || 'a program it does not name'}, whose session could not be read`;
|
|
982
989
|
}
|
|
@@ -1108,7 +1115,7 @@ export async function closeRequested(board, report, { taskId = null, caller = nu
|
|
|
1108
1115
|
if (protectedNow) throw new Error(`close deferred: pane ${session.pane} is owned by an unended run`);
|
|
1109
1116
|
// An ended run's session is closed only between its turns, read last, after the claim and the pane list (a pane
|
|
1110
1117
|
// already gone is settled above with no reading; t_f34710bc's coder was cut mid-proof at 15:01:13Z, 2026-10-04).
|
|
1111
|
-
if (row.ended_at) { const inTurn = await turnAtClose(session, observed.live.get(session.pane)); if (inTurn) throw new Error(`close deferred: ${inTurn}`); }
|
|
1118
|
+
if (row.ended_at) { const inTurn = await turnAtClose(session, observed.live.get(session.pane), { launchWindow: boardSettings(workflowFor(board.root).params).launchWindow }); if (inTurn) throw new Error(`close deferred: ${inTurn}`); }
|
|
1112
1119
|
await stopSession(board, session); observed.live.delete(session.pane);
|
|
1113
1120
|
}
|
|
1114
1121
|
}
|
|
@@ -2053,9 +2060,23 @@ export async function completeAdoptions(board, fleet, settings, report, lanes =
|
|
|
2053
2060
|
report.queued.push([row.task_id, `adoption of ${asked.session} waits: ${message}`]);
|
|
2054
2061
|
continue;
|
|
2055
2062
|
}
|
|
2063
|
+
// A reading that was not taken (the fleet or the machine's harness door did not answer) is no answer about the
|
|
2064
|
+
// session, before this round wrote anything outside the board: the request stays open and the next round reads again
|
|
2065
|
+
if ((error.unread || error.launchDeferred) && !progress.claimed) {
|
|
2066
|
+
report.queued.push([row.task_id, `adoption of ${asked.session} waits for a reading: ${message}`]);
|
|
2067
|
+
continue;
|
|
2068
|
+
}
|
|
2056
2069
|
// the failure answers the request only while it is still open, or claimed by this round: an adoption that bound and
|
|
2057
2070
|
// then failed after (its agent run not recorded) is bound, and a request another round claimed is that round's
|
|
2058
|
-
|
|
2071
|
+
// A failed adoption answers its request and ends the hold the request put on the card: the card is held again
|
|
2072
|
+
// only if it was held before the request (an incident's hold, kept for its own release), and is otherwise the
|
|
2073
|
+
// dispatcher's again. A hold that outlived its failed request held cards nobody adopted until a person released them.
|
|
2074
|
+
const answered = !error.superseded && tx(board, db => {
|
|
2075
|
+
if (!adoptionOpenTo(db, row.task_id, request.id, row.id)) return false;
|
|
2076
|
+
event(db, row.task_id, 'adoption_failed', { session: asked.session, error: message });
|
|
2077
|
+
restoreHoldBefore(db, row.task_id, request.id);
|
|
2078
|
+
return true;
|
|
2079
|
+
});
|
|
2059
2080
|
// A request another round answered or claimed first is that round's, not a failure of this one: it is noted where
|
|
2060
2081
|
// no round prints it. A failure after this round's own claim (bound, then its agent run not recorded) is one.
|
|
2061
2082
|
if (!answered && !progress.claimed) { (report.adoption_skipped ??= []).push([row.task_id, message]); continue; }
|
|
@@ -2064,6 +2085,19 @@ export async function completeAdoptions(board, fleet, settings, report, lanes =
|
|
|
2064
2085
|
}
|
|
2065
2086
|
}
|
|
2066
2087
|
|
|
2088
|
+
/**
|
|
2089
|
+
* The card's hold as it stood before adoption request `request` (an event id), restored once that request failed: a
|
|
2090
|
+
* hold the request found (an incident's) is written again as it was, so its own release still finds it; a card that was
|
|
2091
|
+
* not held, or held only for this adoption (`create --adopt`'s hold, an earlier request this one answers), is released.
|
|
2092
|
+
*/
|
|
2093
|
+
const ADOPTION_HOLDS = new Set(['adoption requested', 'awaiting adoption']);
|
|
2094
|
+
export function restoreHoldBefore(db, id, request) {
|
|
2095
|
+
const before = db.prepare("SELECT kind, payload FROM task_events WHERE task_id = ? AND id < ? AND kind IN ('dispatch_held','dispatch_released','session_adopted') ORDER BY id DESC LIMIT 1").get(id, request);
|
|
2096
|
+
const held = before?.kind === 'dispatch_held' ? json(before.payload, {}) ?? {} : null;
|
|
2097
|
+
if (held && !ADOPTION_HOLDS.has(held.reason)) { const { by: _by, ...kept } = held; event(db, id, 'dispatch_held', { ...kept, restored_after: request }); }
|
|
2098
|
+
else event(db, id, 'dispatch_released', { reason: 'adoption failed', request });
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2067
2101
|
/** Whether request `request` is still this round's to answer: the card's latest answer is `open`, or this process's claim of it. */
|
|
2068
2102
|
function adoptionOpenTo(db, id, request, open) {
|
|
2069
2103
|
const last = latestAnswer(db, id);
|
|
@@ -2078,8 +2112,9 @@ async function nativeListing(who) {
|
|
|
2078
2112
|
const m = /^sc:([^:]+):([^:]+):(.+)$/.exec(String(who));
|
|
2079
2113
|
if (!m || !HARNESSES[m[2]]) return null;
|
|
2080
2114
|
const [, host, harness, session_id] = m;
|
|
2115
|
+
// a discovery its machine did not answer is unknown, not absent: it is thrown (unread), never taken as no listing
|
|
2081
2116
|
try { return nativeReading(await readNative(host, harness, session_id), { session_id, harness }).alive === true ? { address: who, machine: normalMachine(host), harness, session_id, pane: null, name: null } : null; }
|
|
2082
|
-
catch { return null; }
|
|
2117
|
+
catch (error) { if (error.launchDeferred) throw error; return null; }
|
|
2083
2118
|
}
|
|
2084
2119
|
|
|
2085
2120
|
/** The verb an adoption with no current run fires: the workflow's own `adopt` where it declares one, else its dispatch start. */
|
|
@@ -2100,7 +2135,8 @@ function adoptedRole(db, board, entry, task, run) {
|
|
|
2100
2135
|
|
|
2101
2136
|
async function adoptSession(board, id, who, { request = null, open = request, progress = {}, workspace = null, ttl, assignee = null, role = null, fleet = null, lanes = null } = {}) {
|
|
2102
2137
|
fleet ??= await observeFleet();
|
|
2103
|
-
|
|
2138
|
+
// a fleet that could not be read says nothing of the session: the request stays open for the next reading
|
|
2139
|
+
if (!fleet.ok) throw Object.assign(new Error(`supercode could not read the fleet: ${fleet.error}`), { unread: true });
|
|
2104
2140
|
const found = fleet.sessions.find((s) => s.address === who || s.address.endsWith(`:${who}`) || s.name === who) ?? await nativeListing(who);
|
|
2105
2141
|
if (!found) throw Object.assign(new Error(`the fleet lists no live session ${who}`), { notListed: true });
|
|
2106
2142
|
const [, host, harness, sessionId] = found.address.split(':');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-orchestrator",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.119",
|
|
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": {
|