@volter/supercode-orchestrator 0.5.26 → 0.5.27

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.
@@ -2,7 +2,6 @@
2
2
  // The orchestrator's synchronous host reaches Teams through the installed neutral SDK.
3
3
  import { fromContext } from '@volter/teams/context';
4
4
  import { supercodeHome } from '@volter/teams/home';
5
- import { createHash } from 'node:crypto';
6
5
  import { openMachine, normalMachineName } from '@volter/teams/machine-door';
7
6
  import { hostname } from 'node:os';
8
7
  import { StreamRpc } from '@volter/teams/client';
@@ -31,10 +30,21 @@ try{
31
30
  }
32
31
  else if(op==='reader')result=await team.agents.delegateReader(args[0],{},{...options,ifMatch:args[1]});
33
32
  else if(op==='bindContext'){
34
- const key=createHash('sha256').update(`${scope.origin}/${scope.team_id}/${args[0]}`).digest('hex');
35
- const channel=await openMachine({machine:reach(args[2]),context:context??process.env.SUPERCODE_CONTEXT});
36
- try{const rpc=new StreamRpc(await channel.open('node'));result=await rpc.call('harness.v1.agents.bindSession',{session:args[1],context:`agent-${key.slice(0,32)}`,assignments:args[3]??[],intended:args[4]??null});}
37
- finally{channel.close();}
33
+ // The binding names the agent's reader context on the session's machine, so it installs that context itself: a
34
+ // launch installs one before its session starts, but an adopted session never had one, and a context installed a
35
+ // day before has expired. An ended agent has neither a reader nor a live session: Teams revoked its credentials
36
+ // and ended its sessions when it ended, so there is nothing to bind.
37
+ const agent=(await team.agents.get(args[0],options)).data;
38
+ if(agent.state==='ended')result={bound:false,reason:`agent ${agent.id} ended`};
39
+ else{
40
+ const reader=await team.agents.delegateReader(agent.id,{expires_in_ms:86400000},{...options,ifMatch:agent.revision});
41
+ const channel=await openMachine({machine:reach(args[2]),context:context??process.env.SUPERCODE_CONTEXT});
42
+ try{
43
+ const rpc=new StreamRpc(await channel.open('node'));
44
+ const installed=await rpc.call('harness.v1.agents.context',{origin:scope.origin,team:scope.team_id,agent:agent.id,credential:reader.data});
45
+ result=await rpc.call('harness.v1.agents.bindSession',{session:args[1],context:installed.context,assignments:args[3]??[],intended:args[4]??null});
46
+ }finally{channel.close();}
47
+ }
38
48
  }
