@noetaris/harness 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/index.d.ts +51 -35
- package/dist/index.js +103 -78
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -190,6 +190,8 @@ const resumed = agent.resume(response, sessionId, interruptId)
|
|
|
190
190
|
- ESM only (`"type": "module"`)
|
|
191
191
|
- Zero runtime dependencies
|
|
192
192
|
|
|
193
|
+
See [LIMITATIONS.md](LIMITATIONS.md) for known limitations.
|
|
194
|
+
|
|
193
195
|
## Related Packages
|
|
194
196
|
|
|
195
197
|
- [`@noetaris/harness-store`](https://github.com/noetaris-lab/harness-store) — session store implementations (`InMemorySessionStore`, `LocalFileSessionStore`, etc.)
|
package/dist/index.d.ts
CHANGED
|
@@ -195,38 +195,38 @@ interface Observer {
|
|
|
195
195
|
onEvent?: (ctx: StepContext, type: string, payload: unknown) => void;
|
|
196
196
|
}
|
|
197
197
|
/**
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
* implements this interface before the first step runs.
|
|
198
|
+
* The telemetry a resource needs to attribute an `invoke()` call to the correct
|
|
199
|
+
* run and step: the run's {@link Observer} and the current {@link StepContext}.
|
|
201
200
|
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
201
|
+
* The harness passes this per call rather than storing it on the resource instance,
|
|
202
|
+
* so concurrently-running fork branches that share one instance never race on it —
|
|
203
|
+
* each call carries its own context by value.
|
|
204
|
+
*/
|
|
205
|
+
interface TelemetryContext {
|
|
206
|
+
readonly observer: Observer;
|
|
207
|
+
readonly stepContext: StepContext;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Implemented by resources (e.g. LLM adapters) that emit per-step telemetry.
|
|
211
|
+
*
|
|
212
|
+
* The harness calls {@link ObserverAware.withTelemetry} at the start of each step for every
|
|
213
|
+
* slot that exposes it, and installs the returned value as that slot on the step's `ctx`.
|
|
214
|
+
* Step code then calls the returned view (e.g. `ctx.llm.invoke(...)`) with no telemetry
|
|
215
|
+
* arguments, and the view forwards the {@link TelemetryContext} into the call it wraps — so
|
|
216
|
+
* telemetry travels *with the call* rather than living in mutable instance state.
|
|
217
|
+
*
|
|
218
|
+
* The return is typed `unknown` because core exports no LLM-domain type; a concrete adapter
|
|
219
|
+
* narrows it (e.g. to its `LLM` view). The harness treats the returned value as an opaque
|
|
220
|
+
* slot, exactly as it treats every provided slot.
|
|
205
221
|
*/
|
|
206
222
|
interface ObserverAware {
|
|
207
223
|
/**
|
|
208
|
-
*
|
|
209
|
-
*
|
|
224
|
+
* Return a view of this resource scoped to the given telemetry. The harness installs
|
|
225
|
+
* the returned value as the resource's slot on the step's `ctx` before calling `step.run`.
|
|
226
|
+
* Called once per step (and once per step per branch), so each concurrent branch gets its
|
|
227
|
+
* own scoped view and there is no shared field to race on.
|
|
210
228
|
*/
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Receive the current step context. The harness calls this at the start of
|
|
214
|
-
* each step for every slot that exposes this method, before calling `step.run`.
|
|
215
|
-
*
|
|
216
|
-
* Adapters that emit `observer.onEvent` in their `invoke()` method store the
|
|
217
|
-
* provided `StepContext` and use it as the first argument to `onEvent`, so
|
|
218
|
-
* events are attributed to the correct step automatically.
|
|
219
|
-
*
|
|
220
|
-
* **Known limitation (fork/join branches):** the store-in-a-field pattern above is safe only
|
|
221
|
-
* because exactly one step runs at a time in a non-forked run. When a fork's branches run
|
|
222
|
-
* concurrently, they typically share one `ObserverAware` slot instance (`h.provide('llm', ...)`
|
|
223
|
-
* provides it once), so concurrent branches race on that stored field and `onEvent` calls may
|
|
224
|
-
* be attributed to the wrong branch/step. This is a known v1 limitation of the built-in LLM
|
|
225
|
-
* adapters (`@noetaris/harness-anthropic`/`-openai`/`-google`/`-ollama`), not fixed for this
|
|
226
|
-
* release — a correct fix means passing `StepContext` through the call instead of storing it.
|
|
227
|
-
* Treat per-step LLM telemetry as unreliable for concurrently-running branches until then.
|
|
228
|
-
*/
|
|
229
|
-
setStepContext?(ctx: StepContext): void;
|
|
229
|
+
withTelemetry(ctx: TelemetryContext): unknown;
|
|
230
230
|
}
|
|
231
231
|
/**
|
|
232
232
|
* Combine multiple {@link Observer} instances into one. Each hook on the
|
|
@@ -234,10 +234,27 @@ interface ObserverAware {
|
|
|
234
234
|
*
|
|
235
235
|
* @example
|
|
236
236
|
* ```ts
|
|
237
|
-
* const observer = composeObservers(otelObserver, metricsObserver)
|
|
237
|
+
* const observer = composeObservers([otelObserver, metricsObserver])
|
|
238
238
|
* ```
|
|
239
239
|
*/
|
|
240
|
-
|
|
240
|
+
/**
|
|
241
|
+
* Identifies which observer hook threw. Passed to an {@link ObserverErrorSink}.
|
|
242
|
+
*/
|
|
243
|
+
interface ObserverErrorContext {
|
|
244
|
+
/** The observer hook that threw, e.g. `'onStepStart'`, `'onRunEnd'`, `'onEvent'`. */
|
|
245
|
+
readonly hookName: string;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Opt-in sink for errors thrown by observer callbacks. Supplied under the
|
|
249
|
+
* `'onObserverError'` key in `agent.run()` / `agent.resume()` resources.
|
|
250
|
+
*
|
|
251
|
+
* Telemetry is a side-channel: an observer that throws must never crash a run.
|
|
252
|
+
* The harness routes every such throw here and continues. The sink is called at
|
|
253
|
+
* most once per failed hook invocation and MUST NOT rethrow into the run — if it
|
|
254
|
+
* does, the harness contains that too (see {@link safeInvoke}).
|
|
255
|
+
*/
|
|
256
|
+
type ObserverErrorSink = (error: unknown, ctx: ObserverErrorContext) => void;
|
|
257
|
+
declare function composeObservers(observers: Observer[], onObserverError?: ObserverErrorSink): Observer;
|
|
241
258
|
|
|
242
259
|
type Cursor = string | ForkCursor;
|
|
243
260
|
/**
|
|
@@ -664,11 +681,10 @@ interface LoopBuilder<S, Ctx> {
|
|
|
664
681
|
* with .branch() immediately after. The step immediately following the fork automatically
|
|
665
682
|
* waits for every branch to settle (the "join") — no separate join primitive exists.
|
|
666
683
|
*
|
|
667
|
-
*
|
|
668
|
-
*
|
|
669
|
-
*
|
|
670
|
-
*
|
|
671
|
-
* — not fixed for this release.
|
|
684
|
+
* Per-step telemetry is attributed correctly across concurrent branches: the harness scopes
|
|
685
|
+
* each branch's slots per step via {@link ObserverAware.withTelemetry}, so telemetry travels
|
|
686
|
+
* with the call rather than through shared instance state. See `ObserverAware`'s doc comment
|
|
687
|
+
* (`agent/observer.ts`).
|
|
672
688
|
*/
|
|
673
689
|
fork(name: string): LoopBuilder<S, Ctx>;
|
|
674
690
|
/**
|
|
@@ -967,4 +983,4 @@ declare class LeaseExpiredError extends Error {
|
|
|
967
983
|
constructor(sessionId: string);
|
|
968
984
|
}
|
|
969
985
|
|
|
970
|
-
export { type Agent, type BranchCursor, type BranchDef, type ClaimOptions, type Cursor, type DeepWithMarkers, type FieldDefinition, type ForkCursor, type ForkDef, type FrameworkState, type Harness, type Lease, LeaseExpiredError, type LoopDefinition, type LoopNode, LoopNotDefinedError, NoInterruptError, type Observer, type ObserverAware, REQUIRED_TAG, RUNTIME_TAG, type RequiredMarker, type RouteFn, type RunContext, type RunFn, type RuntimeMarker, SessionBusyError, SessionInFlightError, SessionPendingInterruptError, type SessionStore, type SignalTransition, type StateFromSchema, type StepContext, type StepDef, type StepState, StoreLoadError, type StoredRun, type StoredRunMetadata, type TransitionTarget, composeObservers, createAgent, createHarness, field, isForkCursor, isForkDef, isRequiredMarker, isRuntimeMarker, required, runtime };
|
|
986
|
+
export { type Agent, type BranchCursor, type BranchDef, type ClaimOptions, type Cursor, type DeepWithMarkers, type FieldDefinition, type ForkCursor, type ForkDef, type FrameworkState, type Harness, type Lease, LeaseExpiredError, type LoopDefinition, type LoopNode, LoopNotDefinedError, NoInterruptError, type Observer, type ObserverAware, type ObserverErrorContext, type ObserverErrorSink, REQUIRED_TAG, RUNTIME_TAG, type RequiredMarker, type RouteFn, type RunContext, type RunFn, type RuntimeMarker, SessionBusyError, SessionInFlightError, SessionPendingInterruptError, type SessionStore, type SignalTransition, type StateFromSchema, type StepContext, type StepDef, type StepState, StoreLoadError, type StoredRun, type StoredRunMetadata, type TelemetryContext, type TransitionTarget, composeObservers, createAgent, createHarness, field, isForkCursor, isForkDef, isRequiredMarker, isRuntimeMarker, required, runtime };
|
package/dist/index.js
CHANGED
|
@@ -491,6 +491,51 @@ function createBuilderInstance(internals) {
|
|
|
491
491
|
return builder;
|
|
492
492
|
}
|
|
493
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
|
+
|
|
494
539
|
// src/agent/ctx-interrupt.ts
|
|
495
540
|
var InterruptPause = class extends Error {
|
|
496
541
|
interruptId;
|
|
@@ -523,11 +568,12 @@ function createInterruptFn(state, callCountRef, branchPath) {
|
|
|
523
568
|
}
|
|
524
569
|
|
|
525
570
|
// src/agent/ctx-emit.ts
|
|
526
|
-
function createEmitFn(listeners, observer, stepCtxRef) {
|
|
571
|
+
function createEmitFn(listeners, observer, stepCtxRef, onObserverError) {
|
|
527
572
|
return (name, payload) => {
|
|
528
573
|
listeners[name]?.(payload);
|
|
529
574
|
if (observer !== void 0 && stepCtxRef !== void 0 && stepCtxRef.current !== null) {
|
|
530
|
-
|
|
575
|
+
const ctx = stepCtxRef.current;
|
|
576
|
+
safeInvoke("onEvent", () => observer.onEvent?.(ctx, name, payload), onObserverError);
|
|
531
577
|
}
|
|
532
578
|
};
|
|
533
579
|
}
|
|
@@ -540,6 +586,7 @@ function extractRunListeners(resources) {
|
|
|
540
586
|
}
|
|
541
587
|
|
|
542
588
|
// src/loop/loop-executor.ts
|
|
589
|
+
var NOOP_OBSERVER = {};
|
|
543
590
|
function isForkCursor(cursor) {
|
|
544
591
|
return typeof cursor !== "string";
|
|
545
592
|
}
|
|
@@ -601,6 +648,7 @@ function applyUpdate(state, update, schema, touchedKeys) {
|
|
|
601
648
|
async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callbacks) {
|
|
602
649
|
const { onBeforeStep, onAfterStep, onError, onComplete, onInterrupt } = callbacks ?? {};
|
|
603
650
|
const obs = callbacks?.observer;
|
|
651
|
+
const onObserverError = callbacks?.onObserverError;
|
|
604
652
|
const implicitNextMap = buildImplicitNextMap(graph);
|
|
605
653
|
if (!("$error" in state)) state.$error = null;
|
|
606
654
|
if (!("$interrupt" in state)) state.$interrupt = null;
|
|
@@ -614,15 +662,15 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
|
|
|
614
662
|
...typeof ctx.instanceId === "string" ? { instanceId: ctx.instanceId } : {},
|
|
615
663
|
...callbacks?.branchPath !== void 0 && callbacks.branchPath.length > 0 ? { branchPath: callbacks.branchPath } : {}
|
|
616
664
|
};
|
|
617
|
-
obs?.onRunStart?.(runCtx);
|
|
665
|
+
safeInvoke("onRunStart", () => obs?.onRunStart?.(runCtx), onObserverError);
|
|
618
666
|
const callCountRef = { current: 0 };
|
|
619
667
|
const stepCtxRef = { current: null };
|
|
620
668
|
ctx["interrupt"] = createInterruptFn(state, callCountRef, callbacks?.branchPath);
|
|
621
|
-
ctx["emit"] = createEmitFn(callbacks?.listeners ?? {}, obs, stepCtxRef);
|
|
669
|
+
ctx["emit"] = createEmitFn(callbacks?.listeners ?? {}, obs, stepCtxRef, onObserverError);
|
|
622
670
|
let cursor = startCursor ?? graph.entryStep;
|
|
623
671
|
while (true) {
|
|
624
672
|
if (shouldStop?.()) {
|
|
625
|
-
obs?.onRunEnd?.(runCtx, { signal: "$stopped", durationMs: Date.now() - runStart });
|
|
673
|
+
safeInvoke("onRunEnd", () => obs?.onRunEnd?.(runCtx, { signal: "$stopped", durationMs: Date.now() - runStart }), onObserverError);
|
|
626
674
|
return { state, signal: null, cursor, paused: true };
|
|
627
675
|
}
|
|
628
676
|
if (isForkCursor(cursor)) {
|
|
@@ -685,10 +733,12 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
|
|
|
685
733
|
};
|
|
686
734
|
stepCtxRef.current = stepCtx;
|
|
687
735
|
const stepStart = Date.now();
|
|
688
|
-
obs?.onStepStart?.(stepCtx);
|
|
689
|
-
if (callbacks?.
|
|
690
|
-
|
|
691
|
-
|
|
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);
|
|
692
742
|
}
|
|
693
743
|
}
|
|
694
744
|
let runSucceeded = false;
|
|
@@ -708,19 +758,19 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
|
|
|
708
758
|
} catch (e) {
|
|
709
759
|
if (isInterruptPause(e)) {
|
|
710
760
|
onInterrupt?.(e.prompt, e.interruptId);
|
|
711
|
-
obs?.onInterrupt?.(stepCtx, { prompt: e.prompt, interruptId: e.interruptId });
|
|
712
|
-
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);
|
|
713
763
|
return { state, signal: "$interrupt", cursor, paused: true };
|
|
714
764
|
}
|
|
715
765
|
state.$error = e instanceof Error ? e : new Error(String(e));
|
|
716
766
|
onError?.(state.$error, cursor);
|
|
717
|
-
obs?.onStepError?.(stepCtx, { error: state.$error, durationMs: Date.now() - stepStart });
|
|
767
|
+
safeInvoke("onStepError", () => obs?.onStepError?.(stepCtx, { error: state.$error, durationMs: Date.now() - stepStart }), onObserverError);
|
|
718
768
|
}
|
|
719
769
|
}
|
|
720
770
|
if (runSucceeded) {
|
|
721
771
|
state.$error = null;
|
|
722
772
|
onAfterStep?.(cursor, state);
|
|
723
|
-
obs?.onStepEnd?.(stepCtx, { durationMs: Date.now() - stepStart });
|
|
773
|
+
safeInvoke("onStepEnd", () => obs?.onStepEnd?.(stepCtx, { durationMs: Date.now() - stepStart }), onObserverError);
|
|
724
774
|
}
|
|
725
775
|
const callRoute = step.route !== void 0 && (runSucceeded || step.run === void 0 || step.errorAware);
|
|
726
776
|
if (callRoute) {
|
|
@@ -730,7 +780,7 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
|
|
|
730
780
|
signal = routeFn(state);
|
|
731
781
|
} catch (e) {
|
|
732
782
|
state.$error = e instanceof Error ? e : new Error(String(e));
|
|
733
|
-
obs?.onRunEnd?.(runCtx, { signal: "$error", durationMs: Date.now() - runStart });
|
|
783
|
+
safeInvoke("onRunEnd", () => obs?.onRunEnd?.(runCtx, { signal: "$error", durationMs: Date.now() - runStart }), onObserverError);
|
|
734
784
|
return { state, signal: "$error", cursor, paused: true };
|
|
735
785
|
}
|
|
736
786
|
const transition = step.transitions.find((t) => t.signal === signal);
|
|
@@ -739,7 +789,7 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
|
|
|
739
789
|
}
|
|
740
790
|
if (transition.target.kind === "end") {
|
|
741
791
|
onComplete?.(state, signal);
|
|
742
|
-
obs?.onRunEnd?.(runCtx, { signal, durationMs: Date.now() - runStart });
|
|
792
|
+
safeInvoke("onRunEnd", () => obs?.onRunEnd?.(runCtx, { signal, durationMs: Date.now() - runStart }), onObserverError);
|
|
743
793
|
return { state, signal, cursor: null, paused: false };
|
|
744
794
|
}
|
|
745
795
|
cursor = transition.target.name;
|
|
@@ -747,7 +797,7 @@ async function runLoop(graph, state, ctx, schema, shouldStop, startCursor, callb
|
|
|
747
797
|
if (graph.onError !== void 0) {
|
|
748
798
|
cursor = graph.onError;
|
|
749
799
|
} else {
|
|
750
|
-
obs?.onRunEnd?.(runCtx, { signal: "$error", durationMs: Date.now() - runStart });
|
|
800
|
+
safeInvoke("onRunEnd", () => obs?.onRunEnd?.(runCtx, { signal: "$error", durationMs: Date.now() - runStart }), onObserverError);
|
|
751
801
|
return { state, signal: "$error", cursor, paused: true };
|
|
752
802
|
}
|
|
753
803
|
} else {
|
|
@@ -859,24 +909,25 @@ async function handleFork(forkNode, priorBranches, state, ctx, schema, shouldSto
|
|
|
859
909
|
...callbacks?.branchPath !== void 0 && callbacks.branchPath.length > 0 ? { branchPath: callbacks.branchPath } : {}
|
|
860
910
|
};
|
|
861
911
|
const stepStart = Date.now();
|
|
862
|
-
|
|
912
|
+
const onObserverError = callbacks?.onObserverError;
|
|
913
|
+
safeInvoke("onStepStart", () => obs?.onStepStart?.(stepCtx), onObserverError);
|
|
863
914
|
const dispatch = await dispatchFork(forkNode, priorBranches, state, ctx, schema, shouldStop, callbacks);
|
|
864
915
|
if (dispatch.kind === "paused") {
|
|
865
|
-
obs?.onRunEnd?.(runCtx, { signal: "$interrupt", durationMs: Date.now() - runStart });
|
|
916
|
+
safeInvoke("onRunEnd", () => obs?.onRunEnd?.(runCtx, { signal: "$interrupt", durationMs: Date.now() - runStart }), onObserverError);
|
|
866
917
|
return { done: true, result: { state, signal: "$interrupt", cursor: dispatch.forkCursor, paused: true } };
|
|
867
918
|
}
|
|
868
919
|
if (dispatch.kind === "failed") {
|
|
869
920
|
state.$error = dispatch.error;
|
|
870
921
|
callbacks?.onError?.(state.$error, forkNode.name);
|
|
871
|
-
obs?.onStepError?.(stepCtx, { error: state.$error, durationMs: Date.now() - stepStart });
|
|
922
|
+
safeInvoke("onStepError", () => obs?.onStepError?.(stepCtx, { error: state.$error, durationMs: Date.now() - stepStart }), onObserverError);
|
|
872
923
|
if (graph.onError !== void 0) {
|
|
873
924
|
return { done: false, nextCursor: graph.onError };
|
|
874
925
|
}
|
|
875
|
-
obs?.onRunEnd?.(runCtx, { signal: "$error", durationMs: Date.now() - runStart });
|
|
926
|
+
safeInvoke("onRunEnd", () => obs?.onRunEnd?.(runCtx, { signal: "$error", durationMs: Date.now() - runStart }), onObserverError);
|
|
876
927
|
return { done: true, result: { state, signal: "$error", cursor: forkNode.name, paused: true } };
|
|
877
928
|
}
|
|
878
929
|
state.$error = null;
|
|
879
|
-
obs?.onStepEnd?.(stepCtx, { durationMs: Date.now() - stepStart });
|
|
930
|
+
safeInvoke("onStepEnd", () => obs?.onStepEnd?.(stepCtx, { durationMs: Date.now() - stepStart }), onObserverError);
|
|
880
931
|
callbacks?.onAfterStep?.(forkNode.name, state);
|
|
881
932
|
const next = forkNode.next ?? implicitNextMap.get(forkNode.name) ?? null;
|
|
882
933
|
if (next === null) {
|
|
@@ -1315,12 +1366,24 @@ function extractRunEvents(resources) {
|
|
|
1315
1366
|
}
|
|
1316
1367
|
|
|
1317
1368
|
// src/agent/create-agent.ts
|
|
1318
|
-
|
|
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
|
+
}
|
|
1319
1378
|
function extractRunObserver(resources) {
|
|
1320
1379
|
const raw = resources["observer"];
|
|
1321
1380
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
1322
1381
|
return raw;
|
|
1323
1382
|
}
|
|
1383
|
+
function extractRunErrorSink(resources) {
|
|
1384
|
+
const raw = resources["onObserverError"];
|
|
1385
|
+
return typeof raw === "function" ? raw : void 0;
|
|
1386
|
+
}
|
|
1324
1387
|
var MissingLoopError = class extends Error {
|
|
1325
1388
|
constructor() {
|
|
1326
1389
|
super("harness has no loop \u2014 call h.loop() before createAgent(id, h, slots)");
|
|
@@ -1478,10 +1541,10 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1478
1541
|
runtimeKeys,
|
|
1479
1542
|
requiredKeys
|
|
1480
1543
|
};
|
|
1481
|
-
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"]);
|
|
1482
1545
|
const inFlightSessions = /* @__PURE__ */ new Set();
|
|
1483
1546
|
const interruptPendingSessions = /* @__PURE__ */ new Set();
|
|
1484
|
-
const makeAgentResumeHandle = (resp, sId, iId, resumeOpts, observer,
|
|
1547
|
+
const makeAgentResumeHandle = (resp, sId, iId, resumeOpts, observer, onObserverError, telemetrySlots) => {
|
|
1485
1548
|
let _stopped = false;
|
|
1486
1549
|
const abortController = new AbortController();
|
|
1487
1550
|
const flag = {
|
|
@@ -1556,7 +1619,8 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1556
1619
|
...resumeOpts?.onStoreError !== void 0 ? { onStoreError: resumeOpts.onStoreError } : {},
|
|
1557
1620
|
...leaseRef !== void 0 ? { leaseRef, claimTtlMs: claimOptions.ttlMs } : {},
|
|
1558
1621
|
...observer !== void 0 ? { observer } : {},
|
|
1559
|
-
...
|
|
1622
|
+
...onObserverError !== void 0 ? { onObserverError } : {},
|
|
1623
|
+
...telemetrySlots !== void 0 && telemetrySlots.length > 0 ? { telemetrySlots } : {}
|
|
1560
1624
|
}
|
|
1561
1625
|
);
|
|
1562
1626
|
if (r.signal === "$interrupt") interruptPendingSessions.add(sId);
|
|
@@ -1652,17 +1716,11 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1652
1716
|
const events = extractRunEvents(resources);
|
|
1653
1717
|
const listeners = extractRunListeners(resources);
|
|
1654
1718
|
const observer = extractRunObserver(resources);
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
const setStepContextSlots = [];
|
|
1661
|
-
for (const slot of [...agentInternals.resolvedProviders.values(), ...Object.values(runtimeSlots)]) {
|
|
1662
|
-
if (slot !== null && typeof slot === "object" && typeof slot["setStepContext"] === "function") {
|
|
1663
|
-
setStepContextSlots.push(slot);
|
|
1664
|
-
}
|
|
1665
|
-
}
|
|
1719
|
+
const onObserverError = extractRunErrorSink(resources);
|
|
1720
|
+
const telemetrySlots = buildTelemetrySlots([
|
|
1721
|
+
...agentInternals.resolvedProviders.entries(),
|
|
1722
|
+
...Object.entries(runtimeSlots)
|
|
1723
|
+
]);
|
|
1666
1724
|
const parentRunId = typeof resources["parentRunId"] === "string" ? resources["parentRunId"] : void 0;
|
|
1667
1725
|
const options = {
|
|
1668
1726
|
shouldStop: () => stopFlag.stopped,
|
|
@@ -1678,9 +1736,10 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1678
1736
|
...events.onInterrupt !== void 0 ? { onInterrupt: events.onInterrupt } : {},
|
|
1679
1737
|
...Object.keys(listeners).length > 0 ? { listeners } : {},
|
|
1680
1738
|
...observer !== void 0 ? { observer } : {},
|
|
1739
|
+
...onObserverError !== void 0 ? { onObserverError } : {},
|
|
1681
1740
|
...agentOptions?.instanceId !== void 0 ? { instanceId: agentOptions.instanceId } : {},
|
|
1682
1741
|
...parentRunId !== void 0 ? { parentRunId } : {},
|
|
1683
|
-
...
|
|
1742
|
+
...telemetrySlots.length > 0 ? { telemetrySlots } : {}
|
|
1684
1743
|
};
|
|
1685
1744
|
let lastResult = null;
|
|
1686
1745
|
const execution = (async () => {
|
|
@@ -1797,7 +1856,8 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1797
1856
|
...events.onInterrupt !== void 0 ? { onInterrupt: events.onInterrupt } : {},
|
|
1798
1857
|
...Object.keys(listeners).length > 0 ? { listeners } : {},
|
|
1799
1858
|
...observer !== void 0 ? { observer } : {},
|
|
1800
|
-
...
|
|
1859
|
+
...onObserverError !== void 0 ? { onObserverError } : {},
|
|
1860
|
+
...telemetrySlots.length > 0 ? { telemetrySlots } : {}
|
|
1801
1861
|
}
|
|
1802
1862
|
);
|
|
1803
1863
|
if (r2.signal === "$interrupt") interruptPendingSessions.add(sessionId);
|
|
@@ -1829,7 +1889,8 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1829
1889
|
...events.onInterrupt !== void 0 ? { onInterrupt: events.onInterrupt } : {},
|
|
1830
1890
|
...Object.keys(listeners).length > 0 ? { listeners } : {},
|
|
1831
1891
|
...observer !== void 0 ? { observer } : {},
|
|
1832
|
-
...
|
|
1892
|
+
...onObserverError !== void 0 ? { onObserverError } : {},
|
|
1893
|
+
...telemetrySlots.length > 0 ? { telemetrySlots } : {}
|
|
1833
1894
|
}
|
|
1834
1895
|
);
|
|
1835
1896
|
if (r.signal === "$interrupt") interruptPendingSessions.add(sessionId);
|
|
@@ -1858,51 +1919,15 @@ function createAgent(id, h, slots, agentOptions) {
|
|
|
1858
1919
|
const events = extractRunEvents(resources ?? {});
|
|
1859
1920
|
const resumeOpts = events.onStoreError !== void 0 ? { onStoreError: events.onStoreError } : void 0;
|
|
1860
1921
|
const observer = extractRunObserver(resources ?? {});
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
}
|
|
1865
|
-
}
|
|
1866
|
-
const setStepContextSlots = [];
|
|
1867
|
-
for (const slot of agentInternals.resolvedProviders.values()) {
|
|
1868
|
-
if (slot !== null && typeof slot === "object" && typeof slot["setStepContext"] === "function") {
|
|
1869
|
-
setStepContextSlots.push(slot);
|
|
1870
|
-
}
|
|
1871
|
-
}
|
|
1872
|
-
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);
|
|
1873
1925
|
},
|
|
1874
1926
|
status: (sessionId) => querySessionPhase(capturedStore, id, sessionId),
|
|
1875
1927
|
[_agentInternals]: agentInternals
|
|
1876
1928
|
};
|
|
1877
1929
|
return agent;
|
|
1878
1930
|
}
|
|
1879
|
-
|
|
1880
|
-
// src/agent/observer.ts
|
|
1881
|
-
function composeObservers(...observers) {
|
|
1882
|
-
return {
|
|
1883
|
-
onRunStart: (ctx) => {
|
|
1884
|
-
for (const o of observers) o.onRunStart?.(ctx);
|
|
1885
|
-
},
|
|
1886
|
-
onRunEnd: (ctx, event) => {
|
|
1887
|
-
for (const o of observers) o.onRunEnd?.(ctx, event);
|
|
1888
|
-
},
|
|
1889
|
-
onStepStart: (ctx) => {
|
|
1890
|
-
for (const o of observers) o.onStepStart?.(ctx);
|
|
1891
|
-
},
|
|
1892
|
-
onStepEnd: (ctx, event) => {
|
|
1893
|
-
for (const o of observers) o.onStepEnd?.(ctx, event);
|
|
1894
|
-
},
|
|
1895
|
-
onStepError: (ctx, event) => {
|
|
1896
|
-
for (const o of observers) o.onStepError?.(ctx, event);
|
|
1897
|
-
},
|
|
1898
|
-
onInterrupt: (ctx, event) => {
|
|
1899
|
-
for (const o of observers) o.onInterrupt?.(ctx, event);
|
|
1900
|
-
},
|
|
1901
|
-
onEvent: (ctx, type, payload) => {
|
|
1902
|
-
for (const o of observers) o.onEvent?.(ctx, type, payload);
|
|
1903
|
-
}
|
|
1904
|
-
};
|
|
1905
|
-
}
|
|
1906
1931
|
export {
|
|
1907
1932
|
LeaseExpiredError,
|
|
1908
1933
|
LoopNotDefinedError,
|