@volter/supercode-orchestrator 0.5.193 → 0.5.195
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/capacity.mjs +23 -9
- package/board/dispatch.mjs +39 -12
- package/ontology.d.ts +2 -0
- package/package.json +2 -2
package/board/capacity.mjs
CHANGED
|
@@ -16,6 +16,13 @@ const GROUP = {
|
|
|
16
16
|
/** The fleet statuses of a session in a turn (`waiting`: a turn stopped on a question it asked). */
|
|
17
17
|
const IN_TURN = new Set(['busy', 'waiting']);
|
|
18
18
|
|
|
19
|
+
// A kept dispatcher session is recorded as adopt too, but retains its launch key. An unissued claim reserves a
|
|
20
|
+
// place; a deferred launch has not started a session and waits for admission on its retry.
|
|
21
|
+
export const dispatched = (s) => Boolean(s?.launch_key || s?.surface === 'headless' || ['fresh', 'resume'].includes(s?.mode) || (!s?.mode && !s?.launch_deferred));
|
|
22
|
+
|
|
23
|
+
// Headless workers execute on the board host; only panes execute on their named target.
|
|
24
|
+
export const executionHost = (lane, target, home, normalise) => lane?.surface === 'pane' ? normalise(target ?? home) : home;
|
|
25
|
+
|
|
19
26
|
/** Whether `thing` (a counted card or session, or a card being started) is one the filter is about. */
|
|
20
27
|
export function matches(filter, thing) {
|
|
21
28
|
for (const [key, names] of Object.entries(filter ?? {})) {
|
|
@@ -31,10 +38,10 @@ const applies = (cap, thing) => matches(cap.where, thing) && !(Object.keys(cap.u
|
|
|
31
38
|
/** Each slot's latest recorded session on a card (implementer `worker`, `reviewer`, …), newest run first. */
|
|
32
39
|
function sessionsOf(db, taskId) {
|
|
33
40
|
const out = new Map();
|
|
34
|
-
for (const row of db.prepare('SELECT metadata FROM task_runs WHERE task_id = ? AND metadata IS NOT NULL ORDER BY id DESC').all(taskId)) {
|
|
41
|
+
for (const row of db.prepare('SELECT id, metadata FROM task_runs WHERE task_id = ? AND metadata IS NOT NULL ORDER BY id DESC').all(taskId)) {
|
|
35
42
|
const s = json(row.metadata, {})?.supercode;
|
|
36
43
|
const slot = s?.slot ?? 'worker';
|
|
37
|
-
if (s?.session_id && !out.has(slot)) out.set(slot, s);
|
|
44
|
+
if (s?.session_id && !out.has(slot)) out.set(slot, { ...s, run: row.id });
|
|
38
45
|
}
|
|
39
46
|
return out;
|
|
40
47
|
}
|
|
@@ -46,7 +53,7 @@ function sessionsOf(db, taskId) {
|
|
|
46
53
|
* own transcript reports it runs, or null where none can be read; `machineOf` normalises a machine name; `lanes` gives
|
|
47
54
|
* a profile's harness and model.
|
|
48
55
|
*/
|
|
49
|
-
export function holdings(board, { lanes, fleetOk, alive, liveRecord, modelOf = () => null, storedOf = null, machineOf, home = null }) {
|
|
56
|
+
export function holdings(board, { lanes, fleetOk, alive, liveRecord, modelOf = () => null, storedOf = null, machineOf, home = null, runLaneOf = null }) {
|
|
50
57
|
const out = [];
|
|
51
58
|
const rows = board.db.prepare("SELECT t.id, t.status, t.assignee, t.current_run_id, t.model_override, c.machine, c.subtask_of FROM tasks t LEFT JOIN supercode_cards c ON c.task_id = t.id WHERE t.status != 'archived'").all();
|
|
52
59
|
for (const row of rows) {
|
|
@@ -64,14 +71,16 @@ export function holdings(board, { lanes, fleetOk, alive, liveRecord, modelOf = (
|
|
|
64
71
|
const sessionOf = (s, slot) => {
|
|
65
72
|
const configured = s.model ?? card.model;
|
|
66
73
|
return { ...card, counts: 'sessions', slot, session: s.session_id ?? null, unconfigured: !configured,
|
|
67
|
-
|
|
74
|
+
dispatch: dispatched(s), run: s.run ?? null,
|
|
75
|
+
machine: s.surface === 'headless' ? home : s.machine || s.surface ? machineOf(s.machine ?? row.machine ?? home)
|
|
76
|
+
: executionHost(runLaneOf ? runLaneOf(row) : lane, row.machine, home, machineOf), harness: s.harness ?? card.harness ?? 'claude-code',
|
|
68
77
|
model: (s.session_id ? modelOf(s) : null) ?? configured };
|
|
69
78
|
};
|
|
70
79
|
const sessions = fleetOk ? sessionsOf(board.db, row.id) : new Map();
|
|
71
80
|
const held = [];
|
|
72
81
|
let current = null;
|
|
73
82
|
if (row.current_run_id) {
|
|
74
|
-
current = json(board.db.prepare('SELECT metadata FROM task_runs WHERE id = ?').get(row.current_run_id)?.metadata, {})?.supercode
|
|
83
|
+
current = { ...json(board.db.prepare('SELECT metadata FROM task_runs WHERE id = ?').get(row.current_run_id)?.metadata, {})?.supercode, run: row.current_run_id };
|
|
75
84
|
out.push({ ...sessionOf(current, current.slot ?? 'worker'), session: undefined, slot: undefined, counts: 'runs' });
|
|
76
85
|
}
|
|
77
86
|
for (const [slot, s] of sessions) {
|
|
@@ -114,6 +123,7 @@ export function harnessDefaults(held) {
|
|
|
114
123
|
*/
|
|
115
124
|
export function capacity(caps, held, evaluate) {
|
|
116
125
|
const list = (caps ?? []).map((cap) => ({ ...cap, where: cap.where ?? {} }));
|
|
126
|
+
const trackDispatcherSlots = list.some(cap => cap.counts === 'sessions' && cap.where.dispatch === true);
|
|
117
127
|
// shared by every board of a tick, so what one board starts counts on the next
|
|
118
128
|
const things = held;
|
|
119
129
|
const counted = (cap, group) => things.filter((x) => x.counts === cap.counts && applies(cap, x) && GROUP[cap.scope](x) === group);
|
|
@@ -138,7 +148,8 @@ export function capacity(caps, held, evaluate) {
|
|
|
138
148
|
* start. `scope` evaluates each max; `waiting` is every card still to be planned this tick, for reserves (a waiting card
|
|
139
149
|
* the cap counts already takes no new place, so none is held for it). A card
|
|
140
150
|
* that already holds a place in a count (its implementer's live session while its review starts) keeps it: that
|
|
141
|
-
* cap does not hold it.
|
|
151
|
+
* cap does not hold it. A dispatcher-only sessions cap keeps a place only for the same slot: review is another
|
|
152
|
+
* session, so it must have room.
|
|
142
153
|
*/
|
|
143
154
|
admit(entry, scope, waiting = []) {
|
|
144
155
|
for (const cap of list) {
|
|
@@ -147,7 +158,8 @@ export function capacity(caps, held, evaluate) {
|
|
|
147
158
|
if (max === null || max === undefined || max === '') continue;
|
|
148
159
|
const group = GROUP[cap.scope](entry);
|
|
149
160
|
const now = counted(cap, group);
|
|
150
|
-
|
|
161
|
+
const bySlot = cap.counts === 'sessions' && cap.where.dispatch === true;
|
|
162
|
+
if (now.some((x) => x.card === entry.card && (!bySlot || x.slot === entry.slot))) continue;
|
|
151
163
|
const reserved = cap.reserve && !matches(cap.reserve.for, entry) && waiting.some((w) => w.card !== entry.card && matches(cap.reserve.for, w) && GROUP[cap.scope](w) === group && !now.some((x) => x.card === w.card))
|
|
152
164
|
? Number(evaluate(cap.reserve.slots, scope) ?? 0) : 0;
|
|
153
165
|
if (now.length >= Number(max) - reserved) {
|
|
@@ -160,11 +172,13 @@ export function capacity(caps, held, evaluate) {
|
|
|
160
172
|
},
|
|
161
173
|
/**
|
|
162
174
|
* Count `entry` as started: its run, its card, its session (in a turn from its first prompt) and its start. What the card
|
|
163
|
-
* already holds is not counted again: the run takes the place its card holds.
|
|
175
|
+
* already holds is not counted again: the run takes the place its card holds. Dispatcher sessions are retained
|
|
176
|
+
* separately by slot so two reviews started in one round cannot share one place.
|
|
164
177
|
*/
|
|
165
178
|
take(entry) {
|
|
166
179
|
for (const counts of ['runs', 'cards', 'sessions', 'turns', 'starts']) {
|
|
167
|
-
const had = things.find((x) => x.card === entry.card && x.counts === counts
|
|
180
|
+
const had = things.find((x) => x.card === entry.card && x.counts === counts &&
|
|
181
|
+
(!trackDispatcherSlots || !entry.dispatch || !['sessions', 'turns'].includes(counts) || (x.dispatch && x.slot === entry.slot)));
|
|
168
182
|
// the card is counted already: it is now in the status its start moved it to
|
|
169
183
|
// the start is the card's next run, in its assignee's harness and model now (a card reassigned from Codex to
|
|
170
184
|
// Claude Code counts against Claude Code's caps from this start, not as the harness its last session ran)
|
package/board/dispatch.mjs
CHANGED
|
@@ -44,7 +44,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
44
44
|
import { view, boardCopyOf, boardPath, briefPath, claimer, claimHolds, copyRefusal, event, json, listBoards, logsDir, newClaim, now, openBoard, pendingLaunches, sessionBound, setActor, storageFault, tx } from './store.mjs';
|
|
45
45
|
import { doors, fire, loadWorkflow, resumeOf, runSpec, runWorkingSeconds, requestSessionClose, retireSupersededRuns, refreshCardInputs, refreshRelationAnswers, transitionsFor, scopeOf, statusOf, projectStatus, storedStatus, templateValue, workflowFor } from './workflow.mjs';
|
|
46
46
|
import { checkWorkspace, assertWorkspaceOwner, notePresence, resolveWorkspace } from './workspace.mjs';
|
|
47
|
-
import { capacity, harnessDefaults, holdings } from './capacity.mjs';
|
|
47
|
+
import { capacity, dispatched, executionHost, harnessDefaults, holdings } from './capacity.mjs';
|
|
48
48
|
import { evaluate, holds, render } from './expr.mjs';
|
|
49
49
|
import { identifyNotice } from './notices.mjs';
|
|
50
50
|
import { pendingRequestMail } from './request-mail.mjs';
|
|
@@ -391,6 +391,13 @@ export function lanesOf(profiles = {}) {
|
|
|
391
391
|
return out;
|
|
392
392
|
}
|
|
393
393
|
|
|
394
|
+
/** The current run's role can select a different execution surface from the card's assignee. */
|
|
395
|
+
function runLane(db, board, entry, task, lanes) {
|
|
396
|
+
const spec = runSpec(entry, statusOf(db, task), task);
|
|
397
|
+
const profile = spec ? evaluate(entry.workflow.roles?.[spec.role] ?? 'card.assignee', scopeOf(db, board, task, { params: entry.params })) : task.assignee;
|
|
398
|
+
return lanes[profile] ?? {};
|
|
399
|
+
}
|
|
400
|
+
|
|
394
401
|
// The latest presence reading (observeFleet). A machine that did not answer it (offline, or unread this round) is not
|
|
395
402
|
// asked again until the next reading: each remote call would wait out its timeout, and one silent machine held every
|
|
396
403
|
// close and launch on this one. Its door answers `machine_offline`, which every caller tries again next round.
|
|
@@ -754,7 +761,8 @@ async function homeHoldings(root, slugs, lanes, fleet, entry) {
|
|
|
754
761
|
// each run's session read before it is counted live or not (unknown stays unknown)
|
|
755
762
|
for (const r of b.db.prepare('SELECT r.metadata FROM tasks t JOIN task_runs r ON r.id = t.current_run_id').all()) await readBefore(fleet, json(r.metadata, {})?.supercode);
|
|
756
763
|
const storedOf = (id) => storedStatus(b.db, b, entry, id).stored;
|
|
757
|
-
out.push(...holdings(b, { lanes, fleetOk: fleet.ok, alive: (s) => sessionAlive(fleet, s), liveRecord: live, modelOf: transcriptModel, storedOf, machineOf: normalMachine, home: localMachine()
|
|
764
|
+
out.push(...holdings(b, { lanes, fleetOk: fleet.ok, alive: (s) => sessionAlive(fleet, s), liveRecord: live, modelOf: transcriptModel, storedOf, machineOf: normalMachine, home: localMachine(),
|
|
765
|
+
runLaneOf: row => runLane(b.db, b, entry, getTask(b.db, row.id), lanes) }));
|
|
758
766
|
}
|
|
759
767
|
finally { b.close(); }
|
|
760
768
|
} catch { /* its own tick reports it */ }
|
|
@@ -851,7 +859,7 @@ async function startCards(board, fleet, lanes, settings, report, entry, effects,
|
|
|
851
859
|
if (!startable.has(state.status)) { waiting(task, `no ${d.start} transition from ${state.status}`); continue; }
|
|
852
860
|
const plan = planOf(task, scopeFor(task));
|
|
853
861
|
if (!plan) { waiting(task, `start guard refused: ${transitionsFor(workflow, state.status, d.start).map(t => t.guard ?? 'sender role').join(' | ')}`); continue; }
|
|
854
|
-
const scope = scopeFor(task, { lane: plan.lane ?? {}, run: { surface: plan.lane?.surface ?? null, local: plan.lane
|
|
862
|
+
const scope = scopeFor(task, { lane: plan.lane ?? {}, run: { surface: plan.lane?.surface ?? null, local: executionHost(plan.lane, task.machine, localMachine(), normalMachine) === localMachine() } });
|
|
855
863
|
if (!holds(d.eligible ?? null, scope)) { waiting(task, `dispatch eligibility refused (memory ${pressure}, local starts ${tickState.local_started}): ${d.eligible}`); continue; }
|
|
856
864
|
candidates.push({ task, plan, scope });
|
|
857
865
|
}
|
|
@@ -867,7 +875,8 @@ async function startCards(board, fleet, lanes, settings, report, entry, effects,
|
|
|
867
875
|
});
|
|
868
876
|
// the caps, over what the home holds now; what each candidate would be once started
|
|
869
877
|
const caps = capacity(d.capacity, held, evaluate);
|
|
870
|
-
const entryOf = (c) => ({ card: c.task.id, board: board.slug, machine:
|
|
878
|
+
const entryOf = (c) => ({ card: c.task.id, board: board.slug, machine: executionHost(c.plan.lane, c.task.machine, localMachine(), normalMachine), assignee: c.plan.profile ?? null,
|
|
879
|
+
dispatch: true, slot: String(templateValue(c.plan.runSpec?.session ?? 'worker', c.scope) ?? 'worker'),
|
|
871
880
|
status: c.plan.target, stored: c.plan.target, model: c.task.model_override ?? c.plan.lane?.model ?? held.defaults?.[c.plan.lane?.harness] ?? null, harness: c.plan.lane?.harness ?? null });
|
|
872
881
|
const waitingEntries = candidates.map(entryOf);
|
|
873
882
|
// a dry run (dispatch --dry-run) plans the same way and writes nothing: no notes, no moves, no launches
|
|
@@ -882,7 +891,7 @@ async function startCards(board, fleet, lanes, settings, report, entry, effects,
|
|
|
882
891
|
if (!why) why = launcherCheck(board.db, task, plan.lane, fleet);
|
|
883
892
|
if (why) { note(task.id, why); report.queued.push([task.id, why]); continue; }
|
|
884
893
|
if (dry) {
|
|
885
|
-
report.would.push({ id: task.id, status: plan.target, role: plan.runSpec?.role ?? null, assignee: plan.profile, machine:
|
|
894
|
+
report.would.push({ id: task.id, status: plan.target, role: plan.runSpec?.role ?? null, assignee: plan.profile, machine: waitingEntries[i].machine, surface: plan.lane.surface });
|
|
886
895
|
caps.take(waitingEntries[i]);
|
|
887
896
|
continue;
|
|
888
897
|
}
|
|
@@ -895,9 +904,13 @@ async function startCards(board, fleet, lanes, settings, report, entry, effects,
|
|
|
895
904
|
// still waits where it did and still fits.
|
|
896
905
|
const now = getTask(db, task.id);
|
|
897
906
|
if (!now || now.current_run_id || now.archived || statusOf(db, now) !== scope.card.status) throw Object.assign(new Error('started or moved by another round'), { claimedElsewhere: true });
|
|
898
|
-
for (const row of db.prepare('SELECT t.id, t.assignee, t.model_override, c.machine FROM tasks t LEFT JOIN supercode_cards c ON c.task_id = t.id WHERE t.current_run_id IS NOT NULL').all()) {
|
|
907
|
+
for (const row of db.prepare('SELECT t.id, t.assignee, t.model_override, c.machine, r.metadata FROM tasks t LEFT JOIN supercode_cards c ON c.task_id = t.id JOIN task_runs r ON r.id=t.current_run_id WHERE t.current_run_id IS NOT NULL').all()) {
|
|
899
908
|
const lane = lanes[row.assignee] ?? {}, status = statusOf(db, row);
|
|
900
|
-
|
|
909
|
+
const session = json(row.metadata, {})?.supercode ?? {};
|
|
910
|
+
const host = session.surface === 'headless' ? localMachine() : session.machine || session.surface ? normalMachine(session.machine ?? row.machine ?? localMachine())
|
|
911
|
+
: executionHost(runLane(db, board, entry, getTask(db, row.id), lanes), row.machine, localMachine(), normalMachine);
|
|
912
|
+
caps.take({ card: row.id, board: board.slug, machine: host, assignee: row.assignee ?? null, status, stored: status, model: row.model_override ?? lane.model ?? null, harness: lane.harness ?? null,
|
|
913
|
+
dispatch: dispatched(session), slot: session.slot ?? 'worker' });
|
|
901
914
|
}
|
|
902
915
|
const refused = caps.admit(waitingEntries[i], scope, waitingEntries);
|
|
903
916
|
if (refused) throw Object.assign(new Error(refused.why), { claimedElsewhere: true });
|
|
@@ -921,7 +934,7 @@ async function startCards(board, fleet, lanes, settings, report, entry, effects,
|
|
|
921
934
|
try {
|
|
922
935
|
const session = await launchRun(board, entry, task.id, plan.lane, fleet, settings);
|
|
923
936
|
if (session) {
|
|
924
|
-
if (
|
|
937
|
+
if (normalMachine(session.machine ?? localMachine()) === localMachine()) tickState.local_started += 1;
|
|
925
938
|
report.started.push([task.id, session.address ?? `pid ${session.pid}`, session.mode]);
|
|
926
939
|
}
|
|
927
940
|
} catch (error) {
|
|
@@ -1001,6 +1014,20 @@ async function launchAttempt(board, entry, id, lane, fleet, settings, runId) {
|
|
|
1001
1014
|
// the run's session record knows its status, role and slot (a reviewer's slot is never the implementer's); the slot
|
|
1002
1015
|
// is a template, so a step can ask for isolation by naming a new one
|
|
1003
1016
|
const slot = String(templateValue(spec.session ?? 'worker', scope) ?? 'worker');
|
|
1017
|
+
// Explicit starts and deferred/resumed launches reach this path without startCards. Recheck the dispatcher-session
|
|
1018
|
+
// caps here too, excluding this attempt's own reservation. Other cap kinds keep their existing admission rules.
|
|
1019
|
+
const sessionCaps = entry.workflow.dispatch.capacity?.filter(cap => cap.counts === 'sessions' && cap.where?.dispatch === true) ?? [];
|
|
1020
|
+
if (sessionCaps.length) {
|
|
1021
|
+
const held = await homeHoldings(board.root, listBoards(board.root), lanesOf(entry.profiles), fleet, entry);
|
|
1022
|
+
const others = held.filter(x => !(x.board === board.slug && x.run === runId));
|
|
1023
|
+
const refused = capacity(sessionCaps, others, evaluate).admit({ card: id, board: board.slug, machine: executionHost(lane, task.machine, localMachine(), normalMachine),
|
|
1024
|
+
assignee: lane.name ?? task.assignee, status, stored: scope.card.stored ?? status, model: task.model_override ?? lane.model ?? null, harness: lane.harness,
|
|
1025
|
+
dispatch: true, slot }, { ...scope, lane });
|
|
1026
|
+
if (refused) {
|
|
1027
|
+
runTx(board, id, runId, db => setRunSession(db, runId, { status, role: spec.role, slot, launched_at: now(), launch_deferred: true, launch_deferred_error: refused.why }));
|
|
1028
|
+
throw Object.assign(new Error(refused.why), { launchPending: true });
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1004
1031
|
if (spec.role === 'reviewer') {
|
|
1005
1032
|
const worker = view(board, (db) => lastSession(db, id, 'worker'));
|
|
1006
1033
|
const reviewer = view(board, (db) => lastSession(db, id, slot));
|
|
@@ -1024,7 +1051,7 @@ async function launchAttempt(board, entry, id, lane, fleet, settings, runId) {
|
|
|
1024
1051
|
prompt=`${agentPreamble(agent,{previous:previous.map(item=>({session:item.session,machine:item.machine,ended_at:item.ended_at,end_reason:item.end_reason}))})}\n\n${prompt}`;
|
|
1025
1052
|
Object.assign(meta,{agent_id:agent.identity.id,agent_org:team.org,agent_context:context??null,
|
|
1026
1053
|
agent_profile_intent:structuredClone(agent.intended.resolved),
|
|
1027
|
-
agent_env:agentEnvironment(agent,{root:board.root,context:
|
|
1054
|
+
agent_env:agentEnvironment(agent,{root:board.root,context:executionHost(lane,task.machine,localMachine(),normalMachine)===localMachine()?localReaderContext(team.org,agent.identity.id)??undefined:undefined})});
|
|
1028
1055
|
// The agent's own memory reaches every session of it: headless runs open its home; a pane, which may sit on
|
|
1029
1056
|
// another machine, gets the home's snapshot in its run's brief. A shared profile's memory is never used.
|
|
1030
1057
|
const agentHome=materializeAgentHome(board.root,team.org,agent.identity.id,lane);
|
|
@@ -1699,7 +1726,7 @@ export async function mergeReviewed(board, e) {
|
|
|
1699
1726
|
function launcherCheck(db, task, lane, fleet) {
|
|
1700
1727
|
if (lane.surface !== 'pane') return null;
|
|
1701
1728
|
if (!task.machine) return 'the card names no machine; a pane session runs on the machine its card names';
|
|
1702
|
-
const machine =
|
|
1729
|
+
const machine = executionHost(lane, task.machine, localMachine(), normalMachine);
|
|
1703
1730
|
if (!fleet.ok) return `supercode could not read the fleet: ${fleet.error}`;
|
|
1704
1731
|
if (fleet.unknown?.has(machine)) return `machine ${machine}'s door did not answer; the card starts when it does`;
|
|
1705
1732
|
// a machine is scheduled only when it can take a board launch: its board process guard runs this harness, and its
|
|
@@ -1991,7 +2018,7 @@ async function reconcileLaunches(board, fleet, report, entry, effects, settings,
|
|
|
1991
2018
|
}
|
|
1992
2019
|
|
|
1993
2020
|
async function launchPane(board, task, runId, lane, fleet, { input = null, slot = 'worker', meta = {} } = {}) {
|
|
1994
|
-
const machine =
|
|
2021
|
+
const machine = executionHost(lane, task.machine, localMachine(), normalMachine);
|
|
1995
2022
|
// Whether a board session opens a terminal window on its machine is the workflow's choice, stated on every launch;
|
|
1996
2023
|
// no machine default decides it.
|
|
1997
2024
|
const window = workflowFor(board.root).params.session_window;
|
|
@@ -2247,7 +2274,7 @@ async function launchHeadless(board, task, runId, lane, settings, { prompt = nul
|
|
|
2247
2274
|
},
|
|
2248
2275
|
});
|
|
2249
2276
|
child.unref();
|
|
2250
|
-
const session = { surface: 'headless', pid: child.pid, machine: localMachine(), mode: 'fresh', launched_at: now(), exit_file: exitFile, launch_secret: createHash('sha256').update(launchSecret).digest('hex'), ...meta };
|
|
2277
|
+
const session = { surface: 'headless', pid: child.pid, machine: executionHost(lane, task.machine, localMachine(), normalMachine), mode: 'fresh', launched_at: now(), exit_file: exitFile, launch_secret: createHash('sha256').update(launchSecret).digest('hex'), ...meta };
|
|
2251
2278
|
// recorded against this run only while it is still the card's current one; a run ended meanwhile gets no worker
|
|
2252
2279
|
const recorded = tx(board, (db) => {
|
|
2253
2280
|
if (!runCurrent(db, task.id, runId)) return false;
|
package/ontology.d.ts
CHANGED
|
@@ -178,6 +178,8 @@ export type CapCount =
|
|
|
178
178
|
|
|
179
179
|
/** Which cards and sessions a cap is about: each key given narrows it; a key's names are alternatives. */
|
|
180
180
|
export interface CapFilter {
|
|
181
|
+
/** Whether the session was started or resumed by the dispatcher, rather than attached by hand. */
|
|
182
|
+
dispatch?: boolean | null;
|
|
181
183
|
/** The card itself, by id. */
|
|
182
184
|
card?: Names;
|
|
183
185
|
/** The model a session runs (the card's model, else its profile's). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-orchestrator",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.195",
|
|
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": {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@volter/supercode-terminal": "^0.3.8",
|
|
32
32
|
"@volter/supercode-harness-sdk": "^0.3.101",
|
|
33
|
-
"@volter/teams": "0.2.
|
|
33
|
+
"@volter/teams": "0.2.90",
|
|
34
34
|
"@volter/ztrack": "^2.4.23",
|
|
35
35
|
"yaml": "^2.9.0"
|
|
36
36
|
},
|