@volter/supercode-orchestrator 0.4.117 → 0.4.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.
Files changed (2) hide show
  1. package/board/dispatch.mjs +37 -12
  2. package/package.json +1 -1
@@ -116,11 +116,13 @@ export function observeFleet() {
116
116
  const list = sc(['message', 'list', '--json']);
117
117
  const sessions = list.code === 0 ? json(list.stdout.trim(), null) : null;
118
118
  if (!Array.isArray(sessions)) return { ok: false, alive: new Set(), online: new Set(), error: `message list: ${(list.stderr || list.stdout).trim().slice(-300)}` };
119
- const machines = sc(['teams', 'machines', 'list', '--json']);
120
- const rows = machines.code === 0 ? lastJson(machines.stdout)?.items : null;
119
+ const machines = sc(['teams', 'machines', 'events', '--json']);
120
+ const presence = machines.code === 0 ? lastJson(machines.stdout) : null;
121
121
  const online = new Set([localMachine()]);
122
- for (const m of rows ?? []) if (m.enrollment === 'active' && m.connection === 'online') online.add(normalMachine(m.name));
123
- return { ok: true, sessions, alive: new Set(sessions.map((s) => s.address).filter(Boolean)), online, error: rows ? null : 'teams machines list gave no answer; only this machine counts as online' };
122
+ for (const m of presence?.items ?? []) if (m.linked) online.add(normalMachine(m.name));
123
+ return { ok: true, sessions, alive: new Set(sessions.map((s) => s.address).filter(Boolean)), online,
124
+ presenceKnown: presence?.connected === true, machineEvents: presence,
125
+ error: presence?.connected ? null : 'machine event stream unavailable; retaining existing runs until availability is known' };
124
126
  }
125
127
 
126
128
  // how the launcher opens each harness a pane lane can run, and how it names the session: Claude Code is told the id
@@ -247,6 +249,30 @@ function supervise(board, fleet, settings, report, entry, effects) {
247
249
  }
248
250
  };
249
251
  const limits = limitsOf(db, board, entry, task, session);
252
+ // Availability is the same retained linked/unlinked feed subscribers hear.
253
+ // A failed stream is unknown, not evidence that every remote machine left.
254
+ if (session?.session_id && session.surface !== 'headless') {
255
+ const panes = session.pane ? observed.get(session.machine) : null;
256
+ const reachable = panes instanceof Set || fleet.online.has(session.machine);
257
+ if (!reachable && !fleet.presenceKnown) return;
258
+ if (!reachable) {
259
+ const next = {...session, offline_since: session.offline_since ?? t};
260
+ setRunSession(db,runRow.id,next);
261
+ observe('machine_offline',{machine:session.machine,seconds:t-next.offline_since},null);
262
+ const age = Number(entry.params.block_age_seconds ?? 3600);
263
+ if (t-Number(next.offline_notified_at ?? next.offline_since)>=age) {
264
+ setRunSession(db,runRow.id,{...currentRun(db,id)?.metadata?.supercode,offline_notified_at:t});
265
+ observe('machine_wait_aged',{machine:session.machine,seconds:t-next.offline_since,reason:`machine ${session.machine} has been unlinked for ${t-next.offline_since}s`},null);
266
+ }
267
+ if (getTask(db,id)?.current_run_id===runRow.id) extendClaim(db,id,{ttl:settings.ttl,reason:'machine_offline'});
268
+ return;
269
+ }
270
+ if (session.offline_since != null) {
271
+ setRunSession(db,runRow.id,{...session,offline_since:null,offline_notified_at:null});
272
+ observe('machine_online',{machine:session.machine,reason:'machine linked again'},null);
273
+ session=currentRun(db,id)?.metadata?.supercode ?? session;
274
+ }
275
+ }
250
276
  // Hermes's orphan reconciliation: a running card whose claim bookkeeping is broken, with nothing live behind it
251
277
  const paneReading = session?.pane ? observed.get(session.machine) : undefined;
252
278
  const paneLive = paneReading instanceof Set ? paneReading.has(session.pane) : null;
