@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 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
@@ -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 first prompt. A shared profile's memory is never used.
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}.\n\n${input}` : `The board resumed this session after it was lost; you are still on card ${task.id}: ${task.title}. Carry on.`;
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.123",
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": {