@volter/supercode-orchestrator 0.4.160 → 0.5.1
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/README.md +16 -5
- package/adapters/chat-sdk.mjs +4 -1
- package/adapters/rh2.mjs +18 -20
- package/agent-home.mjs +35 -0
- package/agent-profiles.mjs +53 -0
- package/agents.mjs +443 -0
- package/bin/agent-door.mjs +53 -0
- package/bin/runtime-door.mjs +23 -0
- package/bin/runtime.mjs +59 -4
- package/board/agent-declarations.mjs +29 -0
- package/board/ci.mjs +6 -1
- package/board/cli.mjs +158 -31
- package/board/dispatch.mjs +299 -121
- package/board/engine.mjs +30 -10
- package/board/event-fleet.mjs +59 -0
- package/board/event-relay.mjs +45 -0
- package/board/event-stream.mjs +104 -0
- package/board/incidents.mjs +57 -0
- package/board/published.mjs +86 -0
- package/board/runtime-door.mjs +12 -0
- package/board/store.mjs +39 -6
- package/board/tools.mjs +1 -0
- package/board/wake.mjs +17 -56
- package/board/workflow.mjs +28 -7
- package/board/workspace.mjs +18 -15
- package/commands.mjs +2 -2
- package/conversations.mjs +108 -0
- package/cron.mjs +30 -4
- package/gates.mjs +3 -2
- package/hermes-cli.mjs +19 -2
- package/index.d.ts +43 -2
- package/index.mjs +4 -0
- package/installed-apps.mjs +52 -0
- package/ir.mjs +3 -3
- package/kanban-worker.mjs +16 -6
- package/loop.mjs +211 -21
- package/mcp/bridge.mjs +13 -2
- package/mcp/tools.mjs +17 -0
- package/mcp/verbs.mjs +29 -7
- package/ontology.d.ts +246 -2
- package/operator.mjs +6 -1
- package/package.json +2 -2
- package/reducer.mjs +133 -28
- package/webhook.mjs +8 -4
package/README.md
CHANGED
|
@@ -301,11 +301,22 @@ platforms:
|
|
|
301
301
|
key: ${API_SERVER_KEY}
|
|
302
302
|
```
|
|
303
303
|
|
|
304
|
-
RH2
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
304
|
+
RH2 Room chat is an installed app ([ADR 0017](../../docs/adr/0017-room-chat-is-an-installed-app.md)).
|
|
305
|
+
Register the Room-chat app in the RH2 organization (`organization.read`, `room.read`; a Room's
|
|
306
|
+
manager seats its principal where it should answer), have an admin install it, and exchange the
|
|
307
|
+
code the install returns:
|
|
308
|
+
|
|
309
|
+
```sh
|
|
310
|
+
supercode orchestrator apps install room-chat --origin https://pilot.runhuman.com \
|
|
311
|
+
--client-id app_… --client-secret-file ./room-chat.secret --code … --profile default
|
|
312
|
+
supercode orchestrator apps list
|
|
313
|
+
supercode orchestrator apps remove room-chat
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
The record (with the installation's token) is kept owner-only under `<home>/apps/`; the next
|
|
317
|
+
daemon start attaches the `rh2` channel for that profile. Uninstalling in RH2 revokes the token and
|
|
318
|
+
the channel stops. A `platforms: rh2` block grants no integration authority and the runtime reports
|
|
319
|
+
it as unsupported. Native Hermes channel configuration remains the worker's configuration.
|
|
309
320
|
|
|
310
321
|
**The Chat SDK sidecar** mounts `chat@4.39.0` (npm package `chat`, repo `vercel/chat`; a
|
|
311
322
|
devDependency of this package, installed alongside `@chat-adapter/state-memory@4.39.0`). The
|
package/adapters/chat-sdk.mjs
CHANGED
|
@@ -196,7 +196,10 @@ export class ChatSdkAdapter {
|
|
|
196
196
|
async credentials() {
|
|
197
197
|
const slug = String(this.extra('slug', this.platform));
|
|
198
198
|
const hermes = HERMES_PLATFORMS[slug];
|
|
199
|
-
|
|
199
|
+
// A block naming only Hermes's own variables (as the loader fills them in) is Hermes's block: Hermes's transport
|
|
200
|
+
// and its whole variable set, never a partial credential set in the package's default mode.
|
|
201
|
+
const refs = Object.values(this.channel?.credentials ?? {});
|
|
202
|
+
if (hermes && refs.every((ref) => Object.values(hermes.env).includes(ref.dotenv ?? ref.env))) {
|
|
200
203
|
// Hermes's own variables, from the owning profile's own .env only (a
|
|
201
204
|
// named profile's token never serves the root's block, Hermes #59739)
|
|
202
205
|
const file = this.profileDir ? join(this.profileDir, '.env') : null;
|
package/adapters/rh2.mjs
CHANGED
|
@@ -16,9 +16,8 @@
|
|
|
16
16
|
// PATCH /rooms/:room/conversations/:c/messages/:id {body} — a streamed reply grows, a progress line updates, by edit
|
|
17
17
|
// POST /rooms/:room/conversations/:c/presence {state: "typing"} — best effort, forgotten on 404
|
|
18
18
|
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
// See docs/adr/0006-teams-installable-apps.md (C1) and the Room-chat module (M4).
|
|
19
|
+
// The Room-chat app (docs/adr/0017-room-chat-is-an-installed-app.md) attaches it with its RH2 installation's
|
|
20
|
+
// origin, organization, principal and token; a `platforms:` block never does.
|
|
22
21
|
//
|
|
23
22
|
// The surface key is `{platform: 'rh2', kind: 'channel', chat_id: '<roomId>'}`: a Room is one surface, so the agent it
|
|
24
23
|
// seats is one session however many of the Room's conversations it takes part in. A line wakes it where RH2 says the bot
|
|
@@ -67,31 +66,24 @@ export class Rh2Adapter {
|
|
|
67
66
|
return e[`extra.${name}`] ?? e[name] ?? fallback;
|
|
68
67
|
}
|
|
69
68
|
|
|
70
|
-
/** The bearer:
|
|
69
|
+
/** The bearer: the installation's token. */
|
|
71
70
|
token() {
|
|
72
|
-
const
|
|
73
|
-
if (
|
|
74
|
-
const name = ref.dotenv ?? ref.env;
|
|
75
|
-
const value = this.vault.get(name) ?? (ref.env ? process.env[ref.env] : undefined);
|
|
76
|
-
if (value === undefined) throw new Error(`platforms.${this.platform}.token: ref ${name} is not in the vault`);
|
|
77
|
-
return value;
|
|
78
|
-
}
|
|
79
|
-
const direct = this.extra('token') ?? process.env.RH2_SESSION_TOKEN;
|
|
80
|
-
if (!direct) throw new Error(`platforms.${this.platform}: no token (credentials.token or RH2_SESSION_TOKEN)`);
|
|
71
|
+
const direct = this.extra('token');
|
|
72
|
+
if (!direct) throw new Error(`${this.platform}: the installation carries no token`);
|
|
81
73
|
return direct;
|
|
82
74
|
}
|
|
83
75
|
|
|
84
|
-
apiUrl() { return String(this.extra('api_url') ??
|
|
85
|
-
organization() { return String(this.extra('organization') ??
|
|
86
|
-
principalId() { return String(this.extra('principal_id') ??
|
|
87
|
-
pollMs() { const n = Number(this.extra('poll_seconds'
|
|
76
|
+
apiUrl() { return String(this.extra('api_url') ?? '').replace(/\/+$/, ''); }
|
|
77
|
+
organization() { return String(this.extra('organization') ?? ''); }
|
|
78
|
+
principalId() { return String(this.extra('principal_id') ?? ''); }
|
|
79
|
+
pollMs() { const n = Number(this.extra('poll_seconds')); return Number.isFinite(n) && n > 0 ? Math.max(500, n * 1000) : DEFAULT_POLL_MS; }
|
|
88
80
|
followedRooms() {
|
|
89
|
-
const raw = this.extra('rooms') ??
|
|
81
|
+
const raw = this.extra('rooms') ?? '';
|
|
90
82
|
return (Array.isArray(raw) ? raw : String(raw).split(',')).map((s) => String(s).trim()).filter(Boolean);
|
|
91
83
|
}
|
|
92
84
|
headers() {
|
|
93
85
|
let extra = {};
|
|
94
|
-
const raw = this.extra('headers')
|
|
86
|
+
const raw = this.extra('headers');
|
|
95
87
|
if (raw) {
|
|
96
88
|
try { extra = typeof raw === 'string' ? JSON.parse(raw) : raw; } catch { this.log('warn', `${this.platform}: headers is not a JSON object; ignored`); }
|
|
97
89
|
}
|
|
@@ -125,7 +117,7 @@ export class Rh2Adapter {
|
|
|
125
117
|
|
|
126
118
|
async connect(emit) {
|
|
127
119
|
this.emit = emit;
|
|
128
|
-
if (!this.apiUrl() || !this.organization()) throw new Error(
|
|
120
|
+
if (!this.apiUrl() || !this.organization()) throw new Error(`${this.platform}: the installation names no origin or organization`);
|
|
129
121
|
this.token();
|
|
130
122
|
await this.refreshRooms();
|
|
131
123
|
this.running = true;
|
|
@@ -208,6 +200,12 @@ export class Rh2Adapter {
|
|
|
208
200
|
await this.sleep(this.pollMs());
|
|
209
201
|
} catch (error) {
|
|
210
202
|
if (!this.running) return;
|
|
203
|
+
// The installation's token refused for good: the app was uninstalled in RH2, and its channel stops.
|
|
204
|
+
if (error.status === 401) {
|
|
205
|
+
this.running = false;
|
|
206
|
+
this.log('error', `${this.platform}: the installation's token was refused (uninstalled in RH2?); the channel stops: ${error.message}`);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
211
209
|
failures += 1;
|
|
212
210
|
const delay = Math.min(60_000, 2000 * 2 ** Math.min(failures, 5));
|
|
213
211
|
this.log('warn', `${this.platform}: poll error ${error.message}; retrying in ${delay / 1000}s`);
|
package/agent-home.mjs
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Configuration is a profile's template; Hermes's mutable memory and sessions belong to one agent.
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { join, resolve } from 'node:path';
|
|
5
|
+
import { linkTo, skillSources } from './hermes-home.mjs';
|
|
6
|
+
|
|
7
|
+
/** The immutable agent id keeps one home across sessions, profile changes and its move from local to an Org.
|
|
8
|
+
* Ids are minted globally unique, so the Org is checked but not part of the key. */
|
|
9
|
+
export function agentStateHome(root, org, id) {
|
|
10
|
+
if (!org || !id) throw new Error('agent state requires an Org and an immutable agent id');
|
|
11
|
+
const key = createHash('sha256').update(JSON.stringify(['agent', id])).digest('hex');
|
|
12
|
+
return join(resolve(root), '.agent-state', key, 'hermes');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Refresh only declared inputs. Never copy a profile's memories, session database or environment. */
|
|
16
|
+
export function materializeAgentHome(root, org, id, profile) {
|
|
17
|
+
const home = agentStateHome(root, org, id);
|
|
18
|
+
mkdirSync(home, { recursive: true, mode: 0o700 });
|
|
19
|
+
for (const name of ['config.yaml', 'AGENTS.md', '.mcp.json']) {
|
|
20
|
+
const source = join(profile.dir, name), target = join(home, name);
|
|
21
|
+
const contents = existsSync(source) ? readFileSync(source) : Buffer.from(name === 'AGENTS.md' ? '' : '{}\n');
|
|
22
|
+
if (existsSync(target) && readFileSync(target).equals(contents)) continue;
|
|
23
|
+
const temporary = `${target}.${randomUUID()}.tmp`;
|
|
24
|
+
writeFileSync(temporary, contents, { mode: 0o600 });
|
|
25
|
+
renameSync(temporary, target);
|
|
26
|
+
}
|
|
27
|
+
linkTo(join(home, 'SOUL.md'), 'AGENTS.md');
|
|
28
|
+
const skills = join(home, '.agents', 'skills');
|
|
29
|
+
mkdirSync(skills, { recursive: true, mode: 0o700 });
|
|
30
|
+
// These are template inputs. The agent's mutable state directories are never linked to a profile.
|
|
31
|
+
const inputs=skillSources(profile.dir);
|
|
32
|
+
for(const entry of readdirSync(skills,{withFileTypes:true}))if(entry.isSymbolicLink()&&!inputs.has(entry.name))unlinkSync(join(skills,entry.name));
|
|
33
|
+
for (const [name, source] of inputs) linkTo(join(skills, name), resolve(source));
|
|
34
|
+
return home;
|
|
35
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Profile intent is layered configuration. It never owns the agent's memory.
|
|
2
|
+
const object=value=>value!=null&&typeof value==='object'&&!Array.isArray(value);
|
|
3
|
+
const copy=value=>structuredClone(value);
|
|
4
|
+
const failure=message=>{throw new Error(message);};
|
|
5
|
+
|
|
6
|
+
/** Permission layers are retained separately: an evaluator must admit the action in every layer. */
|
|
7
|
+
export function resolveAgentProfile(profiles, name, overlays=[]) {
|
|
8
|
+
const seen=new Set(),chain=[];
|
|
9
|
+
function visit(id) {
|
|
10
|
+
if(seen.has(id))failure(`profile inheritance cycle at ${id}`);
|
|
11
|
+
const profile=profiles[id];if(!object(profile))failure(`profile ${id} does not exist`);
|
|
12
|
+
seen.add(id);if(profile.parent)visit(profile.parent);chain.push({name:id,value:profile});seen.delete(id);
|
|
13
|
+
}
|
|
14
|
+
visit(name);
|
|
15
|
+
for(const [index,value] of overlays.entries()){
|
|
16
|
+
if(!object(value))failure('a session profile overlay must be an object');
|
|
17
|
+
chain.push({name:`session:${index}`,value});
|
|
18
|
+
}
|
|
19
|
+
let resolved={},permissionLayers=[];
|
|
20
|
+
for(const {value} of chain){
|
|
21
|
+
if(Object.hasOwn(value,'memory'))failure('memory is agent state, not profile configuration');
|
|
22
|
+
resolved=merge(resolved,Object.fromEntries(Object.entries(value).filter(([key])=>key!=='permissions'&&key!=='parent')));
|
|
23
|
+
if(value.permissions!=null)permissionLayers.push(copy(value.permissions));
|
|
24
|
+
}
|
|
25
|
+
delete resolved.parent;resolved.permissions={layers:permissionLayers};
|
|
26
|
+
return {profile:name,layers:chain.map(x=>x.name),resolved};
|
|
27
|
+
}
|
|
28
|
+
function merge(parent,child){
|
|
29
|
+
const result=copy(parent);
|
|
30
|
+
for(const [key,value] of Object.entries(child)){
|
|
31
|
+
result[key]=object(value)&&object(result[key])?merge(result[key],value):copy(value);
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A difference is evidence of actual execution, never an update to the intended profile. */
|
|
37
|
+
export function profileDifferences(intended, actual) {
|
|
38
|
+
const differences=[];
|
|
39
|
+
const visit=(left,right,path)=>{
|
|
40
|
+
if(object(left)&&object(right))for(const key of new Set([...Object.keys(left),...Object.keys(right)]))visit(left[key],right[key],path?`${path}.${key}`:key);
|
|
41
|
+
else if(JSON.stringify(left)!==JSON.stringify(right))differences.push({field:path,intended:left??null,actual:right??null});
|
|
42
|
+
};
|
|
43
|
+
visit(intended,actual,'');return differences;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Principal kinds never flow into an agent. A plain session remains plain absent a declaration/profile identity. */
|
|
47
|
+
export function sessionAgent({starter,declared=null,asAgent=null,profileAgent=null}) {
|
|
48
|
+
if(declared)return declared;
|
|
49
|
+
if(asAgent)return asAgent;
|
|
50
|
+
if(starter?.kind==='agent')return starter.id;
|
|
51
|
+
if(starter?.kind==='session')return starter.agent??null;
|
|
52
|
+
return profileAgent??null;
|
|
53
|
+
}
|