@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/README.md +77 -6
- package/dist/index.d.ts +59 -2
- package/dist/index.js +208 -152
- package/dist/index.js.map +1 -1
- package/package.json +13 -9
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
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
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
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
const
|
|
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
|
-
|
|
767
|
-
|
|
768
|
-
if (hadResponses) {
|
|
769
|
-
state.$interrupt = null;
|
|
770
|
-
state.$interruptResponses = {};
|
|
766
|
+
if (outcome.done) {
|
|
767
|
+
return outcome.result;
|
|
771
768
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
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
|
-
|
|
782
|
-
|
|
783
|
-
safeInvoke("onStepError", () => obs?.onStepError?.(stepCtx, { error: state.$error, durationMs: Date.now() - stepStart }), onObserverError);
|
|
792
|
+
cursor = outcome.nextCursor;
|
|
793
|
+
continue;
|
|
784
794
|
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
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
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
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 (
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
safeInvoke("
|
|
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
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
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
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1039
|
+
endRun("$error");
|
|
984
1040
|
return { done: true, result: { state, signal: "$error", cursor: forkNode.name, paused: true } };
|
|
985
1041
|
}
|
|
986
1042
|
state.$error = null;
|