@volter/supercode-orchestrator 0.5.228 → 0.5.229

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.
Files changed (80) hide show
  1. package/README.md +18 -2
  2. package/activation.mjs +1 -1
  3. package/adapters/agent-mailbox.mjs +10 -0
  4. package/adapters/api-server.mjs +6 -0
  5. package/agents.mjs +149 -40
  6. package/apply/doors.mjs +3 -1
  7. package/bin/agent-channel.mjs +7 -2
  8. package/bin/connection-runner.mjs +60 -19
  9. package/bin/hermes-worker.mjs +2 -1
  10. package/bin/orchestrator.mjs +3 -3
  11. package/bin/runtime.mjs +3 -2
  12. package/board/agent-current.mjs +82 -0
  13. package/board/agent-declarations.mjs +10 -3
  14. package/board/args.mjs +137 -6
  15. package/board/call.mjs +16 -1
  16. package/board/caller-session.mjs +3 -1
  17. package/board/capacity.mjs +59 -45
  18. package/board/card-state.mjs +14 -5
  19. package/board/ci.mjs +521 -178
  20. package/board/cli.mjs +614 -209
  21. package/board/client.mjs +64 -21
  22. package/board/close-current.mjs +36 -0
  23. package/board/current-records.mjs +44 -0
  24. package/board/custody.mjs +2 -1
  25. package/board/dispatch.mjs +1973 -846
  26. package/board/doors.mjs +2 -2
  27. package/board/engine.mjs +623 -85
  28. package/board/event-stream.mjs +10 -5
  29. package/board/fence.mjs +2 -0
  30. package/board/files.mjs +93 -62
  31. package/board/generation-owner.mjs +188 -53
  32. package/board/git-document.mjs +3 -1
  33. package/board/github-rest.mjs +29 -7
  34. package/board/incidents.mjs +5 -17
  35. package/board/listing.mjs +14 -8
  36. package/board/local-admission.mjs +59 -0
  37. package/board/lock-holder.mjs +18 -6
  38. package/board/machine-fleet.mjs +66 -13
  39. package/board/owner-door-worker.mjs +3 -3
  40. package/board/owner-door.mjs +17 -7
  41. package/board/owner-health.mjs +12 -15
  42. package/board/owner-interface.mjs +6 -0
  43. package/board/publication-reader.mjs +50 -0
  44. package/board/published.mjs +40 -12
  45. package/board/queries.mjs +24 -4
  46. package/board/record-view.mjs +30 -3
  47. package/board/request-mail.mjs +37 -6
  48. package/board/resting-admission.mjs +318 -0
  49. package/board/resting-codec-qualifications.json +33 -0
  50. package/board/resting-protocol.mjs +71 -0
  51. package/board/resting-state-protocol.json +120 -0
  52. package/board/resting-state.mjs +113 -0
  53. package/board/runtime-door.mjs +2 -2
  54. package/board/session-continuity.mjs +127 -0
  55. package/board/session-work.mjs +25 -13
  56. package/board/standalone-declaration.mjs +24 -0
  57. package/board/storage-contract.mjs +129 -7
  58. package/board/store.mjs +73 -11
  59. package/board/tools.mjs +56 -13
  60. package/board/wake.mjs +18 -5
  61. package/board/workflow.mjs +284 -82
  62. package/board/workspace.mjs +86 -9
  63. package/board/ztrack.mjs +344 -91
  64. package/connection.mjs +72 -17
  65. package/gates.mjs +2 -1
  66. package/hand-off.mjs +4 -3
  67. package/hermes-cli.mjs +5 -3
  68. package/hermes-locks.mjs +5 -2
  69. package/hosted.mjs +2 -1
  70. package/index.d.ts +2 -0
  71. package/index.mjs +1 -1
  72. package/kanban-worker.mjs +63 -13
  73. package/loop.mjs +33 -0
  74. package/mcp/hermes-kanban-tools.json +109 -50
  75. package/mcp/server.mjs +7 -1
  76. package/ontology.d.ts +26 -0
  77. package/orchestration.mjs +15 -4
  78. package/package.json +19 -12
  79. package/session-recovery.mjs +2 -1
  80. package/worker-route.mjs +37 -19
