@arnilo/prism-supervisor 0.3.1 → 0.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.2] - 2026-08-29 (plan 050)
4
+
5
+ ### Added
6
+ - **FEATURE-4 (Clay integration findings)**: opt-in child event passthrough.
7
+ `createSupervisor({ childEvents: true })` projects a redacted, capped milestone
8
+ subset of child `AgentEvent`s (`agent_started`/`finished`/`suspended`/`denied`
9
+ and tool-execution events) onto the supervisor stream as `delegation_child_event`
10
+ tagged with `childId`/`delegationId`/`depth`. Default off (stream unchanged).
11
+ Caps `maxChildEventsPerDelegation` (256/4096) and `maxChildEventBytes`
12
+ (32 KiB/256 KiB); overflow drops further child events and emits one
13
+ `delegation_child_events_capped` marker. Live passthrough is the initial
14
+ `delegate()` session; `resumeNestedRun` rebuilds are not projected in v1.
15
+
16
+ ### Fixed
17
+ - **BUG-2 (Clay integration findings)**: both child-factory consumption sites
18
+ (initial delegation and durable resume rebuild) now validate the factory
19
+ result shape (`config` object + `createSession` function) before any policy
20
+ intersection is read. A factory returning a session or any non-`Agent` value
21
+ fails closed at `delegate()`/resume time with
22
+ `SupervisorError: child "<id>" factory must return an Agent, got <type>`
23
+ (constructor name, e.g. `RuntimeAgentSession`) instead of a cryptic
24
+ `Cannot read properties of undefined (reading 'permission')` TypeError.
25
+
3
26
  ## [0.3.1] - 2026-08-29
4
27
 
5
28
  ### Changed
package/dist/limits.d.ts CHANGED
@@ -14,6 +14,10 @@ export declare const DEFAULT_DELEGATION_TIMEOUT_MS = 60000;
14
14
  export declare const HARD_DELEGATION_TIMEOUT_MS: number;
15
15
  export declare const DEFAULT_MAX_SUPERVISOR_QUEUED_EVENTS = 128;
16
16
  export declare const HARD_MAX_SUPERVISOR_QUEUED_EVENTS = 4096;
