@volter/supercode-orchestrator 0.4.55 → 0.4.57

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/cli.mjs CHANGED
@@ -164,7 +164,7 @@ async function laneState(root) {
164
164
 
165
165
  /** The caller's own supercode address, when it is a session supercode can reach (for its card's notices). */
166
166
  function callerAddress() {
167
- const id = process.env.CLAUDE_CODE_SESSION_ID || process.env.CODEX_THREAD_ID;
167
+ const id = callerSession();
168
168
  if (!id) return null;
169
169
  const fleet = observeFleet();
170
170
  return [...fleet.alive].find((a) => a.endsWith(`:${id}`)) ?? null;
@@ -233,7 +233,7 @@ export async function runWorkflow(argv) {
233
233
  const ids = (first = rest) => [...first, ...(args.ids ?? [])];
234
234
 
235
235
  // a caller that is a card's own session (its harness names it; the board recorded which session it launched)
236
- const sid = process.env.CLAUDE_CODE_SESSION_ID || process.env.CODEX_THREAD_ID;
236
+ const sid = callerSession();
237
237
  if (sid && !READS.has(verb) && boardExists(root, slug)) {
238
238
  withBoard((b) => { const own = ownCard(b.db, sid); if (own) authorize(b.db, verb, rest, args, own); });
239
239
  }
@@ -266,7 +266,7 @@ export async function runWorkflow(argv) {
266
266
  workspace, branch: args.branch, parents: args.parent, priority: args.priority, tenant: args.tenant,
267
267
  triage: args.triage, idempotencyKey: args['idempotency-key'], maxRuntime: duration(args['max-runtime']),
268
268
  maxRetries, skills: args.skill, model: args.model, provider: args.provider, createdBy: args['created-by'] ?? callerName(),
269
- sessionId: process.env.CLAUDE_CODE_SESSION_ID ?? null, subtaskOf: args['subtask-of'] ?? null, initialStatus: args['initial-status'] ?? null,
269
+ sessionId: callerSession(), subtaskOf: args['subtask-of'] ?? null, initialStatus: args['initial-status'] ?? null,
270
270
  project: args.project ?? meta.project_id ?? null, goal: args.goal ? { maxTurns: args['goal-max-turns'] ? Number(args['goal-max-turns']) : null } : args['no-goal'] ? false : null,
271
271
  }), true);
272
272
  let answer = { id: created.id, existing: created.existing };
@@ -72,6 +72,31 @@ const lastJson = (text) => {
72
72
  * What supercode reports about the fleet now: `{ ok, alive: Set<address>, online: Set<machine>, error }`. `alive` is
73
73
  * every session that can be messaged on every machine; `online` every enrolled machine connected to the team.
74
74
  */
75
+ /**
76
+ * The session a command runs in. Each harness names its own in a variable (Claude Code CLAUDE_CODE_SESSION_ID, Codex
77
+ * CODEX_THREAD_ID), and a pane inherits every variable the program that started its terminal server had, so a Codex
78
+ * pane can carry a Claude session's id too: the nearest harness among the command's ancestors says which is its own.
79
+ */
80
+ export function callerSession() {
81
+ const claude = process.env.CLAUDE_CODE_SESSION_ID || null;
82
+ const codex = process.env.CODEX_THREAD_ID || null;
83
+ if (!claude || !codex) return claude ?? codex;
84
+ const table = spawnSync('ps', ['-A', '-o', 'pid=,ppid=,comm='], { encoding: 'utf8' });
85
+ const processes = new Map();
86
+ for (const row of String(table.stdout ?? '').split('\n')) {
87
+ const m = row.trim().match(/^(\d+)\s+(\d+)\s+(.*)$/);
88
+ if (m) processes.set(Number(m[1]), { parent: Number(m[2]), name: basename(m[3]) });
89
+ }
90
+ for (let pid = process.ppid, depth = 0; pid > 1 && depth < 64; depth++) {
91
+ const p = processes.get(pid);
92
+ if (!p) break;
93
+ if (p.name === 'codex') return codex;
94
+ if (/^claude(\.exe)?$/.test(p.name)) return claude;
95
+ pid = p.parent;
96
+ }
97
+ return claude;
98
+ }
99
+
75
100
  export function observeFleet() {
76
101
  const list = sc(['message', 'list', '--json']);
77
102
  const sessions = list.code === 0 ? json(list.stdout.trim(), null) : null;
@@ -491,7 +516,7 @@ export async function sessionEffect(board, e) {
491
516
  const session = json(row.metadata, {})?.supercode;
492
517
  if ((session?.slot ?? 'worker') !== e.slot || !session?.pane || session.closed_at) continue;
493
518
  // the session that sent the event is not closed under its own command: the dispatcher closes it next tick
494
- const caller = process.env.CLAUDE_CODE_SESSION_ID || process.env.CODEX_THREAD_ID;
519
+ const caller = callerSession();
495
520
  if (caller && caller === session.session_id) { tx(board, (db) => setRunSession(db, row.id, { ...session, close_requested: now() })); return; }
496
521
  stopSession(board, session);
497
522
  // a closed session hears nothing more of the card
@@ -837,6 +862,27 @@ export function noticeOf(entry, audience, kind, task, payload, comment = null) {
837
862
  return text.trim() ? text : null;
838
863
  }
839
864
 
865
+ /**
866
+ * Who hears a card without having asked: each running card's session hears its card and its subtasks (a subscription a
867
+ * launch or a missed fleet read lost comes back), and each of the workflow's managers (`params.managers`) hears every
868
+ * open card as the `manager` audience, whoever filed it, when the workflow's notify names one.
869
+ */
870
+ function ensureSubscriptions(board, entry) {
871
+ const managers = Array.isArray(entry.params.managers) ? entry.params.managers.map(String) : [];
872
+ const toManagers = Object.keys(entry.workflow.notify?.manager ?? {}).length > 0;
873
+ tx(board, (db) => {
874
+ for (const t of db.prepare("SELECT id, current_run_id FROM tasks WHERE status NOT IN ('done', 'archived')").all()) {
875
+ if (toManagers) for (const m of managers) subscribeIn(db, t.id, m, 'manager');
876
+ if (!t.current_run_id) continue;
877
+ const session = json(db.prepare('SELECT metadata FROM task_runs WHERE id = ?').get(t.current_run_id)?.metadata, {})?.supercode;
878
+ const address = addressOf(session);
879
+ if (!address || (session.slot ?? 'worker') !== 'worker') continue;
880
+ subscribeIn(db, t.id, address, 'worker');
881
+ for (const st of db.prepare('SELECT task_id FROM supercode_cards WHERE subtask_of = ?').all(t.id)) subscribeIn(db, st.task_id, address, 'worker');
882
+ }
883
+ });
884
+ }
885
+
840
886
  /** The event kinds an audience hears (the workflow's notify). */
841
887
  const heardBy = (entry, audience) => Object.keys(entry.workflow.notify?.[audience] ?? {});
842
888
 
@@ -844,7 +890,7 @@ async function notify(board, fleet, report, entry) {
844
890
  const from = boardAddress(board.label);
845
891
  // each subscription's notices go in order; different subscribers are told side by side
846
892
  await pool(subscriptions(board.db).filter((sub) => sub.platform === 'supercode'), SEND_CONCURRENCY, async (sub) => {
847
- const role = ['worker', 'dependency'].includes(sub.delivery_metadata?.role) ? sub.delivery_metadata.role : 'creator';
893
+ const role = ['worker', 'dependency', 'manager'].includes(sub.delivery_metadata?.role) ? sub.delivery_metadata.role : 'creator';
848
894
  // a session the fleet does not list this tick is told next tick: one reading is not a loss (a loaded box drops
849
895
  // sessions from a read, and a dropped subscription never came back), and a session that is replaced on its card
850
896
  // has its subscription moved when the new one starts
@@ -1164,6 +1210,7 @@ export async function tick({ root, lanes, settings, only = null, slugs = null, d
1164
1210
  // what the moves asked for, once they are committed (messages to actors, closed panes, cleaned workspaces)
1165
1211
  await runEffects(board, effects);
1166
1212
  phase('goals');
1213
+ ensureSubscriptions(board, entry);
1167
1214
  await notify(board, fleet, report, entry); phase('notify');
1168
1215
  fireHooks(board, settings, report);
1169
1216
  if (String(board.db.prepare('PRAGMA journal_mode').get()?.journal_mode).toLowerCase() === 'wal') board.db.prepare('PRAGMA wal_checkpoint(PASSIVE)').get();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-orchestrator",
3
- "version": "0.4.55",
3
+ "version": "0.4.57",
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": {