agentfootprint 2.14.5 → 3.1.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 +1 -1
- package/dist/cache/CacheDecisionSubflow.js +13 -16
- package/dist/cache/CacheDecisionSubflow.js.map +1 -1
- package/dist/cache/CacheGateDecider.js +18 -3
- package/dist/cache/CacheGateDecider.js.map +1 -1
- package/dist/cache/cacheRecorder.js +12 -3
- package/dist/cache/cacheRecorder.js.map +1 -1
- package/dist/conventions.js +155 -4
- package/dist/conventions.js.map +1 -1
- package/dist/core/Agent.js +115 -32
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/LLMCall.js +213 -41
- package/dist/core/LLMCall.js.map +1 -1
- package/dist/core/RunnerBase.js +187 -0
- package/dist/core/RunnerBase.js.map +1 -1
- package/dist/core/agent/buildAgentChart.js +127 -48
- package/dist/core/agent/buildAgentChart.js.map +1 -1
- package/dist/core/agent/buildAgentMessageApiChart.js +201 -0
- package/dist/core/agent/buildAgentMessageApiChart.js.map +1 -0
- package/dist/core/agent/buildCacheSubflow.js +62 -0
- package/dist/core/agent/buildCacheSubflow.js.map +1 -0
- package/dist/core/agent/buildDynamicAgentChart.js +364 -0
- package/dist/core/agent/buildDynamicAgentChart.js.map +1 -0
- package/dist/core/agent/buildMessageApiChart.js +154 -0
- package/dist/core/agent/buildMessageApiChart.js.map +1 -0
- package/dist/core/agent/stages/callLLM.js +11 -0
- package/dist/core/agent/stages/callLLM.js.map +1 -1
- package/dist/core/agent/stages/reliabilityExecution.js +64 -9
- package/dist/core/agent/stages/reliabilityExecution.js.map +1 -1
- package/dist/core/humanizeLLMError.js +66 -0
- package/dist/core/humanizeLLMError.js.map +1 -0
- package/dist/core/runner.js +4 -3
- package/dist/core/runner.js.map +1 -1
- package/dist/core/slots/buildMessagesSlot.js +2 -2
- package/dist/core/slots/buildMessagesSlot.js.map +1 -1
- package/dist/core/slots/buildSystemPromptSlot.js +1 -1
- package/dist/core/slots/buildSystemPromptSlot.js.map +1 -1
- package/dist/core/slots/buildThinkingSubflow.js +1 -1
- package/dist/core/slots/buildThinkingSubflow.js.map +1 -1
- package/dist/core/slots/buildToolsSlot.js +3 -1
- package/dist/core/slots/buildToolsSlot.js.map +1 -1
- package/dist/core/translator.js +32 -0
- package/dist/core/translator.js.map +1 -0
- package/dist/core-flow/Conditional.js +72 -10
- package/dist/core-flow/Conditional.js.map +1 -1
- package/dist/core-flow/Loop.js +59 -16
- package/dist/core-flow/Loop.js.map +1 -1
- package/dist/core-flow/Parallel.js +239 -92
- package/dist/core-flow/Parallel.js.map +1 -1
- package/dist/core-flow/Sequence.js +50 -8
- package/dist/core-flow/Sequence.js.map +1 -1
- package/dist/esm/cache/CacheDecisionSubflow.js +11 -15
- package/dist/esm/cache/CacheDecisionSubflow.js.map +1 -1
- package/dist/esm/cache/CacheGateDecider.js +18 -3
- package/dist/esm/cache/CacheGateDecider.js.map +1 -1
- package/dist/esm/cache/cacheRecorder.js +12 -3
- package/dist/esm/cache/cacheRecorder.js.map +1 -1
- package/dist/esm/conventions.js +151 -3
- package/dist/esm/conventions.js.map +1 -1
- package/dist/esm/core/Agent.js +116 -33
- package/dist/esm/core/Agent.js.map +1 -1
- package/dist/esm/core/LLMCall.js +213 -41
- package/dist/esm/core/LLMCall.js.map +1 -1
- package/dist/esm/core/RunnerBase.js +187 -0
- package/dist/esm/core/RunnerBase.js.map +1 -1
- package/dist/esm/core/agent/buildAgentChart.js +128 -49
- package/dist/esm/core/agent/buildAgentChart.js.map +1 -1
- package/dist/esm/core/agent/buildAgentMessageApiChart.js +197 -0
- package/dist/esm/core/agent/buildAgentMessageApiChart.js.map +1 -0
- package/dist/esm/core/agent/buildCacheSubflow.js +58 -0
- package/dist/esm/core/agent/buildCacheSubflow.js.map +1 -0
- package/dist/esm/core/agent/buildDynamicAgentChart.js +360 -0
- package/dist/esm/core/agent/buildDynamicAgentChart.js.map +1 -0
- package/dist/esm/core/agent/buildMessageApiChart.js +150 -0
- package/dist/esm/core/agent/buildMessageApiChart.js.map +1 -0
- package/dist/esm/core/agent/stages/callLLM.js +11 -0
- package/dist/esm/core/agent/stages/callLLM.js.map +1 -1
- package/dist/esm/core/agent/stages/reliabilityExecution.js +64 -9
- package/dist/esm/core/agent/stages/reliabilityExecution.js.map +1 -1
- package/dist/esm/core/humanizeLLMError.js +61 -0
- package/dist/esm/core/humanizeLLMError.js.map +1 -0
- package/dist/esm/core/runner.js +4 -3
- package/dist/esm/core/runner.js.map +1 -1
- package/dist/esm/core/slots/buildMessagesSlot.js +2 -2
- package/dist/esm/core/slots/buildMessagesSlot.js.map +1 -1
- package/dist/esm/core/slots/buildSystemPromptSlot.js +1 -1
- package/dist/esm/core/slots/buildSystemPromptSlot.js.map +1 -1
- package/dist/esm/core/slots/buildThinkingSubflow.js +1 -1
- package/dist/esm/core/slots/buildThinkingSubflow.js.map +1 -1
- package/dist/esm/core/slots/buildToolsSlot.js +3 -1
- package/dist/esm/core/slots/buildToolsSlot.js.map +1 -1
- package/dist/esm/core/translator.js +31 -0
- package/dist/esm/core/translator.js.map +1 -0
- package/dist/esm/core-flow/Conditional.js +72 -10
- package/dist/esm/core-flow/Conditional.js.map +1 -1
- package/dist/esm/core-flow/Loop.js +59 -16
- package/dist/esm/core-flow/Loop.js.map +1 -1
- package/dist/esm/core-flow/Parallel.js +240 -93
- package/dist/esm/core-flow/Parallel.js.map +1 -1
- package/dist/esm/core-flow/Sequence.js +50 -8
- package/dist/esm/core-flow/Sequence.js.map +1 -1
- package/dist/esm/events/registry.js +10 -0
- package/dist/esm/events/registry.js.map +1 -1
- package/dist/esm/index.js +22 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/injection-engine/buildInjectionEngineSubflow.js +16 -9
- package/dist/esm/lib/injection-engine/buildInjectionEngineSubflow.js.map +1 -1
- package/dist/esm/memory/causal/snapshotPipeline.js +6 -2
- package/dist/esm/memory/causal/snapshotPipeline.js.map +1 -1
- package/dist/esm/memory/pipeline/auto.js +2 -2
- package/dist/esm/memory/pipeline/auto.js.map +1 -1
- package/dist/esm/memory/pipeline/default.js +4 -2
- package/dist/esm/memory/pipeline/default.js.map +1 -1
- package/dist/esm/memory/pipeline/ephemeral.js +3 -1
- package/dist/esm/memory/pipeline/ephemeral.js.map +1 -1
- package/dist/esm/memory/pipeline/fact.js +4 -2
- package/dist/esm/memory/pipeline/fact.js.map +1 -1
- package/dist/esm/memory/pipeline/narrative.js +4 -2
- package/dist/esm/memory/pipeline/narrative.js.map +1 -1
- package/dist/esm/memory/pipeline/semantic.js +2 -2
- package/dist/esm/memory/pipeline/semantic.js.map +1 -1
- package/dist/esm/observe.js +1 -1
- package/dist/esm/observe.js.map +1 -1
- package/dist/esm/patterns/MapReduce.js +5 -5
- package/dist/esm/patterns/MapReduce.js.map +1 -1
- package/dist/esm/patterns/Swarm.js +1 -1
- package/dist/esm/patterns/Swarm.js.map +1 -1
- package/dist/esm/recorders/core/ContextEvaluatedRecorder.js +31 -0
- package/dist/esm/recorders/core/ContextEvaluatedRecorder.js.map +1 -0
- package/dist/esm/recorders/core/ContextRecorder.js +12 -14
- package/dist/esm/recorders/core/ContextRecorder.js.map +1 -1
- package/dist/esm/recorders/core/ErrorBridge.js +59 -0
- package/dist/esm/recorders/core/ErrorBridge.js.map +1 -0
- package/dist/esm/recorders/core/ReliabilityRecorder.js +29 -0
- package/dist/esm/recorders/core/ReliabilityRecorder.js.map +1 -0
- package/dist/esm/recorders/observability/BoundaryRecorder.js +338 -36
- package/dist/esm/recorders/observability/BoundaryRecorder.js.map +1 -1
- package/dist/esm/recorders/observability/FlowchartRecorder.js +10 -0
- package/dist/esm/recorders/observability/FlowchartRecorder.js.map +1 -1
- package/dist/esm/recorders/observability/LiveStateRecorder.js +120 -21
- package/dist/esm/recorders/observability/LiveStateRecorder.js.map +1 -1
- package/dist/esm/recorders/observability/RunStepRecorder.js +652 -0
- package/dist/esm/recorders/observability/RunStepRecorder.js.map +1 -0
- package/dist/esm/recorders/observability/commentary/commentaryTemplates.js +1 -0
- package/dist/esm/recorders/observability/commentary/commentaryTemplates.js.map +1 -1
- package/dist/esm/recorders/observability/internal/ActorArrowClassifier.js +34 -0
- package/dist/esm/recorders/observability/internal/ActorArrowClassifier.js.map +1 -0
- package/dist/esm/recorders/observability/internal/CandidateAnswerBuffer.js +32 -0
- package/dist/esm/recorders/observability/internal/CandidateAnswerBuffer.js.map +1 -0
- package/dist/esm/recorders/observability/internal/ForkTracker.js +84 -0
- package/dist/esm/recorders/observability/internal/ForkTracker.js.map +1 -0
- package/dist/esm/recorders/observability/internal/RootInferrer.js +114 -0
- package/dist/esm/recorders/observability/internal/RootInferrer.js.map +1 -0
- package/dist/esm/recorders/observability/internal/SequenceSiblingTracker.js +31 -0
- package/dist/esm/recorders/observability/internal/SequenceSiblingTracker.js.map +1 -0
- package/dist/esm/recorders/observability/observeRunId.js +21 -0
- package/dist/esm/recorders/observability/observeRunId.js.map +1 -0
- package/dist/esm/reliability/buildReliabilityGateChart.js +11 -5
- package/dist/esm/reliability/buildReliabilityGateChart.js.map +1 -1
- package/dist/events/registry.js +10 -0
- package/dist/events/registry.js.map +1 -1
- package/dist/index.js +30 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/injection-engine/buildInjectionEngineSubflow.js +16 -9
- package/dist/lib/injection-engine/buildInjectionEngineSubflow.js.map +1 -1
- package/dist/memory/causal/snapshotPipeline.js +6 -2
- package/dist/memory/causal/snapshotPipeline.js.map +1 -1
- package/dist/memory/pipeline/auto.js +2 -2
- package/dist/memory/pipeline/auto.js.map +1 -1
- package/dist/memory/pipeline/default.js +4 -2
- package/dist/memory/pipeline/default.js.map +1 -1
- package/dist/memory/pipeline/ephemeral.js +3 -1
- package/dist/memory/pipeline/ephemeral.js.map +1 -1
- package/dist/memory/pipeline/fact.js +4 -2
- package/dist/memory/pipeline/fact.js.map +1 -1
- package/dist/memory/pipeline/narrative.js +4 -2
- package/dist/memory/pipeline/narrative.js.map +1 -1
- package/dist/memory/pipeline/semantic.js +2 -2
- package/dist/memory/pipeline/semantic.js.map +1 -1
- package/dist/observe.js +1 -1
- package/dist/observe.js.map +1 -1
- package/dist/patterns/MapReduce.js +5 -5
- package/dist/patterns/MapReduce.js.map +1 -1
- package/dist/patterns/Swarm.js +1 -1
- package/dist/patterns/Swarm.js.map +1 -1
- package/dist/recorders/core/ContextEvaluatedRecorder.js +35 -0
- package/dist/recorders/core/ContextEvaluatedRecorder.js.map +1 -0
- package/dist/recorders/core/ContextRecorder.js +11 -13
- package/dist/recorders/core/ContextRecorder.js.map +1 -1
- package/dist/recorders/core/ErrorBridge.js +64 -0
- package/dist/recorders/core/ErrorBridge.js.map +1 -0
- package/dist/recorders/core/ReliabilityRecorder.js +33 -0
- package/dist/recorders/core/ReliabilityRecorder.js.map +1 -0
- package/dist/recorders/observability/BoundaryRecorder.js +337 -35
- package/dist/recorders/observability/BoundaryRecorder.js.map +1 -1
- package/dist/recorders/observability/FlowchartRecorder.js +10 -0
- package/dist/recorders/observability/FlowchartRecorder.js.map +1 -1
- package/dist/recorders/observability/LiveStateRecorder.js +119 -20
- package/dist/recorders/observability/LiveStateRecorder.js.map +1 -1
- package/dist/recorders/observability/RunStepRecorder.js +658 -0
- package/dist/recorders/observability/RunStepRecorder.js.map +1 -0
- package/dist/recorders/observability/commentary/commentaryTemplates.js +1 -0
- package/dist/recorders/observability/commentary/commentaryTemplates.js.map +1 -1
- package/dist/recorders/observability/internal/ActorArrowClassifier.js +38 -0
- package/dist/recorders/observability/internal/ActorArrowClassifier.js.map +1 -0
- package/dist/recorders/observability/internal/CandidateAnswerBuffer.js +36 -0
- package/dist/recorders/observability/internal/CandidateAnswerBuffer.js.map +1 -0
- package/dist/recorders/observability/internal/ForkTracker.js +88 -0
- package/dist/recorders/observability/internal/ForkTracker.js.map +1 -0
- package/dist/recorders/observability/internal/RootInferrer.js +118 -0
- package/dist/recorders/observability/internal/RootInferrer.js.map +1 -0
- package/dist/recorders/observability/internal/SequenceSiblingTracker.js +35 -0
- package/dist/recorders/observability/internal/SequenceSiblingTracker.js.map +1 -0
- package/dist/recorders/observability/observeRunId.js +25 -0
- package/dist/recorders/observability/observeRunId.js.map +1 -0
- package/dist/reliability/buildReliabilityGateChart.js +11 -5
- package/dist/reliability/buildReliabilityGateChart.js.map +1 -1
- package/dist/types/cache/CacheDecisionSubflow.d.ts +7 -10
- package/dist/types/cache/CacheDecisionSubflow.d.ts.map +1 -1
- package/dist/types/cache/CacheGateDecider.d.ts +16 -2
- package/dist/types/cache/CacheGateDecider.d.ts.map +1 -1
- package/dist/types/cache/cacheRecorder.d.ts.map +1 -1
- package/dist/types/conventions.d.ts +101 -1
- package/dist/types/conventions.d.ts.map +1 -1
- package/dist/types/core/Agent.d.ts +28 -18
- package/dist/types/core/Agent.d.ts.map +1 -1
- package/dist/types/core/LLMCall.d.ts +73 -11
- package/dist/types/core/LLMCall.d.ts.map +1 -1
- package/dist/types/core/RunnerBase.d.ts +136 -4
- package/dist/types/core/RunnerBase.d.ts.map +1 -1
- package/dist/types/core/agent/buildAgentChart.d.ts +38 -19
- package/dist/types/core/agent/buildAgentChart.d.ts.map +1 -1
- package/dist/types/core/agent/buildAgentMessageApiChart.d.ts +41 -0
- package/dist/types/core/agent/buildAgentMessageApiChart.d.ts.map +1 -0
- package/dist/types/core/agent/buildCacheSubflow.d.ts +36 -0
- package/dist/types/core/agent/buildCacheSubflow.d.ts.map +1 -0
- package/dist/types/core/agent/buildDynamicAgentChart.d.ts +57 -0
- package/dist/types/core/agent/buildDynamicAgentChart.d.ts.map +1 -0
- package/dist/types/core/agent/buildMessageApiChart.d.ts +48 -0
- package/dist/types/core/agent/buildMessageApiChart.d.ts.map +1 -0
- package/dist/types/core/agent/stages/callLLM.d.ts.map +1 -1
- package/dist/types/core/agent/stages/reliabilityExecution.d.ts.map +1 -1
- package/dist/types/core/agent/types.d.ts +96 -0
- package/dist/types/core/agent/types.d.ts.map +1 -1
- package/dist/types/core/humanizeLLMError.d.ts +24 -0
- package/dist/types/core/humanizeLLMError.d.ts.map +1 -0
- package/dist/types/core/runner.d.ts +51 -5
- package/dist/types/core/runner.d.ts.map +1 -1
- package/dist/types/core/slots/buildMessagesSlot.d.ts.map +1 -1
- package/dist/types/core/slots/buildSystemPromptSlot.d.ts.map +1 -1
- package/dist/types/core/slots/buildThinkingSubflow.d.ts.map +1 -1
- package/dist/types/core/slots/buildToolsSlot.d.ts.map +1 -1
- package/dist/types/core/translator.d.ts +95 -0
- package/dist/types/core/translator.d.ts.map +1 -0
- package/dist/types/core-flow/Conditional.d.ts +48 -4
- package/dist/types/core-flow/Conditional.d.ts.map +1 -1
- package/dist/types/core-flow/Loop.d.ts +42 -3
- package/dist/types/core-flow/Loop.d.ts.map +1 -1
- package/dist/types/core-flow/Parallel.d.ts +99 -4
- package/dist/types/core-flow/Parallel.d.ts.map +1 -1
- package/dist/types/core-flow/Sequence.d.ts +49 -3
- package/dist/types/core-flow/Sequence.d.ts.map +1 -1
- package/dist/types/events/payloads.d.ts +99 -1
- package/dist/types/events/payloads.d.ts.map +1 -1
- package/dist/types/events/registry.d.ts +11 -1
- package/dist/types/events/registry.d.ts.map +1 -1
- package/dist/types/index.d.ts +8 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/buildInjectionEngineSubflow.d.ts.map +1 -1
- package/dist/types/memory/causal/snapshotPipeline.d.ts.map +1 -1
- package/dist/types/memory/pipeline/auto.d.ts.map +1 -1
- package/dist/types/memory/pipeline/default.d.ts.map +1 -1
- package/dist/types/memory/pipeline/ephemeral.d.ts.map +1 -1
- package/dist/types/memory/pipeline/fact.d.ts.map +1 -1
- package/dist/types/memory/pipeline/narrative.d.ts.map +1 -1
- package/dist/types/memory/pipeline/semantic.d.ts.map +1 -1
- package/dist/types/observe.d.ts +2 -2
- package/dist/types/observe.d.ts.map +1 -1
- package/dist/types/recorders/core/ContextEvaluatedRecorder.d.ts +24 -0
- package/dist/types/recorders/core/ContextEvaluatedRecorder.d.ts.map +1 -0
- package/dist/types/recorders/core/ContextRecorder.d.ts +0 -2
- package/dist/types/recorders/core/ContextRecorder.d.ts.map +1 -1
- package/dist/types/recorders/core/ErrorBridge.d.ts +39 -0
- package/dist/types/recorders/core/ErrorBridge.d.ts.map +1 -0
- package/dist/types/recorders/core/ReliabilityRecorder.d.ts +25 -0
- package/dist/types/recorders/core/ReliabilityRecorder.d.ts.map +1 -0
- package/dist/types/recorders/observability/BoundaryRecorder.d.ts +167 -6
- package/dist/types/recorders/observability/BoundaryRecorder.d.ts.map +1 -1
- package/dist/types/recorders/observability/FlowchartRecorder.d.ts.map +1 -1
- package/dist/types/recorders/observability/LiveStateRecorder.d.ts +42 -6
- package/dist/types/recorders/observability/LiveStateRecorder.d.ts.map +1 -1
- package/dist/types/recorders/observability/RunStepRecorder.d.ts +232 -0
- package/dist/types/recorders/observability/RunStepRecorder.d.ts.map +1 -0
- package/dist/types/recorders/observability/commentary/commentaryTemplates.d.ts.map +1 -1
- package/dist/types/recorders/observability/internal/ActorArrowClassifier.d.ts +26 -0
- package/dist/types/recorders/observability/internal/ActorArrowClassifier.d.ts.map +1 -0
- package/dist/types/recorders/observability/internal/CandidateAnswerBuffer.d.ts +29 -0
- package/dist/types/recorders/observability/internal/CandidateAnswerBuffer.d.ts.map +1 -0
- package/dist/types/recorders/observability/internal/ForkTracker.d.ts +61 -0
- package/dist/types/recorders/observability/internal/ForkTracker.d.ts.map +1 -0
- package/dist/types/recorders/observability/internal/RootInferrer.d.ts +52 -0
- package/dist/types/recorders/observability/internal/RootInferrer.d.ts.map +1 -0
- package/dist/types/recorders/observability/internal/SequenceSiblingTracker.d.ts +25 -0
- package/dist/types/recorders/observability/internal/SequenceSiblingTracker.d.ts.map +1 -0
- package/dist/types/recorders/observability/observeRunId.d.ts +37 -0
- package/dist/types/recorders/observability/observeRunId.d.ts.map +1 -0
- package/dist/types/reliability/buildReliabilityGateChart.d.ts.map +1 -1
- package/package.json +6 -5
- package/dist/core/agent/stages/iterationStart.js +0 -24
- package/dist/core/agent/stages/iterationStart.js.map +0 -1
- package/dist/esm/core/agent/stages/iterationStart.js +0 -20
- package/dist/esm/core/agent/stages/iterationStart.js.map +0 -1
- package/dist/types/core/agent/stages/iterationStart.d.ts +0 -16
- package/dist/types/core/agent/stages/iterationStart.d.ts.map +0 -1
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RunStepRecorder — slider-ready ordered list of RunSteps, BUILT
|
|
3
|
+
* INCREMENTALLY during traversal. Real-time recorder, not a walker.
|
|
4
|
+
*
|
|
5
|
+
* Pattern: extends `SequenceRecorder<RunStep>` (shared storage shelf)
|
|
6
|
+
* and implements `CombinedRecorder` (FlowRecorder hooks).
|
|
7
|
+
* Subscribes to the agentfootprint typed-event dispatcher
|
|
8
|
+
* for actor-arrow events. Each event handler decides whether
|
|
9
|
+
* to emit a step; state lives on the instance and persists
|
|
10
|
+
* across the run.
|
|
11
|
+
* Role: The single source of truth for "what slider positions
|
|
12
|
+
* exist in this run, and what transitions does each light
|
|
13
|
+
* up." Lens consumers attach the recorder once and read
|
|
14
|
+
* `getSteps()` — no per-render re-derivation.
|
|
15
|
+
*
|
|
16
|
+
* Why this matters: the older `buildRunSteps(events)` walker violated
|
|
17
|
+
* footprintjs's core principle ("collect during traversal, never
|
|
18
|
+
* post-process"). Each call walked the full event log multiple times;
|
|
19
|
+
* the playground triggered a full walk on every flowchart update,
|
|
20
|
+
* yielding O(N²) total work for a streaming run. The recorder pattern
|
|
21
|
+
* is O(N) — one handler call per event — and matches BoundaryRecorder /
|
|
22
|
+
* FlowchartRecorder / KeyedRecorder idioms throughout the library.
|
|
23
|
+
*
|
|
24
|
+
* The `buildRunSteps(...)` function is RETAINED as a thin compatibility
|
|
25
|
+
* shim that constructs a fresh recorder, replays events through it,
|
|
26
|
+
* and returns the resulting entries. Useful for snapshot-from-saved-
|
|
27
|
+
* events use cases (replay, testing, post-hoc analysis). Live consumers
|
|
28
|
+
* should attach the recorder directly via `runner.attach(rec)`.
|
|
29
|
+
*/
|
|
30
|
+
import { ROOT_RUNTIME_STAGE_ID, ROOT_SUBFLOW_ID, SequenceStore, splitStageId, } from 'footprintjs/trace';
|
|
31
|
+
import { createRunIdObserver } from './observeRunId.js';
|
|
32
|
+
import { ForkTracker } from './internal/ForkTracker.js';
|
|
33
|
+
import { SequenceSiblingTracker } from './internal/SequenceSiblingTracker.js';
|
|
34
|
+
import { CandidateAnswerBuffer } from './internal/CandidateAnswerBuffer.js';
|
|
35
|
+
import { RootInferrer } from './internal/RootInferrer.js';
|
|
36
|
+
import { ActorArrowClassifier } from './internal/ActorArrowClassifier.js';
|
|
37
|
+
let _counter = 0;
|
|
38
|
+
/** Factory — matches the `boundaryRecorder()` / `topologyRecorder()` style. */
|
|
39
|
+
export function runStepRecorder(options = {}) {
|
|
40
|
+
return new RunStepRecorder(options);
|
|
41
|
+
}
|
|
42
|
+
// ─── Constants ──────────────────────────────────────────────────────
|
|
43
|
+
const ACTOR_USER = 'actor:user';
|
|
44
|
+
const LEAF_PRIMITIVES = new Set(['Agent', 'LLMCall']);
|
|
45
|
+
function isLeafPrimitive(kind) {
|
|
46
|
+
return kind !== undefined && LEAF_PRIMITIVES.has(kind);
|
|
47
|
+
}
|
|
48
|
+
function lastSegment(subflowId) {
|
|
49
|
+
const i = subflowId.lastIndexOf('/');
|
|
50
|
+
return i >= 0 ? subflowId.slice(i + 1) : subflowId;
|
|
51
|
+
}
|
|
52
|
+
function isPathPrefix(prefix, path) {
|
|
53
|
+
if (prefix.length > path.length)
|
|
54
|
+
return false;
|
|
55
|
+
for (let i = 0; i < prefix.length; i++) {
|
|
56
|
+
if (prefix[i] !== path[i])
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
function pathFromCtx(s) {
|
|
62
|
+
const segments = s ? s.split('/').filter(Boolean) : [];
|
|
63
|
+
return [ROOT_SUBFLOW_ID, ...segments];
|
|
64
|
+
}
|
|
65
|
+
// ─── Recorder class ────────────────────────────────────────────────
|
|
66
|
+
/**
|
|
67
|
+
* Real-time slider-step recorder. Emits a `RunStep` whenever an event
|
|
68
|
+
* marks a meaningful slider transition. State persists on the instance
|
|
69
|
+
* so successive events update bookkeeping in O(1).
|
|
70
|
+
*
|
|
71
|
+
* Attach via `runner.attach(rec)` for FlowRecorder events; call
|
|
72
|
+
* `rec.subscribe(runner.dispatcher)` for actor-arrow events. The
|
|
73
|
+
* `getSteps(drillPath?)` method returns the already-built list (no
|
|
74
|
+
* walking) with optional drill-scope filtering.
|
|
75
|
+
*/
|
|
76
|
+
export class RunStepRecorder {
|
|
77
|
+
id;
|
|
78
|
+
/** Composition: storage shelf for the slider-step sequence. */
|
|
79
|
+
store = new SequenceStore();
|
|
80
|
+
/** Run-boundary observer — fires this.clear() when traversalContext.runId
|
|
81
|
+
* changes between events. THIS IS THE FIX for the Parallel multi-run
|
|
82
|
+
* aliasing bug — without it `forkKey = ${parent}@${rid}` collides
|
|
83
|
+
* because rid resets to `seed#0` on each run. */
|
|
84
|
+
runIdGuard = createRunIdObserver(() => this.resetForNewRun());
|
|
85
|
+
// ── Composed sub-trackers (one concern each) ─────────────────────
|
|
86
|
+
/** Stack of currently-open boundaries. The recorder owns this
|
|
87
|
+
* directly because it's a simple stack and frames are recorder-
|
|
88
|
+
* shaped. */
|
|
89
|
+
boundaryStack = [];
|
|
90
|
+
/** Fork-emission coalescing + branch-exit tally. */
|
|
91
|
+
forks = new ForkTracker();
|
|
92
|
+
/** Tracks the most-recent leaf exit per depth → "forwards" handoff. */
|
|
93
|
+
siblings = new SequenceSiblingTracker();
|
|
94
|
+
/** Buffers a "this MIGHT be the answer" leaf until onRunEnd. */
|
|
95
|
+
answerBuffer = new CandidateAnswerBuffer();
|
|
96
|
+
/** Run-root inference state machine (leaf vs composition). */
|
|
97
|
+
rootInferrer = new RootInferrer();
|
|
98
|
+
/** llm.start / llm.end actor-arrow classifier. */
|
|
99
|
+
actorArrows = new ActorArrowClassifier();
|
|
100
|
+
/** Has the first `asks` step fired? */
|
|
101
|
+
asksEmitted = false;
|
|
102
|
+
constructor(options = {}) {
|
|
103
|
+
this.id = options.id ?? `run-step-${++_counter}`;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Emit a RunStep, auto-mirroring `anchor.runtimeStageId` to the
|
|
107
|
+
* top-level `runtimeStageId` field that the keyed index uses. Single
|
|
108
|
+
* source of truth (the anchor) — never inconsistent with the storage
|
|
109
|
+
* key.
|
|
110
|
+
*/
|
|
111
|
+
push(step) {
|
|
112
|
+
this.store.push({ ...step, runtimeStageId: step.anchor.runtimeStageId });
|
|
113
|
+
}
|
|
114
|
+
/** Internal seq-numbering helper — mirrors the store size so each
|
|
115
|
+
* RunStep gets a unique 0-based index in emit order. */
|
|
116
|
+
get entryCount() {
|
|
117
|
+
return this.store.size;
|
|
118
|
+
}
|
|
119
|
+
clear() {
|
|
120
|
+
this.resetForNewRun();
|
|
121
|
+
this.runIdGuard.reset();
|
|
122
|
+
}
|
|
123
|
+
/** Internal — wipe all per-run state WITHOUT resetting the runIdGuard
|
|
124
|
+
* itself. Called by `clear()` (which then resets the guard) AND by
|
|
125
|
+
* the runIdGuard's onNewRun callback (where the guard is mid-update
|
|
126
|
+
* and must NOT be reset, only the recorder's data should be).
|
|
127
|
+
*
|
|
128
|
+
* Note: each sub-tracker owns its OWN clear; the orchestrator just
|
|
129
|
+
* fans out. Adding new state to a sub-tracker requires no edit here. */
|
|
130
|
+
resetForNewRun() {
|
|
131
|
+
this.store.clear();
|
|
132
|
+
this.boundaryStack = [];
|
|
133
|
+
this.forks.clear();
|
|
134
|
+
this.siblings.clear();
|
|
135
|
+
this.answerBuffer.clear();
|
|
136
|
+
this.rootInferrer.clear();
|
|
137
|
+
this.actorArrows.clear();
|
|
138
|
+
this.asksEmitted = false;
|
|
139
|
+
}
|
|
140
|
+
observeRunId(runId) {
|
|
141
|
+
this.runIdGuard.observe(runId);
|
|
142
|
+
}
|
|
143
|
+
// ── FlowRecorder hooks ─────────────────────────────────────────
|
|
144
|
+
onRunStart(event) {
|
|
145
|
+
this.observeRunId(event.traversalContext?.runId);
|
|
146
|
+
// Nothing to emit yet — the first leaf entry / fork event starts
|
|
147
|
+
// the slider.
|
|
148
|
+
}
|
|
149
|
+
onRunEnd(event) {
|
|
150
|
+
this.observeRunId(event.traversalContext?.runId);
|
|
151
|
+
// Emit the deferred `answers` step for the last leaf exit at run
|
|
152
|
+
// scope. Without this, runs with a Sequence-rooted shape never
|
|
153
|
+
// see their final answer reflected on the slider.
|
|
154
|
+
this.flushCandidateAnswer();
|
|
155
|
+
}
|
|
156
|
+
onSubflowEntry(event) {
|
|
157
|
+
if (!event.subflowId)
|
|
158
|
+
return;
|
|
159
|
+
this.observeRunId(event.traversalContext?.runId);
|
|
160
|
+
const ctx = event.traversalContext;
|
|
161
|
+
const runtimeStageId = ctx?.runtimeStageId ?? '';
|
|
162
|
+
const subflowPath = pathFromCtx(ctx?.subflowPath);
|
|
163
|
+
const depth = subflowPath.length - 1;
|
|
164
|
+
const ts = Date.now();
|
|
165
|
+
const subflowId = event.subflowId;
|
|
166
|
+
const description = event.description;
|
|
167
|
+
const primitiveKind = parsePrimitiveKind(description);
|
|
168
|
+
// Delegate root inference to the dedicated state machine.
|
|
169
|
+
this.rootInferrer.observeSubflowEntry(depth, primitiveKind);
|
|
170
|
+
const frame = {
|
|
171
|
+
subflowId,
|
|
172
|
+
subflowPath,
|
|
173
|
+
...(primitiveKind ? { primitiveKind } : {}),
|
|
174
|
+
depth,
|
|
175
|
+
};
|
|
176
|
+
const isLeaf = isLeafPrimitive(primitiveKind);
|
|
177
|
+
if (this.forks.isForkChild(subflowId)) {
|
|
178
|
+
// Branch wrapper of a tracked fork — push frame so the matching
|
|
179
|
+
// exit can tally for the merge step. No sequential emission.
|
|
180
|
+
this.boundaryStack.push(frame);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (isLeaf) {
|
|
184
|
+
const prevSibling = this.siblings.peekPrevSibling(depth);
|
|
185
|
+
if (prevSibling) {
|
|
186
|
+
// Sequence-style sibling handoff: previous leaf exited, new
|
|
187
|
+
// leaf entered at the same depth → emit `forwards`.
|
|
188
|
+
this.push({
|
|
189
|
+
seq: this.entryCount,
|
|
190
|
+
kind: 'sequential',
|
|
191
|
+
transitions: [{ from: prevSibling, to: subflowId, via: 'next', label: 'forwards' }],
|
|
192
|
+
anchor: { runtimeStageId, subflowPath },
|
|
193
|
+
label: 'forwards',
|
|
194
|
+
tsMs: ts,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
else if (!this.asksEmitted) {
|
|
198
|
+
// First leaf in the run — User asks it.
|
|
199
|
+
this.push({
|
|
200
|
+
seq: this.entryCount,
|
|
201
|
+
kind: 'sequential',
|
|
202
|
+
transitions: [{ from: ACTOR_USER, to: subflowId, via: 'next', label: 'asks' }],
|
|
203
|
+
anchor: { runtimeStageId, subflowPath },
|
|
204
|
+
label: 'asks',
|
|
205
|
+
tsMs: ts,
|
|
206
|
+
});
|
|
207
|
+
this.asksEmitted = true;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
this.boundaryStack.push(frame);
|
|
211
|
+
}
|
|
212
|
+
onSubflowExit(event) {
|
|
213
|
+
this.observeRunId(event.traversalContext?.runId);
|
|
214
|
+
const ctx = event.traversalContext;
|
|
215
|
+
const runtimeStageId = ctx?.runtimeStageId ?? '';
|
|
216
|
+
const ts = Date.now();
|
|
217
|
+
const frame = this.boundaryStack.pop();
|
|
218
|
+
if (!frame)
|
|
219
|
+
return;
|
|
220
|
+
const isLeaf = isLeafPrimitive(frame.primitiveKind);
|
|
221
|
+
// Fork-branch exit: ForkTracker tallies completion and signals
|
|
222
|
+
// when ALL branches have exited so we can emit the merge step.
|
|
223
|
+
const merge = this.forks.recordChildExit(frame.subflowId);
|
|
224
|
+
if (merge) {
|
|
225
|
+
this.push({
|
|
226
|
+
seq: this.entryCount,
|
|
227
|
+
kind: 'merge',
|
|
228
|
+
transitions: merge.branches.map((sid) => ({
|
|
229
|
+
from: sid,
|
|
230
|
+
to: ACTOR_USER,
|
|
231
|
+
via: 'next',
|
|
232
|
+
label: lastSegment(sid),
|
|
233
|
+
})),
|
|
234
|
+
anchor: { runtimeStageId, subflowPath: frame.subflowPath },
|
|
235
|
+
label: `merge (${merge.branches.length})`,
|
|
236
|
+
tsMs: ts,
|
|
237
|
+
meta: { kind: 'merge', mergedCount: merge.branches.length },
|
|
238
|
+
});
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
// If it WAS a fork child but not the last to exit, still skip the
|
|
242
|
+
// leaf-handoff path below.
|
|
243
|
+
if (this.forks.isForkChild(frame.subflowId))
|
|
244
|
+
return;
|
|
245
|
+
if (isLeaf) {
|
|
246
|
+
// Sibling handoff + answer-candidate buffering. Both delegated.
|
|
247
|
+
this.siblings.recordExit(frame.depth, frame.subflowId);
|
|
248
|
+
this.answerBuffer.set(frame, ts, runtimeStageId);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
onFork(event) {
|
|
252
|
+
this.observeRunId(event.traversalContext?.runId);
|
|
253
|
+
const ctx = event.traversalContext;
|
|
254
|
+
const runtimeStageId = ctx?.runtimeStageId ?? '';
|
|
255
|
+
const subflowPath = pathFromCtx(ctx?.subflowPath);
|
|
256
|
+
const ts = Date.now();
|
|
257
|
+
const depth = subflowPath.length - 1;
|
|
258
|
+
// ForkTracker handles registration + dedup. Cross-run collisions
|
|
259
|
+
// (Committee + TolerantCommittee back-to-back, both emitting
|
|
260
|
+
// `seed#0` as the parent stage) are prevented by observeRunId
|
|
261
|
+
// above — the tracker's state is wiped when runId changes.
|
|
262
|
+
const reg = this.forks.registerFork(event.parent, runtimeStageId, event.children);
|
|
263
|
+
if (!reg.fresh)
|
|
264
|
+
return;
|
|
265
|
+
// Fork at depth 0 = Parallel root signal.
|
|
266
|
+
this.rootInferrer.observeFork(depth);
|
|
267
|
+
const branches = [...event.children];
|
|
268
|
+
this.push({
|
|
269
|
+
seq: this.entryCount,
|
|
270
|
+
kind: 'fork',
|
|
271
|
+
transitions: branches.map((childName) => ({
|
|
272
|
+
from: ACTOR_USER,
|
|
273
|
+
to: childName,
|
|
274
|
+
via: 'fork-branch',
|
|
275
|
+
label: childName,
|
|
276
|
+
})),
|
|
277
|
+
anchor: { runtimeStageId, subflowPath },
|
|
278
|
+
label: `fork (${branches.length})`,
|
|
279
|
+
tsMs: ts,
|
|
280
|
+
meta: { kind: 'fork', parentSubflowId: event.parent },
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
onDecision(event) {
|
|
284
|
+
this.observeRunId(event.traversalContext?.runId);
|
|
285
|
+
const ctx = event.traversalContext;
|
|
286
|
+
// Skip Agent-internal decisions — those are encoded by the actor
|
|
287
|
+
// arrows that follow.
|
|
288
|
+
const stageId = ctx?.stageId ?? '';
|
|
289
|
+
const { localStageId } = splitStageId(stageId);
|
|
290
|
+
if (isAgentInternalStageId(localStageId))
|
|
291
|
+
return;
|
|
292
|
+
const runtimeStageId = ctx?.runtimeStageId ?? '';
|
|
293
|
+
const subflowPath = pathFromCtx(ctx?.subflowPath);
|
|
294
|
+
const ts = Date.now();
|
|
295
|
+
const depth = subflowPath.length - 1;
|
|
296
|
+
this.rootInferrer.observeDecision(depth);
|
|
297
|
+
this.push({
|
|
298
|
+
seq: this.entryCount,
|
|
299
|
+
kind: 'decide',
|
|
300
|
+
transitions: [
|
|
301
|
+
{
|
|
302
|
+
from: runtimeStageId,
|
|
303
|
+
to: event.chosen,
|
|
304
|
+
via: 'decision-branch',
|
|
305
|
+
label: event.chosen,
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
anchor: { runtimeStageId, subflowPath },
|
|
309
|
+
label: `routes to ${event.chosen}`,
|
|
310
|
+
tsMs: ts,
|
|
311
|
+
meta: {
|
|
312
|
+
kind: 'decide',
|
|
313
|
+
chosen: event.chosen,
|
|
314
|
+
...(event.rationale ? { rationale: event.rationale } : {}),
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
onLoop(event) {
|
|
319
|
+
this.observeRunId(event.traversalContext?.runId);
|
|
320
|
+
const ctx = event.traversalContext;
|
|
321
|
+
const runtimeStageId = ctx?.runtimeStageId ?? '';
|
|
322
|
+
const subflowPath = pathFromCtx(ctx?.subflowPath);
|
|
323
|
+
const ts = Date.now();
|
|
324
|
+
const depth = subflowPath.length - 1;
|
|
325
|
+
this.rootInferrer.observeLoop(depth);
|
|
326
|
+
this.push({
|
|
327
|
+
seq: this.entryCount,
|
|
328
|
+
kind: 'iteration',
|
|
329
|
+
transitions: [
|
|
330
|
+
{
|
|
331
|
+
from: event.target,
|
|
332
|
+
to: event.target,
|
|
333
|
+
via: 'loop-iteration',
|
|
334
|
+
label: `iter ${event.iteration}`,
|
|
335
|
+
},
|
|
336
|
+
],
|
|
337
|
+
anchor: { runtimeStageId, subflowPath },
|
|
338
|
+
label: `iter ${event.iteration}`,
|
|
339
|
+
tsMs: ts,
|
|
340
|
+
meta: { kind: 'iteration', index: event.iteration, target: event.target },
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
// ── Typed-event subscription (actor arrows) ──────────────────────
|
|
344
|
+
/**
|
|
345
|
+
* Subscribe to the runner's typed-event dispatcher and emit a
|
|
346
|
+
* `react` RunStep on every `llm.start` / `llm.end`. The recorder
|
|
347
|
+
* classifies `actorArrow` locally (mirrors BoundaryRecorder's
|
|
348
|
+
* pattern) so consumers don't have to depend on BoundaryRecorder's
|
|
349
|
+
* own subscription order.
|
|
350
|
+
*/
|
|
351
|
+
subscribe(dispatcher) {
|
|
352
|
+
return dispatcher.on('*', (event) => this.ingestTypedEvent(event));
|
|
353
|
+
}
|
|
354
|
+
/** Internal — also called by `ingestDomainEvent` for shim replay.
|
|
355
|
+
*
|
|
356
|
+
* NOTE: deliberately does NOT call observeRunId(event.meta.runId).
|
|
357
|
+
* The agentfootprint dispatcher's runId is a DIFFERENT generator
|
|
358
|
+
* than footprintjs's traversalContext.runId — mixing them would
|
|
359
|
+
* toggle lastRunId on every event and trigger a false reset.
|
|
360
|
+
* Run-boundary detection happens reliably on the FlowRecorder side
|
|
361
|
+
* (onRunStart fires FIRST in any new run before any typed event). */
|
|
362
|
+
ingestTypedEvent(event) {
|
|
363
|
+
if (event.type === 'agentfootprint.stream.llm_start') {
|
|
364
|
+
const meta = event.meta;
|
|
365
|
+
const runtimeStageId = meta.runtimeStageId ?? '';
|
|
366
|
+
const subflowPath = [ROOT_SUBFLOW_ID, ...(meta.subflowPath ?? [])];
|
|
367
|
+
const ts = meta.wallClockMs;
|
|
368
|
+
const arrow = this.actorArrows.classifyStart();
|
|
369
|
+
const llmStage = `stage:llm:${runtimeStageId}`;
|
|
370
|
+
const from = arrow === 'tool→llm' ? `stage:tool:${runtimeStageId}` : ACTOR_USER;
|
|
371
|
+
this.push({
|
|
372
|
+
seq: this.entryCount,
|
|
373
|
+
kind: 'react',
|
|
374
|
+
transitions: [{ from, to: llmStage, via: 'actor-arrow', label: arrow }],
|
|
375
|
+
anchor: { runtimeStageId, subflowPath },
|
|
376
|
+
label: arrow,
|
|
377
|
+
tsMs: ts,
|
|
378
|
+
meta: { kind: 'react', actorArrow: arrow },
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
else if (event.type === 'agentfootprint.stream.llm_end') {
|
|
382
|
+
const p = event.payload;
|
|
383
|
+
const meta = event.meta;
|
|
384
|
+
const runtimeStageId = meta.runtimeStageId ?? '';
|
|
385
|
+
const subflowPath = [ROOT_SUBFLOW_ID, ...(meta.subflowPath ?? [])];
|
|
386
|
+
const ts = meta.wallClockMs;
|
|
387
|
+
const arrow = this.actorArrows.classifyEnd(p.toolCallCount);
|
|
388
|
+
const llmStage = `stage:llm:${runtimeStageId}`;
|
|
389
|
+
const to = arrow === 'llm→user' ? ACTOR_USER : `stage:tool:${runtimeStageId}`;
|
|
390
|
+
this.push({
|
|
391
|
+
seq: this.entryCount,
|
|
392
|
+
kind: 'react',
|
|
393
|
+
transitions: [{ from: llmStage, to, via: 'actor-arrow', label: arrow }],
|
|
394
|
+
anchor: { runtimeStageId, subflowPath },
|
|
395
|
+
label: arrow,
|
|
396
|
+
tsMs: ts,
|
|
397
|
+
meta: { kind: 'react', actorArrow: arrow },
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
// ── Replay API (for shim / tests / offline analysis) ─────────────
|
|
402
|
+
/**
|
|
403
|
+
* Feed a single recorded `DomainEvent` (from BoundaryRecorder) into
|
|
404
|
+
* this recorder as if it had fired live. Used by `buildRunSteps`
|
|
405
|
+
* for snapshot replay; tests use it for fixture-driven projection.
|
|
406
|
+
*
|
|
407
|
+
* Live consumers should use `runner.attach(rec)` +
|
|
408
|
+
* `rec.subscribe(dispatcher)` instead — the recorder's hooks fire
|
|
409
|
+
* naturally during traversal.
|
|
410
|
+
*/
|
|
411
|
+
ingestDomainEvent(e) {
|
|
412
|
+
const traversalContext = {
|
|
413
|
+
runId: 'replay',
|
|
414
|
+
stageId: lastSegment(e.subflowPath.join('/') || ROOT_SUBFLOW_ID),
|
|
415
|
+
runtimeStageId: e.runtimeStageId,
|
|
416
|
+
stageName: lastSegment(e.subflowPath.join('/') || ROOT_SUBFLOW_ID),
|
|
417
|
+
depth: e.depth,
|
|
418
|
+
...(e.subflowPath.length > 1 ? { subflowPath: e.subflowPath.slice(1).join('/') } : {}),
|
|
419
|
+
};
|
|
420
|
+
switch (e.type) {
|
|
421
|
+
case 'run.entry':
|
|
422
|
+
this.onRunStart({ payload: e.payload, traversalContext });
|
|
423
|
+
break;
|
|
424
|
+
case 'run.exit':
|
|
425
|
+
this.onRunEnd({ payload: e.payload, traversalContext });
|
|
426
|
+
break;
|
|
427
|
+
case 'subflow.entry':
|
|
428
|
+
this.onSubflowEntry({
|
|
429
|
+
name: e.subflowName,
|
|
430
|
+
subflowId: e.subflowId,
|
|
431
|
+
...(e.description ? { description: e.description } : {}),
|
|
432
|
+
traversalContext,
|
|
433
|
+
});
|
|
434
|
+
break;
|
|
435
|
+
case 'subflow.exit':
|
|
436
|
+
this.onSubflowExit({
|
|
437
|
+
name: e.subflowName,
|
|
438
|
+
subflowId: e.subflowId,
|
|
439
|
+
traversalContext,
|
|
440
|
+
});
|
|
441
|
+
break;
|
|
442
|
+
case 'fork.branch':
|
|
443
|
+
// Replay layer; coalescing is handled at the call-site
|
|
444
|
+
// (`buildRunSteps`) which groups events by parent+ts and
|
|
445
|
+
// calls `onFork` once. A single fork.branch slipping through
|
|
446
|
+
// to here is treated as a 1-child fork — degenerate but
|
|
447
|
+
// harmless.
|
|
448
|
+
this.onFork({
|
|
449
|
+
parent: e.parentSubflowId,
|
|
450
|
+
children: [e.childName],
|
|
451
|
+
traversalContext,
|
|
452
|
+
});
|
|
453
|
+
break;
|
|
454
|
+
case 'decision.branch':
|
|
455
|
+
this.onDecision({
|
|
456
|
+
decider: e.decider,
|
|
457
|
+
chosen: e.chosen,
|
|
458
|
+
...(e.rationale ? { rationale: e.rationale } : {}),
|
|
459
|
+
traversalContext,
|
|
460
|
+
});
|
|
461
|
+
break;
|
|
462
|
+
case 'loop.iteration':
|
|
463
|
+
this.onLoop({
|
|
464
|
+
target: e.target,
|
|
465
|
+
iteration: e.iteration,
|
|
466
|
+
traversalContext,
|
|
467
|
+
});
|
|
468
|
+
break;
|
|
469
|
+
case 'llm.start': {
|
|
470
|
+
// Synthesize a typed event matching the dispatcher payload
|
|
471
|
+
// shape so `ingestTypedEvent` produces the same react step.
|
|
472
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
473
|
+
this.ingestTypedEvent({
|
|
474
|
+
type: 'agentfootprint.stream.llm_start',
|
|
475
|
+
payload: {
|
|
476
|
+
provider: e.provider,
|
|
477
|
+
model: e.model,
|
|
478
|
+
...(e.systemPromptChars !== undefined
|
|
479
|
+
? { systemPromptChars: e.systemPromptChars }
|
|
480
|
+
: {}),
|
|
481
|
+
...(e.messagesCount !== undefined ? { messagesCount: e.messagesCount } : {}),
|
|
482
|
+
...(e.toolsCount !== undefined ? { toolsCount: e.toolsCount } : {}),
|
|
483
|
+
},
|
|
484
|
+
meta: {
|
|
485
|
+
wallClockMs: e.ts,
|
|
486
|
+
runOffsetMs: 0,
|
|
487
|
+
runtimeStageId: e.runtimeStageId,
|
|
488
|
+
// Strip the ROOT_SUBFLOW_ID prefix for the typed-event
|
|
489
|
+
// convention (typed events use the inner path, BoundaryRecorder
|
|
490
|
+
// adds the root prefix on ingest).
|
|
491
|
+
subflowPath: e.subflowPath.slice(1),
|
|
492
|
+
compositionPath: [],
|
|
493
|
+
runId: 'replay',
|
|
494
|
+
},
|
|
495
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
496
|
+
});
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
case 'llm.end': {
|
|
500
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
501
|
+
this.ingestTypedEvent({
|
|
502
|
+
type: 'agentfootprint.stream.llm_end',
|
|
503
|
+
payload: {
|
|
504
|
+
content: e.content,
|
|
505
|
+
toolCallCount: e.toolCallCount,
|
|
506
|
+
usage: e.usage,
|
|
507
|
+
...(e.stopReason ? { stopReason: e.stopReason } : {}),
|
|
508
|
+
},
|
|
509
|
+
meta: {
|
|
510
|
+
wallClockMs: e.ts,
|
|
511
|
+
runOffsetMs: 0,
|
|
512
|
+
runtimeStageId: e.runtimeStageId,
|
|
513
|
+
subflowPath: e.subflowPath.slice(1),
|
|
514
|
+
compositionPath: [],
|
|
515
|
+
runId: 'replay',
|
|
516
|
+
},
|
|
517
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
518
|
+
});
|
|
519
|
+
break;
|
|
520
|
+
}
|
|
521
|
+
// tool.start / tool.end / context.injected — not currently used
|
|
522
|
+
// by the projection (tools are paired with the next llm.start;
|
|
523
|
+
// context injections decorate steps but don't add new ones).
|
|
524
|
+
default:
|
|
525
|
+
break;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
// ── Query API ─────────────────────────────────────────────────
|
|
529
|
+
/**
|
|
530
|
+
* Read-only query — returns the already-built step list filtered to
|
|
531
|
+
* `drillPath` scope. O(1) per call when scope is empty; O(N) filter
|
|
532
|
+
* otherwise. Composition-vs-leaf root filter is applied so the
|
|
533
|
+
* slider semantics match the user's mental model:
|
|
534
|
+
*
|
|
535
|
+
* - **Leaf root** (single Agent / LLMCall): show `react` steps only.
|
|
536
|
+
* - **Composition root** (Sequence / Parallel / Conditional / Loop):
|
|
537
|
+
* show composition steps; hide intra-leaf `react` steps.
|
|
538
|
+
*
|
|
539
|
+
* Drill-down filters by `anchor.subflowPath` prefix and re-applies
|
|
540
|
+
* the leaf-vs-composition rule for the drilled scope.
|
|
541
|
+
*/
|
|
542
|
+
getSteps(drillPath) {
|
|
543
|
+
const all = this.store.getAll();
|
|
544
|
+
const path = drillPath ?? [];
|
|
545
|
+
// Leaf-vs-composition filter — delegated to RootInferrer.
|
|
546
|
+
const isLeafRoot = this.rootInferrer.isLeafRoot();
|
|
547
|
+
const kindFiltered = isLeafRoot
|
|
548
|
+
? all.filter((s) => s.kind === 'react')
|
|
549
|
+
: all.filter((s) => s.kind !== 'react');
|
|
550
|
+
if (path.length === 0) {
|
|
551
|
+
return reseq(kindFiltered);
|
|
552
|
+
}
|
|
553
|
+
return reseq(kindFiltered.filter((s) => isPathPrefix(path, s.anchor.subflowPath)));
|
|
554
|
+
}
|
|
555
|
+
// ── Helpers ───────────────────────────────────────────────────
|
|
556
|
+
/** Flush any deferred answer-candidate from the buffer. Called by
|
|
557
|
+
* `onRunEnd` so a single `answers` step appears for runs that end
|
|
558
|
+
* on a leaf exit (no further leaf entries followed). */
|
|
559
|
+
flushCandidateAnswer() {
|
|
560
|
+
const c = this.answerBuffer.flush();
|
|
561
|
+
if (!c)
|
|
562
|
+
return;
|
|
563
|
+
this.push({
|
|
564
|
+
seq: this.entryCount,
|
|
565
|
+
kind: 'sequential',
|
|
566
|
+
transitions: [{ from: c.frame.subflowId, to: ACTOR_USER, via: 'next', label: 'answers' }],
|
|
567
|
+
anchor: {
|
|
568
|
+
runtimeStageId: c.runtimeStageId,
|
|
569
|
+
subflowPath: c.frame.subflowPath,
|
|
570
|
+
},
|
|
571
|
+
label: 'answers',
|
|
572
|
+
tsMs: c.tsMs,
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
// ─── Helpers ─────────────────────────────────────────────────────
|
|
577
|
+
function parsePrimitiveKind(description) {
|
|
578
|
+
if (!description)
|
|
579
|
+
return undefined;
|
|
580
|
+
const colon = description.indexOf(':');
|
|
581
|
+
if (colon < 0)
|
|
582
|
+
return undefined;
|
|
583
|
+
const prefix = description.slice(0, colon).trim();
|
|
584
|
+
return prefix.length > 0 ? prefix : undefined;
|
|
585
|
+
}
|
|
586
|
+
const AGENT_INTERNAL_STAGES = new Set(['route', 'tool-calls', 'final', 'merge']);
|
|
587
|
+
function isAgentInternalStageId(localStageId) {
|
|
588
|
+
return AGENT_INTERNAL_STAGES.has(localStageId);
|
|
589
|
+
}
|
|
590
|
+
/** Renumber `seq` after filtering so consumers see contiguous indices. */
|
|
591
|
+
function reseq(steps) {
|
|
592
|
+
return steps.map((s, i) => (s.seq === i ? s : { ...s, seq: i }));
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Compatibility shim for snapshot-from-events use cases (replay,
|
|
596
|
+
* post-hoc analysis, tests). For LIVE use, prefer attaching a
|
|
597
|
+
* `RunStepRecorder` directly via `runner.attach(rec)` —
|
|
598
|
+
* `buildRunSteps(events)` constructs a fresh recorder, replays the
|
|
599
|
+
* events through its handlers, and returns the resulting entries.
|
|
600
|
+
*
|
|
601
|
+
* @deprecated Prefer `runStepRecorder()` + `runner.attach(rec)` for
|
|
602
|
+
* live consumers. This shim remains for offline / testing
|
|
603
|
+
* scenarios where only a recorded event list is available.
|
|
604
|
+
*/
|
|
605
|
+
export function buildRunSteps(source, options = {}) {
|
|
606
|
+
const events = Array.isArray(source)
|
|
607
|
+
? source
|
|
608
|
+
: source.getEvents();
|
|
609
|
+
const rec = new RunStepRecorder();
|
|
610
|
+
// Coalesce fork.branch bursts before replay. BoundaryRecorder emits
|
|
611
|
+
// N fork.branch events per fork (all sharing parent+ts); the
|
|
612
|
+
// recorder expects ONE `onFork` call carrying all children. We pass
|
|
613
|
+
// through the first occurrence of each (parent, ts) pair as a single
|
|
614
|
+
// synthetic event with all children, and drop the rest.
|
|
615
|
+
const forkSeen = new Set();
|
|
616
|
+
for (let i = 0; i < events.length; i++) {
|
|
617
|
+
const e = events[i];
|
|
618
|
+
if (e.type === 'fork.branch') {
|
|
619
|
+
const key = `${e.parentSubflowId}@${e.ts}`;
|
|
620
|
+
if (forkSeen.has(key))
|
|
621
|
+
continue;
|
|
622
|
+
forkSeen.add(key);
|
|
623
|
+
const children = [];
|
|
624
|
+
for (let j = i; j < events.length; j++) {
|
|
625
|
+
const f = events[j];
|
|
626
|
+
if (f.type === 'fork.branch' && f.parentSubflowId === e.parentSubflowId && f.ts === e.ts) {
|
|
627
|
+
children.push(f.childName);
|
|
628
|
+
}
|
|
629
|
+
else if (f.ts > e.ts) {
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
rec.onFork({
|
|
634
|
+
parent: e.parentSubflowId,
|
|
635
|
+
children,
|
|
636
|
+
traversalContext: {
|
|
637
|
+
runId: 'replay',
|
|
638
|
+
stageId: lastSegment(e.subflowPath.join('/') || ROOT_SUBFLOW_ID),
|
|
639
|
+
runtimeStageId: e.runtimeStageId,
|
|
640
|
+
stageName: lastSegment(e.subflowPath.join('/') || ROOT_SUBFLOW_ID),
|
|
641
|
+
depth: e.depth,
|
|
642
|
+
},
|
|
643
|
+
});
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
rec.ingestDomainEvent(e);
|
|
647
|
+
}
|
|
648
|
+
return [...rec.getSteps(options.drillPath)];
|
|
649
|
+
}
|
|
650
|
+
// Touch unused imports defensively for tree-shaking.
|
|
651
|
+
void ROOT_RUNTIME_STAGE_ID;
|
|
652
|
+
//# sourceMappingURL=RunStepRecorder.js.map
|