@noetaris/harness 0.6.0 → 0.8.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,178 @@ 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, failure) => {
729
+ runEnded = true;
730
+ const event = { signal, durationMs: Date.now() - runStart, ...failure !== void 0 ? { error: failure.error } : {} };
731
+ safeInvoke("onRunEnd", () => obs?.onRunEnd?.(runCtx, event), onObserverError);
732
+ };
678
733
  const callCountRef = { current: 0 };
679
734
  const stepCtxRef = { current: null };
680
735
  ctx["interrupt"] = createInterruptFn(state, callCountRef, callbacks?.branchPath);
681
736
  ctx["emit"] = createEmitFn(callbacks?.listeners ?? {}, obs, stepCtxRef, onObserverError);
737
+ ctx["within"] = createWithinFn(obs, () => stepCtxRef.current, onObserverError);
738
+ const telemetryObserver = obs !== void 0 ? composeObservers([obs], onObserverError) : NOOP_OBSERVER;
682
739
  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);
740
+ try {
741
+ while (true) {
742
+ if (shouldStop?.()) {
743
+ endRun("$stopped");
744
+ return { state, signal: null, cursor, paused: true };
754
745
  }
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(
746
+ if (isForkCursor(cursor)) {
747
+ const forkCursor = cursor;
748
+ const forkNode = graph.steps.find((s) => s.name === forkCursor.fork);
749
+ if (!isForkDef(forkNode)) {
750
+ throw new NoNextStepError(forkCursor.fork);
751
+ }
752
+ onBeforeStep?.(forkNode.name, { ...state });
753
+ const outcome = await handleFork(
754
+ forkNode,
755
+ forkCursor.branches,
763
756
  state,
764
- ctx
757
+ ctx,
758
+ schema,
759
+ shouldStop,
760
+ callbacks,
761
+ obs,
762
+ runCtx,
763
+ endRun,
764
+ implicitNextMap,
765
+ graph
765
766
  );
766
- applyUpdate(state, update, schema, callbacks?.touchedKeys);
767
- applied = update;
768
- if (hadResponses) {
769
- state.$interrupt = null;
770
- state.$interruptResponses = {};
767
+ if (outcome.done) {
768
+ return outcome.result;
771
769
  }
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 };
770
+ cursor = outcome.nextCursor;
771
+ continue;
772
+ }
773
+ onBeforeStep?.(cursor, { ...state });
774
+ const node = graph.steps.find((s) => s.name === cursor);
775
+ if (isForkDef(node)) {
776
+ const outcome = await handleFork(
777
+ node,
778
+ void 0,
779
+ state,
780
+ ctx,
781
+ schema,
782
+ shouldStop,
783
+ callbacks,
784
+ obs,
785
+ runCtx,
786
+ endRun,
787
+ implicitNextMap,
788
+ graph
789
+ );
790
+ if (outcome.done) {
791
+ return outcome.result;
780
792
  }
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);
793
+ cursor = outcome.nextCursor;
794
+ continue;
784
795
  }
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 };
796
+ const step = node;
797
+ callCountRef.current = 0;
798
+ const stepCtx = stepContextOf(runCtx, cursor);
799
+ stepCtxRef.current = stepCtx;
800
+ const stepStart = Date.now();
801
+ safeInvoke("onStepStart", () => obs?.onStepStart?.(stepCtx), onObserverError);
802
+ if (callbacks?.telemetrySlots !== void 0) {
803
+ const telemetry = {
804
+ observer: telemetryObserver,
805
+ stepContext: stepCtx,
806
+ within: createWithinFn(obs, () => stepCtx, onObserverError)
807
+ };
808
+ for (const { key, slot } of callbacks.telemetrySlots) {
809
+ ;
810
+ ctx[key] = slot.withTelemetry(telemetry);
811
+ }
802
812
  }
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;
813
+ let runSucceeded = false;
814
+ let applied;
815
+ const settle = (fields) => fireSettled(obs, stepCtx, stepStart, { ...fields, ...applied !== void 0 ? { update: applied } : {} }, onObserverError);
816
+ if (step.run !== void 0) {
817
+ const hadResponses = Object.keys(state.$interruptResponses).length > 0;
818
+ try {
819
+ const run = step.run;
820
+ const update = await runStepScope(obs, stepCtx, () => run(
821
+ state,
822
+ ctx
823
+ ), onObserverError);
824
+ applyUpdate(state, update, schema, callbacks?.touchedKeys);
825
+ applied = update;
826
+ if (hadResponses) {
827
+ state.$interrupt = null;
828
+ state.$interruptResponses = {};
829
+ }
830
+ runSucceeded = true;
831
+ } catch (e) {
832
+ if (isInterruptPause(e)) {
833
+ onInterrupt?.(e.prompt, e.interruptId);
834
+ safeInvoke("onInterrupt", () => obs?.onInterrupt?.(stepCtx, { prompt: e.prompt, interruptId: e.interruptId }), onObserverError);
835
+ settle({ outcome: "interrupt", interrupt: { interruptId: e.interruptId, prompt: e.prompt }, next: { kind: "pause" } });
836
+ endRun("$interrupt");
837
+ return { state, signal: "$interrupt", cursor, paused: true };
838
+ }
839
+ state.$error = e instanceof Error ? e : new Error(String(e));
840
+ onError?.(state.$error, cursor);
841
+ safeInvoke("onStepError", () => obs?.onStepError?.(stepCtx, { error: state.$error, durationMs: Date.now() - stepStart }), onObserverError);
842
+ }
809
843
  }
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 };
844
+ if (runSucceeded) {
845
+ state.$error = null;
846
+ onAfterStep?.(cursor, state);
847
+ safeInvoke("onStepEnd", () => obs?.onStepEnd?.(stepCtx, { durationMs: Date.now() - stepStart }), onObserverError);
815
848
  }
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;
849
+ const callRoute = step.route !== void 0 && (runSucceeded || step.run === void 0 || step.errorAware);
850
+ if (callRoute) {
851
+ const routeFn = step.route;
852
+ let signal;
853
+ try {
854
+ signal = routeFn(state);
855
+ } catch (e) {
856
+ state.$error = e instanceof Error ? e : new Error(String(e));
857
+ settle({ outcome: "error", error: state.$error, next: { kind: "pause" } });
858
+ endRun("$error", { error: state.$error });
859
+ return { state, signal: "$error", cursor, paused: true };
860
+ }
861
+ const routed = runSucceeded || step.run === void 0 ? { outcome: "ok", signal } : { outcome: "error", signal, error: state.$error };
862
+ const transition = step.transitions.find((t) => t.signal === signal);
863
+ if (transition === void 0) {
864
+ const unknownSignal = new UnknownSignalError(cursor, signal);
865
+ settle({ outcome: "error", signal, error: unknownSignal, next: { kind: "throw" } });
866
+ throw unknownSignal;
867
+ }
868
+ if (transition.target.kind === "end") {
869
+ settle({ ...routed, next: { kind: "end" } });
870
+ onComplete?.(state, signal);
871
+ endRun(signal);
872
+ return { state, signal, cursor: null, paused: false };
873
+ }
874
+ settle({ ...routed, next: { kind: "step", name: transition.target.name } });
875
+ cursor = transition.target.name;
876
+ } else if (step.run !== void 0 && !runSucceeded) {
877
+ if (graph.onError !== void 0) {
878
+ settle({ outcome: "error", error: state.$error, next: { kind: "step", name: graph.onError } });
879
+ cursor = graph.onError;
880
+ } else {
881
+ settle({ outcome: "error", error: state.$error, next: { kind: "pause" } });
882
+ endRun("$error", { error: state.$error });
883
+ return { state, signal: "$error", cursor, paused: true };
884
+ }
822
885
  } 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;