17
+ export declare const DEFAULT_MAX_CHILD_EVENTS_PER_DELEGATION = 256;
18
+ export declare const HARD_MAX_CHILD_EVENTS_PER_DELEGATION = 4096;
19
+ export declare const DEFAULT_MAX_CHILD_EVENT_BYTES: number;
20
+ export declare const HARD_MAX_CHILD_EVENT_BYTES: number;
17
21
  export interface SupervisorLimits {
18
22
  readonly maxDepth?: number;
19
23
  readonly maxActiveChildren?: number;
@@ -23,6 +27,10 @@ export interface SupervisorLimits {
23
27
  readonly maxTokens?: number;
24
28
  readonly timeoutMs?: number;
25
29
  readonly maxQueuedEvents?: number;
30
+ /** Milestone child events projected per delegation when `childEvents` is on. */
31
+ readonly maxChildEventsPerDelegation?: number;
32
+ /** Serialized byte ceiling for one projected child event. */
33
+ readonly maxChildEventBytes?: number;
26
34
  }
27
35
  export interface ResolvedSupervisorLimits {
28
36
  readonly maxDepth: number;
@@ -33,6 +41,8 @@ export interface ResolvedSupervisorLimits {
33
41
  readonly maxTokens: number;
34
42
  readonly timeoutMs: number;
35
43
  readonly maxQueuedEvents: number;
44
+ readonly maxChildEventsPerDelegation: number;
45
+ readonly maxChildEventBytes: number;
36
46
  }
37
47
  export declare function resolveSupervisorLimits(input?: SupervisorLimits): ResolvedSupervisorLimits;
38
48
  export declare function narrowSupervisorLimits(parent: ResolvedSupervisorLimits, input?: SupervisorLimits): ResolvedSupervisorLimits;
package/dist/limits.js CHANGED
@@ -15,6 +15,10 @@ export const DEFAULT_DELEGATION_TIMEOUT_MS = 60_000;
15
15
  export const HARD_DELEGATION_TIMEOUT_MS = 30 * 60_000;
16
16
  export const DEFAULT_MAX_SUPERVISOR_QUEUED_EVENTS = 128;
17
17
  export const HARD_MAX_SUPERVISOR_QUEUED_EVENTS = 4096;
18
+ export const DEFAULT_MAX_CHILD_EVENTS_PER_DELEGATION = 256;
19
+ export const HARD_MAX_CHILD_EVENTS_PER_DELEGATION = 4096;
20
+ export const DEFAULT_MAX_CHILD_EVENT_BYTES = 32 * 1024;
21
+ export const HARD_MAX_CHILD_EVENT_BYTES = 256 * 1024;
18
22
  const SPECS = {
19
23
  maxDepth: [DEFAULT_MAX_DELEGATION_DEPTH, HARD_MAX_DELEGATION_DEPTH],
20
24
  maxActiveChildren: [DEFAULT_MAX_ACTIVE_CHILDREN, HARD_MAX_ACTIVE_CHILDREN],
@@ -24,6 +28,8 @@ const SPECS = {
24
28
  maxTokens: [DEFAULT_MAX_DELEGATION_TOKENS, HARD_MAX_DELEGATION_TOKENS],
25
29
  timeoutMs: [DEFAULT_DELEGATION_TIMEOUT_MS, HARD_DELEGATION_TIMEOUT_MS],
26
30
  maxQueuedEvents: [DEFAULT_MAX_SUPERVISOR_QUEUED_EVENTS, HARD_MAX_SUPERVISOR_QUEUED_EVENTS],
31
+ maxChildEventsPerDelegation: [DEFAULT_MAX_CHILD_EVENTS_PER_DELEGATION, HARD_MAX_CHILD_EVENTS_PER_DELEGATION],
32
+ maxChildEventBytes: [DEFAULT_MAX_CHILD_EVENT_BYTES, HARD_MAX_CHILD_EVENT_BYTES],
27
33
  };
28
34
  export function resolveSupervisorLimits(input = {}) {
29
35
  return Object.fromEntries(Object.entries(SPECS).map(([key, [fallback, hard]]) => {
@@ -3,6 +3,75 @@ import { SupervisorDeniedError, SupervisorError, SupervisorLimitError, Superviso
3
3
  import { narrowSupervisorLimits, resolveSupervisorLimits } from "./limits.js";
4
4
  const ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
5
5
  const DELEGATION_NAMESPACE = "prism.supervisor-delegation";
6
+ /**
7
+ * BUG-2 guard (Clay integration findings): a child factory returning a session
8
+ * (or anything non-Agent) used to crash at `.config.permission` with a cryptic
9
+ * TypeError. One shared shape check at both factory-result consumption sites;
10
+ * constructor name ("AgentSession", "Object", ...) names the received type.
11
+ */
12
+ function assertChildAgent(value, childId) {
13
+ const candidate = value;
14
+ if (!candidate ||
15
+ typeof candidate !== "object" ||
16
+ !candidate.config ||
17
+ typeof candidate.config !== "object" ||
18
+ typeof candidate.createSession !== "function") {
19
+ const name = value === null || value === undefined
20
+ ? String(value)
21
+ : typeof value === "object" || typeof value === "function"
22
+ ? (value.constructor?.name ?? typeof value)
23
+ : typeof value;
24
+ throw new SupervisorError(`child "${childId}" factory must return an Agent, got ${name}`);
25
+ }
26
+ }
27
+ /** Milestone subset for v1 child-event passthrough (tool calls + run start/finish). */
28
+ const MILESTONE_CHILD_EVENT_TYPES = new Set([
29
+ "agent_started",
30
+ "agent_finished",
31
+ "agent_suspended",
32
+ "agent_denied",
33
+ "tool_execution_started",
34
+ "tool_execution_finished",
35
+ "tool_execution_error",
36
+ "tool_execution_blocked",
37
+ ]);
38
+ /** FEATURE-4: wrap a child session subscribe with filter + redact + cap + tag. */
39
+ function startChildEventPump(session, tags, limits, redactor, publish) {
40
+ const iterator = session.subscribe()[Symbol.asyncIterator]();
41
+ let emitted = 0;
42
+ let capped = false;
43
+ const pump = (async () => {
44
+ try {
45
+ for (;;) {
46
+ const next = await iterator.next();
47
+ if (next.done)
48
+ break;
49
+ const event = next.value;
50
+ if (!MILESTONE_CHILD_EVENT_TYPES.has(event.type) || capped)
51
+ continue;
52
+ const payload = redactor ? redactor.redact(event) : event;
53
+ if (emitted >= limits.maxChildEventsPerDelegation || JSON.stringify(payload).length > limits.maxChildEventBytes) {
54
+ capped = true;
55
+ publish({
56
+ type: "delegation_child_events_capped",
57
+ ...tags,
58
+ maxChildEvents: limits.maxChildEventsPerDelegation,
59
+ });
60
+ continue;
61
+ }
62
+ emitted += 1;
63
+ publish({ type: "delegation_child_event", ...tags, childEvent: payload });
64
+ }
65
+ }
66
+ catch {
67
+ // subscriber closed with the delegation
68
+ }
69
+ })();
70
+ return async () => {
71
+ await iterator.return?.();
72
+ await pump;
73
+ };
74
+ }
6
75
  export function createSupervisor(options) {
7
76
  requireOwnership(options.ownership);
8
77
  if (options.checkpoints && !options.definitionRevision?.trim()) {
@@ -96,6 +165,7 @@ export function createSupervisor(options) {
96
165
  signal: controller.signal,
97
166
  delegate: (nested) => delegate(nested, { path, signal: controller.signal }),
98
167
  }))), controller.signal);
168
+ assertChildAgent(childAgent, request.childId);
99
169
  const agent = createAgent({
100
170
  ...childAgent.config,
101
171
  permission: intersectPolicies(preliminaryPermission, childAgent.config.permission),
@@ -108,6 +178,9 @@ export function createSupervisor(options) {
108
178
  id: `${delegationId}-session`,
109
179
  metadata: { supervisorId: id, delegationId, resourceId, threadId },
110
180
  });
181
+ const stopChildEvents = options.childEvents === true
182
+ ? startChildEventPump(session, { childId: request.childId, delegationId, depth }, limits, options.redactor, (event) => events.publish(event))
183
+ : undefined;
111
184
  let result;
112
185
  try {
113
186
  result = await abortable(session.run(input, {
@@ -146,6 +219,9 @@ export function createSupervisor(options) {
146
219
  }
147
220
  throw error;
148
221
  }
222
+ finally {
223
+ await stopChildEvents?.();
224
+ }
149
225
  if (result.status === "suspended") {
150
226
  // Child approvals surface on the hosting root run: persist the rebuild mapping, then
151
227
  // signal core with the child's pending decisions (core hashes/attributes the ids).
@@ -261,6 +337,7 @@ export function createSupervisor(options) {
261
337
  signal: controller.signal,
262
338
  delegate: (nestedRequest) => delegate(nestedRequest, { path: mapping.path, signal: controller.signal }),
263
339
  }));
340
+ assertChildAgent(childAgent, mapping.childId);
264
341
  const agent = createAgent({
265
342
  ...childAgent.config,
266
343
  permission: intersectPolicies(permission, childAgent.config.permission),
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Agent, AgentIdentity, AgentRunResult, CheckpointStore, OwnershipScope, PermissionPolicy, ResumeNestedRun, SecretRedactor, ToolEffectStore } from "@arnilo/prism";
1
+ import type { Agent, AgentEvent, AgentIdentity, AgentRunResult, CheckpointStore, OwnershipScope, PermissionPolicy, ResumeNestedRun, SecretRedactor, ToolEffectStore } from "@arnilo/prism";
2
2
  import type { ResolvedSupervisorLimits, SupervisorLimits } from "./limits.js";
3
3
  export interface DelegationRequest {
4
4
  readonly childId: string;
@@ -86,6 +86,18 @@ export type SupervisorEvent = {
86
86
  readonly delegationId: string;
87
87
  readonly depth: number;
88
88
  readonly error: string;
89
+ } | {
90
+ readonly type: "delegation_child_event";
91
+ readonly childId: string;
92
+ readonly delegationId: string;
93
+ readonly depth: number;
94
+ readonly childEvent: AgentEvent;
95
+ } | {
96
+ readonly type: "delegation_child_events_capped";
97
+ readonly childId: string;
98
+ readonly delegationId: string;
99
+ readonly depth: number;
100
+ readonly maxChildEvents: number;
89
101
  };
90
102
  export interface CreateSupervisorOptions {
91
103
  readonly id?: string;
@@ -99,6 +111,13 @@ export interface CreateSupervisorOptions {
99
111
  readonly limits?: SupervisorLimits;
100
112
  readonly hooks?: SupervisorHooks;
101
113
  readonly redactor?: SecretRedactor;
114
+ /**
115
+ * Opt-in: project a redacted, capped milestone subset of child `AgentEvent`s
116
+ * (`agent_started`/`finished`/`suspended`/`denied` and tool-execution events)
117
+ * onto the supervisor stream as `delegation_child_event`. Default off — the
118
+ * stream is unchanged when unset/false. Full per-token streaming is out of scope.
119
+ */
120
+ readonly childEvents?: boolean;
102
121
  /**
103
122
  * Durable child runs: with `checkpoints` + `definitionRevision`, every child runs with
104
123
  * `interruptBeforeTool`; a child that suspends on pending decisions throws
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arnilo/prism-supervisor",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Bounded supervisor delegation and A2A 1.0 durable task, rich-part, reconnect, and push interoperability.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -25,7 +25,7 @@
25
25
  "pack:dry-run": "npm pack --dry-run"
26
26
  },
27
27
  "peerDependencies": {
28
- "@arnilo/prism": "^0.3.1"
28
+ "@arnilo/prism": "^0.3.2"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@arnilo/prism": "file:../.."