@volter/supercode-orchestrator 0.5.131 → 0.5.133
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/ci.mjs +9 -4
- package/board/dispatch.mjs +13 -5
- package/package.json +1 -1
package/board/ci.mjs
CHANGED
|
@@ -333,22 +333,27 @@ async function knownRepositories() {
|
|
|
333
333
|
|
|
334
334
|
/** Whether the arc's open PRs (`[{ url, head }]`) call for its review: some head no verdict judged that is new work.
|
|
335
335
|
* Before any verdict every unjudged head is. After a verdict, a PR it judged with a new head is, and so is a head
|
|
336
|
-
* committed after that verdict (a new PR carrying the fixes, or any PR after a verdict that named none).
|
|
337
|
-
* joins with a head committed before the verdict is not:
|
|
338
|
-
*
|
|
336
|
+
* committed after that verdict (a new PR carrying the fixes, or any PR after a verdict that named none). After changes
|
|
337
|
+
* are requested, a PR that joins with a head committed before the verdict is not: the next run is the implementer's
|
|
338
|
+
* until it pushes. After a pass nothing is owed by the implementer, so a PR that verdict's request did not name is due
|
|
339
|
+
* at whatever head it has: one that was a draft or stacked on another PR's branch when the request was made, and
|
|
340
|
+
* became the arc's once that PR merged (t_bbbd2086's #1355, committed 2 h before the pass that named only #1353). */
|
|
339
341
|
export async function reviewDue(board, id, prs) {
|
|
340
342
|
const unjudged = prs.filter((p) => !judgedHead(board, id, p.head));
|
|
341
343
|
if (!unjudged.length) return false;
|
|
342
344
|
const voided = board.db.prepare("SELECT r.payload FROM task_events v JOIN task_events r ON r.id=json_extract(v.payload,'$.review_request') AND r.task_id=v.task_id WHERE v.task_id=? AND v.kind='review_voided' ORDER BY v.id DESC LIMIT 1").get(id);
|
|
343
345
|
if (candidatePullRequests(voided && JSON.parse(voided.payload)?.ci).some(j => unjudged.some(p => p.url === j.pr.url && p.head === j.commit))) return true;
|
|
344
346
|
const last = board.db.prepare(`SELECT r.payload, (SELECT MIN(v.created_at) FROM task_events v WHERE v.task_id = r.task_id
|
|
345
|
-
AND v.kind IN ('changes_requested', 'review_pass') AND v.id > r.id) AS verdict_at
|
|
347
|
+
AND v.kind IN ('changes_requested', 'review_pass') AND v.id > r.id) AS verdict_at,
|
|
348
|
+
(SELECT v.kind FROM task_events v WHERE v.task_id = r.task_id
|
|
349
|
+
AND v.kind IN ('changes_requested', 'review_pass') AND v.id > r.id ORDER BY v.id LIMIT 1) AS verdict
|
|
346
350
|
FROM task_events r WHERE r.task_id = ? AND r.kind = 'review_requested'
|
|
347
351
|
AND EXISTS (SELECT 1 FROM task_events v WHERE v.task_id = r.task_id AND v.kind IN ('changes_requested', 'review_pass') AND v.id > r.id)
|
|
348
352
|
ORDER BY r.id DESC LIMIT 1`).get(id);
|
|
349
353
|
if (!last) return true;
|
|
350
354
|
const judged = candidatePullRequests(JSON.parse(last.payload)?.ci);
|
|
351
355
|
if (judged.some((j) => prs.some((p) => p.url === j.pr.url && p.head !== j.commit))) return true;
|
|
356
|
+
if (last.verdict === 'review_pass' && unjudged.some((p) => !judged.some((j) => j.pr.url === p.url))) return true;
|
|
352
357
|
for (const p of unjudged) if (((await commitTime(p)) ?? 0) > Number(last.verdict_at)) return true;
|
|
353
358
|
return false;
|
|
354
359
|
}
|
package/board/dispatch.mjs
CHANGED
|
@@ -642,7 +642,7 @@ async function stopSession(board, session) {
|
|
|
642
642
|
return;
|
|
643
643
|
}
|
|
644
644
|
if (session.pane) {
|
|
645
|
-
const r = await runtimeDoor(normalMachine(session.machine??localMachine())===localMachine()?null:session.machine,'harness.v1.panes.close',{pane:session.pane},{env:commandEnv(),timeout:60_000,as:boardAddress(board.label)});
|
|
645
|
+
const r = await runtimeDoor(normalMachine(session.machine??localMachine())===localMachine()?null:session.machine,'harness.v1.panes.close',{pane:session.pane,...(session.launch_key?{launch:session.launch_key}:{})},{env:commandEnv(),timeout:60_000,as:boardAddress(board.label)});
|
|
646
646
|
// a door that timed out or could not be reached says nothing about the pane: the close waits for the next round
|
|
647
647
|
const transient = doorTransient(r);
|
|
648
648
|
if (r.code !== 0 || json(r.stdout.trim(), null)?.closed !== session.pane) throw new Error(`${transient ? 'close deferred: ' : ''}closing pane ${session.pane}: ${(r.stderr || r.stdout || `exit ${r.code}`).trim().slice(-500)}`);
|
|
@@ -1141,12 +1141,20 @@ export async function closeRequested(board, report, { taskId = null, caller = nu
|
|
|
1141
1141
|
}
|
|
1142
1142
|
// A final refusal stops further close attempts. Only an authorized observation of this exact pane may establish
|
|
1143
1143
|
// absence: panes.list can be filtered or carry missing context keys. Unknown/invisible keeps the original failure.
|
|
1144
|
-
|
|
1144
|
+
// A refusal's reason can end (the machine's daemon was replaced by one that reads the launch's starter): the same
|
|
1145
|
+
// reading says whether this board's close of the pane would be admitted now (`yours`), and a close it would admit
|
|
1146
|
+
// is tried again, unless an unended run works in the pane. A machine that does not say stays refused.
|
|
1147
|
+
let finalFailure = closeRefused(session);
|
|
1145
1148
|
if (finalFailure) {
|
|
1146
1149
|
if (!session.pane) continue;
|
|
1147
|
-
const r = await doorOn(session.machine ?? localMachine(), 'harness.v1.panes.presence', { pane: session.pane }, { timeout: 10_000, as: boardAddress(board.label) });
|
|
1150
|
+
const r = await doorOn(session.machine ?? localMachine(), 'harness.v1.panes.presence', { pane: session.pane, ...(session.launch_key ? { launch: session.launch_key } : {}) }, { timeout: 10_000, as: boardAddress(board.label) });
|
|
1148
1151
|
const observed = r.code === 0 ? json(r.stdout.trim(), null) : null;
|
|
1149
|
-
if (observed?.pane !== session.pane
|
|
1152
|
+
if (observed?.pane !== session.pane) continue;
|
|
1153
|
+
if (observed.present === true && observed.yours === true) {
|
|
1154
|
+
if (view(board, (db) => db.prepare(`SELECT metadata FROM task_runs r WHERE r.ended_at IS NULL OR EXISTS (SELECT 1 FROM tasks t WHERE t.current_run_id = r.id)`).all())
|
|
1155
|
+
.some((r) => { const s = json(r.metadata, {})?.supercode; return s?.pane === session.pane && normalMachine(s.machine) === normalMachine(session.machine); })) continue;
|
|
1156
|
+
finalFailure = false;
|
|
1157
|
+
} else if (observed.present !== false) continue;
|
|
1150
1158
|
}
|
|
1151
1159
|
const active = view(board, (db) => db.prepare(`SELECT r.id, r.metadata FROM task_runs r WHERE r.ended_at IS NULL OR EXISTS (SELECT 1 FROM tasks t WHERE t.current_run_id = r.id)`).all());
|
|
1152
1160
|
// Persist retries even for historical ends that did not have a close_session action. The close is claimed in the
|
|
@@ -1226,7 +1234,7 @@ export async function closeRequested(board, report, { taskId = null, caller = nu
|
|
|
1226
1234
|
setRunSession(db, row.id, { ...current, close_attempted_at: at, close_attempts: Number(current.close_attempts ?? 0) + 1, close_error: error, ...(final ? { close_failed_at: at } : {}) });
|
|
1227
1235
|
// Retain every attempt's latest fact, but avoid an unbounded thread of identical failures.
|
|
1228
1236
|
if (current.close_error !== error) event(db, row.task_id, 'session_close_failed', { session_id: session.session_id, pane: session.pane, error, retry: !final }, row.id);
|
|
1229
|
-
if (final) event(db, row.task_id, 'run_incident', { observation: 'close_failed', run: row.id, reason: `the board could not close ${addressOf(session)} and will not try again: ${error}`, session: session.session_id, machine: session.machine, pane: session.pane ?? null, evidence: { error } }, row.id);
|
|
1237
|
+
if (final && current.close_error !== error) event(db, row.task_id, 'run_incident', { observation: 'close_failed', run: row.id, reason: `the board could not close ${addressOf(session)} and will not try again: ${error}`, session: session.session_id, machine: session.machine, pane: session.pane ?? null, evidence: { error } }, row.id);
|
|
1230
1238
|
} else if (!current.close_confirmed_at) {
|
|
1231
1239
|
// a close another closer already confirmed (a claim that expired under it) is said once, by that closer
|
|
1232
1240
|
const { close_error: _error, ...rest } = current;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-orchestrator",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.133",
|
|
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": {
|