@volter/supercode-orchestrator 0.5.6 → 0.5.7

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/hermes-cli.mjs CHANGED
@@ -336,7 +336,8 @@ export function hermesTool(profileDir, name, args, { sessionId = null, env = nul
336
336
  * other tool (Hermes's file tools are not gated); a dangerous one is asked in a conversation (unless
337
337
  * `approvals.mode: off`), and answered by `approvals.cron_mode` in a fire and `approvals.single_query_mode`
338
338
  * in a dispatched worker, each deny by default. Answers `{ verdict: allow|ask|deny, why }`, or null without
339
- * Hermes (the worker's own prompt is then relayed as it stands).
339
+ * Hermes (the worker's own prompt is then relayed as it stands). A request to widen the worker's own sandbox
340
+ * is never asked here (loop.mjs `widensSandbox`): this rule cannot see it.
340
341
  */
341
342
  export async function hermesApproval(profileDir, { command = null, context = 'conversation' } = {}) {
342
343
  if (!hermesInstall()?.python) return null;
package/kanban-worker.mjs CHANGED
@@ -14,10 +14,10 @@ import { SupercodeHarnessClient } from '@volter/supercode-harness-sdk';
14
14
  import { load } from './orchestration.mjs';
15
15
  import { INSTRUCTION_ARGUMENTS, activationCwd, activationEnv, sandboxArguments, approvalPolicy, limitArguments, materializePersona, materializeSkills, modelArguments, turnCounter, userHomeArguments, userHomePersona } from './activation.mjs';
16
16
  import { workerRoute } from './worker-route.mjs';
17
- import { KANBAN_ENV, untrustedServerVerdict, hermesInstall, memorySnapshot } from './hermes-cli.mjs';
17
+ import { KANBAN_ENV, untrustedServerVerdict, hermesApproval, hermesInstall, memorySnapshot } from './hermes-cli.mjs';
18
18
  import { pinnedSkills, taskProtocol } from './board/worker-prompt.mjs';
19
19
  import { mountOrchestratorMcp } from './mcp/mount.mjs';
20
- import { normalizeWorkerEvent } from './loop.mjs';
20
+ import { normalizeWorkerEvent, widensSandbox } from './loop.mjs';
21
21
  import { DEFAULT_BOARD, json, openBoard, tx } from './board/store.mjs';
22
22
  import { setRunSession } from './board/engine.mjs';
23
23
  import { permissionResponse } from './reducer.mjs';
@@ -112,12 +112,20 @@ async function runTurn({ client, profile, harness, worker, route, root, vault, s
112
112
  if (w.kind === 'final' && counter) w = counter.settle(w);
113
113
  if (w.kind === 'text') text += w.block && text ? `\n\n${w.delta}` : w.delta;
114
114
  else if (w.kind === 'request') {
115
- // nobody attends a dispatched worker: an untrusted server's tool is denied, anything else is answered by the
116
- // profile's `worker.permission.unattended` (deny unless it says approve)
117
- const gated = untrustedServerVerdict(profile, w.tool, 'single_query', { mcp: w.mcp && !String(w.tool ?? '').startsWith('mcp__orchestrator__') });
118
- const approve = gated ? gated.verdict === 'allow' : profile.worker?.permission?.unattended === 'approve';
119
- say(`hermes-worker: ${approve ? 'approved' : 'denied'} a permission request with nobody attending (${gated ? gated.why : 'worker.permission.unattended'}): ${w.subject}`);
120
- runtime.respond(JSON.parse(w.id), permissionResponse(w.options, approve, gated?.message ?? (approve ? null : 'Denied: nobody is attending this run to approve it (worker.permission.unattended). Find an alternative approach that avoids this action.'))).catch(() => {});
115
+ // nobody attends a dispatched worker: an untrusted server's tool is denied; anything else is answered by Hermes's
116
+ // approval rule in single-query mode, as the daemon's path asks it (loop.mjs): a safe command runs, a dangerous
117
+ // one is answered by `approvals.single_query_mode` (deny by default). A request to widen the worker's own
118
+ // sandbox (`widensSandbox`), and any request without Hermes, is answered by the profile's
119
+ // `worker.permission.unattended` (deny unless it says approve): Hermes's rule cannot see those.
120
+ void (async () => {
121
+ const gated = untrustedServerVerdict(profile, w.tool, 'single_query', { mcp: w.mcp && !String(w.tool ?? '').startsWith('mcp__orchestrator__') });
122
+ const hermes = gated || widensSandbox(w) ? null : await hermesApproval(profile.dir, { command: w.command ?? null, context: 'single_query' })
123
+ .catch((error) => { say(`hermes-worker: Hermes's approval rule: ${error.message}`); return null; });
124
+ const approve = gated ? gated.verdict === 'allow' : hermes ? hermes.verdict === 'allow' : profile.worker?.permission?.unattended === 'approve';
125
+ const rule = gated ? gated.why : hermes ? `Hermes: ${hermes.verdict === 'allow' ? 'not a dangerous command' : hermes.why ?? 'a dangerous command'}` : 'worker.permission.unattended';
126
+ say(`hermes-worker: ${approve ? 'approved' : 'denied'} a permission request with nobody attending (${rule}): ${w.subject}`);
127
+ await runtime.respond(JSON.parse(w.id), permissionResponse(w.options, approve, gated?.message ?? hermes?.message ?? (approve ? null : 'Denied: nobody is attending this run to approve it (worker.permission.unattended). Find an alternative approach that avoids this action.')));
128
+ })().catch(() => {});
121
129
  } else if (w.kind === 'final') {
122
130
  if (w.outcome === 'error') say(`hermes-worker: the turn failed: ${w.message ?? 'no reason given'}`);
123
131
  done(w.outcome === 'error' ? 1 : 0);
package/loop.mjs CHANGED
@@ -698,7 +698,9 @@ export class Orchestrator {
698
698
  // a tool of one of the profile's untrusted MCP servers is gated as Hermes's trust gate gates it
699
699
  const gated = untrustedServerVerdict(profile, w.tool, context, { mcp: w.mcp && !String(w.tool ?? '').startsWith('mcp__orchestrator__') });
700
700
  if (gated) { this.enqueue(events.worker({ profile: profileName, keyString, runtime_id: runtime.handle.runtime_id, ev: { ...w, hermes: gated }, at })); return; }
701
- void hermesApproval(profile.dir, { command: w.command ?? null, context })
701
+ // a request to widen the worker's own sandbox is not Hermes's to judge (its rule cannot see it): it is asked in a
702
+ // conversation and answered by `worker.permission.unattended` with nobody present
703
+ void (widensSandbox(w) ? Promise.resolve(null) : hermesApproval(profile.dir, { command: w.command ?? null, context }))
702
704
  .catch((error) => { this.log('warn', `approval ${keyString}: ${error.message}`); return null; })
703
705
  .then((hermes) => this.enqueue(events.worker({ profile: profileName, keyString, runtime_id: runtime.handle.runtime_id, ev: { ...w, hermes }, at })));
704
706
  return;
@@ -1097,6 +1099,17 @@ function claudePermissionRequest(payload) {
1097
1099
  }
1098
1100
 
1099
1101
  /** The shell command a worker asks to run (Codex's `params.command`, a string or an argv), for Hermes's rule. */
1102
+ /**
1103
+ * Whether a worker's request asks to widen its own sandbox: Codex's `item/permissions/requestApproval` (network and
1104
+ * file-system roots for the turn) and `item/fileChange/requestApproval` (a write outside its writable roots). Hermes's
1105
+ * approval rule judges commands and lets its own file tools through, which have no sandbox behind them; it never sees
1106
+ * these, so they keep the profile's own answer.
1107
+ */
1108
+ export function widensSandbox(w) {
1109
+ const kind = String(w?.subject ?? '').toLowerCase();
1110
+ return kind.includes('permissions/requestapproval') || kind.includes('filechange/requestapproval');
1111
+ }
1112
+
1100
1113
  function requestCommand(payload) {
1101
1114
  const raw = payload?.params?.command ?? payload?.command ?? payload?.params?.input?.command ?? null;
1102
1115
  if (Array.isArray(raw)) return raw.map(String).join(' ');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-orchestrator",
3
- "version": "0.5.6",
3
+ "version": "0.5.7",
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": {