@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 +2 -1
- package/kanban-worker.mjs +16 -8
- package/loop.mjs +14 -1
- package/package.json +1 -1
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
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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.
|
|
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": {
|