@volter/supercode-orchestrator 0.4.47 → 0.4.48

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.
@@ -369,7 +369,8 @@ function startCards(board, fleet, lanes, settings, report, entry, effects, { onl
369
369
  report.started.push([task.id, session.address ?? `pid ${session.pid}`, session.mode]);
370
370
  }
371
371
  } catch (error) {
372
- try { const r = tx(board, (db) => fire(db, board, entry, task.id, 'spawn_failed', { roles: ['platform'], payload: { error: String(error.message).slice(-500) } })); effects.push(...r.effects); } catch { /* no transition for it */ }
372
+ const payload = { error: String(error.message).slice(-500), ...(error.resumeFailed ? { resume_failed: true, ...error.resumeFailed } : {}) };
373
+ try { const r = tx(board, (db) => fire(db, board, entry, task.id, 'spawn_failed', { roles: ['platform'], payload })); effects.push(...r.effects); } catch { /* no transition for it */ }
373
374
  report.failed.push([task.id, String(error.message).slice(-300)]);
374
375
  }
375
376
  }
@@ -388,10 +389,12 @@ export function launchRun(board, entry, id, lane, fleet, settings) {
388
389
  // a subtask's run also sees its arc: the arc's text, tasks and thread (where its owner's rulings are)
389
390
  const arcContext = task.subtask_of ? tx(board, (db) => cardContext(db, task.subtask_of)) : '';
390
391
  const prompt = render(spec.prompt, { ...scope, where, context: tx(board, (db) => cardContext(db, id)), arc_context: arcContext, doors: doors(entry.workflow, status, spec.role, id, where, surface) });
391
- // the run's session record knows its status, role and slot (a reviewer's slot is never the implementer's)
392
- const meta = { status, role: spec.role, slot: spec.session };
392
+ // the run's session record knows its status, role and slot (a reviewer's slot is never the implementer's); the slot
393
+ // is a template, so a step can ask for isolation by naming a new one
394
+ const slot = String(templateValue(spec.session ?? 'worker', scope) ?? 'worker');
395
+ const meta = { status, role: spec.role, slot };
393
396
  return surface === 'pane'
394
- ? launchPane(board, task, runId, lane, fleet, { input: prompt, slot: spec.session, meta })
397
+ ? launchPane(board, task, runId, lane, fleet, { input: prompt, slot, meta })
395
398
  : launchHeadless(board, task, runId, lane, settings, { prompt, skills: spec.skills ?? [], meta });
396
399
  }
397
400
 
