@volter/supercode-orchestrator 0.5.180 → 0.5.182
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 +43 -9
- package/board/doors.mjs +42 -3
- package/board/machine-fleet.mjs +4 -2
- package/package.json +2 -2
package/board/dispatch.mjs
CHANGED
|
@@ -2107,7 +2107,7 @@ async function launchPane(board, task, runId, lane, fleet, { input = null, slot
|
|
|
2107
2107
|
if (next === 'refuse') throw new Error(`session ${prev.session_id} still has live pane ${prev.pane}; cannot resume or replace it`);
|
|
2108
2108
|
if (next === 'keep') {
|
|
2109
2109
|
// the session is kept; the heartbeat's count belongs to the run that ended, so the new run starts it again
|
|
2110
|
-
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_unread: _cu, close_claim: _cl, monitor_held: _mh, released: _rl, ...kept } = prev;
|
|
2110
|
+
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_unread: _cu, close_claim: _cl, monitor_held: _mh, released: _rl, review_held: _rh, ...kept } = prev;
|
|
2111
2111
|
session = { ...kept, mode: 'adopt', launched_at: now(), seen_at: now() };
|
|
2112
2112
|
// a kept session is told what this run is for (its card came back to it, with what happened since), only while
|
|
2113
2113
|
// this run is still the card's current one (its pane and native reads were awaited)
|
|
@@ -3070,16 +3070,42 @@ function outcomeTicked(body) {
|
|
|
3070
3070
|
/** An opened PR asks for the declared review transition once the arc's tasks are closed. */
|
|
3071
3071
|
// A task written in the card's own Tasks section that did not become a subtask holds its review too.
|
|
3072
3072
|
/** The arc's session still in a turn: its review, and the close of that session the request brings (end_run), wait for
|
|
3073
|
-
* the turn to end, so work in flight is never cut
|
|
3074
|
-
*
|
|
3075
|
-
|
|
3073
|
+
* the turn to end, so work in flight is never cut and the reviewer never starts in the coder's checkout beside a turn
|
|
3074
|
+
* still running there (t_f34710bc's coder had a proof take running when its review was requested at 15:01:13Z on
|
|
3075
|
+
* 2026-10-04, and the close ended it). A session the reading cannot place waits on nothing.
|
|
3076
|
+
*
|
|
3077
|
+
* A Codex session with an active thread goal is never read between turns: the goal starts the next turn the moment one
|
|
3078
|
+
* ends (t_374d7f70, codex 01a108b7, 2026-10-14). For it the turn boundary is the one its harness records: the open turn
|
|
3079
|
+
* was started by the goal (Codex's own `turn_trigger`, published as the activity's `turn_trigger`), not by a person or
|
|
3080
|
+
* by mail, and it started after `heldSince`, the first round that found the review due with nothing but this turn
|
|
3081
|
+
* holding it. So the turn that was running when the review fell due, the one that closed the tasks and pushed the
|
|
3082
|
+
* heads, has ended with the coder's own completion; a goal turn that began before that round, which may be the one
|
|
3083
|
+
* running a proof, still holds the review. */
|
|
3084
|
+
async function turnInFlight(board, id, fleet, heldSince = null) {
|
|
3076
3085
|
if (!fleet?.ok) return null;
|
|
3077
3086
|
const session = currentRun(board.db, id)?.metadata?.supercode;
|
|
3078
3087
|
if (!session?.session_id || session.surface === 'headless' || session.close_requested) return null;
|
|
3079
|
-
let
|
|
3080
|
-
if (turn == null && session.surface === 'native') try {
|
|
3088
|
+
let live = fleet.sessions?.find((x) => x.address === addressOf(session)) ?? null;
|
|
3089
|
+
if (live?.turn == null && session.surface === 'native') try {
|
|
3090
|
+
const activity = nativeReading(await readNative(session.machine, session.harness ?? 'claude-code', session.session_id), session).row?.activity;
|
|
3091
|
+
live = activity ? { turn: activity.turn ?? null, turn_trigger: activity.evidence?.turn_trigger ?? null, turn_since_ms: activity.evidence?.native_state_since_ms ?? null } : live;
|
|
3092
|
+
} catch { /* unread: waits on nothing */ }
|
|
3093
|
+
const turn = live?.turn ?? null;
|
|
3081
3094
|
// a turn stopped on a permission or a question (needs_input) is a turn not finished, as one still working is
|
|
3082
|
-
|
|
3095
|
+
if (!['working', 'needs_input'].includes(turn)) return null;
|
|
3096
|
+
const started = Number(live.turn_since_ms) / 1000;
|
|
3097
|
+
if (turn === 'working' && live.turn_trigger === 'goal' && heldSince != null && started > heldSince) return null;
|
|
3098
|
+
return { why: `its session ${addressOf(session)} is in a turn (${turn}${live.turn_trigger ? `, started by ${live.turn_trigger}` : ''}); the review is requested once the turn ends${live.turn_trigger === 'goal' ? ', or once a goal turn starts after this round' : ''}` };
|
|
3099
|
+
}
|
|
3100
|
+
|
|
3101
|
+
/** What a review held on its session's turn was due on: the arc's PR heads (or its no-code act) and its last task close.
|
|
3102
|
+
* A hold is kept only while this stays the same; anything else due is a new hold from its own round. */
|
|
3103
|
+
function reviewDueKey(board, id, payload) {
|
|
3104
|
+
const closed = board.db.prepare(`SELECT MAX(id) AS id FROM task_events WHERE kind IN ('completed', 'cancelled')
|
|
3105
|
+
AND task_id IN (SELECT task_id FROM supercode_cards WHERE subtask_of = ?)`).get(id)?.id ?? null;
|
|
3106
|
+
const source = payload.no_code ? `no_code:${payload.no_code.kind ?? ''}:${payload.no_code.repository ?? ''}:${payload.no_code.base ?? ''}`
|
|
3107
|
+
: payload.pr.map((url, index) => `${url}@${payload.pr_head[index]}`).join(',');
|
|
3108
|
+
return `${source}|${closed ?? ''}`;
|
|
3083
3109
|
}
|
|
3084
3110
|
|
|
3085
3111
|
async function activatePullRequestReviews(board, entry, report, effects, { only = null, dry = false, fleet = null } = {}) {
|
|
@@ -3101,8 +3127,6 @@ async function activatePullRequestReviews(board, entry, report, effects, { only
|
|
|
3101
3127
|
const ready = view(board, eligible);
|
|
3102
3128
|
if (!ready) continue;
|
|
3103
3129
|
if (ready !== true) { report.queued.push([id, `review waits: ${ready.length} open task(s) in the card's Tasks: ${ready[0].id} ${ready[0].title}`]); continue; }
|
|
3104
|
-
const inTurn = await turnInFlight(board, id, fleet);
|
|
3105
|
-
if (inTurn) { report.queued.push([id, `review waits: ${inTurn}`]); continue; }
|
|
3106
3130
|
let payload = null;
|
|
3107
3131
|
try {
|
|
3108
3132
|
payload = await openReviewPullRequests(board, id);
|
|
@@ -3121,6 +3145,16 @@ async function activatePullRequestReviews(board, entry, report, effects, { only
|
|
|
3121
3145
|
}
|
|
3122
3146
|
const evidence = payload.no_code ? { required: false, ready: true, reason: null, phase: 'source', no_code: payload.no_code } : await inspectCompletionEvidence(board, id, entry, payload, 'request_review');
|
|
3123
3147
|
if (!evidence.ready) throw new Error(evidence.reason);
|
|
3148
|
+
// Due in every other respect: only the session's turn may still hold it. The first round that finds it so is
|
|
3149
|
+
// recorded on the run (`review_held`), with what it was due on, so a goal turn can be told to have started after.
|
|
3150
|
+
const heldRun = currentRun(board.db, id), dueKey = reviewDueKey(board, id, payload);
|
|
3151
|
+
const held = heldRun?.metadata?.supercode?.review_held?.key === dueKey ? heldRun.metadata.supercode.review_held : null;
|
|
3152
|
+
const inTurn = await turnInFlight(board, id, fleet, held ? Number(held.since) : null);
|
|
3153
|
+
if (inTurn) {
|
|
3154
|
+
if (!held && !dry && heldRun) tx(board, (db) => { if (currentRun(db, id)?.id === heldRun.id) setRunSession(db, heldRun.id, { review_held: { key: dueKey, since: now() } }); });
|
|
3155
|
+
report.queued.push([id, `review waits: ${inTurn.why}`]);
|
|
3156
|
+
continue;
|
|
3157
|
+
}
|
|
3124
3158
|
// a dry run names the review a tick would request and writes nothing
|
|
3125
3159
|
if (dry) { (report.would_review ??= []).push({ id, prs: payload.pr, heads: payload.pr_head, no_code: payload.no_code ?? null }); continue; }
|
|
3126
3160
|
await refreshCardInputs(board, id, { commits: true });
|
package/board/doors.mjs
CHANGED
|
@@ -26,8 +26,11 @@ async function opened(machine) {
|
|
|
26
26
|
}
|
|
27
27
|
const opening = (async () => {
|
|
28
28
|
const channel = await openMachine({ machine: machine || null });
|
|
29
|
-
const door = { channel, node: null, harness: null };
|
|
29
|
+
const door = { channel, node: null, harness: null, heardAt: performance.now() };
|
|
30
|
+
// when the channel last carried anything (an answer, a stream's data, the keep-alive's pong): see retireSilent
|
|
31
|
+
channel.socket?.on?.('data', () => { door.heardAt = performance.now(); });
|
|
30
32
|
channel.on('close', () => { if (held.get(key) === opening) held.delete(key); });
|
|
33
|
+
door.opening = opening;
|
|
31
34
|
return door;
|
|
32
35
|
})();
|
|
33
36
|
held.set(key, opening);
|
|
@@ -43,13 +46,14 @@ async function opened(machine) {
|
|
|
43
46
|
export async function machineCall(machine, method, params = {}, { timeoutMs = 60_000 } = {}) {
|
|
44
47
|
if (!method.startsWith('harness.v1.')) throw new Error('a machine door is a harness.v1 method');
|
|
45
48
|
const started = performance.now();
|
|
49
|
+
let door = null;
|
|
46
50
|
try {
|
|
47
|
-
|
|
51
|
+
door = await opened(machine);
|
|
48
52
|
if (method.startsWith('harness.v1.sessions.')) {
|
|
49
53
|
// The harness client starts its transport again itself once it ends (a daemon restart there); one that never
|
|
50
54
|
// started is let go, so the next call starts another.
|
|
51
55
|
if (!door.harness) {
|
|
52
|
-
const starting = (async () => { const client = new SupercodeHarnessClient({ transport:
|
|
56
|
+
const starting = (async () => { const client = new SupercodeHarnessClient({ transport: channelHarness(door.channel, machine) }); await client.start?.(); return client; })();
|
|
53
57
|
starting.catch(() => { if (door.harness === starting) door.harness = null; });
|
|
54
58
|
door.harness = starting;
|
|
55
59
|
}
|
|
@@ -66,11 +70,46 @@ export async function machineCall(machine, method, params = {}, { timeoutMs = 60
|
|
|
66
70
|
door.node = opening;
|
|
67
71
|
}
|
|
68
72
|
return await (await door.node).call(method, params, { timeoutMs });
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (machine && door && (error?.code === 'REQUEST_TIMEOUT' || error?.code === 504)) retireSilent(machine, door, started);
|
|
75
|
+
throw error;
|
|
69
76
|
} finally {
|
|
70
77
|
noteStep(`door ${method}${machine ? ' (another machine)' : ''}`, performance.now() - started);
|
|
71
78
|
}
|
|
72
79
|
}
|
|
73
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Another machine's channel that carried nothing at all for the whole of a request's bound is closed, so the next call
|
|
83
|
+
* opens a fresh one. Such a channel's socket to the Teams server can stay open after the server has lost the far side:
|
|
84
|
+
* every request on it waits out its bound, and nothing ends the channel until the server closes the socket. On
|
|
85
|
+
* 2026-10-14 the board owner's discoveries on other machines timed out five times running (00:05:16 to 00:06:31), the
|
|
86
|
+
* server closed their channel at 00:06:37 (two calls failed "connection closed"), and the next channel answered in
|
|
87
|
+
* 1.4 s. The keep-alive's ping is answered by the far machine's own channel, so a channel that carried anything
|
|
88
|
+
* meanwhile (another answer, a pong) reaches that machine and is kept: the request was slow there.
|
|
89
|
+
*/
|
|
90
|
+
function retireSilent(machine, door, started) {
|
|
91
|
+
if (door.heardAt >= started || door.channel.closed) return;
|
|
92
|
+
if (held.get(machine) === door.opening) held.delete(machine);
|
|
93
|
+
door.channel.close();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The harness transport over a machine channel, saying what ended when it ends: the channel's stream is no harness
|
|
98
|
+
* process, and its close (`connection closed`) read as "Volter Harness service exited" sent F81 looking for a child
|
|
99
|
+
* that never exited.
|
|
100
|
+
*/
|
|
101
|
+
function channelHarness(channel, machine) {
|
|
102
|
+
const inner = harnessTransport(channel);
|
|
103
|
+
const where = machine ? `the Teams server channel to ${machine}` : "the socket to this machine's daemon";
|
|
104
|
+
return {
|
|
105
|
+
get running() { return inner.running; },
|
|
106
|
+
start: (handlers) => inner.start({ ...handlers,
|
|
107
|
+
onExit: (code, reason) => handlers.onExit(code, `${reason ?? 'closed'}: ${where} ended, no harness process exited`) }),
|
|
108
|
+
write: (line) => inner.write(line),
|
|
109
|
+
close: () => inner.close(),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
74
113
|
export async function machineRecordStream(params = {}) {
|
|
75
114
|
const door = await opened(null);
|
|
76
115
|
return door.channel.open('machine-record-changes', params);
|
package/board/machine-fleet.mjs
CHANGED
|
@@ -100,8 +100,10 @@ export async function readMachineFleet(machines, local) {
|
|
|
100
100
|
sessions.push({ address, machine: row.machine, harness: activity.harness, session_id: activity.session_id, pane: pane.pane, name: pane.title ?? pane.label ?? null,
|
|
101
101
|
starter_session: pane.starter_session ?? null,
|
|
102
102
|
state: running ? 'running' : 'unknown', turn: activity.turn ?? null, native_state: activity.evidence?.native_state ?? null,
|
|
103
|
-
// what a session that needs input says it waits for
|
|
104
|
-
|
|
103
|
+
// what a session that needs input says it waits for, what started its open turn (Codex: `goal` for a thread
|
|
104
|
+
// goal's continuation) and since when, where its harness publishes them
|
|
105
|
+
waiting_for: activity.evidence?.waiting_for ?? null, turn_source: activity.evidence?.source ?? null, turn_since_ms: activity.evidence?.native_state_since_ms ?? null,
|
|
106
|
+
turn_trigger: activity.evidence?.turn_trigger ?? null });
|
|
105
107
|
}
|
|
106
108
|
}
|
|
107
109
|
const ok = online.has(local);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-orchestrator",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.182",
|
|
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": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"check-types": "node scripts/generate-types.mjs --check"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@volter/supercode-harness-sdk": "^0.3.
|
|
27
|
+
"@volter/supercode-harness-sdk": "^0.3.101",
|
|
28
28
|
"@volter/teams": "0.2.79",
|
|
29
29
|
"@volter/ztrack": "^2.4.23",
|
|
30
30
|
"yaml": "^2.9.0"
|