@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/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 currentStep = state.steps.at(-1);
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?.transitions.push({
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?.transitions.push({
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 currentStep = state.steps.at(-1);
96
- if (currentStep !== void 0) {
97
- currentStep.next = name;
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((step) => {
116
- const transitions = Object.freeze([...step.transitions]);
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: step.name,
123
- run: step.run,
124
- route: step.route,
171
+ name: node.name,
172
+ run: node.run,
173
+ route: node.route,
125
174
  transitions,
126
- next: step.next,
127
- errorAware: step.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
- for (const step of def.steps) {
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 def.steps) {
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, step] of def.steps.entries()) {
164
- if (step === void 0) continue;
165
- if (step.route === void 0) {
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 && step.next === void 0) {
271
+ if (isLast && node.next === void 0) {
168
272
  violations.push(
169
- `step "${step.name}" has no route, no explicit .next(), and no following step \u2014 no exit path`
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 def.steps) {
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 def.steps) {
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
- if (step.next !== void 0) {
197
- if (!names.has(step.next)) {
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 "${step.name}" has .next("${step.next}") but step "${step.next}" is not declared`
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 def.steps) {
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 currentStep = def.steps.find((s) => s.name === currentName);
240
- if (!currentStep) continue;
241
- for (const transition of currentStep.transitions) {
242
- if (transition.target.kind === "step") {
243
- if (!reachable.has(transition.target.name)) {
244
- reachable.add(transition.target.name);
245
- queue.push(transition.target.name);
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
- if (currentStep.route !== void 0) {
250
- if (currentStep.next !== void 0 && !reachable.has(currentStep.next)) {
251
- reachable.add(currentStep.next);
252
- queue.push(currentStep.next);
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 (currentStep.next !== void 0) {
256
- if (!reachable.has(currentStep.next)) {
257
- reachable.add(currentStep.next);
258
- queue.push(currentStep.next);
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 nextStep = def.steps[currentIndex + 1];
264
- if (nextStep !== void 0) {
265
- const nextStepName = nextStep.name;
266
- if (!reachable.has(nextStepName)) {
267
- reachable.add(nextStepName);
268
- queue.push(nextStepName);
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 def.steps) {
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
- if (violations.length > 0) {
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
- observer.onEvent?.(stepCtxRef.current, name, payload);
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 step = graph.steps.find((s) => s.name === cursor);
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 = { agentId: ctx.agentId, sessionId: ctx.sessionId, stepName: cursor };
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?.setStepContextSlots !== void 0) {
509
- for (const slot of callbacks.setStepContextSlots) {
510
- slot.setStepContext(stepCtx);
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 interrupt = loaded.finalState.$interrupt;
874
- if (interrupt === null) throw new NoInterruptError();
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: newState,
892
- ...loaded.step !== void 0 ? { step: loaded.step } : {},
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
- var NOOP_OBSERVER = {};
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, setStepContextSlots) => {
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
- ...setStepContextSlots !== void 0 && setStepContextSlots.length > 0 ? { setStepContextSlots } : {}
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
- for (const slot of [...agentInternals.resolvedProviders.values(), ...Object.values(runtimeSlots)]) {
1298
- if (slot !== null && typeof slot === "object" && typeof slot["bindObserver"] === "function") {
1299
- slot.bindObserver(observer ?? NOOP_OBSERVER);
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
- ...setStepContextSlots.length > 0 ? { setStepContextSlots } : {}
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
- ...setStepContextSlots.length > 0 ? { setStepContextSlots } : {}
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 state = prev.state;
1452
- const existing = state.$interruptResponses ?? {};
1453
- state.$interruptResponses = { ...existing, [interruptId]: response };
1454
- state.$interrupt = null;
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
- ...setStepContextSlots.length > 0 ? { setStepContextSlots } : {}
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
- for (const slot of agentInternals.resolvedProviders.values()) {
1505
- if (slot !== null && typeof slot === "object" && typeof slot["bindObserver"] === "function") {
1506
- slot.bindObserver(observer ?? NOOP_OBSERVER);
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,