@volter/supercode-orchestrator 0.5.136 → 0.5.137
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 +51 -1
- package/package.json +1 -1
package/board/dispatch.mjs
CHANGED
|
@@ -390,6 +390,54 @@ function progressedSince(db, task, runRow) {
|
|
|
390
390
|
return at != null && Number(at) >= started;
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
+
/**
|
|
394
|
+
* What the board had typed for a card that bounced (the recipient's screen showed no input box for the mailbox's bound,
|
|
395
|
+
* and nothing was typed): a notice it filed, or the first input of a session it launched. Each is recorded on its card
|
|
396
|
+
* once, as a delivery that did not land, and the card's manager is told; nothing is sent again for it. An idle reminder
|
|
397
|
+
* that bounced was never read, so it is not counted against its session. Read at most twice a minute.
|
|
398
|
+
*/
|
|
399
|
+
const bouncesRead = new Map(); // board root → when its bounces were last read (ms)
|
|
400
|
+
async function recordBounces(board, fleet, entry, report) {
|
|
401
|
+
const at = Date.now();
|
|
402
|
+
if (at - (bouncesRead.get(board.root) ?? 0) < 30_000) return;
|
|
403
|
+
bouncesRead.set(board.root, at);
|
|
404
|
+
const managers = [].concat(entry.params.managers ?? []).filter(Boolean);
|
|
405
|
+
const tell = (db, id, text) => { if (managers[0]) event(db, id, 'board_message', { to: managers[0], text }, null); };
|
|
406
|
+
try {
|
|
407
|
+
const answer = await runtimeDoor(null, 'harness.v1.mail.expired', {}, { timeout: 15_000 });
|
|
408
|
+
const expired = answer.code === 0 ? lastJson(answer.stdout)?.rows ?? [] : [];
|
|
409
|
+
for (const expiry of expired.filter((row) => row?.bounced === true && row.id && at - Number(row.expired_at_ms) < 24 * 3600_000)) tx(board, (db) => {
|
|
410
|
+
const filed = db.prepare("SELECT task_id, payload FROM task_events WHERE kind IN ('notice_filed', 'session_notice_filed', 'actor_notice_filed') AND json_extract(payload, '$.message_id') = ? ORDER BY id DESC LIMIT 1").get(expiry.id);
|
|
411
|
+
if (!filed || db.prepare("SELECT 1 FROM task_events WHERE task_id = ? AND kind = 'notice_bounced' AND json_extract(payload, '$.message_id') = ?").get(filed.task_id, expiry.id)) return;
|
|
412
|
+
const run = currentRun(db, filed.task_id), session = run?.metadata?.supercode;
|
|
413
|
+
const uncounted = Boolean(run && !run.ended_at && json(filed.payload, {})?.verb === 'session_idle' && addressOf(session) === expiry.to && Number(session.goal_turns) > 0);
|
|
414
|
+
if (uncounted) setRunSession(db, run.id, { goal_turns: Number(session.goal_turns) - 1 });
|
|
415
|
+
event(db, filed.task_id, 'notice_bounced', { message_id: expiry.id, to: expiry.to, reason: expiry.reason, expired_at_ms: expiry.expired_at_ms, idle_turn_uncounted: uncounted }, run?.id ?? null);
|
|
416
|
+
tell(db, filed.task_id, `${filed.task_id}: the board's notice ${expiry.id} to ${expiry.to} bounced: ${expiry.reason}. It stays unread in that mailbox and is not sent again.`);
|
|
417
|
+
(report.bounced ??= []).push([filed.task_id, expiry.to, expiry.id]);
|
|
418
|
+
});
|
|
419
|
+
} catch (error) { report.undelivered?.push(['mail', 'bounces', String(error.message).slice(-200)]); }
|
|
420
|
+
// the first input of each session launched in the last ten minutes, until its machine's launch record settles it
|
|
421
|
+
const running = view(board, (db) => db.prepare('SELECT t.id, r.id run_id, r.metadata FROM tasks t JOIN task_runs r ON r.id = t.current_run_id WHERE r.ended_at IS NULL').all());
|
|
422
|
+
for (const row of running) {
|
|
423
|
+
const session = json(row.metadata, {})?.supercode;
|
|
424
|
+
if (!session?.launch_key || session.first_input || !fleet?.ok || now() - Number(session.launched_at ?? 0) > 600) continue;
|
|
425
|
+
if (!fleet.capabilities?.get(normalMachine(session.machine ?? localMachine()))?.launches) continue;
|
|
426
|
+
let receipt; try { receipt = await launchReceipt(board, session); } catch { continue; }
|
|
427
|
+
const settled = receipt?.inputBounced ? 'bounced' : receipt?.inputDelivered === true ? 'delivered' : receipt?.inputRefused || receipt?.inputError ? 'undelivered' : null;
|
|
428
|
+
if (!settled) continue;
|
|
429
|
+
tx(board, (db) => {
|
|
430
|
+
const run = db.prepare('SELECT ended_at, metadata FROM task_runs WHERE id = ?').get(row.run_id);
|
|
431
|
+
if (!run || run.ended_at || json(run.metadata, {})?.supercode?.first_input) return;
|
|
432
|
+
setRunSession(db, row.run_id, { first_input: settled });
|
|
433
|
+
if (settled !== 'bounced') return;
|
|
434
|
+
event(db, row.id, 'first_input_bounced', { launch_key: session.launch_key, pane: session.pane ?? null, reason: receipt.inputError ?? null }, row.run_id);
|
|
435
|
+
tell(db, row.id, `${row.id}: the first input of the session the board launched for it${session.pane ? ` (pane ${session.pane})` : ''} bounced: ${receipt.inputError}. The session runs without its prompt; its quiet is not counted as idleness.`);
|
|
436
|
+
(report.bounced ??= []).push([row.id, session.pane ?? session.launch_key, 'first input']);
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
393
441
|
async function supervise(board, fleet, settings, report, entry, effects) {
|
|
394
442
|
const rows = board.db.prepare(entry.workflow.run ? "SELECT id FROM tasks WHERE current_run_id IS NOT NULL" : "SELECT id FROM tasks WHERE status = 'running'").all();
|
|
395
443
|
// Published native observations are the only liveness input. Missing data remains unknown. A launch the
|
|
@@ -1432,6 +1480,8 @@ async function observeIdle(board, fleet, settings, report, entry, effects) {
|
|
|
1432
1480
|
if (session.idle_since !== interval.since) setRunSession(db, runRow.id, { idle_since: interval.since });
|
|
1433
1481
|
if (reading.quiet_seconds < settings.idleQuiet) { retain('below_threshold'); return; }
|
|
1434
1482
|
if (session.goal_nudged_at && observedAt - session.goal_nudged_at < settings.idleQuiet) { retain('nudge_cooldown'); return; }
|
|
1483
|
+
// a session whose first input bounced was never told its work: its quiet is not idleness (recordBounces told its manager)
|
|
1484
|
+
if (session.first_input === 'bounced') { retain('first_input_bounced'); return; }
|
|
1435
1485
|
// A reminder says the card shows no progress, and the last of them ends the run for it. With the card's checkout
|
|
1436
1486
|
// unread that is not known: nothing is said or counted until it is read (the reading's `progress` says why).
|
|
1437
1487
|
if (read.commit == null) { retain('progress_unread'); return; }
|
|
@@ -3131,7 +3181,7 @@ export async function tick({ root, lanes, settings, only = null, slugs = null, d
|
|
|
3131
3181
|
await readMailbox(board, boardAddress(board.label), report); phase('mailbox');
|
|
3132
3182
|
await refreshRelationAnswers(board); phase('answers');
|
|
3133
3183
|
const effects = [];
|
|
3134
|
-
await completeAdoptions(board, fleet, settings, report, lanes); await reconnectContinuedSessions(board, fleet, entry, report); await reconcileLaunches(board, fleet, report, entry, effects, settings, lanes); await supervise(board, fleet, settings, report, entry, effects); queueEffects(board, effects.splice(0)); await closeRequested(board, report, { ttl: settings.ttl, fleet }); phase('supervise');
|
|
3184
|
+
await completeAdoptions(board, fleet, settings, report, lanes); await reconnectContinuedSessions(board, fleet, entry, report); await reconcileLaunches(board, fleet, report, entry, effects, settings, lanes); await recordBounces(board, fleet, entry, report); await supervise(board, fleet, settings, report, entry, effects); queueEffects(board, effects.splice(0)); await closeRequested(board, report, { ttl: settings.ttl, fleet }); phase('supervise');
|
|
3135
3185
|
report.unsettled = [];
|
|
3136
3186
|
report.promoted = recomputeReady(board, effects, report.unsettled);
|
|
3137
3187
|
tx(board,db=>auditEndedRuns(db,board,entry));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-orchestrator",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.137",
|
|
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": {
|