package/orchestration.mjs CHANGED
@@ -25,8 +25,8 @@ import { existsSync, readFileSync } from 'node:fs';
25
25
  import { join } from 'node:path';
26
26
  import { SupercodeHarnessClient } from '@volter/supercode-harness-sdk';
27
27
  import { emptyRuntime, persistedView } from './ir.mjs';
28
- import { lockedProgram } from './source-lock.mjs';
29
- import { sourceRoot } from '@volter/supercode-teams/source-lock';
28
+ import { lockedProgram } from './source-lock.mjs'; // layer: the orchestration names the program it runs from its pinned source (the host's source lock); owed: the host hands it in
29
+ import { sourceRoot } from '@volter/supercode-teams/source-lock'; // layer: the orchestration names the program it runs from its pinned source (the host's source lock); owed: the host hands it in
30
30
  import { SESSION_READ_REQUEST_UNITS, stringifySessionWire } from '@volter/supercode-teams/session-read-client';
31
31
 
32
32
  /** Selected CLI for clients that need it: the locked Node entry, explicit
@@ -41,6 +41,9 @@ export function supercodeCommand() {
41
41
 
42
42
  const IDLE_MS = 250;
43
43
  let shared = null;
44
+ // A new door can open while its predecessor closes. Keep each close until it succeeds, so an explicit shutdown
45
+ // joins an idle close already in flight and still reports an earlier unconfirmed close.
46
+ const closing = new Map();
44
47
  let holders = 0;
45
48
  let inFlight = 0;
46
49
  let idle = null;
@@ -55,13 +58,21 @@ export async function closeWorld() {
55
58
  if (idle) { clearTimeout(idle); idle = null; }
56
59
  const client = shared;
57
60
  shared = null;
58
- if (client) await client.close();
61
+ if (client && !closing.has(client)) {
62
+ const work = Promise.resolve().then(() => client.close());
63
+ closing.set(client, work);
64
+ void work.then(() => closing.delete(client), () => { /* retained for the next explicit close */ });
65
+ }
66
+ const results = await Promise.allSettled([...closing.values()]);
67
+ const errors = results.filter(result => result.status === 'rejected').map(result => result.reason);
68
+ if (errors.length === 1) throw errors[0];
69
+ if (errors.length) throw new AggregateError(errors, 'orchestration door shutdown failed', { cause: errors[0] });
59
70
  }
60
71
 
61
72
  function settle() {
62
73
  if (idle) { clearTimeout(idle); idle = null; }
63
74
  if (holders > 0 || inFlight > 0 || !shared) return;
64
- idle = setTimeout(() => { idle = null; if (holders === 0 && inFlight === 0) void closeWorld(); }, IDLE_MS);
75
+ idle = setTimeout(() => { idle = null; if (holders === 0 && inFlight === 0) void closeWorld().catch(() => { /* retained for explicit shutdown */ }); }, IDLE_MS);
65
76
  idle.unref?.();
66
77
  }
67
78
 
package/package.json CHANGED
@@ -1,6 +1,13 @@
1
1
  {
2
2
  "name": "@volter/supercode-orchestrator",
3
- "version": "0.5.228",
3
+ "devEngines": {
4
+ "packageManager": {
5
+ "name": "pnpm",
6
+ "version": ">=12.15.0",
7
+ "onFail": "error"
8
+ }
9
+ },
10
+ "version": "0.5.229",
4
11
  "type": "module",
5
12
  "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
13
  "exports": {
@@ -31,16 +38,12 @@
31
38
  }
32
39
  },
33
40
  "types": "./index.d.ts",
34
- "scripts": {
35
- "generate-types": "node scripts/generate-types.mjs",
36
- "check-types": "node scripts/generate-types.mjs --check"
37
- },
38
41
  "dependencies": {
39
- "@volter/supercode-teams": "^0.3.344",
40
- "@volter/supercode-terminal": "^0.3.10",
41
- "@volter/supercode-harness-sdk": "^0.3.104",
42
- "@volter/teams": "0.2.110",
43
- "@volter/ztrack": "2.4.23",
42
+ "@volter/supercode-teams": "0.3.393",
43
+ "@volter/supercode-terminal": "0.3.15",
44
+ "@volter/supercode-harness-sdk": "0.3.112",
45
+ "@volter/teams": "0.2.112",
46
+ "@volter/ztrack": "3.0.1-reader.0",
44
47
  "yaml": "^2.9.0"
45
48
  },
