@volter/supercode-orchestrator 0.4.160 → 0.5.2

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/mcp/verbs.mjs CHANGED
@@ -22,6 +22,8 @@ import { compactStamp, gateFields, homeZone, nextRunAt, parseCron, payloadError,
22
22
  import { applyOperator, hermesJobArgs } from '../operator.mjs';
23
23
  import { hermesSchedule, hermesTool } from '../hermes-cli.mjs';
24
24
  import { modelError } from '../activation.mjs';
25
+ import { agentRunForSession } from '../agents.mjs';
26
+ import { materializeAgentHome } from '../agent-home.mjs';
25
27
 
26
28
  /**
27
29
  * cron/jobs.py `_normalize_workdir`, transcribed: returns the absolute,
@@ -64,7 +66,9 @@ export function routedProfile(state, key) {
64
66
  const profiles = Object.values(state.profiles);
65
67
  const owner = profiles.find((p) => p.channels && p.channels[key.platform]) ?? state.profiles.default ?? profiles[0];
66
68
  if (!owner) return null;
67
- return resolveRoute(owner.routes, key) ?? owner.name;
69
+ // a route names an agent (G1); its profile is the agent's own or the declared one's
70
+ const agent = resolveRoute(owner.routes, key) ?? owner.name;
71
+ return state.agents?.[agent]?.profile ?? (state.profiles[agent] ? agent : owner.name);
68
72
  }
69
73
 
70
74
  const UNIT_MINUTES = { m: 1, min: 1, mins: 1, minute: 1, minutes: 1, h: 60, hr: 60, hour: 60, hours: 60, d: 1440, day: 1440, days: 1440 };
@@ -321,10 +325,15 @@ export async function runVerb(orch, { verb, args = {}, profile: profileName, sur
321
325
  case 'memory':
322
326
  case 'skills_list':
323
327
  case 'skill_view': {
324
- // Hermes's own handler against this profile's home: its sessions (the workers' mirrored beside
325
- // Hermes's), its memories, its skills; answered in Hermes's words
326
- const verbatim = await hermesTool(profile.dir, verb, args, { sessionId: binding?.worker?.session_id ?? null });
327
- return { effects, result: { ran: `${verb} in profile ${profile.name}`, verbatim: typeof verbatim === 'string' ? verbatim : JSON.stringify(verbatim) } };
328
+ const worker=binding?.worker;
329
+ let home=profile.dir,scope=`profile ${profile.name}`;
330
+ if(verb==='memory'||verb==='session_search') {
331
+ const owner=worker&&agentRunForSession(state.root,[`${worker.harness}:${worker.session_id}`,`${worker.harness}:${worker.runtime_id}`]);
332
+ if(!owner)throw new Error(`${verb} requires the bound session's agent ownership; a profile is not an agent`);
333
+ home=materializeAgentHome(state.root,owner.org,owner.agent_id,profile);scope=`agent ${owner.agent_id}`;
334
+ }
335
+ const verbatim = await hermesTool(home, verb, args, { sessionId: worker?.session_id ?? null });
336
+ return { effects, result: { ran: `${verb} in ${scope}`, verbatim: typeof verbatim === 'string' ? verbatim : JSON.stringify(verbatim) } };
328
337
  }
329
338
 
330
339
  case 'session_handoff': {
@@ -333,8 +342,21 @@ export async function runVerb(orch, { verb, args = {}, profile: profileName, sur
333
342
  const b = surface ? liveBinding(profile, surface) : null;
334
343
  if (!b) throw new Error('No live session to hand off.');
335
344
  await orch.dispatch(events.operator({ verb: 'session_handoff', profile: profile.name, args: { keyString: surface, to, note: args.note ? String(args.note) : null }, at: now }));
336
- if (b.handoff?.state !== 'pending') return { effects, error: `Cannot hand off: ${b.handoff?.error ?? 'unknown reason'}.` };
337
- return { effects, result: { ran: `handed off to ${to}; the binding is pending pickup`, session_id: b.worker.session_id } };
345
+ const record = [...(orch.state.handoffs ?? [])].reverse().find((h) => h.session === `${b.worker.harness}:${b.worker.session_id ?? ''}` && h.at === now);
346
+ if (record?.state !== 'pending') return { effects, error: `Cannot hand off: ${record?.error ?? 'unknown reason'}.` };
347
+ return { effects, result: { ran: `handed off to ${to}; the session is pending pickup`, session_id: b.worker.session_id } };
348
+ }
349
+
350
+ case 'conversation_transfer': {
351
+ const to = args.to ? String(args.to) : '';
352
+ if (!to) throw new Error('to is required');
353
+ const b = surface ? liveBinding(profile, surface) : null;
354
+ if (!b) throw new Error('No live conversation to transfer.');
355
+ const history = ['full', 'filtered', 'none'].includes(args.history) ? args.history : 'full';
356
+ await orch.dispatch(events.operator({ verb: 'conversation_transfer', profile: profile.name, args: { keyString: surface, to, reason: String(args.reason ?? ''), history, ...(history === 'filtered' ? { filter: { last: Number(args.last ?? 20) } } : {}) }, at: now }));
357
+ const record = [...(orch.state.transfers ?? [])].reverse().find((t) => t.at === now && t.to === to);
358
+ if (record?.state !== 'done') return { effects, error: `Transfer refused: ${record?.error ?? 'unknown reason'}.` };
359
+ return { effects, result: { ran: `transferred the conversation to ${to}`, conversation: record.conversation } };
338
360
  }
339
361
 
340
362
  default:
package/ontology.d.ts CHANGED
@@ -31,6 +31,26 @@ export interface Action {
31
31
  args: unknown;
32
32
  }
33
33
 
34
+ /**
35
+ * One agent this home declares: who answers, and which profile is its template.
36
+ * A named profile implies its own agent of the same name; a declaration names others (several
37
+ * agents may share one profile) and the parent each is declared under.
38
+ */
39
+ export interface AgentDecl {
40
+ /** Unique among the home's agents; a route, a speaker policy and a binding name it. */
41
+ name: string;
42
+ /** The profile it runs as (its layered template). */
43
+ profile: string;
44
+ /** A display label, not identity. */
45
+ label?: string | null;
46
+ /** The agent it is declared under, if not the home's root. */
47
+ parent?: string | null;
48
+ /** A person set on purpose as its sponsor; absent, the sponsor is inherited. */
49
+ sponsor?: string | null;
50
+ /** What a source format said about it that the IR has no field for, verbatim. */
51
+ residue?: Record<string, unknown>;
52
+ }
53
+
34
54
  /** What one artifact reached on the way in or out. */
35
55
  export interface ArtifactFidelity {
36
56
  /** Path relative to the home the codec read or wrote. */
@@ -65,6 +85,10 @@ export interface Binding {
65
85
  key: SurfaceKey;
66
86
  /** The profile that owns it (`None` = the home's default). */
67
87
  profile?: string | null;
88
+ /** The conversation it holds a session in (G1/G5); `None` where the source records none. */
89
+ conversation?: string | null;
90
+ /** The agent whose session it is; with `conversation`, its key. `None` where the source records none. */
91
+ agent?: string | null;
68
92
  /** The worker session. */
69
93
  worker: Worker;
70
94
  /**
@@ -89,6 +113,34 @@ export interface Binding {
89
113
  residue?: Residue;
90
114
  }
91
115
 
116
+ /**
117
+ * 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.
119
+ */
120
+ export interface Budget {
121
+ /** The window. */
122
+ period: BudgetPeriod;
123
+ /** The bound. */
124
+ limit: BudgetLimit;
125
+ }
126
+
127
+ /** What a budget bounds. */
128
+ export interface BudgetLimit {
129
+ /** Total tokens. */
130
+ tokens?: number | null;
131
+ /** Total cost. */
132
+ cost?: Cost | null;
133
+ }
134
+
135
+ /** A budget's window. */
136
+ export type BudgetPeriod =
137
+ /** One run. */
138
+ | 'run'
139
+ /** A calendar day. */
140
+ | 'day'
141
+ /** A calendar month. */
142
+ | 'month';
143
+
92
144
  /** One platform's configuration. */
93
145
  export interface ChannelConfig {
94
146
  /** The platform name (`telegram`, `slack`, `api_server`, …). */
@@ -101,6 +153,43 @@ export interface ChannelConfig {
101
153
  extra?: Record<string, unknown>;
102
154
  }
103
155
 
156
+ /** A conversation, a Resource separate from the sessions bound to it (G5, amended by M3). */
157
+ export interface Conversation {
158
+ /** Its id. */
159
+ id: string;
160
+ /** Thread or private. */
161
+ kind: ConversationKind;
162
+ /** Where a thread is shown; no profile is part of it. */
163
+ surface?: SurfaceKey | null;
164
+ /** People and agents in it. */
165
+ participants?: Participant[];
166
+ /** Who receives a line. */
167
+ speaker?: SpeakerPolicy;
168
+ /** Where its record lives. */
169
+ history?: HistoryRef;
170
+ /** The agent a completed transfer gave the conversation to; an unaddressed line goes to it. */
171
+ holder?: string | null;
172
+ /** The agent that took the last line (round robin's position). */
173
+ last_speaker?: string | null;
174
+ }
175
+
176
+ /** `thread`: a root and its replies, wherever it is shown; `private`: a harness session's own input. */
177
+ export type ConversationKind =
178
+ /** A root and its replies (a Room conversation, a chat thread, a mailbox exchange). */
179
+ | 'thread'
180
+ /** A harness session's own conversation with its owner; its history is that transcript. */
181
+ | 'private';
182
+
183
+ /** A cost, as the provider reported it or a rate table priced it. */
184
+ export interface Cost {
185
+ /** The amount. */
186
+ amount: number;
187
+ /** ISO currency. */
188
+ currency: string;
189
+ /** `provider` or `rate_table`. */
190
+ source?: string | null;
191
+ }
192
+
104
193
  /** A transition taken once a duration has passed in the status (XState's `after`, ASL's `Wait`). */
105
194
  export interface Delayed {
106
195
  /** How long, in seconds (an expression). */
@@ -152,6 +241,10 @@ export type EndReason =
152
241
  | 'new'
153
242
  /** Moved to another surface. */
154
243
  | 'handoff'
244
+ /** The conversation was transferred to another agent (G2). */
245
+ | 'transfer'
246
+ /** The agent left the conversation. */
247
+ | 'left'
155
248
  /** The worker failed. */
156
249
  | 'error';
157
250
 
@@ -240,9 +333,43 @@ export interface Handoff {
240
333
  error?: string | null;
241
334
  }
242
335
 
336
+ /** A session moving to another conversation (Hermes `/handoff`, kept by the naming rule). */
337
+ export interface Handoff2 {
338
+ /** The session (`<harness>:<id>`). */
339
+ session: string;
340
+ /** The target conversation. */
341
+ to: string;
342
+ /** Who asked. */
343
+ by: string;
344
+ /** Its progress. */
345
+ state: HandoffState;
346
+ /** Why it failed. */
347
+ error?: string | null;
348
+ /** RFC3339. */
349
+ at: string;
350
+ }
351
+
352
+ /** A handoff's progress. */
353
+ export type HandoffState =
354
+ /** Announced on the target, not yet picked up. */
355
+ | 'pending'
356
+ /** The session is bound there. */
357
+ | 'done'
358
+ /** It could not move. */
359
+ | 'failed';
360
+
243
361
  /** Extensible identifier for a coding harness. */
244
362
  export type HarnessId = string;
245
363
 
364
+ /** Where a conversation's record lives. */
365
+ export type HistoryRef =
366
+ /** The platform's own thread (a chat thread, a Room conversation). */
367
+ | { kind: 'platform' }
368
+ /** The mailbox thread. */
369
+ | { thread: string; kind: 'mailbox' }
370
+ /** A private conversation's history: its owning session's transcript. */
371
+ | { session: string; kind: 'transcript' };
372
+
246
373
  /** One scheduled job. */
247
374
  export interface Job {
248
375
  /** The id (a file-name-safe token). */
@@ -270,6 +397,11 @@ export interface Job {
270
397
  * the worker allows), instead of a session of its own; Hermes mirrors it into that transcript.
271
398
  */
272
399
  attach_to_session?: boolean | null;
400
+ /**
401
+ * Which session a fire runs in: `main` (its agent's main session), `current` (the binding's session),
402
+ * `isolated` (a new one) or `session:<id>` (that one). Absent: the source's own default.
403
+ */
404
+ session_target?: string | null;
273
405
  /** Remaining-runs counter for `once` jobs. */
274
406
  repeat?: Repeat | null;
275
407
  /** Whether it fires at all. */
@@ -373,6 +505,16 @@ export interface Orchestration {
373
505
  profiles: Record<string, Profile>;
374
506
  /** O — how the home's boards are worked (`workflow.yaml` at the root; Hermes's dispatcher when there is none). */
375
507
  workflow?: Workflow;
508
+ /** O — declared agents by name (§2.10); a named profile implies its own agent without one. */
509
+ agents?: Record<string, AgentDecl>;
510
+ /** O — conversations by id, separate from the sessions bound to them (G5). */
511
+ conversations?: Record<string, Conversation>;
512
+ /** O — transfers between agents, in order (G2). */
513
+ transfers?: Transfer[];
514
+ /** O — sessions moved to other conversations, in order (G2). */
515
+ handoffs?: Handoff2[];
516
+ /** O — usage per session (`<harness>:<id>`), summed per agent (G3). */
517
+ usage?: Record<string, SessionUsage>;
376
518
  }
377
519
 
378
520
  /** What is delivered. */
@@ -387,6 +529,25 @@ export interface OutboundContent {
387
529
  format?: string | null;
388
530
  }
389
531
 
532
+ /** A person or an agent in a conversation. */
533
+ export interface Participant {
534
+ /** An agent's name, or a person as `<platform>:<user id>` (an identity observation). */
535
+ principal: string;
536
+ /** To or CC. */
537
+ role: ParticipantRole;
538
+ /** RFC3339. */
539
+ since: string;
540
+ /** RFC3339, when it left. */
541
+ until?: string | null;
542
+ }
543
+
544
+ /** How a participant is addressed on a thread (amendment M3). */
545
+ export type ParticipantRole =
546
+ /** Addressed: expected to act. */
547
+ | 'to'
548
+ /** Copied: receives every reply. */
549
+ | 'cc';
550
+
390
551
  /** What happens to a worker's permission prompt when no human answers. */
391
552
  export type PermissionDefault =
392
553
  /** Refuse after the timeout. */
@@ -459,6 +620,8 @@ export interface Profile {
459
620
  fires?: Fire[];
460
621
  /** H — `delivery_obligations`. */
461
622
  obligations?: Obligation[];
623
+ /** O — per-action gates on this profile's agents (G3); they only narrow down the layers. */
624
+ budgets?: Budget[];
462
625
  /** What the model does not interpret. */
463
626
  residue?: ProfileResidue;
464
627
  }
@@ -504,8 +667,8 @@ export interface Route {
504
667
  name?: string | null;
505
668
  /** What it matches. */
506
669
  match: RouteMatch;
507
- /** The profile that answers. */
508
- profile: string;
670
+ /** The agent that answers (G1); a named profile's own agent bears the profile's name. */
671
+ agent: string;
509
672
  /** Unmodeled fields of the route, verbatim. */
510
673
  residue?: Residue;
511
674
  }
@@ -581,6 +744,27 @@ export type SecretRef =
581
744
  /** A key in the home's own `.env` file. */
582
745
  | { dotenv: string };
583
746
 
747
+ /** A session's usage, kept on the session and summed upward (session → agent → sponsor → Org). */
748
+ export interface SessionUsage {
749
+ /** The agent the session belongs to, when it has one. */
750
+ agent?: string | null;
751
+ /** The records. */
752
+ usage?: Usage[];
753
+ }
754
+
755
+ /** Which bound agents receive a line on a shared surface (G1). */
756
+ export type SpeakerPolicy =
757
+ /** Mentions pick the agent; `default` takes the rest (a Hermes or OpenClaw surface: one agent). */
758
+ | { default?: string | null; kind: 'addressed' }
759
+ /** Every bound agent gets every line. */
760
+ | { kind: 'all' }
761
+ /** Agents take lines in this order. */
762
+ | { order: string[]; kind: 'round_robin' }
763
+ /** An agent picks the next speaker (an event out and back, never a hidden model call). */
764
+ | { selector: string; kind: 'selector' }
765
+ /** The meeting's adapter decides (RH2's floor, the voice pack). */
766
+ | { kind: 'floor' };
767
+
584
768
  /** One status a card can be in. */
585
769
  export interface Status {
586
770
  /**
@@ -647,6 +831,48 @@ export type Target =
647
831
  /** A named surface, as `hermes send --to` spells one. */
648
832
  | { platform: string; chat_id?: string | null; thread_id?: string | null; kind: 'explicit' };
649
833
 
834
+ /** One agent handing a conversation to another (OpenAI handoff, AutoGen Swarm, voice `hand_off`). */
835
+ export interface Transfer {
836
+ /** The conversation. */
837
+ conversation: string;
838
+ /** The giving agent. */
839
+ from: string;
840
+ /** The receiving agent. */
841
+ to: string;
842
+ /** Who did it (the giving agent, or whom the speaker policy allows). */
843
+ by: string;
844
+ /** Why. */
845
+ reason: string;
846
+ /** What history the receiver is given. */
847
+ history: TransferHistory;
848
+ /** For `filtered`: which part (`{"last": N}`). */
849
+ filter?: unknown;
850
+ /** Its progress. */
851
+ state: TransferState;
852
+ /** RFC3339. */
853
+ at: string;
854
+ /** Why it was refused. */
855
+ error?: string | null;
856
+ }
857
+
858
+ /** How much of the conversation a transfer's receiver is given. */
859
+ export type TransferHistory =
860
+ /** All of it (the default). */
861
+ | 'full'
862
+ /** What the filter keeps. */
863
+ | 'filtered'
864
+ /** None. */
865
+ | 'none';
866
+
867
+ /** A transfer's progress. */
868
+ export type TransferState =
869
+ /** Asked, not yet applied. */
870
+ | 'pending'
871
+ /** The receiver holds the conversation. */
872
+ | 'done'
873
+ /** The receiver may not take part. */
874
+ | 'refused';
875
+
650
876
  /** A move from the card's status on an event (or on none: `always`, `after`). */
651
877
  export interface Transition {
652
878
  /** Source statuses; empty applies from every status. */
@@ -689,6 +915,24 @@ export type Trigger =
689
915
  /** The source does not say. */
690
916
  | 'unknown';
691
917
 
918
+ /** One measured use of a model, from the harness's own records (G3). */
919
+ export interface Usage {
920
+ /** RFC3339. */
921
+ at: string;
922
+ /** The model. */
923
+ model: string;
924
+ /** Input tokens. */
925
+ input_tokens: number;
926
+ /** Output tokens. */
927
+ output_tokens: number;
928
+ /** Cache reads, when measured. */
929
+ cache_read_tokens?: number | null;
930
+ /** Cache writes, when measured. */
931
+ cache_write_tokens?: number | null;
932
+ /** Its cost, when known. */
933
+ cost?: Cost | null;
934
+ }
935
+
692
936
  /** A validator: the expression must hold, else the event is refused with the message. */
693
937
  export interface Validation {
694
938
  /** What must hold. */
package/operator.mjs CHANGED
@@ -43,6 +43,7 @@ export const OPERATOR_OPS = Object.freeze([
43
43
  'profiles.create', 'profiles.delete',
44
44
  'sessions.new', 'sessions.reset',
45
45
  'access.pair_approve',
46
+ 'agents.fork',
46
47
  ]);
47
48
 
48
49
  /** Where a deleted profile folder is renamed to, relative to the root. */
@@ -387,10 +388,14 @@ async function run(state, profile, op, args, now, send) {
387
388
  const code = String(args.code ?? '').trim();
388
389
  if (!code) throw new Error('code is required');
389
390
  // Hermes's pairing store holds the code; the daemon asks it, and a grant reaches the allowlist
390
- await send('pair_approve', { code });
391
+ await send('pair_approve', { code, principal: args.principal ? String(args.principal) : null });
391
392
  return { ran: `asked Hermes's pairing store to approve ${code} for profile ${profile.name}`, code };
392
393
  }
393
394
 
395
+ case 'agents.fork':
396
+ // The live daemon's door performs it (mcp/bridge.mjs): it declares a child and starts its session.
397
+ throw new Error('agents.fork starts the child agent\'s session; it runs only through a live orchestrator daemon');
398
+
394
399
  default:
395
400
  throw new Error(`unknown operator op ${op}`);
396
401
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-orchestrator",
3
- "version": "0.4.160",
3
+ "version": "0.5.2",
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": {
@@ -27,7 +27,7 @@
27
27
  "@volter/supercode-harness-sdk": "^0.3.41",
28
28
  "@volter/ztrack": "^2.4.21",
29
29
  "yaml": "^2.9.0",
30
- "@volter/supercode-teams": "^0.2.145"
30
+ "@volter/teams": "0.2.1"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@chat-adapter/state-memory": "4.39.0",