@volter/supercode-orchestrator 0.5.69 → 0.5.70
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/agent-door.mjs +75 -0
- package/agents.mjs +42 -23
- package/bin/orchestrator.mjs +1 -2
- package/board/agent-declarations.mjs +3 -1
- package/board/args.mjs +36 -0
- package/board/call.mjs +45 -0
- package/board/caller-session.mjs +25 -0
- package/board/cli.mjs +250 -120
- package/board/client.mjs +87 -0
- package/board/dispatch.mjs +70 -76
- package/board/doors.mjs +60 -0
- package/board/engine.mjs +9 -5
- package/board/event-stream.mjs +7 -7
- package/board/fence.mjs +45 -0
- package/board/files.mjs +23 -3
- package/board/git-document.mjs +2 -1
- package/board/lock-holder.mjs +49 -0
- package/board/owner-files.mjs +19 -0
- package/board/published.mjs +106 -41
- package/board/store.mjs +38 -11
- package/board/workflow.mjs +109 -13
- package/board/ztrack.mjs +24 -14
- package/hand-off.mjs +2 -2
- package/index.d.ts +1 -1
- package/kanban-worker.mjs +11 -16
- package/loop.mjs +3 -2
- package/mcp/server.mjs +12 -9
- package/package.json +2 -2
- package/bin/agent-door.mjs +0 -53
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
|
|
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
|
-
|
|
100
|
-
|
|
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)
|
|
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 && !
|
|
108
|
-
|
|
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)
|
|
113
|
-
for (const error of beat.errors ?? [])
|
|
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)
|
|
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) {
|
|
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) {
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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 {
|
|
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 =
|
|
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 {
|
|
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
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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.
|
|
3
|
+
"version": "0.5.70",
|
|
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.
|
|
28
|
+
"@volter/teams": "0.2.23",
|
|
29
29
|
"@volter/ztrack": "^2.4.23",
|
|
30
30
|
"yaml": "^2.9.0"
|
|
31
31
|
},
|
package/bin/agent-door.mjs
DELETED
|
@@ -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;}
|