39
49
  else if(op==='readerContext'){
40
50
  const identity=await team.agents.get(args[0],options);
@@ -143,6 +143,16 @@ const pause = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0,
143
143
  // headless worker by its pid, a pane by that machine's pane list. Null only when the machine cannot answer.
144
144
  const sessionAlive = (fleet, session) => fleet.native?.has(addressOf(session)) ? fleet.native.get(addressOf(session)).alive : unpublishedAlive(session);
145
145
  const paneLists = new Map();
146
+ // A run the agent sync holds names only its machine and native session (`harness:id`), never a pane or pid. Teams'
147
+ // fleet answers for a session it published; one it never published (a gateway worker in its profile's directory,
148
+ // outside every synced root) is read through its machine's native discovery, as a native close reads it.
149
+ function syncRowAlive(fleet, row) {
150
+ const machine = row.machine || localMachine(), at = row.session_ref.indexOf(':');
151
+ const session = { machine, harness: row.session_ref.slice(0, at), session_id: row.session_ref.slice(at + 1), address: `sc:${machine}:${row.session_ref}` };
152
+ const alive = sessionAlive(fleet, session);
153
+ if (alive !== null) return alive;
154
+ try { return nativeReading(nativeSessions(machine, session.harness), session).alive; } catch { return null; }
155
+ }
146
156
  function unpublishedAlive(session) {
147
157
  if (!session) return null;
148
158
  if (session.surface === 'headless' && session.pid) return pidAlive(session.pid);
@@ -302,7 +312,10 @@ function supervise(board, fleet, settings, report, entry, effects) {
302
312
  }
303
313
  };
304
314
  const limits = limitsOf(db, board, entry, task, session);
305
- if (runRow && (session?.launch_pending || session?.launch_deferred || (!session?.session_id && !session?.pid))) {
315
+ // A verified adoption binds the run to its session; a launch key still pending beside it is the reconciler's to
316
+ // retire at its machine, and no longer what the run waits on.
317
+ const adopted = session?.mode === 'adopt' && Boolean(session.address);
318
+ if (runRow && ((session?.launch_pending && !adopted) || session?.launch_deferred || (!session?.session_id && !session?.pid))) {
306
319
  const age = t - Number(session?.launched_at ?? runRow.started_at);
307
320
  if (age >= settings.launchWindow) {
308
321
  const reason = session?.launch_deferred_error ?? session?.launch_error ?? 'no session or process was bound to the dispatched run';
@@ -795,13 +808,16 @@ export function closeRequested(board, report, { taskId = null, caller = null } =
795
808
  receipt = launchReceipt(board, session);
796
809
  if (!receipt) receipt = cancelUnissuedLaunch(board, session);
797
810
  } catch (error) { receiptError = error.message; }
798
- if (unissuedLaunch(receipt, session) && retireUnissuedLaunch(board, row, session, receipt)) {
811
+ const retired = unissuedLaunch(receipt, session) && retireUnissuedLaunch(board, row, session, receipt);
812
+ if (retired === true) {
799
813
  (report.closed ??= []).push({ task_id: row.task_id, ...session, receipt });
800
814
  continue;
801
815
  }
816
+ // the key was never issued, and the session adopted onto the run is closed (or released) as its own below
817
+ if (retired === 'released') session = json(board.db.prepare('SELECT metadata FROM task_runs WHERE id = ?').get(row.id)?.metadata, {})?.supercode;
802
818
  // A receipt that names its pane and reads it not live is terminal enough to settle, with or without a status (a
803
819
  // daemon that predates launch statuses answers none): the board's own pane reading below confirms the close
804
- if (receipt?.pane && receipt.status !== 'issued' && (receipt.status || typeof receipt.live === 'boolean' || receipt.exit)) { bindLaunch(board, row, session, receipt.pane, session.session_id); session = { ...session, pane: receipt.pane }; }
820
+ else if (receipt?.pane && receipt.status !== 'issued' && (receipt.status || typeof receipt.live === 'boolean' || receipt.exit)) { bindLaunch(board, row, session, receipt.pane, session.session_id); session = { ...session, pane: receipt.pane }; }
805
821
  else {
806
822
  const error = `close deferred: ${receiptError ?? `awaiting terminal launch receipt ${session.launch_key} (${receipt?.status ?? 'unknown'})`}`;
807
823
  tx(board, (db) => {
@@ -1187,12 +1203,24 @@ function unissuedLaunch(receipt, session) {
1187
1203
  receipt?.outcome === 'not_issued' && receipt?.live === false && receipt?.pane === null;
1188
1204
  }
1189
1205
 
1206
+ /**
1207
+ * Retire a launch key its machine confirmed it never issued. Returns true when the run's launch is settled and closed,
1208
+ * 'released' when the key is retired but the run keeps a session adopted onto it, false when the run is not this key's.
1209
+ */
1190
1210
  function retireUnissuedLaunch(board, row, session, receipt, { entry = null, effects = [] } = {}) {
1191
1211
  if (!unissuedLaunch(receipt, session)) return false;
1192
1212
  const result = tx(board, db => {
1193
1213
  const current = db.prepare('SELECT ended_at, metadata FROM task_runs WHERE id=?').get(row.id);
1194
1214
  const saved = json(current?.metadata, {})?.supercode;
1195
- if (!saved || saved.launch_key !== session.launch_key || saved.pane || saved.address || saved.pid) return { retired: false, effects: [] };
1215
+ if (!saved || saved.launch_key !== session.launch_key) return { retired: false, effects: [] };
1216
+ // A session adopted onto a run whose launch was still pending did not come from that launch (the machine never
1217
+ // issued the key): the key is retired and the adopted session stays the run's, supervised and closed as its own.
1218
+ if (saved.mode === 'adopt' && saved.address && saved.launch_pending) {
1219
+ setRunSession(db, row.id, { launch_pending: false, launch_cancellation: receipt, launch_error: null, launch_key: null });
1220
+ event(db, row.task_id, 'session_launch_cancelled', { key: session.launch_key, machine: session.machine, receipt, kept: saved.address }, row.id);
1221
+ return { retired: 'released', effects: [] };
1222
+ }
1223
+ if (saved.pane || saved.address || saved.pid) return { retired: false, effects: [] };
1196
1224
  if (!current.ended_at && (!entry || getTask(db, row.task_id)?.current_run_id !== row.id)) return { retired: false, effects: [] };
1197
1225
  if (saved.close_confirmed_at) return { retired: true, effects: [] };
1198
1226
  const collected = [];
@@ -1313,7 +1341,7 @@ function reconcileLaunches(board, fleet, report, entry, effects, settings, lanes
1313
1341
  for(const [org,context] of orgs)for(const row of pendingAgentRuns(board.root,org)){
1314
1342
  const launch=agentRuns.find(run=>String(run.id)===String(row.run_id));
1315
1343
  try{synchronizeAgentRun(board.root,org,row,{context,intended:json(launch?.metadata,{})?.supercode?.agent_profile_intent});}
1316
- catch(error){if(error.code==='rate_limited'){report.queued.push([row.run_id,`agent session sync pending: ${error.message}`]);break;}const gone=error.claimRefused&&error.status===404&&fleet.ok&&sessionAlive(fleet,{address:`sc:${row.machine||localMachine()}:${row.session_ref}`,machine:row.machine||localMachine()})===false;if(gone)abandonAgentRun(board.root,org,row.session_ref);report.failed.push([row.run_id,`agent session sync failed: ${error.message}${gone?`; ${row.session_ref} is stopped and Teams refuses it, so it leaves the sync`:''}`]);}
1344
+ catch(error){if(error.code==='rate_limited'){report.queued.push([row.run_id,`agent session sync pending: ${error.message}`]);break;}const gone=error.claimRefused&&error.status===404&&fleet.ok&&syncRowAlive(fleet,row)===false;if(gone)abandonAgentRun(board.root,org,row.session_ref);report.failed.push([row.run_id,`agent session sync failed: ${error.message}${gone?`; ${row.session_ref} is stopped and Teams refuses it, so it leaves the sync`:''}`]);}
1317
1345
  }
1318
1346
  const deferred=view(board,db=>db.prepare("SELECT t.id,r.id run_id,COALESCE(json_extract(r.metadata,'$.supercode.launched_at'),r.started_at) launched_at FROM tasks t JOIN task_runs r ON r.id=t.current_run_id WHERE r.ended_at IS NULL AND json_extract(r.metadata,'$.supercode.launch_deferred')=1").all());
1319
1347
  // A deferred launch was never issued (a reading or door was not yet available): it is the start its manager, creator
@@ -1761,7 +1789,8 @@ function adoptSession(board, id, who, { workspace = null, ttl, assignee = null,
1761
1789
  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(),
1762
1790
  ...(agent ? { agent_id: agent.identity.id, agent_org: team.org, agent_context: context ?? null,
1763
1791
  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 } } } : {}) };
1764
- setRunSession(db, runId, { ...(currentRun(db, id)?.metadata?.supercode ?? {}), ...session, slot: currentRun(db, id)?.metadata?.supercode?.slot ?? beforeSession?.slot ?? expected?.session ?? 'worker', close_requested: null, closed_at: null, close_confirmed_at: null, close_error: null, missing_since: null });
1792
+ // a deferred launch was never issued: the adopted session is the run's, and no later round launches another
1793
+ setRunSession(db, runId, { ...(currentRun(db, id)?.metadata?.supercode ?? {}), ...session, launch_deferred: false, slot: currentRun(db, id)?.metadata?.supercode?.slot ?? beforeSession?.slot ?? expected?.session ?? 'worker', close_requested: null, closed_at: null, close_confirmed_at: null, close_error: null, missing_since: null });
1765
1794
  event(db, id, 'session_adopted', { session_id: sessionId, machine: host, pane: session.pane, address: found.address, mode: 'adopt', role: currentRun(db, id)?.metadata?.supercode?.role }, runId);
1766
1795
  subscribeIn(db, id, found.address, 'worker');
1767
1796
  for (const p of db.prepare('SELECT parent_id FROM task_links WHERE child_id = ?').all(id)) subscribeIn(db, p.parent_id, found.address, 'dependency');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-orchestrator",
3
- "version": "0.5.26",
3
+ "version": "0.5.27",
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": {