@@ -578,7 +581,8 @@ function launchPane(board, task, runId, lane, fleet, { input = null, slot = 'wor
578
581
  const prev = tx(board, (db) => lastSession(db, task.id, slot));
579
582
  let session = null;
580
583
  let replaced = null;
581
- if (prev && prev.machine === machine) {
584
+ if (prev?.forgotten) replaced = { session_id: prev.session_id, why: 'the workflow let it go' };
585
+ else if (prev && prev.machine === machine) {
582
586
  if (sessionAlive(fleet, prev)) {
583
587
  // the session is kept; the heartbeat's count belongs to the run that ended, so the new run starts it again
584
588
  const { missing_since: _m, goal_turns: _t, goal_nudged_at: _n, ...kept } = prev;
@@ -597,6 +601,13 @@ function launchPane(board, task, runId, lane, fleet, { input = null, slot = 'wor
597
601
  else replaced = { session_id: prev.session_id, why: r.code === 0 ? `its resume ended as soon as it started (pane ${pane})` : (r.stderr || r.stdout).trim().slice(-300) };
598
602
  } else replaced = { session_id: prev.session_id, why: 'its resume never came up' };
599
603
  } else if (prev) replaced = { session_id: prev.session_id, why: `the card moved from ${prev.machine} to ${machine}` };
604
+ // a card keeps its context: a slot's session that cannot be kept or resumed stops the launch, and only a session the
605
+ // workflow let go (`forget_session`) is replaced by a new one
606
+ if (replaced && !prev?.forgotten) {
607
+ const failed = new Error(`session ${replaced.session_id} (${slot}) could not be continued: ${replaced.why}. The board keeps a card's context and starts a new session only when the workflow lets this one go`);
608
+ failed.resumeFailed = { session_id: replaced.session_id, slot, why: replaced.why };
609
+ throw failed;
610
+ }
600
611
  if (!session) {
601
612
  const sid = randomUUID();
602
613
  const r = sc([...base, '--key', `board-${task.id}-r${runId}-${sid.slice(0, 8)}`, '--input', input ?? `Card ${task.id}: ${task.title}`, '--', '--session-id', sid, ...tail], { timeout: 180_000, as: boardAddress(board.label) });
@@ -414,9 +414,10 @@ const ACTIONS = {
414
414
  }
415
415
  const expires = t + ttl;
416
416
  const profile = evaluate(ctx.entry.workflow.roles?.[role] ?? 'card.assignee', scope) ?? task.assignee;
417
+ const slot = String(templateValue(ctx.entry.workflow.statuses[ctx.to]?.run?.session ?? 'worker', scope) ?? 'worker');
417
418
  run(ctx.db, 'UPDATE tasks SET claim_lock = ?, claim_expires = ?, started_at = COALESCE(started_at, ?) WHERE id = ?', lock, expires, t, ctx.id);
418
419
  const r = run(ctx.db, "INSERT INTO task_runs (task_id, profile, step_key, status, claim_lock, claim_expires, max_runtime_seconds, started_at, metadata) VALUES (?, ?, ?, 'running', ?, ?, ?, ?, ?)",
419
- ctx.id, profile, ctx.to, lock, expires, task.max_runtime_seconds, t, JSON.stringify({ supercode: { role, status: ctx.to, slot: ctx.entry.workflow.statuses[ctx.to]?.run?.session ?? 'worker' } }));
420
+ ctx.id, profile, ctx.to, lock, expires, task.max_runtime_seconds, t, JSON.stringify({ supercode: { role, status: ctx.to, slot } }));
420
421
  ctx.runId = Number(r.lastInsertRowid);
421
422
  run(ctx.db, 'UPDATE tasks SET current_run_id = ? WHERE id = ?', ctx.runId, ctx.id);
422
423
  record(ctx.db, ctx.id, 'claimed', { lock, expires, run_id: ctx.runId, source_status: resumeOf(ctx.entry.workflow, ctx.to) }, ctx.runId);
@@ -462,6 +463,18 @@ const ACTIONS = {
462
463
  cleanup_workspace(ctx) { ctx.effects.push({ kind: 'cleanup_workspace', id: ctx.id }); },
463
464
  stop_run(ctx) { ctx.effects.push({ kind: 'stop_run', id: ctx.id, runId: ctx.runId }); },
464
465
  close_session(ctx, slot) { ctx.effects.push({ kind: 'close_session', id: ctx.id, slot }); },
466
+ // the slot's last session is let go: its next run starts a new session (a card's context is otherwise kept: a
467
+ // session that cannot be resumed stops the card, it is never replaced on the board's own account)
468
+ forget_session(ctx, slot) {
469
+ const name = String(slot ?? 'worker');
470
+ for (const row of all(ctx.db, 'SELECT id, metadata FROM task_runs WHERE task_id = ? AND metadata IS NOT NULL ORDER BY id DESC', ctx.id)) {
471
+ const whole = json(row.metadata, {}) ?? {};
472
+ if (!whole.supercode?.session_id || (whole.supercode.slot ?? 'worker') !== name) continue;
473
+ whole.supercode = { ...whole.supercode, forgotten: now() };
474
+ run(ctx.db, 'UPDATE task_runs SET metadata = ? WHERE id = ?', JSON.stringify(whole), row.id);
475
+ return;
476
+ }
477
+ },
465
478
  send_to_actor(ctx, text) { ctx.effects.push({ kind: 'send_to_actor', id: ctx.id, text }); },
466
479
  };
467
480
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-orchestrator",
3
- "version": "0.4.47",
3
+ "version": "0.4.48",
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": {