@volter/supercode-orchestrator 0.5.207 → 0.5.208

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/loop.mjs CHANGED
@@ -4,13 +4,14 @@
4
4
  // This is the only module that performs I/O for the loop. It holds the single
5
5
  // state value, the vault, the SDK client, the adapters, and an injected clock.
6
6
 
7
- import { mkdirSync, writeFileSync, existsSync, readdirSync, readFileSync, renameSync, unlinkSync } from 'node:fs';
7
+ import { mkdirSync, writeFileSync, existsSync, readdirSync, readFileSync, renameSync, unlinkSync, realpathSync } from 'node:fs';
8
8
  import { basename, join, dirname } from 'node:path';
9
9
  import { step, leaseId } from './reducer.mjs';
10
10
  import * as events from './events.mjs';
11
- import { read, save, retainWorld, releaseWorld } from './orchestration.mjs';
11
+ import { read, save, retainWorld, releaseWorld, refreshVault } from './orchestration.mjs';
12
12
  import { INSTRUCTION_ARGUMENTS, NAME_ARGUMENTS, activationEnv, activationCwd, workerCwd, declareWorkerFolder, declareWorkerHome, approvalPolicy, sandboxArguments, limitArguments, materializePersona, materializeSkills, modelArguments, turnCounter, userHomeArguments, userHomePersona } from './activation.mjs';
13
- import { workerRoute } from './worker-route.mjs';
13
+ import { workerRoute, codexPaneRoute } from './worker-route.mjs';
14
+ import { projectCodexPaneResume } from './codex-pane-resume.mjs';
14
15
  import { hermesAccess, hermesApproval, untrustedServerVerdict, hermesToolEnv, memorySnapshot, mirrorSession, mirrorState, recordIncident, sealMirror } from './hermes-cli.mjs';
15
16
  import { hermesSessionKey, homeChannel } from './ir.mjs';
16
17
  import { surfaceKeyString } from './ir.mjs';
@@ -536,6 +537,50 @@ export class Orchestrator {
536
537
  return runtime.handle;
537
538
  }
538
539
 
