@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 +3 -3
- package/board/dispatch.mjs +49 -2
- package/package.json +1 -1
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 =
|
|
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 =
|
|
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:
|
|
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 };
|
package/board/dispatch.mjs
CHANGED
|
@@ -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 =
|
|
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.
|
|
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": {
|