@noetaris/harness 0.3.2 → 0.5.0
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 +8 -3
- package/dist/index.d.ts +269 -125
- package/dist/index.js +535 -151
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -20,6 +20,9 @@ function isRuntimeMarker(value) {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
// src/loop/loop-dsl.ts
|
|
23
|
+
function isForkDef(node) {
|
|
24
|
+
return "branches" in node;
|
|
25
|
+
}
|
|
23
26
|
var InvalidLoopBuilderError = class extends Error {
|
|
24
27
|
constructor() {
|
|
25
28
|
super("argument is not a LoopBuilder instance \u2014 was it created by createLoopBuilder?");
|
|
@@ -27,6 +30,9 @@ var InvalidLoopBuilderError = class extends Error {
|
|
|
27
30
|
}
|
|
28
31
|
};
|
|
29
32
|
var _builderState = /* @__PURE__ */ Symbol("builderState");
|
|
33
|
+
function isForkNodeMutable(node) {
|
|
34
|
+
return "branches" in node;
|
|
35
|
+
}
|
|
30
36
|
function deepFreeze(obj) {
|
|
31
37
|
Object.freeze(obj);
|
|
32
38
|
for (const value of Object.values(obj)) {
|
|
@@ -65,6 +71,32 @@ function createLoopBuilder() {
|
|
|
65
71
|
}
|
|
66
72
|
return this;
|
|
67
73
|
},
|
|
74
|
+
fork(name) {
|
|
75
|
+
const forkNode = { name, branches: [], next: void 0 };
|
|
76
|
+
state.steps.push(forkNode);
|
|
77
|
+
if (state.startCalled && state.entryStep === void 0) {
|
|
78
|
+
state.entryStep = name;
|
|
79
|
+
}
|
|
80
|
+
return this;
|
|
81
|
+
},
|
|
82
|
+
branch(name, builderFn) {
|
|
83
|
+
const currentNode = state.steps.at(-1);
|
|
84
|
+
if (currentNode === void 0 || !isForkNodeMutable(currentNode)) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
".branch() must be called immediately after .fork() or another .branch() on the same fork"
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const branchBuilder = createLoopBuilder();
|
|
90
|
+
builderFn(branchBuilder);
|
|
91
|
+
const branchDefinition = extractLoopDefinition(
|
|
92
|
+
branchBuilder
|
|
93
|
+
);
|
|
94
|
+
currentNode.branches.push({ name, definition: branchDefinition });
|
|
95
|
+
return this;
|
|
96
|
+
},
|
|
97
|
+
join(name, options) {
|
|
98
|
+
return this.step(name, options);
|
|
99
|
+
},
|
|
68
100
|
on(signal) {
|
|
69
101
|
if (state.steps.length === 0) {
|
|
70
102
|
return {
|
|
@@ -72,17 +104,23 @@ function createLoopBuilder() {
|
|
|
72
104
|
end: () => this
|
|
73
105
|
};
|
|
74
106
|
}
|
|
75
|
-
const
|
|
107
|
+
const currentNode = state.steps.at(-1);
|
|
108
|
+
if (isForkNodeMutable(currentNode)) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
".on() cannot be called on a fork \u2014 a fork has no run/route to emit a signal from"
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
const currentStep = currentNode;
|
|
76
114
|
return {
|
|
77
115
|
to: (step) => {
|
|
78
|
-
currentStep
|
|
116
|
+
currentStep.transitions.push({
|
|
79
117
|
signal,
|
|
80
118
|
target: { kind: "step", name: step }
|
|
81
119
|
});
|
|
82
120
|
return this;
|
|
83
121
|
},
|
|
84
122
|
end: () => {
|
|
85
|
-
currentStep
|
|
123
|
+
currentStep.transitions.push({
|
|
86
124
|
signal,
|
|
87
125
|
target: { kind: "end" }
|
|
88
126
|
});
|
|
@@ -92,9 +130,9 @@ function createLoopBuilder() {
|
|
|
92
130
|
},
|
|
93
131
|
next(name) {
|
|
94
132
|
if (state.steps.length > 0) {
|
|
95
|
-
const
|
|
96
|
-
if (
|
|
97
|
-
|
|
133
|
+
const currentNode = state.steps.at(-1);
|
|
134
|
+
if (currentNode !== void 0) {
|
|
135
|
+
currentNode.next = name;
|
|
98
136
|
}
|
|
99
137
|
}
|
|
100
138
|
return this;
|
|
@@ -112,19 +150,30 @@ function extractLoopDefinition(builder) {
|
|
|
112
150
|
throw new InvalidLoopBuilderError();
|
|
113
151
|
}
|
|
114
152
|
const state = builder[_builderState];
|
|
115
|
-
const immutableSteps = state.steps.map((
|
|
116
|
-
|
|
153
|
+
const immutableSteps = state.steps.map((node) => {
|
|
154
|
+
if (isForkNodeMutable(node)) {
|
|
155
|
+
const branches = Object.freeze(
|
|
156
|
+
node.branches.map((b) => Object.freeze({ name: b.name, definition: b.definition }))
|
|
157
|
+
);
|
|
158
|
+
const forkDef = {
|
|
159
|
+
name: node.name,
|
|
160
|
+
branches,
|
|
161
|
+
next: node.next
|
|
162
|
+
};
|
|
163
|
+
return Object.freeze(forkDef);
|
|
164
|
+
}
|
|
165
|
+
const transitions = Object.freeze([...node.transitions]);
|
|
117
166
|
for (const transition of transitions) {
|
|
118
167
|
Object.freeze(transition);
|
|
119
168
|
Object.freeze(transition.target);
|
|
120
169
|
}
|
|
121
170
|
const stepDef = {
|
|
122
|
-
name:
|
|
123
|
-
run:
|
|
124
|
-
route:
|
|
171
|
+
name: node.name,
|
|
172
|
+
run: node.run,
|
|
173
|
+
route: node.route,
|
|
125
174
|
transitions,
|
|
126
|
-
next:
|
|
127
|
-
errorAware:
|
|
175
|
+
next: node.next,
|
|
176
|
+
errorAware: node.errorAware
|
|
128
177
|
};
|
|
129
178
|
return Object.freeze(stepDef);
|
|
130
179
|
});
|
|
@@ -148,30 +197,85 @@ ${violations.map((v) => ` - ${v}`).join("\n")}`);
|
|
|
148
197
|
}
|
|
149
198
|
};
|
|
150
199
|
function validateLoop(def) {
|
|
200
|
+
const violations = collectViolations(def, void 0);
|
|
201
|
+
const nameLocations = /* @__PURE__ */ new Map();
|
|
202
|
+
collectNameLocations(def, void 0, nameLocations);
|
|
203
|
+
for (const [name, locations] of nameLocations) {
|
|
204
|
+
if (locations.length > 1) {
|
|
205
|
+
violations.push(
|
|
206
|
+
`name "${name}" is declared more than once across the nested structure: ${locations.join(", ")}`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (violations.length > 0) {
|
|
211
|
+
throw new LoopValidationError(violations);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function collectViolations(def, pathLabel) {
|
|
215
|
+
const prefix = pathLabel !== void 0 ? `in branch "${pathLabel}": ` : "";
|
|
216
|
+
const violations = collectStructuralViolations(def).map((v) => prefix + v);
|
|
217
|
+
for (const node of def.steps) {
|
|
218
|
+
if (!isForkDef(node)) continue;
|
|
219
|
+
if (node.branches.length < 2) {
|
|
220
|
+
violations.push(
|
|
221
|
+
`${prefix}fork "${node.name}" must have at least 2 branches (found ${node.branches.length})`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
const seenBranchNames = /* @__PURE__ */ new Set();
|
|
225
|
+
for (const branch of node.branches) {
|
|
226
|
+
if (seenBranchNames.has(branch.name)) {
|
|
227
|
+
violations.push(`${prefix}fork "${node.name}" has duplicate branch name "${branch.name}"`);
|
|
228
|
+
}
|
|
229
|
+
seenBranchNames.add(branch.name);
|
|
230
|
+
}
|
|
231
|
+
for (const branch of node.branches) {
|
|
232
|
+
const branchPath = pathLabel !== void 0 ? `${pathLabel}/${node.name}/${branch.name}` : `${node.name}/${branch.name}`;
|
|
233
|
+
violations.push(...collectViolations(branch.definition, branchPath));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return violations;
|
|
237
|
+
}
|
|
238
|
+
function collectNameLocations(def, path, acc) {
|
|
239
|
+
for (const node of def.steps) {
|
|
240
|
+
const location = path !== void 0 ? `${path}/${node.name}` : node.name;
|
|
241
|
+
const locations = acc.get(node.name) ?? [];
|
|
242
|
+
locations.push(location);
|
|
243
|
+
acc.set(node.name, locations);
|
|
244
|
+
if (isForkDef(node)) {
|
|
245
|
+
for (const branch of node.branches) {
|
|
246
|
+
const branchPath = path !== void 0 ? `${path}/${node.name}/${branch.name}` : `${node.name}/${branch.name}`;
|
|
247
|
+
collectNameLocations(branch.definition, branchPath, acc);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function collectStructuralViolations(def) {
|
|
151
253
|
const violations = [];
|
|
152
254
|
const names = new Set(def.steps.map((s) => s.name));
|
|
153
|
-
|
|
255
|
+
const stepNodes = def.steps.filter((s) => !isForkDef(s));
|
|
256
|
+
for (const step of stepNodes) {
|
|
154
257
|
if (step.run === void 0 && step.route === void 0) {
|
|
155
258
|
violations.push(`step "${step.name}" must have at least one of run or route`);
|
|
156
259
|
}
|
|
157
260
|
}
|
|
158
|
-
for (const step of
|
|
261
|
+
for (const step of stepNodes) {
|
|
159
262
|
if (step.route !== void 0 && step.transitions.length === 0) {
|
|
160
263
|
violations.push(`step "${step.name}" has route but no .on() transitions \u2014 every signal must be handled`);
|
|
161
264
|
}
|
|
162
265
|
}
|
|
163
|
-
for (const [i,
|
|
164
|
-
if (
|
|
165
|
-
|
|
266
|
+
for (const [i, node] of def.steps.entries()) {
|
|
267
|
+
if (node === void 0) continue;
|
|
268
|
+
const hasRoute = !isForkDef(node) && node.route !== void 0;
|
|
269
|
+
if (!hasRoute) {
|
|
166
270
|
const isLast = i === def.steps.length - 1;
|
|
167
|
-
if (isLast &&
|
|
271
|
+
if (isLast && node.next === void 0) {
|
|
168
272
|
violations.push(
|
|
169
|
-
`step "${
|
|
273
|
+
`step "${node.name}" has no route, no explicit .next(), and no following step \u2014 no exit path`
|
|
170
274
|
);
|
|
171
275
|
}
|
|
172
276
|
}
|
|
173
277
|
}
|
|
174
|
-
for (const step of
|
|
278
|
+
for (const step of stepNodes) {
|
|
175
279
|
if (step.route !== void 0 && step.next !== void 0) {
|
|
176
280
|
violations.push(`step "${step.name}" has both route and .next() \u2014 these are mutually exclusive`);
|
|
177
281
|
}
|
|
@@ -183,7 +287,7 @@ function validateLoop(def) {
|
|
|
183
287
|
}
|
|
184
288
|
seen.add(step.name);
|
|
185
289
|
}
|
|
186
|
-
for (const step of
|
|
290
|
+
for (const step of stepNodes) {
|
|
187
291
|
for (const transition of step.transitions) {
|
|
188
292
|
if (transition.target.kind === "step") {
|
|
189
293
|
if (!names.has(transition.target.name)) {
|
|
@@ -193,10 +297,12 @@ function validateLoop(def) {
|
|
|
193
297
|
}
|
|
194
298
|
}
|
|
195
299
|
}
|
|
196
|
-
|
|
197
|
-
|
|
300
|
+
}
|
|
301
|
+
for (const node of def.steps) {
|
|
302
|
+
if (node.next !== void 0) {
|
|
303
|
+
if (!names.has(node.next)) {
|
|
198
304
|
violations.push(
|
|
199
|
-
`step "${
|
|
305
|
+
`step "${node.name}" has .next("${node.next}") but step "${node.next}" is not declared`
|
|
200
306
|
);
|
|
201
307
|
}
|
|
202
308
|
}
|
|
@@ -213,7 +319,7 @@ function validateLoop(def) {
|
|
|
213
319
|
violations.push(`l.start() was called but no step was declared after it \u2014 declare at least one step`);
|
|
214
320
|
}
|
|
215
321
|
let hasExit = false;
|
|
216
|
-
for (const step of
|
|
322
|
+
for (const step of stepNodes) {
|
|
217
323
|
for (const transition of step.transitions) {
|
|
218
324
|
if (transition.target.kind === "end") {
|
|
219
325
|
hasExit = true;
|
|
@@ -236,36 +342,39 @@ function validateLoop(def) {
|
|
|
236
342
|
}
|
|
237
343
|
while (queue.length > 0) {
|
|
238
344
|
const currentName = queue.shift();
|
|
239
|
-
const
|
|
240
|
-
if (!
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
if (
|
|
244
|
-
reachable.
|
|
245
|
-
|
|
345
|
+
const currentNode = def.steps.find((s) => s.name === currentName);
|
|
346
|
+
if (!currentNode) continue;
|
|
347
|
+
if (!isForkDef(currentNode)) {
|
|
348
|
+
for (const transition of currentNode.transitions) {
|
|
349
|
+
if (transition.target.kind === "step") {
|
|
350
|
+
if (!reachable.has(transition.target.name)) {
|
|
351
|
+
reachable.add(transition.target.name);
|
|
352
|
+
queue.push(transition.target.name);
|
|
353
|
+
}
|
|
246
354
|
}
|
|
247
355
|
}
|
|
248
356
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
357
|
+
const hasRoute = !isForkDef(currentNode) && currentNode.route !== void 0;
|
|
358
|
+
if (hasRoute) {
|
|
359
|
+
if (currentNode.next !== void 0 && !reachable.has(currentNode.next)) {
|
|
360
|
+
reachable.add(currentNode.next);
|
|
361
|
+
queue.push(currentNode.next);
|
|
253
362
|
}
|
|
254
363
|
} else {
|
|
255
|
-
if (
|
|
256
|
-
if (!reachable.has(
|
|
257
|
-
reachable.add(
|
|
258
|
-
queue.push(
|
|
364
|
+
if (currentNode.next !== void 0) {
|
|
365
|
+
if (!reachable.has(currentNode.next)) {
|
|
366
|
+
reachable.add(currentNode.next);
|
|
367
|
+
queue.push(currentNode.next);
|
|
259
368
|
}
|
|
260
369
|
} else {
|
|
261
370
|
const currentIndex = def.steps.findIndex((s) => s.name === currentName);
|
|
262
371
|
if (currentIndex !== -1 && currentIndex < def.steps.length - 1) {
|
|
263
|
-
const
|
|
264
|
-
if (
|
|
265
|
-
const
|
|
266
|
-
if (!reachable.has(
|
|
267
|
-
reachable.add(
|
|
268
|
-
queue.push(
|
|
372
|
+
const nextNode = def.steps[currentIndex + 1];
|
|
373
|
+
if (nextNode !== void 0) {
|
|
374
|
+
const nextNodeName = nextNode.name;
|
|
375
|
+
if (!reachable.has(nextNodeName)) {
|
|
376
|
+
reachable.add(nextNodeName);
|
|
377
|
+
queue.push(nextNodeName);
|
|
269
378
|
}
|
|
270
379
|
}
|
|
271
380
|
}
|
|
@@ -278,16 +387,14 @@ function validateLoop(def) {
|
|
|
278
387
|
}
|
|
279
388
|
}
|
|
280
389
|
}
|
|
281
|
-
for (const step of
|
|
390
|
+
for (const step of stepNodes) {
|
|
282
391
|
if (step.route === void 0 && step.transitions.length > 0) {
|
|
283
392
|
violations.push(
|
|
284
393
|
`step "${step.name}" has .on() transitions but no route \u2014 .on() is only valid when route is set`
|
|
285
394
|
);
|
|
286
395
|
}
|
|
287
396
|
}
|
|
288
|
-
|
|
289
|
-
throw new LoopValidationError(violations);
|
|
290
|
-
}
|
|
397
|
+
return violations;
|
|
291
398
|
}
|
|
292
399
|
|
|
293
400
|
// src/harness/harness-builder.ts
|
|
@@ -384,6 +491,51 @@ function createBuilderInstance(internals) {
|
|
|
384
491
|
return builder;
|
|
385
492
|
}
|
|
386
493
|
|
|
494
|
+
// src/agent/observer.ts
|
|
495
|
+
function reportObserverError(error, ctx, sink) {
|
|
496
|
+
if (sink !== void 0) {
|
|
497
|
+
try {
|
|
498
|
+
sink(error, ctx);
|
|
499
|
+
} catch (sinkError) {
|
|
500
|
+
console.error(`[harness] onObserverError sink threw while handling '${ctx.hookName}':`, sinkError);
|
|
501
|
+
}
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
console.error(`[harness] observer hook '${ctx.hookName}' threw:`, error);
|
|
505
|
+
}
|
|
506
|
+
function safeInvoke(hookName, call, sink) {
|
|
507
|
+
try {
|
|
508
|
+
call();
|
|
509
|
+
} catch (error) {
|
|
510
|
+
reportObserverError(error, { hookName }, sink);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
function composeObservers(observers, onObserverError) {
|
|
514
|
+
return {
|
|
515
|
+
onRunStart: (ctx) => {
|
|
516
|
+
for (const o of observers) safeInvoke("onRunStart", () => o.onRunStart?.(ctx), onObserverError);
|
|
517
|
+
},
|
|
518
|
+
onRunEnd: (ctx, event) => {
|
|
519
|
+
for (const o of observers) safeInvoke("onRunEnd", () => o.onRunEnd?.(ctx, event), onObserverError);
|
|
520
|
+
},
|
|
521
|
+
onStepStart: (ctx) => {
|
|
522
|
+
for (const o of observers) safeInvoke("onStepStart", () => o.onStepStart?.(ctx), onObserverError);
|
|
523
|
+
},
|
|
524
|
+
onStepEnd: (ctx, event) => {
|
|
525
|
+
for (const o of observers) safeInvoke("onStepEnd", () => o.onStepEnd?.(ctx, event), onObserverError);
|
|
526
|
+
},
|
|
527
|
+
onStepError: (ctx, event) => {
|
|
528
|
+
for (const o of observers) safeInvoke("onStepError", () => o.onStepError?.(ctx, event), onObserverError);
|
|
529
|
+
},
|
|
530
|
+
onInterrupt: (ctx, event) => {
|
|
531
|
+
for (const o of observers) safeInvoke("onInterrupt", () => o.onInterrupt?.(ctx, event), onObserverError);
|
|
532
|
+
},
|
|
533
|
+
onEvent: (ctx, type, payload) => {
|
|
534
|
+
for (const o of observers) safeInvoke("onEvent", () => o.onEvent?.(ctx, type, payload), onObserverError);
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
|
|
387
539
|
// src/agent/ctx-interrupt.ts
|
|
388
540
|
var InterruptPause = class extends Error {
|
|
389
541
|
interruptId;
|
|
@@ -398,10 +550,11 @@ var InterruptPause = class extends Error {
|
|
|
398
550
|
function isInterruptPause(error) {
|
|
399
551
|
return error instanceof InterruptPause;
|
|
400
552
|
}
|
|
401
|
-
function createInterruptFn(state, callCountRef) {
|
|
553
|
+
function createInterruptFn(state, callCountRef, branchPath) {
|
|
554
|
+
const prefix = branchPath !== void 0 && branchPath.length > 0 ? `${branchPath.join(":")}:` : "";
|
|
402
555
|
return async (prompt, id) => {
|
|
403
556
|
const isAutoId = id === void 0;
|
|
404
|
-
const effectiveId = isAutoId ? `$auto:${callCountRef.current}` : id;
|
|
557
|
+
const effectiveId = isAutoId ? `$auto:${prefix}${callCountRef.current}` : id;
|
|
405
558
|
if (isAutoId) {
|
|
406
559
|
callCountRef.current++;
|
|
407
560
|
}
|
|
@@ -415,11 +568,12 @@ function createInterruptFn(state, callCountRef) {
|
|
|
415
568
|
}
|
|
416
569
|
|
|
417
570
|
// src/agent/ctx-emit.ts
|
|
418
|
-
function createEmitFn(listeners, observer, stepCtxRef) {
|
|
571
|
+
function createEmitFn(listeners, observer, stepCtxRef, onObserverError) {
|
|
419
572
|
return (name, payload) => {
|
|
420
573
|
listeners[name]?.(payload);
|
|
421
574
|
if (observer !== void 0 && stepCtxRef !== void 0 && stepCtxRef.current !== null) {
|
|
422
|
-
|
|
575
|
+
const ctx = stepCtxRef.current;
|
|
576
|
+
safeInvoke("onEvent", () => observer.onEvent?.(ctx, name, payload), onObserverError);
|
|
423
577
|
}
|
|
424
578
|
};
|
|
425
579
|
}
|
|
@@ -432,6 +586,10 @@ function extractRunListeners(resources) {
|
|
|
432
586
|
}
|
|
433
587
|
|
|
434
588
|
// src/loop/loop-executor.ts
|
|
589
|
+
var NOOP_OBSERVER = {};
|
|
590
|
+
function isForkCursor(cursor) {
|
|
591
|
+
return typeof cursor !== "string";
|
|
592
|
+
}
|
|
435
593
|
var UnknownSignalError = class extends Error {
|
|
436
594
|
step;
|
|
437
595
|
signal;
|
|
@@ -452,6 +610,20 @@ var NoNextStepError = class extends Error {
|
|
|
452
610
|
this.step = step;
|
|
453
611
|
}
|
|
454
612
|
};
|
|
613
|
+
var MissingReducerError = class extends Error {
|
|
614
|
+
fork;
|
|
615
|
+
key;
|
|
616
|
+
branches;
|
|
617
|
+
constructor(fork, key, branches) {
|
|
618
|
+
super(
|
|
619
|
+
`fork "${fork}": state key "${key}" was touched by multiple branches (${branches.join(", ")}) but has no reducer declared \u2014 add field({ reduce }) for "${key}"`
|
|
620
|
+
);
|
|
621
|
+
this.name = "MissingReducerError";
|
|
622
|
+
this.fork = fork;
|
|
623
|
+
this.key = key;
|
|
624
|
+
this.branches = branches;
|
|
625
|
+
}
|
|
626
|
+
};
|
|
455
627
|
function buildImplicitNextMap(graph) {
|
|
456
628
|
const map = /* @__PURE__ */ new Map();
|
|
457
629
|
for (let i = 0; i < graph.steps.length; i++) {
|
|
@@ -461,7 +633,7 @@ function buildImplicitNextMap(graph) {
|
|
|
461
633
|
}
|
|
462
634
|
return map;
|
|
463
635
|
}
|
|
464
|
-
function applyUpdate(state, update, schema) {
|
|
636
|
+
function applyUpdate(state, update, schema, touchedKeys) {
|
|
465
637
|
for (const [key, value] of Object.entries(update)) {
|
|
466
638
|
if (key === "$error" || key === "$interrupt" || key === "$interruptResponses") continue;
|
|
467
639
|
const reducer = schema?.[key]?.reduce;
|
|
@@ -470,11 +642,13 @@ function applyUpdate(state, update, schema) {
|
|
|
470
642
|
} else {
|
|
471
643
|
state[key] = value;
|
|
472
644
|
}
|
|
645
|
+
touchedKeys?.add(key);
|
|
473
646
|
}
|
|
474
647
|
}
|
|
475
648
|
async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callbacks) {
|
|
476
649
|
const { onBeforeStep, onAfterStep, onError, onComplete, onInterrupt } = callbacks ?? {};
|
|
477
650
|
const obs = callbacks?.observer;
|
|
651
|
+
const onObserverError = callbacks?.onObserverError;
|
|
478
652
|
const implicitNextMap = buildImplicitNextMap(graph);
|
|
479
653
|
if (!("$error" in state)) state.$error = null;
|
|
480
654
|
if (!("$interrupt" in state)) state.$interrupt = null;
|
|
@@ -485,29 +659,86 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
|
|
|
485
659
|
sessionId: ctx.sessionId,
|
|
486
660
|
runId: typeof callbacks?.runId === "string" ? callbacks.runId : "",
|
|
487
661
|
...typeof callbacks?.parentRunId === "string" ? { parentRunId: callbacks.parentRunId } : {},
|
|
488
|
-
...typeof ctx.instanceId === "string" ? { instanceId: ctx.instanceId } : {}
|
|
662
|
+
...typeof ctx.instanceId === "string" ? { instanceId: ctx.instanceId } : {},
|
|
663
|
+
...callbacks?.branchPath !== void 0 && callbacks.branchPath.length > 0 ? { branchPath: callbacks.branchPath } : {}
|
|
489
664
|
};
|
|
490
|
-
obs?.onRunStart?.(runCtx);
|
|
665
|
+
safeInvoke("onRunStart", () => obs?.onRunStart?.(runCtx), onObserverError);
|
|
491
666
|
const callCountRef = { current: 0 };
|
|
492
667
|
const stepCtxRef = { current: null };
|
|
493
|
-
ctx["interrupt"] = createInterruptFn(state, callCountRef);
|
|
494
|
-
ctx["emit"] = createEmitFn(callbacks?.listeners ?? {}, obs, stepCtxRef);
|
|
668
|
+
ctx["interrupt"] = createInterruptFn(state, callCountRef, callbacks?.branchPath);
|
|
669
|
+
ctx["emit"] = createEmitFn(callbacks?.listeners ?? {}, obs, stepCtxRef, onObserverError);
|
|
495
670
|
let cursor = startCursor ?? graph.entryStep;
|
|
496
671
|
while (true) {
|
|
497
672
|
if (shouldStop?.()) {
|
|
498
|
-
obs?.onRunEnd?.(runCtx, { signal: "$stopped", durationMs: Date.now() - runStart });
|
|
673
|
+
safeInvoke("onRunEnd", () => obs?.onRunEnd?.(runCtx, { signal: "$stopped", durationMs: Date.now() - runStart }), onObserverError);
|
|
499
674
|
return { state, signal: null, cursor, paused: true };
|
|
500
675
|
}
|
|
676
|
+
if (isForkCursor(cursor)) {
|
|
677
|
+
const forkCursor = cursor;
|
|
678
|
+
const forkNode = graph.steps.find((s) => s.name === forkCursor.fork);
|
|
679
|
+
if (!isForkDef(forkNode)) {
|
|
680
|
+
throw new NoNextStepError(forkCursor.fork);
|
|
681
|
+
}
|
|
682
|
+
onBeforeStep?.(forkNode.name, { ...state });
|
|
683
|
+
const outcome = await handleFork(
|
|
684
|
+
forkNode,
|
|
685
|
+
forkCursor.branches,
|
|
686
|
+
state,
|
|
687
|
+
ctx,
|
|
688
|
+
schema,
|
|
689
|
+
shouldStop,
|
|
690
|
+
callbacks,
|
|
691
|
+
obs,
|
|
692
|
+
runCtx,
|
|
693
|
+
runStart,
|
|
694
|
+
implicitNextMap,
|
|
695
|
+
graph
|
|
696
|
+
);
|
|
697
|
+
if (outcome.done) {
|
|
698
|
+
return outcome.result;
|
|
699
|
+
}
|
|
700
|
+
cursor = outcome.nextCursor;
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
501
703
|
onBeforeStep?.(cursor, { ...state });
|
|
502
|
-
const
|
|
704
|
+
const node = graph.steps.find((s) => s.name === cursor);
|
|
705
|
+
if (isForkDef(node)) {
|
|
706
|
+
const outcome = await handleFork(
|
|
707
|
+
node,
|
|
708
|
+
void 0,
|
|
709
|
+
state,
|
|
710
|
+
ctx,
|
|
711
|
+
schema,
|
|
712
|
+
shouldStop,
|
|
713
|
+
callbacks,
|
|
714
|
+
obs,
|
|
715
|
+
runCtx,
|
|
716
|
+
runStart,
|
|
717
|
+
implicitNextMap,
|
|
718
|
+
graph
|
|
719
|
+
);
|
|
720
|
+
if (outcome.done) {
|
|
721
|
+
return outcome.result;
|
|
722
|
+
}
|
|
723
|
+
cursor = outcome.nextCursor;
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
const step = node;
|
|
503
727
|
callCountRef.current = 0;
|
|
504
|
-
const stepCtx = {
|
|
728
|
+
const stepCtx = {
|
|
729
|
+
agentId: ctx.agentId,
|
|
730
|
+
sessionId: ctx.sessionId,
|
|
731
|
+
stepName: cursor,
|
|
732
|
+
...callbacks?.branchPath !== void 0 && callbacks.branchPath.length > 0 ? { branchPath: callbacks.branchPath } : {}
|
|
733
|
+
};
|
|
505
734
|
stepCtxRef.current = stepCtx;
|
|
506
735
|
const stepStart = Date.now();
|
|
507
|
-
obs?.onStepStart?.(stepCtx);
|
|
508
|
-
if (callbacks?.
|
|
509
|
-
|
|
510
|
-
|
|
736
|
+
safeInvoke("onStepStart", () => obs?.onStepStart?.(stepCtx), onObserverError);
|
|
737
|
+
if (callbacks?.telemetrySlots !== void 0) {
|
|
738
|
+
const telemetry = { observer: obs ?? NOOP_OBSERVER, stepContext: stepCtx };
|
|
739
|
+
for (const { key, slot } of callbacks.telemetrySlots) {
|
|
740
|
+
;
|
|
741
|
+
ctx[key] = slot.withTelemetry(telemetry);
|
|
511
742
|
}
|
|
512
743
|
}
|
|
513
744
|
let runSucceeded = false;
|
|
@@ -518,7 +749,7 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
|
|
|
518
749
|
state,
|
|
519
750
|
ctx
|
|
520
751
|
);
|
|
521
|
-
applyUpdate(state, update, schema);
|
|
752
|
+
applyUpdate(state, update, schema, callbacks?.touchedKeys);
|
|
522
753
|
if (hadResponses) {
|
|
523
754
|
state.$interrupt = null;
|
|
524
755
|
state.$interruptResponses = {};
|
|
@@ -527,19 +758,19 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
|
|
|
527
758
|
} catch (e) {
|
|
528
759
|
if (isInterruptPause(e)) {
|
|
529
760
|
onInterrupt?.(e.prompt, e.interruptId);
|
|
530
|
-
obs?.onInterrupt?.(stepCtx, { prompt: e.prompt, interruptId: e.interruptId });
|
|
531
|
-
obs?.onRunEnd?.(runCtx, { signal: "$interrupt", durationMs: Date.now() - runStart });
|
|
761
|
+
safeInvoke("onInterrupt", () => obs?.onInterrupt?.(stepCtx, { prompt: e.prompt, interruptId: e.interruptId }), onObserverError);
|
|
762
|
+
safeInvoke("onRunEnd", () => obs?.onRunEnd?.(runCtx, { signal: "$interrupt", durationMs: Date.now() - runStart }), onObserverError);
|
|
532
763
|
return { state, signal: "$interrupt", cursor, paused: true };
|
|
533
764
|
}
|
|
534
765
|
state.$error = e instanceof Error ? e : new Error(String(e));
|
|
535
766
|
onError?.(state.$error, cursor);
|
|
536
|
-
obs?.onStepError?.(stepCtx, { error: state.$error, durationMs: Date.now() - stepStart });
|
|
767
|
+
safeInvoke("onStepError", () => obs?.onStepError?.(stepCtx, { error: state.$error, durationMs: Date.now() - stepStart }), onObserverError);
|
|
537
768
|
}
|
|
538
769
|
}
|
|
539
770
|
if (runSucceeded) {
|
|
540
771
|
state.$error = null;
|
|
541
772
|
onAfterStep?.(cursor, state);
|
|
542
|
-
obs?.onStepEnd?.(stepCtx, { durationMs: Date.now() - stepStart });
|
|
773
|
+
safeInvoke("onStepEnd", () => obs?.onStepEnd?.(stepCtx, { durationMs: Date.now() - stepStart }), onObserverError);
|
|
543
774
|
}
|
|
544
775
|
const callRoute = step.route !== void 0 && (runSucceeded || step.run === void 0 || step.errorAware);
|
|
545
776
|
if (callRoute) {
|
|
@@ -549,7 +780,7 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
|
|
|
549
780
|
signal = routeFn(state);
|
|
550
781
|
} catch (e) {
|
|
551
782
|
state.$error = e instanceof Error ? e : new Error(String(e));
|
|
552
|
-
obs?.onRunEnd?.(runCtx, { signal: "$error", durationMs: Date.now() - runStart });
|
|
783
|
+
safeInvoke("onRunEnd", () => obs?.onRunEnd?.(runCtx, { signal: "$error", durationMs: Date.now() - runStart }), onObserverError);
|
|
553
784
|
return { state, signal: "$error", cursor, paused: true };
|
|
554
785
|
}
|
|
555
786
|
const transition = step.transitions.find((t) => t.signal === signal);
|
|
@@ -558,7 +789,7 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
|
|
|
558
789
|
}
|
|
559
790
|
if (transition.target.kind === "end") {
|
|
560
791
|
onComplete?.(state, signal);
|
|
561
|
-
obs?.onRunEnd?.(runCtx, { signal, durationMs: Date.now() - runStart });
|
|
792
|
+
safeInvoke("onRunEnd", () => obs?.onRunEnd?.(runCtx, { signal, durationMs: Date.now() - runStart }), onObserverError);
|
|
562
793
|
return { state, signal, cursor: null, paused: false };
|
|
563
794
|
}
|
|
564
795
|
cursor = transition.target.name;
|
|
@@ -566,7 +797,7 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
|
|
|
566
797
|
if (graph.onError !== void 0) {
|
|
567
798
|
cursor = graph.onError;
|
|
568
799
|
} else {
|
|
569
|
-
obs?.onRunEnd?.(runCtx, { signal: "$error", durationMs: Date.now() - runStart });
|
|
800
|
+
safeInvoke("onRunEnd", () => obs?.onRunEnd?.(runCtx, { signal: "$error", durationMs: Date.now() - runStart }), onObserverError);
|
|
570
801
|
return { state, signal: "$error", cursor, paused: true };
|
|
571
802
|
}
|
|
572
803
|
} else {
|
|
@@ -578,18 +809,162 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
|
|
|
578
809
|
}
|
|
579
810
|
}
|
|
580
811
|
}
|
|
812
|
+
async function runBranch(branch, prior, outerState, outerCtx, schema, shouldStop, branchSignal, branchCallbacks) {
|
|
813
|
+
if (prior !== void 0 && prior.status === "done") {
|
|
814
|
+
return {
|
|
815
|
+
name: branch.name,
|
|
816
|
+
result: { state: prior.state, signal: null, cursor: null, paused: false },
|
|
817
|
+
touchedKeys: new Set(prior.touchedKeys)
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
const touchedKeys = new Set(prior?.status === "paused" ? prior.touchedKeys : []);
|
|
821
|
+
const privateState = prior !== void 0 ? { ...prior.state } : { ...outerState };
|
|
822
|
+
const privateCtx = { ...outerCtx, signal: branchSignal };
|
|
823
|
+
const branchStartCursor = prior?.status === "paused" ? prior.cursor : void 0;
|
|
824
|
+
const branchPath = [...branchCallbacks?.branchPath ?? [], branch.name];
|
|
825
|
+
const result = await runLoop(
|
|
826
|
+
branch.definition,
|
|
827
|
+
privateState,
|
|
828
|
+
privateCtx,
|
|
829
|
+
schema,
|
|
830
|
+
shouldStop,
|
|
831
|
+
branchStartCursor,
|
|
832
|
+
{ ...branchCallbacks, touchedKeys, branchPath }
|
|
833
|
+
);
|
|
834
|
+
return { name: branch.name, result, touchedKeys };
|
|
835
|
+
}
|
|
836
|
+
async function dispatchFork(forkNode, priorBranches, outerState, outerCtx, schema, shouldStop, callbacks) {
|
|
837
|
+
const priorByName = new Map((priorBranches ?? []).map((b) => [b.name, b]));
|
|
838
|
+
const localAborts = forkNode.branches.map(() => new AbortController());
|
|
839
|
+
const outerSignal = outerCtx.signal;
|
|
840
|
+
const branchSignals = forkNode.branches.map(
|
|
841
|
+
(_, i) => outerSignal !== void 0 ? AbortSignal.any([outerSignal, localAborts[i].signal]) : localAborts[i].signal
|
|
842
|
+
);
|
|
843
|
+
const { onBeforeStep: _outerOnBeforeStep, onAfterStep: _outerOnAfterStep, onComplete: _outerOnComplete, ...branchCallbacksRest } = callbacks ?? {};
|
|
844
|
+
const branchCallbacksBase = callbacks !== void 0 ? branchCallbacksRest : void 0;
|
|
845
|
+
const outcomes = await Promise.all(
|
|
846
|
+
forkNode.branches.map(async (branch, i) => {
|
|
847
|
+
const prior = priorByName.get(branch.name);
|
|
848
|
+
const outcome = await runBranch(branch, prior, outerState, outerCtx, schema, shouldStop, branchSignals[i], branchCallbacksBase);
|
|
849
|
+
if (outcome.result.paused && outcome.result.signal === "$error") {
|
|
850
|
+
for (const controller of localAborts) controller.abort();
|
|
851
|
+
}
|
|
852
|
+
return outcome;
|
|
853
|
+
})
|
|
854
|
+
);
|
|
855
|
+
const failed = outcomes.find((o) => o.result.paused && o.result.signal === "$error");
|
|
856
|
+
if (failed !== void 0) {
|
|
857
|
+
return { kind: "failed", error: failed.result.state.$error };
|
|
858
|
+
}
|
|
859
|
+
const anyPaused = outcomes.some((o) => o.result.paused);
|
|
860
|
+
if (anyPaused) {
|
|
861
|
+
const branches = outcomes.map((o) => {
|
|
862
|
+
if (o.result.paused) {
|
|
863
|
+
return {
|
|
864
|
+
name: o.name,
|
|
865
|
+
status: "paused",
|
|
866
|
+
state: o.result.state,
|
|
867
|
+
cursor: o.result.cursor,
|
|
868
|
+
touchedKeys: [...o.touchedKeys]
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
return {
|
|
872
|
+
name: o.name,
|
|
873
|
+
status: "done",
|
|
874
|
+
state: o.result.state,
|
|
875
|
+
touchedKeys: [...o.touchedKeys]
|
|
876
|
+
};
|
|
877
|
+
});
|
|
878
|
+
return { kind: "paused", forkCursor: { kind: "fork", fork: forkNode.name, branches } };
|
|
879
|
+
}
|
|
880
|
+
const keyOwners = /* @__PURE__ */ new Map();
|
|
881
|
+
for (const outcome of outcomes) {
|
|
882
|
+
for (const key of outcome.touchedKeys) {
|
|
883
|
+
const owners = keyOwners.get(key) ?? [];
|
|
884
|
+
owners.push(outcome.name);
|
|
885
|
+
keyOwners.set(key, owners);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
for (const outcome of outcomes) {
|
|
889
|
+
for (const key of outcome.touchedKeys) {
|
|
890
|
+
const owners = keyOwners.get(key);
|
|
891
|
+
if (owners.length > 1) {
|
|
892
|
+
const reducer = schema?.[key]?.reduce;
|
|
893
|
+
if (reducer === void 0) {
|
|
894
|
+
throw new MissingReducerError(forkNode.name, key, owners);
|
|
895
|
+
}
|
|
896
|
+
outerState[key] = reducer(outerState[key], outcome.result.state[key]);
|
|
897
|
+
} else {
|
|
898
|
+
outerState[key] = outcome.result.state[key];
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
return { kind: "success" };
|
|
903
|
+
}
|
|
904
|
+
async function handleFork(forkNode, priorBranches, state, ctx, schema, shouldStop, callbacks, obs, runCtx, runStart, implicitNextMap, graph) {
|
|
905
|
+
const stepCtx = {
|
|
906
|
+
agentId: ctx.agentId,
|
|
907
|
+
sessionId: ctx.sessionId,
|
|
908
|
+
stepName: forkNode.name,
|
|
909
|
+
...callbacks?.branchPath !== void 0 && callbacks.branchPath.length > 0 ? { branchPath: callbacks.branchPath } : {}
|
|
910
|
+
};
|
|
911
|
+
const stepStart = Date.now();
|
|
912
|
+
const onObserverError = callbacks?.onObserverError;
|
|
913
|
+
safeInvoke("onStepStart", () => obs?.onStepStart?.(stepCtx), onObserverError);
|
|
914
|
+
const dispatch = await dispatchFork(forkNode, priorBranches, state, ctx, schema, shouldStop, callbacks);
|
|
915
|
+
if (dispatch.kind === "paused") {
|
|
916
|
+
safeInvoke("onRunEnd", () => obs?.onRunEnd?.(runCtx, { signal: "$interrupt", durationMs: Date.now() - runStart }), onObserverError);
|
|
917
|
+
return { done: true, result: { state, signal: "$interrupt", cursor: dispatch.forkCursor, paused: true } };
|
|
918
|
+
}
|
|
919
|
+
if (dispatch.kind === "failed") {
|
|
920
|
+
state.$error = dispatch.error;
|
|
921
|
+
callbacks?.onError?.(state.$error, forkNode.name);
|
|
922
|
+
safeInvoke("onStepError", () => obs?.onStepError?.(stepCtx, { error: state.$error, durationMs: Date.now() - stepStart }), onObserverError);
|
|
923
|
+
if (graph.onError !== void 0) {
|
|
924
|
+
return { done: false, nextCursor: graph.onError };
|
|
925
|
+
}
|
|
926
|
+
safeInvoke("onRunEnd", () => obs?.onRunEnd?.(runCtx, { signal: "$error", durationMs: Date.now() - runStart }), onObserverError);
|
|
927
|
+
return { done: true, result: { state, signal: "$error", cursor: forkNode.name, paused: true } };
|
|
928
|
+
}
|
|
929
|
+
state.$error = null;
|
|
930
|
+
safeInvoke("onStepEnd", () => obs?.onStepEnd?.(stepCtx, { durationMs: Date.now() - stepStart }), onObserverError);
|
|
931
|
+
callbacks?.onAfterStep?.(forkNode.name, state);
|
|
932
|
+
const next = forkNode.next ?? implicitNextMap.get(forkNode.name) ?? null;
|
|
933
|
+
if (next === null) {
|
|
934
|
+
throw new NoNextStepError(forkNode.name);
|
|
935
|
+
}
|
|
936
|
+
return { done: false, nextCursor: next };
|
|
937
|
+
}
|
|
581
938
|
|
|
582
939
|
// src/agent/session-store.ts
|
|
940
|
+
function collectPendingInterrupts(cursor, state) {
|
|
941
|
+
if (cursor !== void 0 && isForkCursor(cursor)) {
|
|
942
|
+
const result = [];
|
|
943
|
+
for (const branch of cursor.branches) {
|
|
944
|
+
if (branch.status !== "paused") continue;
|
|
945
|
+
const paused = branch;
|
|
946
|
+
for (const pi of collectPendingInterrupts(paused.cursor, paused.state)) {
|
|
947
|
+
result.push({ ...pi, step: `${cursor.fork}/${paused.name}/${pi.step}` });
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
return result;
|
|
951
|
+
}
|
|
952
|
+
const interrupt = state["$interrupt"];
|
|
953
|
+
if (interrupt === null || interrupt === void 0) return [];
|
|
954
|
+
const step = typeof cursor === "string" ? cursor : "";
|
|
955
|
+
return [{ interruptId: interrupt.interruptId, prompt: interrupt.prompt, step }];
|
|
956
|
+
}
|
|
583
957
|
function storedSessionToPhase(loaded) {
|
|
584
958
|
if (loaded === null) {
|
|
585
959
|
return { phase: "fresh" };
|
|
586
960
|
}
|
|
587
961
|
if (loaded.phase === "paused") {
|
|
588
|
-
const step = loaded.step
|
|
962
|
+
const step = loaded.step === void 0 ? "" : isForkCursor(loaded.step) ? loaded.step.fork : loaded.step;
|
|
963
|
+
const pendingInterrupts = collectPendingInterrupts(loaded.step, loaded.finalState);
|
|
589
964
|
if (loaded.signal !== void 0) {
|
|
590
|
-
return { phase: "paused", step, signal: loaded.signal };
|
|
965
|
+
return { phase: "paused", step, signal: loaded.signal, pendingInterrupts };
|
|
591
966
|
}
|
|
592
|
-
return { phase: "paused", step };
|
|
967
|
+
return { phase: "paused", step, pendingInterrupts };
|
|
593
968
|
}
|
|
594
969
|
if (loaded.signal !== void 0) {
|
|
595
970
|
return { phase: "completed", signal: loaded.signal };
|
|
@@ -866,19 +1241,53 @@ var NoInterruptError = class extends Error {
|
|
|
866
1241
|
this.name = "NoInterruptError";
|
|
867
1242
|
}
|
|
868
1243
|
};
|
|
1244
|
+
function injectIntoForkCursor(cursor, interruptId, response) {
|
|
1245
|
+
let matched = false;
|
|
1246
|
+
const branches = cursor.branches.map((branch) => {
|
|
1247
|
+
if (matched || branch.status !== "paused") return branch;
|
|
1248
|
+
const paused = branch;
|
|
1249
|
+
const branchInterrupt = paused.state["$interrupt"];
|
|
1250
|
+
if (branchInterrupt !== null && branchInterrupt !== void 0 && branchInterrupt.interruptId === interruptId) {
|
|
1251
|
+
matched = true;
|
|
1252
|
+
const existing = paused.state["$interruptResponses"] ?? {};
|
|
1253
|
+
return {
|
|
1254
|
+
...paused,
|
|
1255
|
+
state: { ...paused.state, $interrupt: null, $interruptResponses: { ...existing, [interruptId]: response } }
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
if (isForkCursor(paused.cursor)) {
|
|
1259
|
+
const nested = injectIntoForkCursor(paused.cursor, interruptId, response);
|
|
1260
|
+
if (nested !== null) {
|
|
1261
|
+
matched = true;
|
|
1262
|
+
return { ...paused, cursor: nested };
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
return branch;
|
|
1266
|
+
});
|
|
1267
|
+
return matched ? { ...cursor, branches } : null;
|
|
1268
|
+
}
|
|
1269
|
+
function injectInterruptResponseIntoState(cursor, state, interruptId, response) {
|
|
1270
|
+
if (cursor !== void 0 && isForkCursor(cursor)) {
|
|
1271
|
+
const updated = injectIntoForkCursor(cursor, interruptId, response);
|
|
1272
|
+
return updated === null ? { cursor, state, found: false } : { cursor: updated, state, found: true };
|
|
1273
|
+
}
|
|
1274
|
+
const interrupt = state["$interrupt"];
|
|
1275
|
+
if (interrupt === null || interrupt === void 0 || interrupt.interruptId !== interruptId) {
|
|
1276
|
+
return { cursor, state, found: false };
|
|
1277
|
+
}
|
|
1278
|
+
const existing = state["$interruptResponses"] ?? {};
|
|
1279
|
+
return {
|
|
1280
|
+
cursor,
|
|
1281
|
+
state: { ...state, $interrupt: null, $interruptResponses: { ...existing, [interruptId]: response } },
|
|
1282
|
+
found: true
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
869
1285
|
async function injectInterruptResponse(store, agentId, sessionId, interruptId, response) {
|
|
870
1286
|
const loaded = await store.load(agentId, sessionId);
|
|
871
1287
|
if (loaded === null) throw new NoInterruptError();
|
|
872
1288
|
if (loaded.phase !== "paused") throw new NoInterruptError();
|
|
873
|
-
const
|
|
874
|
-
if (
|
|
875
|
-
if (interrupt.interruptId !== interruptId) throw new NoInterruptError();
|
|
876
|
-
const existingResponses = loaded.finalState.$interruptResponses ?? {};
|
|
877
|
-
const newState = {
|
|
878
|
-
...loaded.finalState,
|
|
879
|
-
$interrupt: null,
|
|
880
|
-
$interruptResponses: { ...existingResponses, [interruptId]: response }
|
|
881
|
-
};
|
|
1289
|
+
const result = injectInterruptResponseIntoState(loaded.step, loaded.finalState, interruptId, response);
|
|
1290
|
+
if (!result.found) throw new NoInterruptError();
|
|
882
1291
|
const updated = {
|
|
883
1292
|
agentId: loaded.agentId,
|
|
884
1293
|
runId: loaded.runId,
|
|
@@ -888,8 +1297,8 @@ async function injectInterruptResponse(store, agentId, sessionId, interruptId, r
|
|
|
888
1297
|
settledAt: loaded.settledAt,
|
|
889
1298
|
phase: "paused",
|
|
890
1299
|
initialState: loaded.initialState,
|
|
891
|
-
finalState:
|
|
892
|
-
...
|
|
1300
|
+
finalState: result.state,
|
|
1301
|
+
...result.cursor !== void 0 ? { step: result.cursor } : {},
|
|
893
1302
|
...loaded.signal !== void 0 ? { signal: loaded.signal } : {}
|
|
894
1303
|
};
|
|
895
1304
|
await store.save(agentId, sessionId, updated);
|
|
@@ -957,12 +1366,24 @@ function extractRunEvents(resources) {
|
|
|
957
1366
|
}
|
|
958
1367
|
|
|
959
1368
|
// src/agent/create-agent.ts
|
|
960
|
-
|
|
1369
|
+
function buildTelemetrySlots(entries) {
|
|
1370
|
+
const out = [];
|
|
1371
|
+
for (const [key, slot] of entries) {
|
|
1372
|
+
if (slot !== null && typeof slot === "object" && typeof slot["withTelemetry"] === "function") {
|
|
1373
|
+
out.push({ key, slot });
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
return out;
|
|
1377
|
+
}
|
|
961
1378
|
function extractRunObserver(resources) {
|
|
962
1379
|
const raw = resources["observer"];
|
|
963
1380
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
964
1381
|
return raw;
|
|
965
1382
|
}
|
|
1383
|
+
function extractRunErrorSink(resources) {
|
|
1384
|
+
const raw = resources["onObserverError"];
|
|
1385
|
+
return typeof raw === "function" ? raw : void 0;
|
|
1386
|
+
}
|
|
966
1387
|
var MissingLoopError = class extends Error {
|
|
967
1388
|
constructor() {
|
|
968
1389
|
super("harness has no loop \u2014 call h.loop() before createAgent(id, h, slots)");
|
|
@@ -1120,10 +1541,10 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1120
1541
|
runtimeKeys,
|
|
1121
1542
|
requiredKeys
|
|
1122
1543
|
};
|
|
1123
|
-
const reservedRunKeys = /* @__PURE__ */ new Set(["sessionId", "signal", "events", "listeners", "observer", "claimOptions", "parentRunId"]);
|
|
1544
|
+
const reservedRunKeys = /* @__PURE__ */ new Set(["sessionId", "signal", "events", "listeners", "observer", "onObserverError", "claimOptions", "parentRunId"]);
|
|
1124
1545
|
const inFlightSessions = /* @__PURE__ */ new Set();
|
|
1125
1546
|
const interruptPendingSessions = /* @__PURE__ */ new Set();
|
|
1126
|
-
const makeAgentResumeHandle = (resp, sId, iId, resumeOpts, observer,
|
|
1547
|
+
const makeAgentResumeHandle = (resp, sId, iId, resumeOpts, observer, onObserverError, telemetrySlots) => {
|
|
1127
1548
|
let _stopped = false;
|
|
1128
1549
|
const abortController = new AbortController();
|
|
1129
1550
|
const flag = {
|
|
@@ -1198,7 +1619,8 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1198
1619
|
...resumeOpts?.onStoreError !== void 0 ? { onStoreError: resumeOpts.onStoreError } : {},
|
|
1199
1620
|
...leaseRef !== void 0 ? { leaseRef, claimTtlMs: claimOptions.ttlMs } : {},
|
|
1200
1621
|
...observer !== void 0 ? { observer } : {},
|
|
1201
|
-
...
|
|
1622
|
+
...onObserverError !== void 0 ? { onObserverError } : {},
|
|
1623
|
+
...telemetrySlots !== void 0 && telemetrySlots.length > 0 ? { telemetrySlots } : {}
|
|
1202
1624
|
}
|
|
1203
1625
|
);
|
|
1204
1626
|
if (r.signal === "$interrupt") interruptPendingSessions.add(sId);
|
|
@@ -1294,17 +1716,11 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1294
1716
|
const events = extractRunEvents(resources);
|
|
1295
1717
|
const listeners = extractRunListeners(resources);
|
|
1296
1718
|
const observer = extractRunObserver(resources);
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
const setStepContextSlots = [];
|
|
1303
|
-
for (const slot of [...agentInternals.resolvedProviders.values(), ...Object.values(runtimeSlots)]) {
|
|
1304
|
-
if (slot !== null && typeof slot === "object" && typeof slot["setStepContext"] === "function") {
|
|
1305
|
-
setStepContextSlots.push(slot);
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1719
|
+
const onObserverError = extractRunErrorSink(resources);
|
|
1720
|
+
const telemetrySlots = buildTelemetrySlots([
|
|
1721
|
+
...agentInternals.resolvedProviders.entries(),
|
|
1722
|
+
...Object.entries(runtimeSlots)
|
|
1723
|
+
]);
|
|
1308
1724
|
const parentRunId = typeof resources["parentRunId"] === "string" ? resources["parentRunId"] : void 0;
|
|
1309
1725
|
const options = {
|
|
1310
1726
|
shouldStop: () => stopFlag.stopped,
|
|
@@ -1320,9 +1736,10 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1320
1736
|
...events.onInterrupt !== void 0 ? { onInterrupt: events.onInterrupt } : {},
|
|
1321
1737
|
...Object.keys(listeners).length > 0 ? { listeners } : {},
|
|
1322
1738
|
...observer !== void 0 ? { observer } : {},
|
|
1739
|
+
...onObserverError !== void 0 ? { onObserverError } : {},
|
|
1323
1740
|
...agentOptions?.instanceId !== void 0 ? { instanceId: agentOptions.instanceId } : {},
|
|
1324
1741
|
...parentRunId !== void 0 ? { parentRunId } : {},
|
|
1325
|
-
...
|
|
1742
|
+
...telemetrySlots.length > 0 ? { telemetrySlots } : {}
|
|
1326
1743
|
};
|
|
1327
1744
|
let lastResult = null;
|
|
1328
1745
|
const execution = (async () => {
|
|
@@ -1439,7 +1856,8 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1439
1856
|
...events.onInterrupt !== void 0 ? { onInterrupt: events.onInterrupt } : {},
|
|
1440
1857
|
...Object.keys(listeners).length > 0 ? { listeners } : {},
|
|
1441
1858
|
...observer !== void 0 ? { observer } : {},
|
|
1442
|
-
...
|
|
1859
|
+
...onObserverError !== void 0 ? { onObserverError } : {},
|
|
1860
|
+
...telemetrySlots.length > 0 ? { telemetrySlots } : {}
|
|
1443
1861
|
}
|
|
1444
1862
|
);
|
|
1445
1863
|
if (r2.signal === "$interrupt") interruptPendingSessions.add(sessionId);
|
|
@@ -1448,11 +1866,10 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1448
1866
|
}
|
|
1449
1867
|
const prev = lastResult;
|
|
1450
1868
|
if (prev === null) throw new NoInterruptError();
|
|
1451
|
-
const
|
|
1452
|
-
|
|
1453
|
-
state
|
|
1454
|
-
|
|
1455
|
-
const cursor = prev.cursor ?? void 0;
|
|
1869
|
+
const injected = injectInterruptResponseIntoState(prev.cursor ?? void 0, prev.state, interruptId, response);
|
|
1870
|
+
if (!injected.found) throw new NoInterruptError();
|
|
1871
|
+
const state = injected.state;
|
|
1872
|
+
const cursor = injected.cursor;
|
|
1456
1873
|
const r = await runLoop(
|
|
1457
1874
|
agentInternals.loopDef,
|
|
1458
1875
|
state,
|
|
@@ -1472,7 +1889,8 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1472
1889
|
...events.onInterrupt !== void 0 ? { onInterrupt: events.onInterrupt } : {},
|
|
1473
1890
|
...Object.keys(listeners).length > 0 ? { listeners } : {},
|
|
1474
1891
|
...observer !== void 0 ? { observer } : {},
|
|
1475
|
-
...
|
|
1892
|
+
...onObserverError !== void 0 ? { onObserverError } : {},
|
|
1893
|
+
...telemetrySlots.length > 0 ? { telemetrySlots } : {}
|
|
1476
1894
|
}
|
|
1477
1895
|
);
|
|
1478
1896
|
if (r.signal === "$interrupt") interruptPendingSessions.add(sessionId);
|
|
@@ -1501,51 +1919,15 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1501
1919
|
const events = extractRunEvents(resources ?? {});
|
|
1502
1920
|
const resumeOpts = events.onStoreError !== void 0 ? { onStoreError: events.onStoreError } : void 0;
|
|
1503
1921
|
const observer = extractRunObserver(resources ?? {});
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
}
|
|
1508
|
-
}
|
|
1509
|
-
const setStepContextSlots = [];
|
|
1510
|
-
for (const slot of agentInternals.resolvedProviders.values()) {
|
|
1511
|
-
if (slot !== null && typeof slot === "object" && typeof slot["setStepContext"] === "function") {
|
|
1512
|
-
setStepContextSlots.push(slot);
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1515
|
-
return makeAgentResumeHandle(response, sessionId, interruptId, resumeOpts, observer ?? void 0, setStepContextSlots.length > 0 ? setStepContextSlots : void 0);
|
|
1922
|
+
const onObserverError = extractRunErrorSink(resources ?? {});
|
|
1923
|
+
const telemetrySlots = buildTelemetrySlots(agentInternals.resolvedProviders.entries());
|
|
1924
|
+
return makeAgentResumeHandle(response, sessionId, interruptId, resumeOpts, observer ?? void 0, onObserverError ?? void 0, telemetrySlots.length > 0 ? telemetrySlots : void 0);
|
|
1516
1925
|
},
|
|
1517
1926
|
status: (sessionId) => querySessionPhase(capturedStore, id, sessionId),
|
|
1518
1927
|
[_agentInternals]: agentInternals
|
|
1519
1928
|
};
|
|
1520
1929
|
return agent;
|
|
1521
1930
|
}
|
|
1522
|
-
|
|
1523
|
-
// src/agent/observer.ts
|
|
1524
|
-
function composeObservers(...observers) {
|
|
1525
|
-
return {
|
|
1526
|
-
onRunStart: (ctx) => {
|
|
1527
|
-
for (const o of observers) o.onRunStart?.(ctx);
|
|
1528
|
-
},
|
|
1529
|
-
onRunEnd: (ctx, event) => {
|
|
1530
|
-
for (const o of observers) o.onRunEnd?.(ctx, event);
|
|
1531
|
-
},
|
|
1532
|
-
onStepStart: (ctx) => {
|
|
1533
|
-
for (const o of observers) o.onStepStart?.(ctx);
|
|
1534
|
-
},
|
|
1535
|
-
onStepEnd: (ctx, event) => {
|
|
1536
|
-
for (const o of observers) o.onStepEnd?.(ctx, event);
|
|
1537
|
-
},
|
|
1538
|
-
onStepError: (ctx, event) => {
|
|
1539
|
-
for (const o of observers) o.onStepError?.(ctx, event);
|
|
1540
|
-
},
|
|
1541
|
-
onInterrupt: (ctx, event) => {
|
|
1542
|
-
for (const o of observers) o.onInterrupt?.(ctx, event);
|
|
1543
|
-
},
|
|
1544
|
-
onEvent: (ctx, type, payload) => {
|
|
1545
|
-
for (const o of observers) o.onEvent?.(ctx, type, payload);
|
|
1546
|
-
}
|
|
1547
|
-
};
|
|
1548
|
-
}
|
|
1549
1931
|
export {
|
|
1550
1932
|
LeaseExpiredError,
|
|
1551
1933
|
LoopNotDefinedError,
|
|
@@ -1560,6 +1942,8 @@ export {
|
|
|
1560
1942
|
createAgent,
|
|
1561
1943
|
createHarness,
|
|
1562
1944
|
field,
|
|
1945
|
+
isForkCursor,
|
|
1946
|
+
isForkDef,
|
|
1563
1947
|
isRequiredMarker,
|
|
1564
1948
|
isRuntimeMarker,
|
|
1565
1949
|
required,
|