540
+ /** A private local preparation door for an explicit headful mailbox main.
541
+ * It returns only launch metadata and the configured forward's stand-in key;
542
+ * it creates no hosted conversation, controller lease, process or first turn. */
543
+ async prepareMainPane({ directory, name, resume = null }) {
544
+ if (typeof directory !== 'string' || typeof name !== 'string' || !/^[A-Za-z0-9._-]+$/.test(name))
545
+ throw new Error('a headful main needs its exact profile directory and agent name');
546
+ const folder = realpathSync(directory);
547
+ const profiles = Object.values(this.state.profiles).filter(profile => {
548
+ try { return realpathSync(profile.dir) === folder; } catch { return false; }
549
+ });
550
+ if (!profiles.length) throw Object.assign(new Error('this home has no profile at that directory'), { code: 'not_this_home' });
551
+ if (profiles.length !== 1) throw new Error('the directory names more than one profile; no main was prepared');
552
+ const profile = profiles[0], worker = profile.worker;
553
+ if (worker?.harness !== 'codex') throw new Error('the selected main profile does not run Codex');
554
+ refreshVault(this.state, this.vault);
555
+ const route = codexPaneRoute(profile, worker, this.vault);
556
+ if (resume !== null) {
557
+ if (resume.harness !== 'codex' || typeof resume.session_id !== 'string' || resume.storage?.kind !== 'file')
558
+ throw new Error('a main resume needs its exact Codex transcript locator');
559
+ projectCodexPaneResume(route.env.CODEX_HOME, resume.storage.path, resume.session_id);
560
+ }
561
+ const context = process.env.SUPERCODE_CONTEXT, scope = agentScope(context);
562
+ const agent = await localProfileAgent({ root: this.state.root, org: scope.org, server: scope.server,
563
+ profiles: this.state.profiles, profile: profile.name, agent: name, declarations: this.state.agents });
564
+ const env = { ...route.env, ...agentEnvironment(agent, { root: this.state.root,
565
+ context: localReaderContext(scope.org, agent.identity.id) ?? undefined, main: true }) };
566
+ // The headful main uses the same profile-owned MCP door and identity
567
+ // preamble. The returned plan carries no configured server credentials.
568
+ mountOrchestratorMcp({ root: this.state.root, profile, harness: 'codex',
569
+ socketPath: this.bridge?.path ?? socketPathFor(this.state.root), vault: this.vault,
570
+ agentId: agent.identity.id });
571
+ const instructions = agentPreamble(agent, { main: true,
572
+ previous: localAgentSessions(this.state.root, scope.org, agent.identity.id) });
573
+ const arguments_ = [...route.args, ...INSTRUCTION_ARGUMENTS.codex(instructions)];
574
+ if (resume === null) {
575
+ arguments_.push(...modelArguments({ ...worker, model: route.model }), '--ask-for-approval', approvalPolicy(profile, 'codex'));
576
+ arguments_.push(...sandboxArguments(profile, 'codex'));
577
+ if (worker.reasoning_effort) arguments_.push('-c', `model_reasoning_effort=${JSON.stringify(worker.reasoning_effort)}`);
578
+ }
579
+ declareWorkerHome({ harness: 'codex', env, cwd: folder, log: (level, text) => this.log(level, text) });
580
+ return { ran: `prepared a headful main for profile ${profile.name}`, program: 'codex',
581
+ profile: profile.name, cwd: folder, arguments: arguments_, env };
582
+ }
583
+
539
584
  /**
540
585
  * A new concurrent session of this profile's agent, asked for on its behalf (operator `sessions.launch`): the agent is
541
586
  * the one this home holds for the profile, and must be the one asked for. The session is composed by startRuntime
@@ -570,6 +615,8 @@ export class Orchestrator {
570
615
  const native=prior.session_ref.slice(prior.session_ref.indexOf(':')+1);
571
616
  const page=await this.client.discover({harnesses:[locator.harness],query:native,limit:200});
572
617
  target=page.sessions.find(item=>item.locator.harness===locator.harness&&item.locator.session_id===native)?.locator;
618
+ // a source still being read is no proof the retained session is gone: no replacement is made either way
619
+ if(!target&&page.reconciling?.length)throw new Error(`retained fork session cannot be confirmed yet: the session index is still reading ${page.reconciling.join(', ')}; no replacement was created`);
573
620
  if(!target)throw new Error('retained fork session is not available; no replacement was created');
574
621
  }else {
575
622
  const loaded=await this.client.loadWindow(locator,{message_limit:1});
@@ -814,6 +861,7 @@ export class Orchestrator {
814
861
  if (locator.storage) return locator;
815
862
  const found = await this.client.request('harness.v1.sessions.discover', { query: locator.session_id, harnesses: [locator.harness], limit: 200 });
816
863
  const whole = found.sessions?.find((item) => item.locator?.harness === locator.harness && item.locator?.session_id === locator.session_id)?.locator;
864
+ if (!whole && found.reconciling?.length) throw new Error(`${locator.harness}:${locator.session_id} cannot be found yet: the session index is still reading ${found.reconciling.join(', ')}`);
817
865
  if (!whole) throw new Error(`${locator.harness}:${locator.session_id} is not in its harness catalogue`);
818
866
  return whole;
819
867
  }
package/mcp/bridge.mjs CHANGED
@@ -123,7 +123,10 @@ async function dispatch(orch, call, log) {
123
123
  async function dispatchOperator(orch, { op, args, profile }, log) {
124
124
  const name = typeof profile === 'string' && profile ? profile : 'default';
125
125
  let answer;
126
- if (op === 'agents.fork') {
126
+ if (op === 'agents.main_plan') {
127
+ try { answer = { ok: true, result: await orch.prepareMainPane(args ?? {}) }; }
128
+ catch (error) { answer = { ok: false, error: error.message, ...(error.code ? { code: error.code } : {}) }; }
129
+ } else if (op === 'agents.fork') {
127
130
  // Operation `fork`: a declared child agent, its profile a copy of the parent's, its session a fork of the parent's.
128
131
  try {
129
132
  const a = args ?? {};
@@ -46,7 +46,11 @@
46
46
  },
47
47
  "include_archived": {
48
48
  "type": "boolean",
49
- "description": "Include archived tasks. Defaults to false."
49
+ "description": "Include cancelled and archived cards and their tasks. Defaults to false."
50
+ },
51
+ "include_subtasks": {
52
+ "type": "boolean",
53
+ "description": "Include subtasks as separate rows. Defaults to false; use kanban_show to read a card's tasks inside it."
50
54
  },
51
55
  "limit": {
52
56
  "type": "integer",
package/ontology.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  // GENERATED from `supercode ontology schema` — do not edit; run node sdk/orchestrator/scripts/generate-types.mjs
2
2
  //
3
- // The Rust records in `crates/interchange/src/world/` and
4
- // `crates/interchange/src/ontology/` are the source of truth (`docs/ONTOLOGY.md` §2).
3
+ // The committed `sdk/interchange/ontology-schemas.json` contracts own these types.
4
+ // Native serde records remain only with the capabilities still using them.
5
5
 
6
6
  /** The access record, as Hermes's stores hold it. */
