@volter/supercode-orchestrator 0.4.46 → 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.
- package/board/dispatch.mjs +29 -8
- package/board/workflow.mjs +14 -1
- package/package.json +1 -1
package/board/dispatch.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
|
@@ -419,7 +422,11 @@ function closeRequested(board, report) {
|
|
|
419
422
|
const session = json(row.metadata, {})?.supercode;
|
|
420
423
|
if (!session?.pane) continue;
|
|
421
424
|
try { stopSession(board, session); } catch (error) { report.undelivered.push([row.task_id, session.address, `closing it: ${String(error.message).slice(-200)}`]); continue; }
|
|
422
|
-
|
|
425
|
+
const address = session.address ?? `sc:${session.machine}:claude-code:${session.session_id}`;
|
|
426
|
+
tx(board, (db) => {
|
|
427
|
+
setRunSession(db, row.id, { ...session, closed_at: now() });
|
|
428
|
+
db.prepare("DELETE FROM kanban_notify_subs WHERE task_id = ? AND platform = 'supercode' AND chat_id = ?").run(row.task_id, address);
|
|
429
|
+
});
|
|
423
430
|
}
|
|
424
431
|
}
|
|
425
432
|
|
|
@@ -473,7 +480,12 @@ export async function sessionEffect(board, e) {
|
|
|
473
480
|
const caller = process.env.CLAUDE_CODE_SESSION_ID || process.env.CODEX_THREAD_ID;
|
|
474
481
|
if (caller && caller === session.session_id) { tx(board, (db) => setRunSession(db, row.id, { ...session, close_requested: now() })); return; }
|
|
475
482
|
stopSession(board, session);
|
|
476
|
-
|
|
483
|
+
// a closed session hears nothing more of the card
|
|
484
|
+
const address = session.address ?? `sc:${session.machine}:claude-code:${session.session_id}`;
|
|
485
|
+
tx(board, (db) => {
|
|
486
|
+
setRunSession(db, row.id, { ...session, closed_at: now() });
|
|
487
|
+
db.prepare("DELETE FROM kanban_notify_subs WHERE task_id = ? AND platform = 'supercode' AND chat_id = ?").run(e.id, address);
|
|
488
|
+
});
|
|
477
489
|
return;
|
|
478
490
|
}
|
|
479
491
|
}
|
|
@@ -569,7 +581,8 @@ function launchPane(board, task, runId, lane, fleet, { input = null, slot = 'wor
|
|
|
569
581
|
const prev = tx(board, (db) => lastSession(db, task.id, slot));
|
|
570
582
|
let session = null;
|
|
571
583
|
let replaced = null;
|
|
572
|
-
if (prev
|
|
584
|
+
if (prev?.forgotten) replaced = { session_id: prev.session_id, why: 'the workflow let it go' };
|
|
585
|
+
else if (prev && prev.machine === machine) {
|
|
573
586
|
if (sessionAlive(fleet, prev)) {
|
|
574
587
|
// the session is kept; the heartbeat's count belongs to the run that ended, so the new run starts it again
|
|
575
588
|
const { missing_since: _m, goal_turns: _t, goal_nudged_at: _n, ...kept } = prev;
|
|
@@ -588,6 +601,13 @@ function launchPane(board, task, runId, lane, fleet, { input = null, slot = 'wor
|
|
|
588
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) };
|
|
589
602
|
} else replaced = { session_id: prev.session_id, why: 'its resume never came up' };
|
|
590
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
|
+
}
|
|
591
611
|
if (!session) {
|
|
592
612
|
const sid = randomUUID();
|
|
593
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) });
|
|
@@ -798,7 +818,8 @@ async function notify(board, fleet, report, entry) {
|
|
|
798
818
|
const events = board.db.prepare(`SELECT id, kind, payload, created_at FROM task_events WHERE task_id = ? AND id > ? AND kind IN (${kinds.map(() => '?').join(', ')}) ORDER BY id`)
|
|
799
819
|
.all(sub.task_id, sub.last_event_id, ...kinds);
|
|
800
820
|
if (!events.length) return;
|
|
801
|
-
|
|
821
|
+
// the card as the workflow names it (its status the workflow's: triaging, reviewing, stopped; `stored_as` kanban.db's)
|
|
822
|
+
const task = tx(board, (db) => { const t = getTask(db, sub.task_id); return t ? { ...t, status: statusOf(db, t), stored_as: t.status } : t; });
|
|
802
823
|
let cursor = Number(sub.last_event_id);
|
|
803
824
|
let failed = null;
|
|
804
825
|
try {
|
package/board/workflow.mjs
CHANGED
|
@@ -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
|
|
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.
|
|
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": {
|