46
49
  "devDependencies": {
@@ -65,5 +68,9 @@
65
68
  "board",
66
69
  "apply",
67
70
  "README.md"
68
- ]
69
- }
71
+ ],
72
+ "scripts": {
73
+ "generate-types": "node scripts/generate-types.mjs",
74
+ "check-types": "node scripts/generate-types.mjs --check"
75
+ }
76
+ }
@@ -1,5 +1,6 @@
1
1
  // The optional local-session retry policy. The machine owns observation and native input receipts;
2
2
  // this orchestrator command owns when to ask for another turn. One invocation is bounded to eight seconds.
3
+ import { readMachineRecord } from '@volter/teams/connector-records';
3
4
  import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';
4
5
  import { join, resolve } from 'node:path';
5
6
  import { randomUUID } from 'node:crypto';
@@ -75,7 +76,7 @@ export async function runSessionRecovery(args = []) {
75
76
  log({ ...summary, durationMs: Date.now() - started, error: 'recovery pass exceeded its eight-second deadline' });
76
77
  channel?.close(); owner?.close(); process.exit(1);
77
78
  }, 8500);
78
- const machine = JSON.parse(readFileSync(join(opts.home, 'teams', 'machine.json'), 'utf8'));
79
+ const machine = readMachineRecord(join(opts.home, 'teams'));
79
80
  channel = await openLocalMachine(machine.socket, { timeoutMs: 1000 });
80
81
  const rpc = new StreamRpc(await channel.open('node'));
