@volter/supercode-orchestrator 0.5.27 → 0.5.29
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 +44 -6
- package/board/ztrack.mjs +14 -3
- package/package.json +1 -1
package/board/dispatch.mjs
CHANGED
|
@@ -1304,8 +1304,21 @@ function reconcileLaunches(board, fleet, report, entry, effects, settings, lanes
|
|
|
1304
1304
|
event(db,row.task_id,mismatch.differences.length?'agent_profile_drift':'agent_profile_recorded',{agent:session.agent_id,session:session.session_id,comparison:mismatch},row.id);
|
|
1305
1305
|
});
|
|
1306
1306
|
}
|
|
1307
|
+
const ref=`${session.harness}:${session.session_id}`;
|
|
1308
|
+
// An ended run whose session the agent store already holds, published, for another agent lost that claim when it
|
|
1309
|
+
// ran: no round can change an ended run or a published owner, so the disagreement is history. It is said once on
|
|
1310
|
+
// its card (agent_ownership_conflict) and the run is not recorded again. A live run is never history: it is
|
|
1311
|
+
// recorded below every round, and a conflict there is refused and said each round as before.
|
|
1312
|
+
if(row.ended_at){
|
|
1313
|
+
let owner=null;try{owner=agentRunForSession(board.root,[ref]);}catch{/* conflicting owners: recorded below, refused there */}
|
|
1314
|
+
if(owner?.published===1&&owner.org===session.agent_org&&owner.agent_id!==session.agent_id){
|
|
1315
|
+
const said=db=>db.prepare("SELECT 1 FROM task_events WHERE task_id=? AND run_id=? AND kind='agent_ownership_conflict' LIMIT 1").get(row.task_id,row.id);
|
|
1316
|
+
if(!view(board,said))tx(board,db=>{if(!said(db))event(db,row.task_id,'agent_ownership_conflict',{session:ref,run_agent:session.agent_id,owner:owner.agent_id,owner_run:owner.run_id},row.id);});
|
|
1317
|
+
continue;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1307
1320
|
// One run whose session another agent already owns is that card's problem, said on it; the round goes on for the rest.
|
|
1308
|
-
try{recordAgentRun(board.root,session.agent_org,session.agent_id,{session
|
|
1321
|
+
try{recordAgentRun(board.root,session.agent_org,session.agent_id,{session:ref,machine:session.machine,run:row.id});}
|
|
1309
1322
|
catch(error){report.queued.push([row.task_id,`run ${row.id}: ${error.message}`]);continue;}
|
|
1310
1323
|
orgs.set(session.agent_org,session.agent_context);
|
|
1311
1324
|
}
|
|
@@ -1719,6 +1732,22 @@ function localListing(who) {
|
|
|
1719
1732
|
return { address: `sc:${localMachine()}:${harness}:${sessionId}`, machine: localMachine(), harness, session_id: sessionId, pane, name: null };
|
|
1720
1733
|
}
|
|
1721
1734
|
|
|
1735
|
+
/** The verb an adoption with no current run fires: the workflow's own `adopt` where it declares one, else its dispatch start. */
|
|
1736
|
+
const adoptionVerb = (entry) => (entry.workflow.verbs?.adopt || !entry.workflow.run ? 'adopt' : entry.workflow.dispatch.start);
|
|
1737
|
+
|
|
1738
|
+
/**
|
|
1739
|
+
* The role an adopted session works on its card: a rebound run's own, else the role of the status the adoption's verb
|
|
1740
|
+
* moves the card to, as its claim names it. A card in review adopts its reviewer, never its implementer.
|
|
1741
|
+
*/
|
|
1742
|
+
function adoptedRole(db, board, entry, task, run) {
|
|
1743
|
+
const kept = run?.metadata?.supercode?.role;
|
|
1744
|
+
if (kept) return kept;
|
|
1745
|
+
const verb = adoptionVerb(entry), by = verb === 'adopt' ? 'manager' : 'platform';
|
|
1746
|
+
const status = statusOf(db, task), scope = scopeOf(db, board, task, { params: entry.params });
|
|
1747
|
+
const t = transitionsFor(entry.workflow, status, verb).find((x) => !x.by?.length || x.by.includes(by));
|
|
1748
|
+
return runSpec(entry, t?.target ? String(templateValue(t.target, scope)) : status, task)?.role ?? null;
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1722
1751
|
function adoptSession(board, id, who, { workspace = null, ttl, assignee = null, role = null, fleet = null, lanes = null } = {}) {
|
|
1723
1752
|
fleet ??= observeFleet();
|
|
1724
1753
|
if (!fleet.ok) throw new Error(`supercode could not read the fleet: ${fleet.error}`);
|
|
@@ -1745,12 +1774,12 @@ function adoptSession(board, id, who, { workspace = null, ttl, assignee = null,
|
|
|
1745
1774
|
// Rule 2: a session working a card role is that role's agent, however it started; adopted, it joins the agent as a
|
|
1746
1775
|
// launch does, and its recorded profile is compared with the role's intent (rule 4).
|
|
1747
1776
|
const entry0 = workflowFor(board.root), card0 = view(board, (db) => getTask(db, id));
|
|
1748
|
-
const
|
|
1749
|
-
const laneName = view(board, (db) => evaluate(entry0.workflow.roles?.[
|
|
1777
|
+
const agentRole = view(board, (db) => adoptedRole(db, board, entry0, card0, beforeRun)) ?? 'implementer';
|
|
1778
|
+
const laneName = view(board, (db) => evaluate(entry0.workflow.roles?.[agentRole] ?? 'card.assignee', scopeOf(db, board, card0, { params: entry0.params }))) ?? assignee ?? card0?.assignee;
|
|
1750
1779
|
const lane = laneName ? lanes?.[laneName] : null, context = process.env.SUPERCODE_CONTEXT, team = agentScope(context);
|
|
1751
1780
|
let agent = null;
|
|
1752
1781
|
if (lane && !team.offline) {
|
|
1753
|
-
try { agent = ensureCardAgent({ root: board.root, org: team.org, server: team.server, profiles: entry0.profiles, profile: lane.name, card: id, role:
|
|
1782
|
+
try { agent = ensureCardAgent({ root: board.root, org: team.org, server: team.server, profiles: entry0.profiles, profile: lane.name, card: id, role: agentRole, sponsor: card0?.sponsor ?? null, machine: normalMachine(host) === localMachine() ? null : host, context }); }
|
|
1754
1783
|
catch (error) { if (!teamsUnavailable(error)) throw error; }
|
|
1755
1784
|
}
|
|
1756
1785
|
// A session another agent already owns is refused before anything is recorded: the run would name one agent and the
|
|
@@ -1782,10 +1811,13 @@ function adoptSession(board, id, who, { workspace = null, ttl, assignee = null,
|
|
|
1782
1811
|
event(db, id, 'dispatch_released', { reason: 'verified adoption', session: found.address });
|
|
1783
1812
|
// the manager's adoption is the workflow's own `adopt` verb where it declares one, sent as the manager; else its
|
|
1784
1813
|
// dispatch start, which the platform sends for a verified adoption
|
|
1785
|
-
if (!beforeRun) { const verb = entry
|
|
1814
|
+
if (!beforeRun) { const verb = adoptionVerb(entry);
|
|
1786
1815
|
fire(db, board, entry, id, verb, { roles: [verb === 'adopt' ? 'manager' : 'platform'], payload: { lock: claimer() } }); }
|
|
1787
1816
|
const runId = getTask(db, id).current_run_id;
|
|
1788
1817
|
if (!runId) throw new Error(`${id} waits on parents that are not done`);
|
|
1818
|
+
// the agent joined above is the one of the run's own role; a run of another role would name the wrong agent
|
|
1819
|
+
const runRole = currentRun(db, id)?.metadata?.supercode?.role;
|
|
1820
|
+
if (agent && runRole && runRole !== agentRole) throw new Error(`${id}'s run is its ${runRole}'s, and ${found.address} was joined as its ${agentRole}; adopt again`);
|
|
1789
1821
|
const session = { surface: found.pane ? 'pane' : 'native', session_id: sessionId, harness, machine: host, pane: found.pane ?? null, address: found.address, mode: 'adopt', launched_at: now(), seen_at: now(),
|
|
1790
1822
|
...(agent ? { agent_id: agent.identity.id, agent_org: team.org, agent_context: context ?? null,
|
|
1791
1823
|
agent_profile_intent: { ...structuredClone(agent.intended.resolved), worker: { ...agent.intended.resolved?.worker, harness: lane.harness, model: lane.model ?? null, cwd: dir ?? task.workspace_path ?? null } } } : {}) };
|
|
@@ -2207,7 +2239,13 @@ function readMailbox(board, address, report) {
|
|
|
2207
2239
|
try { renameSync(path, join(box, 'cur', name)); } catch { continue; } // another reader took it
|
|
2208
2240
|
// an answer to a question the board asked is what a card waits on (read by the card's workflow), not a verb
|
|
2209
2241
|
if (envelope.in_reply_to) continue;
|
|
2210
|
-
|
|
2242
|
+
// A notice is news for the board, never a card update: a machine's event, or Claude Code's delivery notice about a
|
|
2243
|
+
// message the board sent (filed by the relay from `sc:<machine>:claude-code:notice`, which is no session). Its sender
|
|
2244
|
+
// asked for no reply (reply_via none), so it is filed as read and never answered.
|
|
2245
|
+
if (envelope.kind === 'notice' || envelope.reply_via?.mode === 'none') {
|
|
2246
|
+
report.mailed.push([envelope.from, envelope.from === `sc:${localMachine()}:operator:teams-events` ? 'machine_event' : 'notice']);
|
|
2247
|
+
continue;
|
|
2248
|
+
}
|
|
2211
2249
|
const argv = json(envelope.body, {})?.workflow;
|
|
2212
2250
|
let answer;
|
|
2213
2251
|
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(', ')}`;
|
package/board/ztrack.mjs
CHANGED
|
@@ -339,12 +339,19 @@ class ZtrackBoard {
|
|
|
339
339
|
}
|
|
340
340
|
|
|
341
341
|
// A document whose hash is neither the published one nor the one a pending publication replaces
|
|
342
|
-
// was edited by hand.
|
|
342
|
+
// was edited by hand. A write also reads again a document its index does not measure: spans whose lengths do not
|
|
343
|
+
// add up to the text cannot place an edit in it (a span's stored text changed outside the board, its length not).
|
|
343
344
|
staleDocuments({ pending }) {
|
|
344
345
|
const published = this.get('published', {});
|
|
345
346
|
const inFlight = pending ? this.get('pending_hashes', null) : null;
|
|
347
|
+
// the repair is said, so a span length written wrong stays visible
|
|
348
|
+
const measured = (doc) => {
|
|
349
|
+
const gap = lf(doc.raw).length - this.entries(doc.role).reduce((sum, e) => sum + e.len, 0);
|
|
350
|
+
if (gap) process.stderr.write(`board: warning: ${doc.path}: its index spans measure ${Math.abs(gap)} character${Math.abs(gap) === 1 ? '' : 's'} ${gap > 0 ? 'short of' : 'past'} its text; this write reads it again\n`);
|
|
351
|
+
return !gap;
|
|
352
|
+
};
|
|
346
353
|
return this.docs.map((doc) => ({ ...doc, ...this.fileHash(doc.path) }))
|
|
347
|
-
.filter((doc) => doc.hash !== published[doc.role] && !(inFlight?.[doc.role] && doc.hash === inFlight[doc.role].before))
|
|
354
|
+
.filter((doc) => (doc.hash !== published[doc.role] && !(inFlight?.[doc.role] && doc.hash === inFlight[doc.role].before)) || (!pending && !measured(doc)))
|
|
348
355
|
.map((doc) => ({ ...doc, text: lf(doc.raw) }));
|
|
349
356
|
}
|
|
350
357
|
|
|
@@ -493,9 +500,13 @@ class ZtrackBoard {
|
|
|
493
500
|
const edit = stale.find((s) => s.role === doc.role);
|
|
494
501
|
if (!edit) { plan.layout.set(doc.role, entries); continue; }
|
|
495
502
|
let base = this.get('published', {})[doc.role] !== undefined && entries.length ? entries : [];
|
|
503
|
+
// Spans are compared by their stored text and spliced by their recorded length: an index whose lengths are not
|
|
504
|
+
// its texts' (a stored span rewritten outside the board) is no base to re-read a region against.
|
|
505
|
+
let texts = this.texts(base);
|
|
506
|
+
if (texts.some((t, i) => t.text.length !== base[i].len)) { base = []; texts = []; }
|
|
496
507
|
let result;
|
|
497
508
|
// A region that cannot be re-read on its own is read with the whole document.
|
|
498
|
-
try { result = this.codec.rechunkBoardDocument(
|
|
509
|
+
try { result = this.codec.rechunkBoardDocument(texts, edit.text, doc.path); }
|
|
499
510
|
catch { base = []; result = this.codec.rechunkBoardDocument([], edit.text, doc.path); }
|
|
500
511
|
const added = result.chunks.slice(result.from, result.to).map((c) => ({ cid: null, id: c.id, level: c.level, len: c.text.length, rec: null, text: c.text }));
|
|
501
512
|
plan.layout.set(doc.role, [...base.slice(0, result.from), ...added, ...base.slice(result.oldTo)]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-orchestrator",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.29",
|
|
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": {
|