886
+ const next = step.next ?? implicitNextMap.get(cursor) ?? null;
887
+ if (next === null) {
888
+ const noNext = new NoNextStepError(cursor);
889
+ settle({ outcome: "error", error: noNext, next: { kind: "throw" } });
890
+ throw noNext;
891
+ }
892
+ settle({ outcome: "ok", next: { kind: "step", name: next } });
893
+ cursor = next;
833
894
  }
834
- settle({ outcome: "ok", next: { kind: "step", name: next } });
835
- cursor = next;
836
895
  }
896
+ } catch (e) {
897
+ if (!runEnded) endRun("$error", { error: e });
898
+ throw e;
837
899
  }
838
900
  }
839
901
  async function runBranch(branch, prior, outerState, outerCtx, schema, shouldStop, branchSignal, branchCallbacks) {
@@ -941,13 +1003,8 @@ async function dispatchFork(forkNode, priorBranches, outerState, outerCtx, schem
941
1003
  branches: outcomes.map((o) => ({ name: o.name, status: "done", touchedKeys: [...o.touchedKeys] }))
942
1004
  };
943
1005
  }
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
- };
1006
+ async function handleFork(forkNode, priorBranches, state, ctx, schema, shouldStop, callbacks, obs, runCtx, endRun, implicitNextMap, graph) {
1007
+ const stepCtx = stepContextOf(runCtx, forkNode.name);
951
1008
  const stepStart = Date.now();
952
1009
  const onObserverError = callbacks?.onObserverError;
953
1010
  safeInvoke("onStepStart", () => obs?.onStepStart?.(stepCtx), onObserverError);
@@ -964,11 +1021,11 @@ async function handleFork(forkNode, priorBranches, state, ctx, schema, shouldSto
964
1021
  };
965
1022
  if (dispatch.interrupted === true) {
966
1023
  fireSettled(obs, stepCtx, stepStart, { outcome: "interrupt", next: { kind: "pause" }, fork }, onObserverError);
967
- safeInvoke("onRunEnd", () => obs?.onRunEnd?.(runCtx, { signal: "$interrupt", durationMs: Date.now() - runStart }), onObserverError);
1024
+ endRun("$interrupt");
968
1025
  return { done: true, result: { state, signal: "$interrupt", cursor: dispatch.forkCursor, paused: true } };
969
1026
  }
970
1027
  fireSettled(obs, stepCtx, stepStart, { outcome: "stopped", next: { kind: "pause" }, fork }, onObserverError);
971
- safeInvoke("onRunEnd", () => obs?.onRunEnd?.(runCtx, { signal: "$stopped", durationMs: Date.now() - runStart }), onObserverError);
1028
+ endRun("$stopped");
972
1029
  return { done: true, result: { state, signal: null, cursor: dispatch.forkCursor, paused: true } };
973
1030
  }
974
1031
  if (dispatch.kind === "failed") {
@@ -980,7 +1037,7 @@ async function handleFork(forkNode, priorBranches, state, ctx, schema, shouldSto
980
1037
  return { done: false, nextCursor: graph.onError };
981
1038
  }
982
1039
  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);
1040
+ endRun("$error", { error: state.$error });
984
1041
  return { done: true, result: { state, signal: "$error", cursor: forkNode.name, paused: true } };
985
1042
  }
986
1043
  state.$error = null;