@volter/supercode-orchestrator 0.5.123 → 0.5.125
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 +12 -3
- package/board/dispatch.mjs +40 -4
- package/board/store.mjs +4 -0
- package/package.json +1 -1
package/board/cli.mjs
CHANGED
|
@@ -26,7 +26,7 @@ import * as slowLog from '@volter/supercode-harness-sdk/slow-log';
|
|
|
26
26
|
import { basename, dirname, join, resolve } from 'node:path';
|
|
27
27
|
import { fileURLToPath } from 'node:url';
|
|
28
28
|
import { homedir, tmpdir, userInfo } from 'node:os';
|
|
29
|
-
import { actorOf, heldConfigWatched, heldDocumentsChanged, heldDocumentsWatched, holdBoards, retireHeldBoards, releaseHeldBoards, publishMarked, view, backingPaths, layOut, backingOf, boardExists, boardPath, DEFAULT_BOARD, event, json, listBoards, logsDir, now, openBoard, setActor, setPrincipal, principalOf, isCaller, tx } from './store.mjs';
|
|
29
|
+
import { actorOf, heldConfigWatched, heldDocumentsChanged, heldDocumentsWatched, holdBoards, retireHeldBoards, releaseHeldBoards, publishMarked, view, backingPaths, layOut, backingOf, boardExists, boardPath, briefPath, DEFAULT_BOARD, event, json, listBoards, logsDir, now, openBoard, setActor, setPrincipal, principalOf, isCaller, tx } from './store.mjs';
|
|
30
30
|
import { isLocalWorkspace } from './workspace.mjs';
|
|
31
31
|
import { openZtrackBoard } from './ztrack.mjs';
|
|
32
32
|
import * as engine from './engine.mjs';
|
|
@@ -90,7 +90,7 @@ The board's verbs, with their flags:
|
|
|
90
90
|
[--tenant] [--priority] [--triage] [--idempotency-key] [--max-runtime 30m] [--created-by] [--skill]... [--max-retries]
|
|
91
91
|
[--model] [--provider] [--goal|--no-goal] [--goal-max-turns] [--initial-status blocked|running]
|
|
92
92
|
swarm <goal> --worker PROFILE:TITLE[:SKILL,SKILL]... --verifier P --synthesizer P
|
|
93
|
-
list [--mine] [--assignee] [--status] [--tenant] [--session] [--archived] [--sort] show <id> context <id> runs <id>
|
|
93
|
+
list [--mine] [--assignee] [--status] [--tenant] [--session] [--archived] [--sort] show <id> context <id> brief <id> [--run N] runs <id>
|
|
94
94
|
set <id> <assignee|machine|model|every> <value|none> [--provider] [--ids ...] assign <id> <profile|none> set-model <id> [model] [--provider] set-goal <id>... [--goal-max-turns N] [--off]
|
|
95
95
|
reassign <id> --agent AGENT [--role implementer] (moves a card role's assignment to another agent; sessions keep their identity)
|
|
96
96
|
claim <id> [--ttl] reclaim <id> [--reason] heartbeat <id> [--note] [--until <time|30m>] link|unlink <parent> <child>
|
|
@@ -634,6 +634,15 @@ export async function runVerb(argv) {
|
|
|
634
634
|
return 0;
|
|
635
635
|
}
|
|
636
636
|
|
|
637
|
+
case 'brief': {
|
|
638
|
+
need(1, 'a card id');
|
|
639
|
+
const run = args.run ?? withBoard((b) => view(b, (db) => engine.getTask(db, id)?.current_run_id));
|
|
640
|
+
const file = run == null ? null : briefPath(root, slug, id, run);
|
|
641
|
+
if (!file || !existsSync(file)) throw new Error(`the board keeps no brief for ${run == null ? 'a run' : `run ${run}`} of ${id}`);
|
|
642
|
+
current().out(readFileSync(file, 'utf8'));
|
|
643
|
+
return 0;
|
|
644
|
+
}
|
|
645
|
+
|
|
637
646
|
case 'context': need(1, 'a card id'); current().out(`${withBoard((b) => view(b, (db) => engine.cardContext(db, id)))}\n`); return 0;
|
|
638
647
|
|
|
639
648
|
case 'runs': {
|
|
@@ -1374,7 +1383,7 @@ function ownCard(db, sessionId) {
|
|
|
1374
1383
|
return engine.lastSession(db, hit.task_id)?.session_id === sessionId ? engine.getTask(db, hit.task_id) : null;
|
|
1375
1384
|
}
|
|
1376
1385
|
|
|
1377
|
-
const READS = new Set(['list', 'ls', 'show', 'context', 'runs', 'log', 'tail', 'watch', 'stats', 'assignees', 'diagnostics', 'diag', 'attachments', 'notify-list', 'capacity', 'help', 'init']);
|
|
1386
|
+
const READS = new Set(['list', 'ls', 'show', 'context', 'brief', 'runs', 'log', 'tail', 'watch', 'stats', 'assignees', 'diagnostics', 'diag', 'attachments', 'notify-list', 'capacity', 'help', 'init']);
|
|
1378
1387
|
|
|
1379
1388
|
/** The main session of each agent the workflow names a manager (`sc:<machine>:agent:<name>`), asked of this machine's
|
|
1380
1389
|
* daemon, which keeps agents' records (mail step 1c), once per call before its verb runs: a manager named by its agent
|
package/board/dispatch.mjs
CHANGED
|
@@ -39,7 +39,7 @@ import { homedir, hostname, tmpdir, totalmem } from 'node:os';
|
|
|
39
39
|
import { basename, dirname, join } from 'node:path';
|
|
40
40
|
import { createHash, randomUUID } from 'node:crypto';
|
|
41
41
|
import { fileURLToPath } from 'node:url';
|
|
42
|
-
import { view, boardCopyOf, boardPath, claimer, claimHolds, copyRefusal, event, json, listBoards, logsDir, newClaim, now, openBoard, setActor, storageFault, tx } from './store.mjs';
|
|
42
|
+
import { view, boardCopyOf, boardPath, briefPath, claimer, claimHolds, copyRefusal, event, json, listBoards, logsDir, newClaim, now, openBoard, setActor, storageFault, tx } from './store.mjs';
|
|
43
43
|
import { doors, fire, loadWorkflow, resumeOf, runSpec, runWorkingSeconds, requestSessionClose, retireSupersededRuns, refreshCardInputs, refreshRelationAnswers, transitionsFor, scopeOf, statusOf, projectStatus, storedStatus, templateValue, workflowFor } from './workflow.mjs';
|
|
44
44
|
import { checkWorkspace, assertWorkspaceOwner, notePresence, resolveWorkspace } from './workspace.mjs';
|
|
45
45
|
import { capacity, harnessDefaults, holdings } from './capacity.mjs';
|
|
@@ -274,6 +274,21 @@ async function discoverNative(machine, harness, sessionId) {
|
|
|
274
274
|
const complete = page.subagent_rows === 'own' && !(Array.isArray(page.failed) && page.failed.length) && !page.receipt?.truncated && page.next_cursor == null;
|
|
275
275
|
return { sessions: page.sessions, complete };
|
|
276
276
|
}
|
|
277
|
+
/**
|
|
278
|
+
* Whether `session`'s machine holds no session of that id at all: its discovery was read whole (every source, nothing
|
|
279
|
+
* truncated) and has no row for it. A launch that stopped before its first turn (a question on its screen that no
|
|
280
|
+
* input answers) leaves a pane and no transcript, and a harness asked to resume that id exits at once. A reading not
|
|
281
|
+
* taken, or not whole, says nothing.
|
|
282
|
+
*/
|
|
283
|
+
async function nativeAbsent(session) {
|
|
284
|
+
if (!session?.session_id || !session.machine) return false;
|
|
285
|
+
const harness = session.harness ?? 'claude-code';
|
|
286
|
+
try {
|
|
287
|
+
const rows = await readNative(session.machine, harness, session.session_id);
|
|
288
|
+
return discoveries.get(nativeKey(session.machine, harness, session.session_id))?.complete === true
|
|
289
|
+
&& !rows.some((r) => r.locator?.session_id === session.session_id);
|
|
290
|
+
} catch { return false; }
|
|
291
|
+
}
|
|
277
292
|
function nativeReading(rows, session) {
|
|
278
293
|
const row = rows.find(r => r.locator?.session_id === session.session_id && r.locator?.harness === session.harness);
|
|
279
294
|
const presence = row?.activity?.presence;
|
|
@@ -907,13 +922,13 @@ async function launchAttempt(board, entry, id, lane, fleet, settings, runId) {
|
|
|
907
922
|
agent_profile_intent:structuredClone(agent.intended.resolved),
|
|
908
923
|
agent_env:agentEnvironment(agent,{root:board.root,context:normalMachine(task.machine??localMachine())===localMachine()?localReaderContext(team.org,agent.identity.id)??undefined:undefined})});
|
|
909
924
|
// The agent's own memory reaches every session of it: headless runs open its home; a pane, which may sit on
|
|
910
|
-
// another machine, gets the home's snapshot in its
|
|
925
|
+
// another machine, gets the home's snapshot in its run's brief. A shared profile's memory is never used.
|
|
911
926
|
const agentHome=materializeAgentHome(board.root,team.org,agent.identity.id,lane);
|
|
912
927
|
assertRunCurrent(board,id,runId);
|
|
913
928
|
if(surface==='headless')meta.agent_env.HERMES_HOME=agentHome;
|
|
914
929
|
else{const memory=await memorySnapshot(agentHome).catch(()=>'');assertRunCurrent(board,id,runId);if(memory)prompt=`${prompt}\n\n${memory}`;}
|
|
915
930
|
const result = surface === 'pane'
|
|
916
|
-
? await launchPane(board, task, runId, lane, fleet, { input: prompt, slot, meta })
|
|
931
|
+
? await launchPane(board, task, runId, lane, fleet, { input: briefByReference(board, task, runId, spec.role ?? slot, where, prompt), slot, meta })
|
|
917
932
|
: await launchHeadless(board, task, runId, lane, settings, { prompt, skills: spec.skills ?? [], meta });
|
|
918
933
|
tx(board,db=>{
|
|
919
934
|
if(!runCurrent(db,id,runId))return; // replaced once launched: its session is the ended run's, closed as one
|
|
@@ -945,6 +960,24 @@ async function launchAttempt(board, entry, id, lane, fleet, settings, runId) {
|
|
|
945
960
|
}
|
|
946
961
|
}
|
|
947
962
|
|
|
963
|
+
/**
|
|
964
|
+
* What the board types into a pane session to start a run: one short line in the board's own words naming the role,
|
|
965
|
+
* the card and where the run's brief is, with the brief itself kept in a file. A terminal harness takes its input as a
|
|
966
|
+
* paste, and Claude Code records a paste of more than two lines or about 800 characters as a `<pasted_content>`
|
|
967
|
+
* attachment with no typed words beside it, which a session rightly reads as material, not as its instruction. The
|
|
968
|
+
* line stays one line and far under that size whatever the card holds; the session on the board's machine reads the
|
|
969
|
+
* file, one elsewhere prints it through the board's door.
|
|
970
|
+
*/
|
|
971
|
+
export function briefByReference(board, task, runId, role, where, prompt) {
|
|
972
|
+
const file = briefPath(board.root, board.slug, task.id, runId);
|
|
973
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
974
|
+
writeFileSync(file, `${prompt}\n`, { mode: 0o600 });
|
|
975
|
+
const local = !task.machine || normalMachine(task.machine) === localMachine();
|
|
976
|
+
const from = local ? `in the file ${file}: read the whole file now`
|
|
977
|
+
: `kept by the board: print it with \`supercode workflow brief ${task.id} ${where}\` (into a file outside your repository when it is long) and read all of it now`;
|
|
978
|
+
return `The ${board.label} board started run ${runId} of card ${task.id} and you are its ${role}. Your instructions from the board are the brief ${from}, then carry it out as your own task without waiting for another message.`;
|
|
979
|
+
}
|
|
980
|
+
|
|
948
981
|
/** Start one card now by a person's word (a triage card's session, a manager's start): the workflow's start event. */
|
|
949
982
|
export async function startCard(board, id, { roles = ['manager'], lanes, settings } = {}) {
|
|
950
983
|
// the board's own published fleet projection, as a tick reads it: a manager's or creator's start that continues the
|
|
@@ -1868,6 +1901,9 @@ async function launchPane(board, task, runId, lane, fleet, { input = null, slot
|
|
|
1868
1901
|
});
|
|
1869
1902
|
// filed in its mailbox, which delivers it
|
|
1870
1903
|
if (input) { assertRunCurrent(board, task.id, runId); await fileMail(board, addressOf(prev), input, `board-run-${task.id}-r${runId}`); }
|
|
1904
|
+
} else if (prev.resumable !== false && await nativeAbsent(prev)) {
|
|
1905
|
+
// nothing to resume and no context to keep: the slot's next session starts fresh
|
|
1906
|
+
replaced = { session_id: prev.session_id, why: `${prev.machine} holds no ${prev.harness ?? 'claude-code'} session of that id (it never took a first turn)`, letGo: true };
|
|
1871
1907
|
} else if (prev.resumable !== false) {
|
|
1872
1908
|
const authorization = view(board, db => currentRun(db,task.id)?.metadata?.supercode?.restart_authorized);
|
|
1873
1909
|
const released = view(board,db=>{
|
|
@@ -1880,7 +1916,7 @@ async function launchPane(board, task, runId, lane, fleet, { input = null, slot
|
|
|
1880
1916
|
if (monitorsRuns(workflowFor(board.root)) && prev.monitor_held && !authorization && !released) throw new Error(`session ${prev.session_id} is stopped by incident ${prev.incident ?? 'unknown'}; only its manager or creator may restart it with workflow start`);
|
|
1881
1917
|
// a resumed session is told what this run is for, as a kept one is: the run's own prompt (a reviewer resumed for
|
|
1882
1918
|
// its next round gets that round's prompt, its arc's context with it), not only that it was resumed
|
|
1883
|
-
const told = input ? `The board resumed this session for this run of card ${task.id}
|
|
1919
|
+
const told = input ? `The board resumed this session for this run of card ${task.id}. ${input}` : `The board resumed this session after it was lost; you are still on card ${task.id}: ${task.title}. Carry on.`;
|
|
1884
1920
|
// a session is resumed in its own harness, whatever the lane's is now
|
|
1885
1921
|
const its = HARNESSES[prev.harness ?? 'claude-code'] ?? HARNESSES['claude-code'];
|
|
1886
1922
|
const launchKey = `board-${task.id}-r${runId}-resume`;
|
package/board/store.mjs
CHANGED
|
@@ -124,6 +124,10 @@ export function boardPath(root, slug = DEFAULT_BOARD) {
|
|
|
124
124
|
/** The directory of the board's worker logs, as Hermes lays them out. */
|
|
125
125
|
export const logsDir = (root, slug = DEFAULT_BOARD) => join(boardDirOf(root, slug), 'logs');
|
|
126
126
|
|
|
127
|
+
/** The file holding the brief of run `run` of card `id`: what the board hands a pane session by reference, kept with
|
|
128
|
+
* the board's logs and removed with them. */
|
|
129
|
+
export const briefPath = (root, slug, id, run) => join(logsDir(root, slug), `${id}-r${run}.brief.md`);
|
|
130
|
+
|
|
127
131
|
/** The home's boards that exist: `default` when it does, and every named one. */
|
|
128
132
|
export function listBoards(root) {
|
|
129
133
|
const out = boardExists(root) ? [DEFAULT_BOARD] : [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-orchestrator",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.125",
|
|
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": {
|