@arnilo/prism-supervisor 0.3.0 → 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 +28 -0
- package/dist/limits.d.ts +10 -0
- package/dist/limits.js +6 -0
- package/dist/supervisor.js +77 -0
- package/dist/types.d.ts +20 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
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
|
+
|
|
26
|
+
## [0.3.1] - 2026-08-29
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
- Plan 035-039 changed-package cut: additive runtime performance, tooling, and documentation deltas; peer window refresh.
|
|
30
|
+
|
|
3
31
|
## [0.1.0] - 2026-08-09
|
|
4
32
|
|
|
5
33
|
### 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]]) => {
|
package/dist/supervisor.js
CHANGED
|
@@ -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.
|
|
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.
|
|
28
|
+
"@arnilo/prism": "^0.3.2"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@arnilo/prism": "file:../.."
|