@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/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 module mounts. */
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 orchestrator's CLI, whose `apps mcp` runs `rh2 mcp` as an installed agent. */
33
- const ORCHESTRATOR_BIN = resolve(dirname(fileURLToPath(import.meta.url)), '../bin/orchestrator.mjs');
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: program, args: [ORCHESTRATOR_BIN, 'apps', 'mcp', 'rh2-agent', '--root', root, '--agent', agentId] };
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.serverPath] defaults to this package's `mcp/server.mjs`
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, serverPath = SERVER_PATH, program = process.execPath, vault = new Map(), task = null, agentId = null }) {
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]: { command: program, args: [serverPath], env: sessionEnv } });
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(program, serverPath, env);
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({ command: program, args: [serverPath], env });
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, { command: program, args: [serverPath], env: sessionEnv }),
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(program, serverPath, env) {
150
+ function tomlBlock({ command, args }, env) {
143
151
  const lines = [
144
152
  `[mcp_servers.${SERVER_NAME}]`,
145
- `command = ${tomlString(program)}`,
146
- `args = [${tomlString(serverPath)}]`,
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 through((d) => d.orchestrationSave({ root, orchestration, preserve_workflow: true }));
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 saved = await through((d) => d.orchestrationSave({ root, orchestration: persistedView(state), preserve_workflow: true }));
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.210",
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.346",
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.98",
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
  }