7
7
  export interface Access {
@@ -45,8 +45,6 @@ export interface AgentDecl {
45
45
  label?: string | null;
46
46
  /** The agent it is declared under, if not the home's root. */
47
47
  parent?: string | null;
48
- /** A person set on purpose as its sponsor; absent, the sponsor is inherited. */
49
- sponsor?: string | null;
50
48
  /** What a source format said about it that the IR has no field for, verbatim. */
51
49
  residue?: Record<string, unknown>;
52
50
  }
@@ -115,7 +113,7 @@ export interface Binding {
115
113
 
116
114
  /**
117
115
  * A per-action gate on a profile's agents: a start or resume over it is refused, with the reason,
118
- * and the refusal is told to the manager and the sponsor. It never pauses or ends anything itself.
116
+ * and the refusal is told to the manager. It never pauses or ends anything itself.
119
117
  */
120
118
  export interface Budget {
121
119
  /** The window. */
@@ -390,19 +388,7 @@ export type FireStatus =
390
388
  | 'unknown';
391
389
 
392
390
  /** Observations a role can subscribe to, separate from a card's transition events. */
393
- export type FleetEventKind =
394
- /** A native session process first observed running. */
395
- | 'session_started'
396
- /** A known native session observed running in a new process incarnation. */
397
- | 'session_resumed'
398
- /** A native source reports a session process ended; unavailable sources are not ends. */
399
- | 'session_ended'
400
- /** More than one observed live process incarnation names the same native session. */
401
- | 'duplicate_session_process'
402
- /** A card's native session reports that it needs its user's input. */
403
- | 'card_waiting_on_user'
404
- /** A machine's health source reports a critical alarm. */
405
- | 'machine_health_crit';
391
+ export type FleetEventKind = 'session_started' | 'session_resumed' | 'session_ended' | 'duplicate_session_process' | 'card_waiting_on_user' | 'machine_health_crit';
406
392
 
407
393
  /** A conversation moved (or moving) to another surface — Hermes `handoff_*`. */
408
394
  export interface Handoff {
@@ -836,7 +822,7 @@ export type SecretRef =
836
822
  /** A key in the home's own `.env` file. */
837
823
  | { dotenv: string };
838
824
 
839
- /** A session's usage, kept on the session and summed upward (session → agent → sponsor → Org). */
825
+ /** A session's usage, kept on the session and summed upward (session → agent → Org). */
840
826
  export interface SessionUsage {
841
827
  /** The agent the session belongs to, when it has one. */
842
828
  agent?: string | null;
@@ -1134,11 +1120,6 @@ export interface Workflow {
1134
1120
  params?: Record<string, unknown>;
1135
1121
  /** Who acts on a card, each an expression naming a profile (`implementer: card.assignee`) or a person. */
1136
1122
  roles?: Record<string, Expr>;
1137
- /**
1138
- * Opt-in fleet observations delivered as mail to each role's declared addresses. These are not card
1139
- * transitions and grant no observation or machine authority. The manager is an implicit workflow role.
1140
- */
1141
- listeners?: Record<string, RoleFleetListener>;
1142
1123
  /** Where a new card starts: the first transition whose guard holds. */
1143
1124
  initial: Transition[];
1144
1125
  /** Every status, by name. */
@@ -1152,4 +1133,6 @@ export interface Workflow {
1152
1133
  * are), the event kinds it hears and the template each is told in. A kind not named is not sent.
1153
1134
  */
1154
1135
  notify?: Record<string, Record<string, string>>;
1136
+ /** Role fleet listeners; a declaration grants no machine or mailbox authority. */
1137
+ listeners?: Record<string, RoleFleetListener>;
1155
1138
  }
package/orchestration.mjs CHANGED
@@ -150,7 +150,7 @@ export async function load(root) {
150
150
  const { orchestration, vault_keys } = await through((d) => d.orchestrationLoad({ root }));
151
151
  let vault = readVault(orchestration);
152
152
  if (vault_keys.some((k) => !vault.has(k))) {
153
- await through((d) => d.orchestrationSave({ root, orchestration }));
153
+ await through((d) => d.orchestrationSave({ root, orchestration, preserve_workflow: true }));
154
154
  vault = readVault(orchestration);
155
155
  const missing = vault_keys.filter((k) => !vault.has(k));
156
156
  if (missing.length) throw new Error(`${root}: ${missing.join(', ')} referenced but not in .env after normalization`);
@@ -164,12 +164,15 @@ export async function read(root) {
164
164
  }
165
165
 
166
166
  /**
167
- * `save(state, root)` — our folder, canonical and byte-stable: an artifact whose record has not changed keeps its bytes.
167
+ * `save(state, root)` — persist runtime state, preserving the home's workflow.yaml.
168
+ * Board policy belongs to the live home, not the runtime's earlier snapshot. Full
169
+ * home creation/replacement uses orchestrationSave or importFrom explicitly.
170
+ * Other artifacts are canonical and byte-stable when their record has not changed.
168
171
  * Answers the entries the door could not read (`rejected`, each `{ collection, key, reason, kept_stored }`): each was
169
172
  * written as the home last held it, or left out when it held none, and none refuses the rest of the save.
170
173
  */
171
174
  export async function save(state, root = state.root) {
172
- const saved = await through((d) => d.orchestrationSave({ root, orchestration: persistedView(state) }));
175
+ const saved = await through((d) => d.orchestrationSave({ root, orchestration: persistedView(state), preserve_workflow: true }));
173
176
  return { rejected: Array.isArray(saved?.rejected) ? saved.rejected : [] };
174
177
  }
175
178
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-orchestrator",
3
- "version": "0.5.207",
3
+ "version": "0.5.208",
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.344",
35
+ "@volter/supercode-teams": "^0.3.345",
36
36
  "@volter/supercode-terminal": "^0.3.10",
37
37
  "@volter/supercode-harness-sdk": "^0.3.104",
38
- "@volter/teams": "0.2.96",
38
+ "@volter/teams": "0.2.98",
39
39
  "@volter/ztrack": "^2.4.23",
40
40
  "yaml": "^2.9.0"
41
41
  },
@@ -61,5 +61,6 @@
61
61
  "board",
62
62
  "apply",
63
63
  "README.md"
64
- ]
64
+ ],
65
+ "gitHead": "db4a88b4d2f670654fa8e57dbc3e93c7daa8d2bb"
65
66
  }
package/worker-route.mjs CHANGED
@@ -27,9 +27,9 @@
27
27
  //
28
28
  // A route this table does not have is refused by name at launch, never run on the worker's default:
29
29
  // that would spend on a model nobody chose.
30
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
30
+ import { existsSync, lstatSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
31
31
  import { join } from 'node:path';
32
- import { MODEL_ARGUMENTS } from './activation.mjs';
32
+ import { MODEL_ARGUMENTS, materializePersona, materializeSkills } from './activation.mjs';
33
33
 
34
34
  /** The Codex provider id a Hermes provider is served under. */
35
35
  const PROVIDER_ID = { custom: 'hermes_custom', openai: 'openai', 'openai-codex': 'hermes_codex' };
@@ -75,6 +75,7 @@ export function workerRoute(profile, worker, vault) {
75
75
  const forward = vault?.get?.('HERMES_CODEX_BASE_URL') ?? process.env.HERMES_CODEX_BASE_URL;
76
76
  if (!forward) throw new Error('model.provider openai-codex needs the Codex forward (HERMES_CODEX_BASE_URL in the home\'s .env, which the kit\'s start writes); refused rather than run on a login the worker would have to hold');
77
77
  table.base_url = forward;
78
+ table.requires_openai_auth = false;
78
79
  table.env_key = LITERAL_KEY_ENV;
79
80
  env[LITERAL_KEY_ENV] = 'valve';
80
81
  writeProviderTable(join(profile.dir, 'codex'), id, table);
@@ -126,7 +127,7 @@ function writeProviderTable(home, id, table) {
126
127
  const file = join(home, 'config.toml');
127
128
  const text = existsSync(file) ? readFileSync(file, 'utf8') : '';
128
129
  const header = `[model_providers.${id}]`;
129
- const block = [header, ...Object.entries(table).map(([k, v]) => `${k} = ${JSON.stringify(String(v))}`)].join('\n');
130
+ const block = [header, ...Object.entries(table).map(([k, v]) => `${k} = ${typeof v === 'boolean' ? String(v) : JSON.stringify(String(v))}`)].join('\n');
130
131
  const lines = text.split('\n');
131
132
  const start = lines.findIndex((line) => line.trim() === header);
132
133
  let next;
@@ -138,3 +139,30 @@ function writeProviderTable(home, id, table) {
138
139
  }
139
140
  if (next !== text) writeFileSync(file, next, { mode: 0o600 });
140
141
  }
142
+
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
+ throw new Error('headful Codex requires model.provider openai-codex and its configured HERMES_CODEX_BASE_URL; no shared-login pane was launched');
149
+ if (worker.home === 'user') throw new Error('headful Codex requires a profile home; no shared-login pane was launched');
150
+ const forward = vault?.get?.('HERMES_CODEX_BASE_URL') ?? process.env.HERMES_CODEX_BASE_URL;
151
+ if (!forward) throw new Error('headful Codex requires the configured HERMES_CODEX_BASE_URL; no shared-login pane was launched');
152
+ let endpoint;
153
+ try { endpoint = new URL(forward); } catch { throw new Error('the configured Codex forward is not an HTTP endpoint; no pane was launched'); }
154
+ if (!['http:', 'https:'].includes(endpoint.protocol) || endpoint.username || endpoint.password)
155
+ throw new Error('the configured Codex forward must be HTTP(S) without URL credentials; no pane was launched');
156
+ const home = join(profile.dir, 'codex');
157
+ if (Object.hasOwn(worker.env ?? {}, 'CODEX_HOME') && worker.env.CODEX_HOME !== home)
158
+ throw new Error('headful Codex has a different worker.env.CODEX_HOME; no shared-login pane was launched');
159
+ // lstat includes a dangling auth symlink. Do not read, unlink or migrate it.
160
+ try {
161
+ lstatSync(join(home, 'auth.json'));
162
+ throw new Error('the headful Codex profile home holds an auth.json entry; a login-free home is required and no credential was changed');
163
+ } catch (error) { if (error.code !== 'ENOENT') throw error; }
164
+ materializePersona(profile);
165
+ materializeSkills(profile);
166
+ const route = workerRoute(profile, worker, vault);
167
+ return { ...route, env: { CODEX_HOME: home, ...route.env } };
168
+ }