@@ -286,13 +312,6 @@ function supervise(board, fleet, settings, report, entry, effects) {
286
312
  const young = t - Number(session.launched_at ?? 0) < Number(limits.grace ?? settings.grace);
287
313
  const missing = !alive && machineUp && !young;
288
314
  const next = { ...session };
289
- // a machine that is off the fleet: how long, for the workflow's machine_offline (the claim is kept meanwhile)
290
- if (machineUp) next.offline_since = null; else next.offline_since ??= t;
291
- if (!machineUp) {
292
- setRunSession(db, runRow.id, next);
293
- observe('machine_offline', { machine: session.machine, seconds: t - next.offline_since }, null);
294
- if (getTask(db, id)?.current_run_id !== runRow.id) return; // the workflow moved it
295
- }
296
315
  if (alive) { next.seen_at ??= t; next.missing_since = null; }
297
316
  if (missing) next.missing_since ??= t;
298
317
  if (missing && t - next.missing_since >= Number(limits.grace ?? settings.grace)) {
@@ -618,7 +637,7 @@ function observeIdle(board, fleet, settings, report, entry, effects) {
618
637
  if (!(entry.workflow.statuses[status]?.on?.session_idle || entry.workflow.on?.session_idle || entry.workflow.run?.on?.session_idle)) return;
619
638
  const runRow = currentRun(db, id);
620
639
  const session = runRow?.metadata?.supercode;
621
- if (!session?.session_id || session.surface !== 'pane') return;
640
+ if (!session?.session_id || session.surface !== 'pane' || session.offline_since != null) return;
622
641
  const live = fleet.sessions.find((x) => x.address?.endsWith(`:${session.session_id}`));
623
642
  const observedAt = now();
624
643
  const lastAt = Math.floor(Number(live?.last_message_at_ms) / 1000);
@@ -727,6 +746,7 @@ function launcherCheck(db, task, lane, fleet) {
727
746
  if (!task.machine) return 'the card names no machine; a pane session runs on the machine its card names';
728
747
  const machine = normalMachine(task.machine);
729
748
  if (!fleet.ok) return `supercode could not read the fleet: ${fleet.error}`;
749
+ if (machine !== localMachine() && !fleet.presenceKnown) return 'machine event stream unavailable; launch waits for a current availability reading';
730
750
  if (!fleet.online.has(machine)) return `machine ${machine} is offline; the card starts when it is back`;
731
751
  // a card on this machine gets its workspace made here (scratch, dir or worktree, as Hermes makes it); another
732
752
  // machine's card names its directory there
@@ -1438,6 +1458,7 @@ function readMailbox(board, address, report) {
1438
1458
  try { renameSync(path, join(box, 'cur', name)); } catch { continue; } // another reader took it
1439
1459
  // an answer to a question the board asked is what a card waits on (read by the card's workflow), not a verb
1440
1460
  if (envelope.in_reply_to) continue;
1461
+ if (envelope.from === `sc:${localMachine()}:operator:teams-events` && json(envelope.body, {})?.machine_event) { report.mailed.push([envelope.from, 'machine_event']); continue; }
1441
1462
  const argv = json(envelope.body, {})?.workflow;
1442
1463
  let answer;
1443
1464
  if (!Array.isArray(argv) || !argv.length || NOT_BY_MAIL.has(argv[0])) answer = `not applied: a mailed card update is one \`supercode workflow\` verb other than ${[...NOT_BY_MAIL].join(', ')}`;
@@ -1548,6 +1569,10 @@ export async function tick({ root, lanes, settings, only = null, slugs = null, d
1548
1569
  // where the tick's time goes, phase by phase (ms), printed by `serve --verbose` and in `--json`
1549
1570
  let mark = Date.now();
1550
1571
  const phase = (name) => { const t = Date.now(); report.ms[name] = t - mark; mark = t; };
1572
+ if (!fleet.machineEvents?.subscriptions?.includes(boardAddress(board.label))) {
1573
+ const subscription = sc(['teams','machines','subscribe',boardAddress(board.label),'--json']);
1574
+ if (subscription.code !== 0) report.failed.push(['machine_events',(subscription.stderr || subscription.stdout).trim().slice(-200)]);
1575
+ }
1551
1576
  readMailbox(board, boardAddress(board.label), report); phase('mailbox');
1552
1577
  await refreshRelationAnswers(board); phase('answers');
1553
1578
  const effects = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-orchestrator",
3
- "version": "0.4.117",
3
+ "version": "0.4.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": {