@volter/supercode-orchestrator 0.5.69 → 0.5.71

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/ztrack.mjs CHANGED
@@ -7,6 +7,8 @@
7
7
  // triggers name the rows it touched. Only the cards whose text changed are rendered, and each is
8
8
  // spliced into the document at its indexed span. A document edited by hand is re-read only in the
9
9
  // region the edit touched. So a write costs what it changes, not the size of the board.
10
+ import { current } from './call.mjs';
11
+ import { fenced } from './fence.mjs';
10
12
  import { createRequire } from 'node:module';
11
13
  import { createHash, randomBytes } from 'node:crypto';
12
14
  import { existsSync, linkSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
@@ -74,10 +76,9 @@ export function openZtrackBoard(marker, { create = false, document = null, decla
74
76
  }
75
77
  const config = JSON.parse(read(marker));
76
78
  const backing = new ZtrackBoard({ ...config, seed, marker, init, root, slug });
77
- return { db: backing.db, tx: (fn) => backing.tx(fn), publishPending: () => backing.git?.publishPending(), close: () => backing.close(), document: backing.path };
79
+ return { db: backing.db, tx: (fn) => backing.tx(fn), publishPending: () => backing.git?.publishPending(), unimported: () => backing.unimported(), close: () => backing.close(), document: backing.path };
78
80
  }
79
81
 
80
- const behindSaid = new Set(), heartbeatSaid = new Set();
81
82
 
82
83
  class ZtrackBoard {
83
84
  constructor({ document, seed, marker, init, root, slug, commit = false, manager = null, archive = {} }) {
@@ -94,23 +95,24 @@ class ZtrackBoard {
94
95
  // own work; the backing commits only the writes the manager does not make.
95
96
  this.manager = manager;
96
97
  this.git = commit ? new GitDocument(this.baseDocs.map((doc) => doc.path), (base, theirs, ours, path) => { try { return this.codec.mergeBoardDocument(base, theirs, ours, path)?.text ?? null; } catch { return null; } }) : null;
97
- // A publish that is behind is said once per process, on whichever workflow verb opened the board: a refused push
98
+ // A publish that is behind is said once per call, on whichever workflow verb opened the board: a refused push
98
99
  // surfaces on the next call instead of only in the dispatcher's report, and stops being said once the push lands.
99
- if (this.git && !behindSaid.has(this.path)) {
100
- behindSaid.add(this.path);
100
+ const said = current().said;
101
+ if (this.git && !said.has(`behind:${this.path}`)) {
102
+ said.add(`behind:${this.path}`);
101
103
  const late = this.git.behind();
102
104
  // A push younger than a minute is still on its way.
103
- if (late && Date.now() - late.since > 60_000) process.stderr.write(`board: warning: ${late.commits} board document commit${late.commits === 1 ? '' : 's'} not published since ${new Date(late.since).toISOString().slice(0, 16)}Z${late.error ? ` (last push: ${late.error})` : ''}; the dispatcher's next round retries\n`);
105
+ if (late && Date.now() - late.since > 60_000) current().err(`board: warning: ${late.commits} board document commit${late.commits === 1 ? '' : 's'} not published since ${new Date(late.since).toISOString().slice(0, 16)}Z${late.error ? ` (last push: ${late.error})` : ''}; the dispatcher's next round retries\n`);
104
106
  }
105
107
  // The dispatcher's standing errors are said the same way, on every workflow call until a round clears them: a round
106
108
  // that has not completed in five minutes, and each card or run the last round could not start, read or sync.
107
- if (root && !heartbeatSaid.has(root)) {
108
- heartbeatSaid.add(root);
109
+ if (root && !said.has(`heartbeat:${root}`)) {
110
+ said.add(`heartbeat:${root}`);
109
111
  try {
110
112
  const beat = JSON.parse(readFileSync(join(root, 'dispatcher-heartbeat.json'), 'utf8'));
111
113
  const age = Date.now() - Date.parse(beat.at);
112
- if (age > 300_000) process.stderr.write(`board: error: the dispatcher has completed no round since ${String(beat.at).slice(0, 16)}Z; nothing starts, moves or is supervised\n`);
113
- for (const error of beat.errors ?? []) process.stderr.write(`board: error: ${error}\n`);
114
+ if (age > 300_000) current().err(`board: error: the dispatcher has completed no round since ${String(beat.at).slice(0, 16)}Z; nothing starts, moves or is supervised\n`);
115
+ for (const error of beat.errors ?? []) current().err(`board: error: ${error}\n`);
114
116
  } catch { /* a board no dispatcher serves yet has no heartbeat */ }
115
117
  }
116
118
  this.seed = seed; this.root = root; this.slug = slug; this.init = init;
@@ -256,6 +258,7 @@ class ZtrackBoard {
256
258
  if (keys.length) db.prepare(`INSERT OR REPLACE INTO ${quote(name)} (${keys.map(quote).join(',')}) VALUES (${keys.map(() => '?').join(',')})`).run(...keys.map((k) => row[k] ?? null));
257
259
  };
258
260
  db.exec('BEGIN');
261
+ fenced(db);
259
262
  const runtime = {}, clocks = {}, sequences = [];
260
263
  for (const row of old.prepare('SELECT name, key, value FROM runtime_rows WHERE value IS NOT NULL').all()) {
261
264
  const value = JSON.parse(row.value);
@@ -354,7 +357,7 @@ class ZtrackBoard {
354
357
  // the repair is said, so a span length written wrong stays visible
355
358
  const measured = (doc) => {
356
359
  const gap = lf(doc.raw).length - this.entries(doc.role).reduce((sum, e) => sum + e.len, 0);
357
- if (gap) process.stderr.write(`board: warning: ${doc.path}: its index spans measure ${Math.abs(gap)} character${Math.abs(gap) === 1 ? '' : 's'} ${gap > 0 ? 'short of' : 'past'} its text; this write reads it again\n`);
360
+ if (gap) current().err(`board: warning: ${doc.path}: its index spans measure ${Math.abs(gap)} character${Math.abs(gap) === 1 ? '' : 's'} ${gap > 0 ? 'short of' : 'past'} its text; this write reads it again\n`);
358
361
  return !gap;
359
362
  };
360
363
  return this.docs.map((doc) => ({ ...doc, ...this.fileHash(doc.path) }))
@@ -376,6 +379,12 @@ class ZtrackBoard {
376
379
  this.applyPlan(plan, { overlay: true });
377
380
  }
378
381
 
382
+ // Whether a document holds an edit its tables have not imported (a hand edit, a git update): the publication owner
383
+ // enters a transaction for it, which imports it and so marks its cards. Unreadable reads as yes: the write reports it.
384
+ unimported() {
385
+ try { return this.staleDocuments({ pending: true }).length > 0; } catch { return true; }
386
+ }
387
+
379
388
  dropOverlay() {
380
389
  if (!this.overlay) return;
381
390
  this.conn.exec('DROP TABLE IF EXISTS temp.tasks; DROP TABLE IF EXISTS temp.supercode_cards; DROP TABLE IF EXISTS temp.task_links');
@@ -389,7 +398,7 @@ class ZtrackBoard {
389
398
  if (this.active) return fn(this.db);
390
399
  // A pull request merged on the remote (a page edited in Git Notes) is in the document before this write reads it.
391
400
  if (this.git) this.git.paths = this.docs.map((doc) => doc.path);
392
- try { timedStep('board git pull', () => this.git?.pull()); } catch (error) { process.stderr.write(`board: ${error.message}\n`); }
401
+ try { timedStep('board git pull', () => this.git?.pull()); } catch (error) { current().err(`board: ${error.message}\n`); }
393
402
  let txStarted = 0;
394
403
  for (let attempt = 0; ; attempt++) {
395
404
  let outcome;
@@ -397,6 +406,7 @@ class ZtrackBoard {
397
406
  txStarted = performance.now();
398
407
  this.dropOverlay();
399
408
  this.conn.exec('BEGIN');
409
+ fenced(this.conn);
400
410
  this.active = true; this.contended = false;
401
411
  this.conn.exec('DELETE FROM temp.board_dirty; DELETE FROM temp.board_touched');
402
412
  this.refused = new Set();
@@ -437,7 +447,7 @@ class ZtrackBoard {
437
447
  const byManager = this.manager !== null && (actorOf() === this.manager || principalOf() === this.manager);
438
448
  if (this.git) this.git.paths = this.docs.map((doc) => doc.path);
439
449
  if (!byManager) try { timedStep('board git commit and push', () => this.git?.commit(actorOf(), `board: ${outcome.files.map((file) => basename(file.path)).join(', ')} written for ${actorOf() ?? 'the board'}`)); }
440
- catch (error) { process.stderr.write(`board: ${error.message}\n`); }
450
+ catch (error) { current().err(`board: ${error.message}\n`); }
441
451
  }
442
452
  return outcome.result;
443
453
  }
@@ -463,7 +473,7 @@ class ZtrackBoard {
463
473
  // A short write of board state alone, retried on contention.
464
474
  small(fn) {
465
475
  for (let attempt = 0; ; attempt++) {
466
- try { this.conn.exec('BEGIN'); fn(); this.conn.exec('COMMIT'); return; }
476
+ try { this.conn.exec('BEGIN'); fenced(this.conn); fn(); this.conn.exec('COMMIT'); return; }
467
477
  catch (error) { try { this.conn.exec('ROLLBACK'); } catch { /* not begun */ } if (!sqliteContention(error)) throw error; backoff(attempt); }
468
478
  }
469
479
  }
package/hand-off.mjs CHANGED
@@ -15,12 +15,12 @@ import { fileURLToPath } from 'node:url';
15
15
 
16
16
  /** The home a command (the whole of it, after the entry) names: --root, wherever it stands (the gateway's form,
17
17
  * `node bin/orchestrator.mjs --root <home>`, has no verb before it); for `import`, which writes the home it names
18
- * with --into, that; else the environment the orchestrator reads (SUPERCODE_ORCHESTRATOR_HOME, HERMES_HOME).
18
+ * with --into, that; else the home's own variable (SUPERCODE_ORCHESTRATOR_HOME).
19
19
  * (`export` names the home it reads with --from and writes none.) */
20
20
  export function homeOfArgs(argv, env = process.env) {
21
21
  const flag = argv[0] === 'import' ? '--into' : '--root';
22
22
  const at = argv.findIndex((arg) => arg === flag || arg.startsWith(`${flag}=`));
23
- const named = at < 0 ? env.SUPERCODE_ORCHESTRATOR_HOME ?? env.HERMES_HOME : argv[at].startsWith(`${flag}=`) ? argv[at].slice(flag.length + 1) : argv[at + 1];
23
+ const named = at < 0 ? env.SUPERCODE_ORCHESTRATOR_HOME : argv[at].startsWith(`${flag}=`) ? argv[at].slice(flag.length + 1) : argv[at + 1];
24
24
  return named ? resolve(named) : null;
25
25
  }
26
26
 
package/index.d.ts CHANGED
@@ -113,7 +113,7 @@ export function declareAgentIntent(options: {root: Path; org: string; server: st
113
113
  export function agentRunForKey(root: Path, org: string, agent: string, key: string): Record<string, unknown> | null;
114
114
  export function agentIntent(root: Path, org: string, id: string): Record<string, unknown> | null;
115
115
  export function assignAgent(root: Path, org: string, id: string, assignment: {kind: string; target: string; role: string}): string;
116
- export function reassignAgent(root: Path, org: string, assignment: string, to: string, options?: {context?: string}): Record<string, unknown>;
116
+ export function reassignAgent(root: Path, org: string, assignment: string, to: string, options?: {context?: string}): Promise<Record<string, unknown>>;
117
117
  export function activeAgentAssignments(root: Path): Array<Record<string, unknown>>;
118
118
  export function finishAgentAssignment(root: Path, org: string, assignment: string, reason: string): void;
119
119
  export function recordAgentRun(root: Path, org: string, id: string, run: {session: string; machine?: string | null; run: string | number; profile?: unknown; main?: boolean; starterSession?: string | null}): void;
package/kanban-worker.mjs CHANGED
@@ -18,8 +18,7 @@ import { KANBAN_ENV, untrustedServerVerdict, hermesApproval, hermesInstall, memo
18
18
  import { pinnedSkills, taskProtocol } from './board/worker-prompt.mjs';
19
19
  import { mountOrchestratorMcp } from './mcp/mount.mjs';
20
20
  import { normalizeWorkerEvent, widensSandbox } from './loop.mjs';
21
- import { DEFAULT_BOARD, json, openBoard, tx } from './board/store.mjs';
22
- import { setRunSession } from './board/engine.mjs';
21
+ import { askBoardOwner, callerFacts } from '@volter/teams/board-owner';
23
22
  import { permissionResponse } from './reducer.mjs';
24
23
  import { boardGuardArguments } from '@volter/teams/board-process-guard';
25
24
 
@@ -98,7 +97,7 @@ async function runTurn({ client, profile, harness, worker, route, root, vault, s
98
97
  const mount = mountOrchestratorMcp({ root, profile, harness, surfaceKey, launch, vault, task });
99
98
  const policy = approvalPolicy(profile, harness);
100
99
  const runtime = await client.startManagedRuntime({ harness, cwd, launch: mount.launch, ...(mount.mcp_servers ? { mcp_servers: mount.mcp_servers } : {}), ...(policy ? { approval_policy: policy } : {}) });
101
- try { recordSession(root, task, harness, runtime.handle.runtime_id); } catch (error) { say(`hermes-worker: recording the session on its run: ${error.message}`); }
100
+ try { await recordSession(root, task, harness, runtime.handle.runtime_id); } catch (error) { say(`hermes-worker: recording the session on its run: ${error.message}`); }
102
101
  let text = '';
103
102
  // `agent.max_turns` for a worker that takes no cap at launch, held as the daemon holds it (loop.mjs `holdTurnLimit`)
104
103
  const counter = turnCounter(profile, harness);
@@ -147,21 +146,17 @@ async function runTurn({ client, profile, harness, worker, route, root, vault, s
147
146
 
148
147
  /**
149
148
  * The worker's session on its run, where supercode keeps it (`metadata.supercode.session_id`, beside the pid the
150
- * dispatcher recorded): the harness's own transcript, found and resumed by supercode like any session.
149
+ * dispatcher recorded): the harness's own transcript, found and resumed by supercode like any session. Written by the
150
+ * board's owner, as every board write is (`workflow run-session`).
151
151
  */
152
- function recordSession(root, task, harness, sessionId) {
152
+ async function recordSession(root, task, harness, sessionId) {
153
153
  if (!task.HERMES_KANBAN_RUN_ID || !sessionId) return;
154
- const board = openBoard(root, task.HERMES_KANBAN_BOARD || DEFAULT_BOARD);
155
- try {
156
- tx(board, (db) => {
157
- const row = db.prepare('SELECT metadata FROM task_runs WHERE id = ?').get(Number(task.HERMES_KANBAN_RUN_ID));
158
- if (!row) return;
159
- const meta = json(row.metadata, {}) ?? {};
160
- setRunSession(db, Number(task.HERMES_KANBAN_RUN_ID), { ...(meta.supercode ?? {}), session_id: sessionId, harness });
161
- });
162
- } finally {
163
- board.close();
164
- }
154
+ const answer = await askBoardOwner(root, {
155
+ argv: ['run-session', String(task.HERMES_KANBAN_RUN_ID), String(sessionId), '--harness', harness, '--task', String(task.HERMES_KANBAN_TASK ?? '')],
156
+ board: task.HERMES_KANBAN_BOARD || null, caller: null, session: null, cwd: process.cwd(), stdin: null,
157
+ facts: { ...callerFacts(), SUPERCODE_CONTEXT: process.env.SUPERCODE_CONTEXT || 'local' },
158
+ });
159
+ if (answer.code) throw new Error((answer.stderr || 'the board owner did not record it').trim());
165
160
  }
166
161
 
167
162
  /** A profile whose worker is Hermes: the dispatcher's own command, run as Hermes would have run it. */
package/loop.mjs CHANGED
@@ -25,7 +25,7 @@ import { checkMonitor, runJobScript } from './gates.mjs';
25
25
  import { SenderIdentityRecord } from '@volter/teams/sender-identity';
26
26
  import { agentScope,localProfileAgent,localAgentRecord,localAgentSessions,localForkAgent,handOffAgents,localReaderContext,agentRunForKey,agentPreamble,agentEnvironment,recordAgentRun,recordNativeAgentSession,agentRunForSession } from './agents.mjs';
27
27
  import { materializeAgentHome } from './agent-home.mjs';
28
- import { localMachine } from './board/dispatch.mjs';
28
+ import { knowLocalMachine } from './board/dispatch.mjs';
29
29
  import { execFile } from 'node:child_process';
30
30
  import { promisify } from 'node:util';
31
31
 
@@ -60,7 +60,7 @@ export class Orchestrator {
60
60
  this.identityRecord = new SenderIdentityRecord();
61
61
  state.runtime ??= {};
62
62
  state.runtime.hostedJobs = !!executeJob;
63
- state.runtime.machine = localMachine(); // a job naming another machine fires there (cron.mjs startFire)
63
+ state.runtime.machine = null; // learned at start(): a job naming another machine fires there (cron.mjs startFire)
64
64
  state.runtime.hostedFires = {};
65
65
  this.vault = vault;
66
66
  // each profile's cron/jobs.json as the state was loaded from it: what changes there later was written by another hand
@@ -84,6 +84,7 @@ export class Orchestrator {
84
84
 
85
85
  async start() {
86
86
  retainWorld();
87
+ this.state.runtime.machine = await knowLocalMachine();
87
88
  for (const [platform, adapter] of Object.entries(this.adapters)) await this.connectAdapter(platform, adapter);
88
89
  this.webhooks = await startWebhookListener({
89
90
  state: this.state, vault: this.vault, port: webhookPort(this.state),
package/mcp/server.mjs CHANGED
@@ -16,12 +16,13 @@
16
16
  // `supercode mcp serve` are a different server for a different purpose and are
17
17
  // untouched by this one.
18
18
 
19
+ import { knowLocalMachine } from '../board/dispatch.mjs';
19
20
  import { createInterface } from 'node:readline';
20
21
  import { connect } from 'node:net';
21
22
  import { join } from 'node:path';
22
23
  import { KANBAN_TOOLS, PROTOCOL_VERSION, SERVER_NAME, TOOLS } from './tools.mjs';
23
24
  import { KANBAN_ENV } from '../hermes-cli.mjs';
24
- import { boardTool } from '../board/tools.mjs';
25
+ import { askBoardOwner, callerFacts } from '@volter/teams/board-owner';
25
26
 
26
27
  /** Env var naming the socket, when the loop wants one off the root's default. */
27
28
  export const SOCKET_ENV = 'SUPERCODE_ORCHESTRATOR_SOCK';
@@ -56,16 +57,17 @@ const HERMES_ANSWERED = new Set(['session_search', 'memory', 'skills_list', 'ski
56
57
  const published = (binding) => (binding.kanban ? [...TOOLS.filter((t) => !HERMES_ANSWERED.has(t.name)), ...KANBAN_TOOLS] : TOOLS);
57
58
 
58
59
  /**
59
- * A board tool, answered by supercode's own board (board/tools.mjs) on the home's board file: the task, its run and
60
- * its claim are this worker's environment, as they are a Hermes worker's, never the daemon's.
60
+ * A board tool, answered by the board's owner (board/tools.mjs, in the owner's process): the board has one writer.
61
+ * The task, its run and its claim are this worker's environment, as they are a Hermes worker's, never the daemon's.
61
62
  */
62
63
  async function kanbanCall(binding, name, args) {
63
- try {
64
- const out = await boardTool(binding.root, name, args, binding.kanban ?? {});
65
- return { ok: true, result: { ran: name, verbatim: out } };
66
- } catch (error) {
67
- return { ok: false, error: error.message };
68
- }
64
+ const answer = await askBoardOwner(binding.root, {
65
+ tool: { name, args, env: binding.kanban ?? {} },
66
+ board: binding.kanban?.HERMES_KANBAN_BOARD ?? null, caller: null, session: null, cwd: binding.root, stdin: null,
67
+ facts: { ...callerFacts(), SUPERCODE_CONTEXT: process.env.SUPERCODE_CONTEXT || 'local' },
68
+ });
69
+ if (answer.code) return { ok: false, error: (answer.stderr || 'the board owner did not answer').trim() };
70
+ return { ok: true, result: { ran: name, verbatim: answer.stdout } };
69
71
  }
70
72
 
71
73
  /**
@@ -96,6 +98,7 @@ export function ask(binding, verb, args) {
96
98
 
97
99
  /** `crates/harness/src/mcp.rs::handle_request`'s shape, in JavaScript. */
98
100
  export async function handleRequest(binding, request) {
101
+ await knowLocalMachine();
99
102
  const id = request?.id;
100
103
  const method = request?.method ?? '';
101
104
  const reply = (result) => ({ jsonrpc: '2.0', id, result });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-orchestrator",
3
- "version": "0.5.69",
3
+ "version": "0.5.71",
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": {
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@volter/supercode-harness-sdk": "^0.3.60",
28
- "@volter/teams": "0.2.20",
28
+ "@volter/teams": "0.2.24",
29
29
  "@volter/ztrack": "^2.4.23",
30
30
  "yaml": "^2.9.0"
31
31
  },
@@ -1,53 +0,0 @@
1
- #!/usr/bin/env node
2
- // The orchestrator's synchronous host reaches Teams through the installed neutral SDK.
3
- import { fromContext, WorkspaceContexts } from '@volter/teams/context';
4
- import { supercodeHome } from '@volter/teams/home';
5
- import { openMachine, normalMachineName } from '@volter/teams/machine-door';
6
- import { hostname } from 'node:os';
7
- import { StreamRpc } from '@volter/teams/client';
8
- // This machine is reached through its own daemon's socket, never through the Teams server: the server route lists the
9
- // org's machines and opens a channel on the shared credential's allowance for a request that never leaves the box.
10
- const reach=machine=>machine&&normalMachineName(machine)!==normalMachineName(hostname())?machine:null;
11
- let input='';for await(const chunk of process.stdin)input+=chunk;
12
- try{
13
- const {op,args=[],context}=JSON.parse(input);
14
- // No context selected: identity is this home's own (Org `local`). A context that is named but unreadable is an error.
15
- const named=context??process.env.SUPERCODE_CONTEXT??new WorkspaceContexts(supercodeHome()).read().current;
16
- if(op==='scope'&&(!named||named==='local')){process.stdout.write(JSON.stringify({server:'local',org:'local',principal:null,offline:true,reason:'no Teams context is selected'})+'\n');process.exit(0);}
17
- const {client,context:scope}=fromContext({supercodeHome:supercodeHome(),name:context??process.env.SUPERCODE_CONTEXT});
18
- const team=client.team(scope.team_id),options={signal:AbortSignal.timeout(20000)};
19
- let result;
20
- if(op==='scope')result={server:scope.origin,server_id:scope.server_id,org:scope.team_id,principal:scope.principal_id};
21
- else if(op==='root')result=await team.agents.root(options);
22
- else if(op==='get')result=await team.agents.get(args[0],options);
23
- else if(op==='management')result=await team.agents.management(args[0],options);
24
- else if(op==='profile')result=await team.agents.update(args[0],{profile:args[1]},{...options,ifMatch:args[2]});
25
- else if(op==='reparent')result=await team.agents.update(args[0],{parent:args[1]},{...options,ifMatch:args[2]});
26
- else if(op==='declare')result=await team.agents.declare(args[0],{...options,idempotencyKey:args[1]});
27
- else if(op==='sessions')result=await team.agents.sessions(args[0],args[1]??{},options);
28
- else if(op==='ownSession')result=await team.agents.ownSession(args[0],args[1],{...options,idempotencyKey:args[2]});
29
- else if(op==='session')result=await team.sessions.get(args[0]).identity(options);
30
- else if(op==='end'){
31
- const agent=(await team.agents.get(args[0],options)).data;
32
- result=agent.state==='ended'?{data:{id:agent.id,state:'ended'}}:await team.agents.end(agent.id,{reason:args[1]},{...options,ifMatch:agent.revision,idempotencyKey:args[2]});
33
- }
34
- else if(op==='reader')result=await team.agents.delegateReader(args[0],{},{...options,ifMatch:args[1]});
35
- else if(op==='readerContext'){
36
- const identity=await team.agents.get(args[0],options);
37
- const reader=await team.agents.delegateReader(args[0],{expires_in_ms:86400000},{...options,ifMatch:identity.data.revision});
38
- const channel=await openMachine({machine:reach(args[1]),context:context??process.env.SUPERCODE_CONTEXT});
39
- try{const rpc=new StreamRpc(await channel.open('node'));result=await rpc.call('harness.v1.agents.context',{origin:scope.origin,team:scope.team_id,agent:args[0],credential:reader.data});}
40
- finally{channel.close();}
41
- }
42
- else if(op==='resolveIdentity')result=await team.identity.resolve(args[0],options);
43
- else if(op==='find'){
44
- let after='',found=null;
45
- do{
46
- const page=await team.agents.list({after,limit:200},options);
47
- found=page.items.find(agent=>agent.name===args[0]&&agent.parent===args[1]&&agent.state!=='ended')??null;
48
- if(found)break;after=page.next_after;
49
- }while(after);
50
- result={data:found};
51
- }else throw Object.assign(new Error('unsupported agent door'),{code:'invalid_request'});
52
- process.stdout.write(JSON.stringify(result)+'\n');
53
- }catch(error){process.stderr.write(JSON.stringify({status:error.status,code:error.code??'unavailable',message:error.message})+'\n');process.exitCode=1;}