@volter/supercode-orchestrator 0.5.135 → 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.
Files changed (2) hide show
  1. package/board/dispatch.mjs +51 -1
  2. package/package.json +3 -3
@@ -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.135",
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": {
@@ -24,8 +24,8 @@
24
24
  "check-types": "node scripts/generate-types.mjs --check"
25
25
  },
26
26
  "dependencies": {
27
- "@volter/supercode-harness-sdk": "^0.3.89",
28
- "@volter/teams": "0.2.52",
27
+ "@volter/supercode-harness-sdk": "^0.3.90",
28
+ "@volter/teams": "0.2.53",
29
29
  "@volter/ztrack": "^2.4.23",
30
30
  "yaml": "^2.9.0"
31
31
  },