81
82
  const call = (method, params, maximum = 1800) => {
package/worker-route.mjs CHANGED
@@ -60,13 +60,20 @@ export function hermesModel(profile) {
60
60
  * writing the provider table into the worker's config home. The launch's `worker.model` (a job's own
61
61
  * model, or the `worker:` block's) wins over `model.default`.
62
62
  */
63
- export function workerRoute(profile, worker, vault) {
63
+ export function workerRoute(profile, worker, vault, nativeHome = null) {
64
64
  const route = hermesModel(profile);
65
65
  // `model.default` reaches only a worker that takes a model at launch; one that reads the home's own
66
66
  // `model:` itself (Hermes over ACP) keeps it there
67
67
  const model = worker?.model ?? (MODEL_ARGUMENTS[worker?.harness] ? route?.model ?? null : null);
68
68
  if (route?.provider && worker?.harness === 'claude-code') return claudeRoute(profile, route, model, vault);
69
69
  if (!route?.provider || worker?.harness !== 'codex') return { model, args: [], env: {} };
70
+ const home = nativeHome?.env?.CODEX_HOME ?? join(profile.dir, 'codex');
71
+ const { id, table, env } = codexProvider(route, vault);
72
+ if (id !== 'openai' || route.base_url) writeProviderTable(home, id, table);
73
+ return { model, args: ['-c', `model_provider=${JSON.stringify(id)}`], env };
74
+ }
75
+
76
+ function codexProvider(route, vault) {
70
77
  const id = PROVIDER_ID[route.provider];
71
78
  if (!id) throw new Error(`model.provider ${route.provider} has no Codex route in this orchestrator yet; refused rather than run on Codex's default`);
72
79
  const env = {};
@@ -78,18 +85,16 @@ export function workerRoute(profile, worker, vault) {
78
85
  table.requires_openai_auth = false;
79
86
  table.env_key = LITERAL_KEY_ENV;
80
87
  env[LITERAL_KEY_ENV] = 'valve';
81
- writeProviderTable(join(profile.dir, 'codex'), id, table);
82
- return { model, args: ['-c', `model_provider=${JSON.stringify(id)}`], env };
88
+ } else {
89
+ if (route.base_url) table.base_url = resolve(route.base_url, vault).value;
90
+ if (route.api_key) {
91
+ const key = resolve(route.api_key, vault);
92
+ const name = key.name ?? LITERAL_KEY_ENV;
93
+ env[name] = key.value;
94
+ table.env_key = name;
95
+ }
83
96
  }
84
- if (route.base_url) table.base_url = resolve(route.base_url, vault).value;
85
- if (route.api_key) {
86
- const key = resolve(route.api_key, vault);
87
- const name = key.name ?? LITERAL_KEY_ENV;
88
- env[name] = key.value;
89
- table.env_key = name;
90
- }
91
- if (id !== 'openai' || route.base_url) writeProviderTable(join(profile.dir, 'codex'), id, table);
92
- return { model, args: ['-c', `model_provider=${JSON.stringify(id)}`], env };
97
+ return { id, table, env };
93
98
  }
94
99
 
95
100
  /**
@@ -140,11 +145,9 @@ function writeProviderTable(home, id, table) {
140
145
  if (next !== text) writeFileSync(file, next, { mode: 0o600 });
141
146
  }
142
147
 
143
- /** The headful Codex lane must never fall back to the shared ChatGPT login.
144
- * This returns only the configured forward's stand-in key and provider flags;
145
- * it neither copies a credential nor starts a program or login. */
146
- export function codexPaneRoute(profile, worker, vault) {
147
- if (worker?.harness !== 'codex' || hermesModel(profile)?.provider !== 'openai-codex')
148
+ function paneProvider(profile, worker, vault) {
149
+ const route = hermesModel(profile);
150
+ if (worker?.harness !== 'codex' || route?.provider !== 'openai-codex')
148
151
  throw new Error('headful Codex requires model.provider openai-codex and its configured HERMES_CODEX_BASE_URL; no shared-login pane was launched');
149
152
  if (worker.home === 'user') throw new Error('headful Codex requires a profile home; no shared-login pane was launched');
150
153
  const forward = vault?.get?.('HERMES_CODEX_BASE_URL') ?? process.env.HERMES_CODEX_BASE_URL;
@@ -153,6 +156,21 @@ export function codexPaneRoute(profile, worker, vault) {
153
156
  try { endpoint = new URL(forward); } catch { throw new Error('the configured Codex forward is not an HTTP endpoint; no pane was launched'); }
154
157
  if (!['http:', 'https:'].includes(endpoint.protocol) || endpoint.username || endpoint.password)
155
158
  throw new Error('the configured Codex forward must be HTTP(S) without URL credentials; no pane was launched');
159
+ return { model: worker.model ?? route.model ?? null, ...codexProvider(route, vault) };
160
+ }
161
+
162
+ /** A login-free Codex route carried entirely in native launch arguments. */
163
+ export function codexPaneRouteArguments(profile, worker, vault) {
164
+ const { id, table, env } = paneProvider(profile, worker, vault);
165
+ return { env, args: ['-c', `model_provider=${JSON.stringify(id)}`,
166
+ ...Object.entries(table).flatMap(([name, value]) => ['-c', `model_providers.${id}.${name}=${JSON.stringify(value)}`])] };
167
+ }
168
+
169
+ /** The headful Codex lane must never fall back to the shared ChatGPT login.
170
+ * This returns only the configured forward's stand-in key and provider flags;
171
+ * it neither copies a credential nor starts a program or login. */
172
+ export function codexPaneRoute(profile, worker, vault) {
173
+ const { model, id, table, env } = paneProvider(profile, worker, vault);
156
174
  const home = join(profile.dir, 'codex');
157
175
  if (Object.hasOwn(worker.env ?? {}, 'CODEX_HOME') && worker.env.CODEX_HOME !== home)
158
176
  throw new Error('headful Codex has a different worker.env.CODEX_HOME; no shared-login pane was launched');
@@ -163,6 +181,6 @@ export function codexPaneRoute(profile, worker, vault) {
163
181
  } catch (error) { if (error.code !== 'ENOENT') throw error; }
164
182
  materializePersona(profile);
165
183
  materializeSkills(profile);
166
- const route = workerRoute(profile, worker, vault);
167
- return { ...route, env: { CODEX_HOME: home, ...route.env } };
184
+ writeProviderTable(home, id, table);
185
+ return { model, args: ['-c', `model_provider=${JSON.stringify(id)}`], env: { CODEX_HOME: home, ...env } };
168
186
  }