@volter/supercode-orchestrator 0.5.210 → 0.5.212
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/README.md +15 -1
- package/agents.mjs +11 -2
- package/bin/orchestrator.mjs +5 -0
- package/board/agent-declarations.mjs +18 -1
- package/board/attachment-write.mjs +68 -0
- package/board/call.mjs +2 -1
- package/board/capacity.mjs +54 -41
- package/board/ci.mjs +58 -29
- package/board/cli.mjs +51 -6
- package/board/credential-guard.mjs +15 -2
- package/board/dispatch.mjs +122 -40
- package/board/engine.mjs +24 -13
- package/board/fleet-listeners.mjs +16 -5
- package/board/published.mjs +18 -17
- package/board/queries.mjs +30 -0
- package/board/store.mjs +18 -2
- package/board/workflow.mjs +42 -16
- package/board/workspace.mjs +21 -10
- package/board-input.d.ts +2 -0
- package/board-input.mjs +33 -1
- package/hermes-cli.mjs +7 -19
- package/index.d.ts +1 -1
- package/loop.mjs +11 -1
- package/mcp/mount.mjs +22 -14
- package/orchestration.mjs +29 -2
- package/package.json +4 -5
package/mcp/mount.mjs
CHANGED
|
@@ -24,13 +24,19 @@ import { SOCKET_ENV, socketPathFor } from './server.mjs';
|
|
|
24
24
|
import { mcpServers } from '../hermes-home.mjs';
|
|
25
25
|
import { KANBAN_ENV } from '../hermes-cli.mjs';
|
|
26
26
|
import { installedAgentRecord } from '../installed-apps.mjs';
|
|
27
|
+
import { supercodeHome } from '@volter/teams/home';
|
|
27
28
|
|
|
28
29
|
export { MCP_DOORS, doorFor } from '@volter/supercode-harness-sdk/mcp-doors';
|
|
29
30
|
|
|
30
|
-
/** The stdio server this
|
|
31
|
+
/** The stdio server `supercode orchestrator mcp-server` runs (the mount names that door, never this path). */
|
|
31
32
|
export const SERVER_PATH = resolve(dirname(fileURLToPath(import.meta.url)), 'server.mjs');
|
|
32
|
-
/** The
|
|
33
|
-
|
|
33
|
+
/** The machine's stable `supercode` (the source pin's shim in the supercode home, else the one on PATH), which runs
|
|
34
|
+
* every program a session mounts (`orchestrator mcp-server`, `orchestrator apps mcp`): a config that names it names no
|
|
35
|
+
* release, so the pin that retires a tree leaves it working. */
|
|
36
|
+
function stableSupercode() {
|
|
37
|
+
const shim = join(supercodeHome(), 'bin', process.platform === 'win32' ? 'supercode.cmd' : 'supercode');
|
|
38
|
+
return existsSync(shim) ? shim : 'supercode';
|
|
39
|
+
}
|
|
34
40
|
/** The MCP server an agent with an installed rh2-agent gets: `rh2 mcp` as that agent (its own name, so the user's own
|
|
35
41
|
* `rh2` server, whatever it holds, is neither replaced nor relied on). */
|
|
36
42
|
export const RH2_AGENT_SERVER = 'rh2-agent';
|
|
@@ -40,7 +46,7 @@ export const RH2_AGENT_SERVER = 'rh2-agent';
|
|
|
40
46
|
* Teams id): a record is found by that id alone, so an agent sharing another's name is never mounted with its record. */
|
|
41
47
|
function rh2AgentServer(root, agentId, program) {
|
|
42
48
|
if (!installedAgentRecord(root, agentId)) return null;
|
|
43
|
-
return { command:
|
|
49
|
+
return { command: stableSupercode(), args: ['orchestrator', 'apps', 'mcp', 'rh2-agent', '--root', root, '--agent', agentId, '--node', program] };
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
const slug = (surfaceKey) => (surfaceKey ? String(surfaceKey).replace(/[^A-Za-z0-9_-]+/g, '-').replace(/^-+|-+$/g, '') || 'session' : 'session');
|
|
@@ -55,13 +61,12 @@ const slug = (surfaceKey) => (surfaceKey ? String(surfaceKey).replace(/[^A-Za-z0
|
|
|
55
61
|
* @param {string|null} o.surfaceKey the surface this session is bound to
|
|
56
62
|
* @param {object|undefined} o.launch the launch the loop resolved, if any
|
|
57
63
|
* @param {string} [o.socketPath] defaults to `<root>/orchestrator.sock`
|
|
58
|
-
* @param {string} [o.
|
|
59
|
-
* @param {string} [o.program] the interpreter, defaults to this node
|
|
64
|
+
* @param {string} [o.program] the node the server runs with, defaults to this node
|
|
60
65
|
* @param {string|null} [o.agentId] the Teams id of the agent whose own session this is, whose installed rh2-agent is
|
|
61
66
|
* mounted beside; null for a worker acting for an agent
|
|
62
67
|
* @returns {{door: string, ran: string, launch: object|undefined, mcp_servers: object[]|null, path: string|null}}
|
|
63
68
|
*/
|
|
64
|
-
export function mountOrchestratorMcp({ root, profile, harness, surfaceKey = null, launch, socketPath,
|
|
69
|
+
export function mountOrchestratorMcp({ root, profile, harness, surfaceKey = null, launch, socketPath, program = process.execPath, vault = new Map(), task = null, agentId = null }) {
|
|
65
70
|
const door = doorFor(harness);
|
|
66
71
|
// the profile's own servers (`.mcp.json`, the workers' form; docs/plans/hermes-compat.md row 4), mounted
|
|
67
72
|
// beside this one through the same door, `${VAR}` resolved from the profile's vault
|
|
@@ -81,6 +86,9 @@ export function mountOrchestratorMcp({ root, profile, harness, surfaceKey = null
|
|
|
81
86
|
// board's tools for it (docs/plans/hermes-compat.md row 14)
|
|
82
87
|
const sessionEnv = { ...env, ...(surfaceKey ? { [ORCH_ENV.surface]: surfaceKey } : {}), ...(task ?? {}) };
|
|
83
88
|
|
|
89
|
+
// the server through the stable supercode, never this release's server.mjs: a pin that retires this tree leaves the
|
|
90
|
+
// session's next server start (a resume, a reconnect) working
|
|
91
|
+
const server = { command: stableSupercode(), args: ['orchestrator', 'mcp-server', '--node', program] };
|
|
84
92
|
switch (door) {
|
|
85
93
|
case 'flag': {
|
|
86
94
|
if (!launch) {
|
|
@@ -88,14 +96,14 @@ export function mountOrchestratorMcp({ root, profile, harness, surfaceKey = null
|
|
|
88
96
|
}
|
|
89
97
|
const file = join(home, `mcp-orchestrator-${slug(surfaceKey)}.json`);
|
|
90
98
|
mkdirSync(home, { recursive: true });
|
|
91
|
-
writeMcpConfigFile(file, { ...own, [SERVER_NAME]: {
|
|
99
|
+
writeMcpConfigFile(file, { ...own, [SERVER_NAME]: { ...server, env: sessionEnv } });
|
|
92
100
|
return { door, ran: `wrote ${file} and passed --mcp-config`, launch: withMcpConfigFlag(launch, file), mcp_servers: null, path: file };
|
|
93
101
|
}
|
|
94
102
|
|
|
95
103
|
case 'toml': {
|
|
96
104
|
const file = join(home, 'config.toml');
|
|
97
105
|
mkdirSync(home, { recursive: true });
|
|
98
|
-
const block = tomlBlock(
|
|
106
|
+
const block = tomlBlock(server, env);
|
|
99
107
|
let text = upsertTomlBlock(existsSync(file) ? readFileSync(file, 'utf8') : '', block, SERVER_NAME);
|
|
100
108
|
const written = ownNames(home);
|
|
101
109
|
for (const name of written) if (!(name in own)) text = removeTomlBlock(text, name);
|
|
@@ -115,7 +123,7 @@ export function mountOrchestratorMcp({ root, profile, harness, surfaceKey = null
|
|
|
115
123
|
for (const name of ownNames(home)) if (!(name in own)) delete config.mcp[name];
|
|
116
124
|
for (const [name, server] of Object.entries(own)) config.mcp[name] = opencodeEntry(server);
|
|
117
125
|
recordOwnNames(home, Object.keys(own));
|
|
118
|
-
config.mcp[SERVER_NAME] = opencodeEntry({
|
|
126
|
+
config.mcp[SERVER_NAME] = opencodeEntry({ ...server, env });
|
|
119
127
|
writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
|
|
120
128
|
return { door, ran: `wrote mcp.${SERVER_NAME} into ${file}`, launch, mcp_servers: null, path: file };
|
|
121
129
|
}
|
|
@@ -131,7 +139,7 @@ export function mountOrchestratorMcp({ root, profile, harness, surfaceKey = null
|
|
|
131
139
|
launch,
|
|
132
140
|
mcp_servers: [
|
|
133
141
|
...Object.entries(own).filter(([, server]) => server.command).map(([name, server]) => acpEntry(name, server)),
|
|
134
|
-
acpEntry(SERVER_NAME, {
|
|
142
|
+
acpEntry(SERVER_NAME, { ...server, env: sessionEnv }),
|
|
135
143
|
],
|
|
136
144
|
path: null,
|
|
137
145
|
};
|
|
@@ -139,11 +147,11 @@ export function mountOrchestratorMcp({ root, profile, harness, surfaceKey = null
|
|
|
139
147
|
}
|
|
140
148
|
|
|
141
149
|
/** The orchestrator's own `[mcp_servers.orchestrator]` block. */
|
|
142
|
-
function tomlBlock(
|
|
150
|
+
function tomlBlock({ command, args }, env) {
|
|
143
151
|
const lines = [
|
|
144
152
|
`[mcp_servers.${SERVER_NAME}]`,
|
|
145
|
-
`command = ${tomlString(
|
|
146
|
-
`args = [${tomlString(
|
|
153
|
+
`command = ${tomlString(command)}`,
|
|
154
|
+
`args = [${args.map(tomlString).join(', ')}]`,
|
|
147
155
|
];
|
|
148
156
|
for (const [k, v] of Object.entries(env)) lines.push(`env.${k} = ${tomlString(v)}`);
|
|
149
157
|
// The per-session half rides codex's own env allowlist: the server is a child
|
package/orchestration.mjs
CHANGED
|
@@ -28,6 +28,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
28
28
|
import { SupercodeHarnessClient } from '@volter/supercode-harness-sdk';
|
|
29
29
|
import { emptyRuntime, persistedView } from './ir.mjs';
|
|
30
30
|
import { lockedNative } from './source-lock.mjs';
|
|
31
|
+
import { SESSION_READ_REQUEST_UNITS, stringifySessionWire } from '@volter/supercode-teams/session-read-client';
|
|
31
32
|
|
|
32
33
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
33
34
|
|
|
@@ -150,7 +151,7 @@ export async function load(root) {
|
|
|
150
151
|
const { orchestration, vault_keys } = await through((d) => d.orchestrationLoad({ root }));
|
|
151
152
|
let vault = readVault(orchestration);
|
|
152
153
|
if (vault_keys.some((k) => !vault.has(k))) {
|
|
153
|
-
await
|
|
154
|
+
await save(stateOf(orchestration), root);
|
|
154
155
|
vault = readVault(orchestration);
|
|
155
156
|
const missing = vault_keys.filter((k) => !vault.has(k));
|
|
156
157
|
if (missing.length) throw new Error(`${root}: ${missing.join(', ')} referenced but not in .env after normalization`);
|
|
@@ -172,10 +173,36 @@ export async function read(root) {
|
|
|
172
173
|
* written as the home last held it, or left out when it held none, and none refuses the rest of the save.
|
|
173
174
|
*/
|
|
174
175
|
export async function save(state, root = state.root) {
|
|
175
|
-
const
|
|
176
|
+
const params = boundedRuntimeSave(state, root);
|
|
177
|
+
const saved = await through((d) => d.orchestrationSave(params));
|
|
178
|
+
// Apply retention in memory only after the owning door accepted it.
|
|
179
|
+
for (const [name, profile] of Object.entries(params.orchestration.profiles)) state.profiles[name].fires = profile.fires;
|
|
176
180
|
return { rejected: Array.isArray(saved?.rejected) ? saved.rejected : [] };
|
|
177
181
|
}
|
|
178
182
|
|
|
183
|
+
// A per-profile row count cannot bound the whole home's request. Keep active fires and all configuration, and
|
|
184
|
+
// remove the oldest terminal history across profiles until the actual native wire value fits, with envelope room.
|
|
185
|
+
export function boundedRuntimeSave(state, root = state.root) {
|
|
186
|
+
const orchestration = { ...persistedView(state), profiles: Object.fromEntries(Object.entries(state.profiles).map(([name, profile]) => [name, { ...profile, fires: [...profile.fires] }])) };
|
|
187
|
+
const params = { root, orchestration, preserve_workflow: true };
|
|
188
|
+
const budget = SESSION_READ_REQUEST_UNITS - 16 * 1024;
|
|
189
|
+
let units = Buffer.byteLength(stringifySessionWire(params));
|
|
190
|
+
if (units <= budget) return params;
|
|
191
|
+
const terminal = Object.entries(orchestration.profiles).flatMap(([name, profile]) => profile.fires
|
|
192
|
+
.filter(fire => !['claimed', 'running'].includes(fire.status)).map(fire => ({ name, fire })));
|
|
193
|
+
terminal.sort((a, b) => String(a.fire.claimed_at ?? '').localeCompare(String(b.fire.claimed_at ?? '')) || String(a.fire.id).localeCompare(String(b.fire.id)));
|
|
194
|
+
const drop = new Set();
|
|
195
|
+
for (const { fire } of terminal) {
|
|
196
|
+
if (units <= budget) break;
|
|
197
|
+
drop.add(fire);
|
|
198
|
+
units -= Buffer.byteLength(stringifySessionWire(fire)) + 1;
|
|
199
|
+
}
|
|
200
|
+
for (const profile of Object.values(orchestration.profiles)) profile.fires = profile.fires.filter(fire => !drop.has(fire));
|
|
201
|
+
const actual = Buffer.byteLength(stringifySessionWire(params));
|
|
202
|
+
if (actual > budget) throw new Error(`orchestrator runtime save needs ${actual} bytes after terminal-history retention (limit ${budget}); configuration and active jobs were preserved; no execution was started`);
|
|
203
|
+
return params;
|
|
204
|
+
}
|
|
205
|
+
|
|
179
206
|
/** Another harness's home as a state value (no secret value; `vault_keys` names the `.env` keys it would need). */
|
|
180
207
|
export async function compile(from, home) {
|
|
181
208
|
const { orchestration, vault_keys } = await through((d) => d.orchestrationCompile({ from, home }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-orchestrator",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.212",
|
|
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": {
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
"check-types": "node scripts/generate-types.mjs --check"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@volter/supercode-teams": "^0.3.
|
|
35
|
+
"@volter/supercode-teams": "^0.3.347",
|
|
36
36
|
"@volter/supercode-terminal": "^0.3.10",
|
|
37
37
|
"@volter/supercode-harness-sdk": "^0.3.104",
|
|
38
|
-
"@volter/teams": "0.2.
|
|
38
|
+
"@volter/teams": "0.2.99",
|
|
39
39
|
"@volter/ztrack": "^2.4.23",
|
|
40
40
|
"yaml": "^2.9.0"
|
|
41
41
|
},
|
|
@@ -61,6 +61,5 @@
|
|
|
61
61
|
"board",
|
|
62
62
|
"apply",
|
|
63
63
|
"README.md"
|
|
64
|
-
]
|
|
65
|
-
"gitHead": "b2585dc5c6b40d48be96fd3c4148f2c53d7a7ef2"
|
|
64
|
+
]
|
|
66
65
|
}
|