@noetaris/harness 0.6.0 → 0.7.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
@@ -510,7 +510,27 @@ function safeInvoke(hookName, call, sink) {
510
510
  reportObserverError(error, { hookName }, sink);
511
511
  }
512
512
  }
513
+ async function runWrapped(wrap, fn, onObserverError) {
514
+ let p;
515
+ const inner = () => {
516
+ if (p === void 0) {
517
+ p = new Promise((resolve) => resolve(fn()));
518
+ p.catch(() => {
519
+ });
520
+ }
521
+ return p;
522
+ };
523
+ try {
524
+ await wrap(inner);
525
+ } catch (e) {
526
+ const started = p;
527
+ const passedThrough = started !== void 0 && await started.then(() => false, (err) => err === e);
528
+ if (!passedThrough) reportObserverError(e, { hookName: "wrap" }, onObserverError);
529
+ }
530
+ return inner();
531
+ }
513
532
  function composeObservers(observers, onObserverError) {
533
+ const wrappers = observers.filter((o) => o.wrap !== void 0);
514
534
  return {
515
535
  onRunStart: (ctx) => {
516
536
  for (const o of observers) safeInvoke("onRunStart", () => o.onRunStart?.(ctx), onObserverError);
@@ -535,6 +555,11 @@ function composeObservers(observers, onObserverError) {
535
555
  },
536
556
  onStepSettled: (ctx, event) => {
537
557
  for (const o of observers) safeInvoke("onStepSettled", () => o.onStepSettled?.(ctx, event), onObserverError);
558
+ },
559
+ // First observer outermost; each layer isolated on its own by runWrapped.
560
+ wrap: (ctx, scope, fn) => {
561
+ const layer = (i) => i === wrappers.length ? fn() : runWrapped((inner) => wrappers[i].wrap(ctx, scope, inner), () => layer(i + 1), onObserverError);
562
+ return layer(0);
538
563
  }
539
564
  };
540
565
  }
@@ -588,6 +613,21 @@ function extractRunListeners(resources) {
588
613
  return raw;
589
614
  }
590
615
 
616
+ // src/agent/ctx-within.ts
617
+ function createWithinFn(observer, currentStep, onObserverError) {
618
+ return (type, payload, fn) => {
619
+ const wrap = observer?.wrap;
620
+ const stepCtx = currentStep();
621
+ if (wrap === void 0 || stepCtx === null) return fn();
622
+ return runWrapped((inner) => wrap(stepCtx, { kind: "event", type, payload }, inner), fn, onObserverError);
623
+ };
624
+ }
625
+ function runStepScope(observer, stepCtx, fn, onObserverError) {
626
+ const wrap = observer?.wrap;
627
+ if (wrap === void 0) return fn();
628
+ return runWrapped((inner) => wrap(stepCtx, { kind: "step" }, inner), fn, onObserverError);
629
+ }
630
+
591
631
  // src/loop/loop-executor.ts
592
632
  var NOOP_OBSERVER = {};
593
633
  function isForkCursor(cursor) {
@@ -654,6 +694,15 @@ function fireSettled(obs, stepCtx, stepStart, fields, onObserverError) {
654
694
  const event = { ...fields, durationMs: Date.now() - stepStart };
655
695
  safeInvoke("onStepSettled", () => hook(stepCtx, event), onObserverError);
656
696
  }
697
+ function stepContextOf(runCtx, stepName) {
698
+ return {
699
+ agentId: runCtx.agentId,
700
+ sessionId: runCtx.sessionId,
701
+ runId: runCtx.runId,
702
+ stepName,
703
+ ...runCtx.branchPath !== void 0 ? { branchPath: runCtx.branchPath } : {}
704
+ };
705
+ }
657
706
  function asError(e) {
658
707
  return e instanceof Error ? e : new Error(String(e));
659
708
  }
@@ -675,165 +724,177 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
675
724
  ...callbacks?.branchPath !== void 0 && callbacks.branchPath.length > 0 ? { branchPath: callbacks.branchPath } : {}
676
725
  };
677
726
  safeInvoke("onRunStart", () => obs?.onRunStart?.(runCtx), onObserverError);
727
+ let runEnded = false;
728
+ const endRun = (signal) => {
729
+ runEnded = true;
730
+ safeInvoke("onRunEnd", () => obs?.onRunEnd?.(runCtx, { signal, durationMs: Date.now() - runStart }), onObserverError);
731
+ };
678
732
  const callCountRef = { current: 0 };
679
733
  const stepCtxRef = { current: null };
680
734
  ctx["interrupt"] = createInterruptFn(state, callCountRef, callbacks?.branchPath);
681
735
  ctx["emit"] = createEmitFn(callbacks?.listeners ?? {}, obs, stepCtxRef, onObserverError);
736
+ ctx["within"] = createWithinFn(obs, () => stepCtxRef.current, onObserverError);
737
+ const telemetryObserver = obs !== void 0 ? composeObservers([obs], onObserverError) : NOOP_OBSERVER;
682
738
  let cursor = startCursor ?? graph.entryStep;
683
- while (true) {
684
- if (shouldStop?.()) {
685
- safeInvoke("onRunEnd", () => obs?.onRunEnd?.(runCtx, { signal: "$stopped", durationMs: Date.now() - runStart }), onObserverError);
686
- return { state, signal: null, cursor, paused: true };
687
- }
688
- if (isForkCursor(cursor)) {
689
- const forkCursor = cursor;
690
- const forkNode = graph.steps.find((s) => s.name === forkCursor.fork);
691
- if (!isForkDef(forkNode)) {
692
- throw new NoNextStepError(forkCursor.fork);
693
- }
694
- onBeforeStep?.(forkNode.name, { ...state });
695
- const outcome = await handleFork(
696
- forkNode,
697
- forkCursor.branches,
698
- state,
699
- ctx,
700
- schema,
701
- shouldStop,
702
- callbacks,
703
- obs,
704
- runCtx,
705
- runStart,
706
- implicitNextMap,
707
- graph
708
- );
709
- if (outcome.done) {
710
- return outcome.result;
711
- }
712
- cursor = outcome.nextCursor;
713
- continue;
714
- }
715
- onBeforeStep?.(cursor, { ...state });
716
- const node = graph.steps.find((s) => s.name === cursor);
717
- if (isForkDef(node)) {
718
- const outcome = await handleFork(
719
- node,
720
- void 0,
721
- state,
722
- ctx,
723
- schema,
724
- shouldStop,
725
- callbacks,
726
- obs,
727
- runCtx,
728
- runStart,
729
- implicitNextMap,
730
- graph
731
- );
732
- if (outcome.done) {
733
- return outcome.result;
734
- }
735
- cursor = outcome.nextCursor;
736
- continue;
737
- }
738
- const step = node;
739
- callCountRef.current = 0;
740
- const stepCtx = {
741
- agentId: ctx.agentId,
742
- sessionId: ctx.sessionId,
743
- stepName: cursor,
744
- ...callbacks?.branchPath !== void 0 && callbacks.branchPath.length > 0 ? { branchPath: callbacks.branchPath } : {}
745
- };
746
- stepCtxRef.current = stepCtx;
747
- const stepStart = Date.now();
748
- safeInvoke("onStepStart", () => obs?.onStepStart?.(stepCtx), onObserverError);
749
- if (callbacks?.telemetrySlots !== void 0) {
750
- const telemetry = { observer: obs ?? NOOP_OBSERVER, stepContext: stepCtx };
751
- for (const { key, slot } of callbacks.telemetrySlots) {
752
- ;
753
- ctx[key] = slot.withTelemetry(telemetry);
739
+ try {
740
+ while (true) {
741
+ if (shouldStop?.()) {
742
+ endRun("$stopped");
743
+ return { state, signal: null, cursor, paused: true };
754
744
  }
755
- }
756
- let runSucceeded = false;
757
- let applied;
758
- const settle = (fields) => fireSettled(obs, stepCtx, stepStart, { ...fields, ...applied !== void 0 ? { update: applied } : {} }, onObserverError);
759
- if (step.run !== void 0) {
760
- const hadResponses = Object.keys(state.$interruptResponses).length > 0;
761
- try {
762
- const update = await step.run(
745
+ if (isForkCursor(cursor)) {
746
+ const forkCursor = cursor;
747
+ const forkNode = graph.steps.find((s) => s.name === forkCursor.fork);
748
+ if (!isForkDef(forkNode)) {
749
+ throw new NoNextStepError(forkCursor.fork);
750
+ }
751
+ onBeforeStep?.(forkNode.name, { ...state });
752
+ const outcome = await handleFork(
753
+ forkNode,
754
+ forkCursor.branches,
763
755
  state,
764
- ctx
756
+ ctx,
757
+ schema,
758
+ shouldStop,
759
+ callbacks,
760
+ obs,
761
+ runCtx,
762
+ endRun,
763
+ implicitNextMap,
764
+ graph
765
765
  );
766
- applyUpdate(state, update, schema, callbacks?.touchedKeys);
767
- applied = update;
768
- if (hadResponses) {
769
- state.$interrupt = null;
770
- state.$interruptResponses = {};
766
+ if (outcome.done) {
767
+ return outcome.result;
771
768
  }
772
- runSucceeded = true;
773
- } catch (e) {
774
- if (isInterruptPause(e)) {
775
- onInterrupt?.(e.prompt, e.interruptId);
776
- safeInvoke("onInterrupt", () => obs?.onInterrupt?.(stepCtx, { prompt: e.prompt, interruptId: e.interruptId }), onObserverError);
777
- settle({ outcome: "interrupt", interrupt: { interruptId: e.interruptId, prompt: e.prompt }, next: { kind: "pause" } });
778
- safeInvoke("onRunEnd", () => obs?.onRunEnd?.(runCtx, { signal: "$interrupt", durationMs: Date.now() - runStart }), onObserverError);
779
- return { state, signal: "$interrupt", cursor, paused: true };
769
+ cursor = outcome.nextCursor;
770
+ continue;
771
+ }
772
+ onBeforeStep?.(cursor, { ...state });
773
+ const node = graph.steps.find((s) => s.name === cursor);
774
+ if (isForkDef(node)) {
775
+ const outcome = await handleFork(
776
+ node,
777
+ void 0,
778
+ state,
779
+ ctx,
780
+ schema,
781
+ shouldStop,
782
+ callbacks,
783
+ obs,
784
+ runCtx,
785
+ endRun,
786
+ implicitNextMap,
787
+ graph
788
+ );
789
+ if (outcome.done) {
790
+ return outcome.result;
780
791
  }
781
- state.$error = e instanceof Error ? e : new Error(String(e));
782
- onError?.(state.$error, cursor);
783
- safeInvoke("onStepError", () => obs?.onStepError?.(stepCtx, { error: state.$error, durationMs: Date.now() - stepStart }), onObserverError);
792
+ cursor = outcome.nextCursor;
793
+ continue;
784
794
  }
785
- }
786
- if (runSucceeded) {
787
- state.$error = null;
788
- onAfterStep?.(cursor, state);
789
- safeInvoke("onStepEnd", () => obs?.onStepEnd?.(stepCtx, { durationMs: Date.now() - stepStart }), onObserverError);
790
- }
791
- const callRoute = step.route !== void 0 && (runSucceeded || step.run === void 0 || step.errorAware);
792
- if (callRoute) {
793
- const routeFn = step.route;
794
- let signal;
795
- try {
796
- signal = routeFn(state);
797
- } catch (e) {
798
- state.$error = e instanceof Error ? e : new Error(String(e));
799
- settle({ outcome: "error", error: state.$error, next: { kind: "pause" } });
800
- safeInvoke("onRunEnd", () => obs?.onRunEnd?.(runCtx, { signal: "$error", durationMs: Date.now() - runStart }), onObserverError);
801
- return { state, signal: "$error", cursor, paused: true };
795
+ const step = node;
796
+ callCountRef.current = 0;
797
+ const stepCtx = stepContextOf(runCtx, cursor);
798
+ stepCtxRef.current = stepCtx;
799
+ const stepStart = Date.now();
800
+ safeInvoke("onStepStart", () => obs?.onStepStart?.(stepCtx), onObserverError);
801
+ if (callbacks?.telemetrySlots !== void 0) {
802
+ const telemetry = {
803
+ observer: telemetryObserver,
804
+ stepContext: stepCtx,
805
+ within: createWithinFn(obs, () => stepCtx, onObserverError)
806
+ };
807
+ for (const { key, slot } of callbacks.telemetrySlots) {
808
+ ;
809
+ ctx[key] = slot.withTelemetry(telemetry);
810
+ }
802
811
  }
803
- const routed = runSucceeded || step.run === void 0 ? { outcome: "ok", signal } : { outcome: "error", signal, error: state.$error };
804
- const transition = step.transitions.find((t) => t.signal === signal);
805
- if (transition === void 0) {
806
- const unknownSignal = new UnknownSignalError(cursor, signal);
807
- settle({ outcome: "error", signal, error: unknownSignal, next: { kind: "throw" } });
808
- throw unknownSignal;
812
+ let runSucceeded = false;
813
+ let applied;
814
+ const settle = (fields) => fireSettled(obs, stepCtx, stepStart, { ...fields, ...applied !== void 0 ? { update: applied } : {} }, onObserverError);
815
+ if (step.run !== void 0) {
816
+ const hadResponses = Object.keys(state.$interruptResponses).length > 0;
817
+ try {
818
+ const run = step.run;
819
+ const update = await runStepScope(obs, stepCtx, () => run(
820
+ state,
821
+ ctx
822
+ ), onObserverError);
823
+ applyUpdate(state, update, schema, callbacks?.touchedKeys);
824
+ applied = update;
825
+ if (hadResponses) {
826
+ state.$interrupt = null;
827
+ state.$interruptResponses = {};
828
+ }
829
+ runSucceeded = true;
830
+ } catch (e) {
831
+ if (isInterruptPause(e)) {
832
+ onInterrupt?.(e.prompt, e.interruptId);
833
+ safeInvoke("onInterrupt", () => obs?.onInterrupt?.(stepCtx, { prompt: e.prompt, interruptId: e.interruptId }), onObserverError);
834
+ settle({ outcome: "interrupt", interrupt: { interruptId: e.interruptId, prompt: e.prompt }, next: { kind: "pause" } });
835
+ endRun("$interrupt");
836
+ return { state, signal: "$interrupt", cursor, paused: true };
837
+ }
838
+ state.$error = e instanceof Error ? e : new Error(String(e));
839
+ onError?.(state.$error, cursor);
840
+ safeInvoke("onStepError", () => obs?.onStepError?.(stepCtx, { error: state.$error, durationMs: Date.now() - stepStart }), onObserverError);
841
+ }
809
842
  }
810
- if (transition.target.kind === "end") {
811
- settle({ ...routed, next: { kind: "end" } });
812
- onComplete?.(state, signal);
813
- safeInvoke("onRunEnd", () => obs?.onRunEnd?.(runCtx, { signal, durationMs: Date.now() - runStart }), onObserverError);
814
- return { state, signal, cursor: null, paused: false };
843
+ if (runSucceeded) {
844
+ state.$error = null;
845
+ onAfterStep?.(cursor, state);
846
+ safeInvoke("onStepEnd", () => obs?.onStepEnd?.(stepCtx, { durationMs: Date.now() - stepStart }), onObserverError);
815
847
  }
816
- settle({ ...routed, next: { kind: "step", name: transition.target.name } });
817
- cursor = transition.target.name;
818
- } else if (step.run !== void 0 && !runSucceeded) {
819
- if (graph.onError !== void 0) {
820
- settle({ outcome: "error", error: state.$error, next: { kind: "step", name: graph.onError } });
821
- cursor = graph.onError;
848
+ const callRoute = step.route !== void 0 && (runSucceeded || step.run === void 0 || step.errorAware);
849
+ if (callRoute) {
850
+ const routeFn = step.route;
851
+ let signal;
852
+ try {
853
+ signal = routeFn(state);
854
+ } catch (e) {
855
+ state.$error = e instanceof Error ? e : new Error(String(e));
856
+ settle({ outcome: "error", error: state.$error, next: { kind: "pause" } });
857
+ endRun("$error");
858
+ return { state, signal: "$error", cursor, paused: true };
859
+ }
860
+ const routed = runSucceeded || step.run === void 0 ? { outcome: "ok", signal } : { outcome: "error", signal, error: state.$error };
861
+ const transition = step.transitions.find((t) => t.signal === signal);
862
+ if (transition === void 0) {
863
+ const unknownSignal = new UnknownSignalError(cursor, signal);
864
+ settle({ outcome: "error", signal, error: unknownSignal, next: { kind: "throw" } });
865
+ throw unknownSignal;
866
+ }
867
+ if (transition.target.kind === "end") {
868
+ settle({ ...routed, next: { kind: "end" } });
869
+ onComplete?.(state, signal);
870
+ endRun(signal);
871
+ return { state, signal, cursor: null, paused: false };
872
+ }
873
+ settle({ ...routed, next: { kind: "step", name: transition.target.name } });
874
+ cursor = transition.target.name;
875
+ } else if (step.run !== void 0 && !runSucceeded) {
876
+ if (graph.onError !== void 0) {
877
+ settle({ outcome: "error", error: state.$error, next: { kind: "step", name: graph.onError } });
878
+ cursor = graph.onError;
879
+ } else {
880
+ settle({ outcome: "error", error: state.$error, next: { kind: "pause" } });
881
+ endRun("$error");
882
+ return { state, signal: "$error", cursor, paused: true };
883
+ }
822
884
  } else {
823
- settle({ outcome: "error", error: state.$error, next: { kind: "pause" } });
824
- safeInvoke("onRunEnd", () => obs?.onRunEnd?.(runCtx, { signal: "$error", durationMs: Date.now() - runStart }), onObserverError);
825
- return { state, signal: "$error", cursor, paused: true };
826
- }
827
- } else {
828
- const next = step.next ?? implicitNextMap.get(cursor) ?? null;
829
- if (next === null) {
830
- const noNext = new NoNextStepError(cursor);
831
- settle({ outcome: "error", error: noNext, next: { kind: "throw" } });
832
- throw noNext;
885
+ const next = step.next ?? implicitNextMap.get(cursor) ?? null;
886
+ if (next === null) {
887
+ const noNext = new NoNextStepError(cursor);
888
+ settle({ outcome: "error", error: noNext, next: { kind: "throw" } });
889
+ throw noNext;
890
+ }
891
+ settle({ outcome: "ok", next: { kind: "step", name: next } });
892
+ cursor = next;
833
893
  }
834
- settle({ outcome: "ok", next: { kind: "step", name: next } });
835
- cursor = next;
836
894
  }
895
+ } catch (e) {
896
+ if (!runEnded) endRun("$error");
897
+ throw e;
837
898
  }
838
899
  }
839
900
  async function runBranch(branch, prior, outerState, outerCtx, schema, shouldStop, branchSignal, branchCallbacks) {
@@ -941,13 +1002,8 @@ async function dispatchFork(forkNode, priorBranches, outerState, outerCtx, schem
941
1002
  branches: outcomes.map((o) => ({ name: o.name, status: "done", touchedKeys: [...o.touchedKeys] }))
942
1003
  };
943
1004
  }
944
- async function handleFork(forkNode, priorBranches, state, ctx, schema, shouldStop, callbacks, obs, runCtx, runStart, implicitNextMap, graph) {
945
- const stepCtx = {
946
- agentId: ctx.agentId,
947
- sessionId: ctx.sessionId,
948
- stepName: forkNode.name,
949
- ...callbacks?.branchPath !== void 0 && callbacks.branchPath.length > 0 ? { branchPath: callbacks.branchPath } : {}
950
- };
1005
+ async function handleFork(forkNode, priorBranches, state, ctx, schema, shouldStop, callbacks, obs, runCtx, endRun, implicitNextMap, graph) {
1006
+ const stepCtx = stepContextOf(runCtx, forkNode.name);
951
1007
  const stepStart = Date.now();
952
1008
  const onObserverError = callbacks?.onObserverError;
953
1009
  safeInvoke("onStepStart", () => obs?.onStepStart?.(stepCtx), onObserverError);
@@ -964,11 +1020,11 @@ async function handleFork(forkNode, priorBranches, state, ctx, schema, shouldSto
964
1020
  };
965
1021
  if (dispatch.interrupted === true) {
966
1022
  fireSettled(obs, stepCtx, stepStart, { outcome: "interrupt", next: { kind: "pause" }, fork }, onObserverError);
967
- safeInvoke("onRunEnd", () => obs?.onRunEnd?.(runCtx, { signal: "$interrupt", durationMs: Date.now() - runStart }), onObserverError);
1023
+ endRun("$interrupt");
968
1024
  return { done: true, result: { state, signal: "$interrupt", cursor: dispatch.forkCursor, paused: true } };
969
1025
  }
970
1026
  fireSettled(obs, stepCtx, stepStart, { outcome: "stopped", next: { kind: "pause" }, fork }, onObserverError);
971
- safeInvoke("onRunEnd", () => obs?.onRunEnd?.(runCtx, { signal: "$stopped", durationMs: Date.now() - runStart }), onObserverError);
1027
+ endRun("$stopped");
972
1028
  return { done: true, result: { state, signal: null, cursor: dispatch.forkCursor, paused: true } };
973
1029
  }
974
1030
  if (dispatch.kind === "failed") {
@@ -980,7 +1036,7 @@ async function handleFork(forkNode, priorBranches, state, ctx, schema, shouldSto
980
1036
  return { done: false, nextCursor: graph.onError };
981
1037
  }
982
1038
  fireSettled(obs, stepCtx, stepStart, { outcome: "error", error: state.$error, next: { kind: "pause" } }, onObserverError);
983
- safeInvoke("onRunEnd", () => obs?.onRunEnd?.(runCtx, { signal: "$error", durationMs: Date.now() - runStart }), onObserverError);
1039
+ endRun("$error");
984
1040
  return { done: true, result: { state, signal: "$error", cursor: forkNode.name, paused: true } };
985
1041
  }
986
1042
  state.$error = null;