footprintjs 4.17.2 → 6.0.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/CLAUDE.md +71 -9
- package/dist/advanced.js +2 -3
- package/dist/esm/advanced.js +2 -2
- package/dist/esm/index.js +3 -3
- package/dist/esm/lib/builder/FlowChartBuilder.js +359 -70
- package/dist/esm/lib/builder/index.js +1 -1
- package/dist/esm/lib/builder/structure/StructureRecorder.js +158 -0
- package/dist/esm/lib/builder/structure/StructureRecorderDispatcher.js +171 -0
- package/dist/esm/lib/builder/types.js +1 -1
- package/dist/esm/lib/decide/decide.js +9 -9
- package/dist/esm/lib/decide/evidence.js +2 -2
- package/dist/esm/lib/engine/errors/errorInfo.js +4 -3
- package/dist/esm/lib/engine/handlers/ChildrenExecutor.js +11 -2
- package/dist/esm/lib/engine/handlers/DeciderHandler.js +5 -7
- package/dist/esm/lib/engine/handlers/RuntimeStructureManager.js +2 -2
- package/dist/esm/lib/engine/handlers/SelectorHandler.js +9 -7
- package/dist/esm/lib/engine/handlers/SubflowExecutor.js +7 -1
- package/dist/esm/lib/engine/handlers/index.js +1 -3
- package/dist/esm/lib/engine/handlers/types.js +2 -2
- package/dist/esm/lib/engine/narrative/CombinedNarrativeRecorder.js +14 -4
- package/dist/esm/lib/engine/narrative/FlowRecorderDispatcher.js +5 -5
- package/dist/esm/lib/engine/narrative/NarrativeFlowRecorder.js +8 -1
- package/dist/esm/lib/engine/narrative/types.js +1 -1
- package/dist/esm/lib/engine/runtimeStageId.js +64 -2
- package/dist/esm/lib/engine/traversal/FlowchartTraverser.js +30 -56
- package/dist/esm/lib/engine/types.js +1 -1
- package/dist/esm/lib/engine/walkSubflowSpec.js +144 -0
- package/dist/esm/lib/memory/StageContext.js +3 -3
- package/dist/esm/lib/memory/backtrack.js +1 -1
- package/dist/esm/lib/reactive/createTypedScope.js +5 -5
- package/dist/esm/lib/reactive/types.js +7 -7
- package/dist/esm/lib/recorder/BoundaryStateStore.js +167 -0
- package/dist/esm/lib/recorder/BoundaryStateTracker.js +4 -4
- package/dist/esm/lib/recorder/CombinedRecorder.js +9 -9
- package/dist/esm/lib/recorder/CommitRangeIndex.js +207 -0
- package/dist/esm/lib/recorder/CompositeRecorder.js +6 -6
- package/dist/esm/lib/recorder/EmitRecorder.js +3 -3
- package/dist/esm/lib/recorder/InOutRecorder.js +1 -1
- package/dist/esm/lib/recorder/KeyedStore.js +113 -0
- package/dist/esm/lib/recorder/QualityRecorder.js +2 -2
- package/dist/esm/lib/recorder/SequenceRecorder.js +2 -2
- package/dist/esm/lib/recorder/SequenceStore.js +195 -0
- package/dist/esm/lib/recorder/TopologyRecorder.js +1 -1
- package/dist/esm/lib/recorder/index.js +13 -3
- package/dist/esm/lib/runner/ExecutionRuntime.js +1 -1
- package/dist/esm/lib/runner/FlowChartExecutor.js +75 -44
- package/dist/esm/lib/runner/RunContext.js +2 -2
- package/dist/esm/lib/runner/RunnableChart.js +1 -1
- package/dist/esm/lib/runner/getSubtreeSnapshot.js +3 -2
- package/dist/esm/lib/runner/runId.js +65 -0
- package/dist/esm/lib/scope/ScopeFacade.js +6 -6
- package/dist/esm/lib/scope/index.js +1 -1
- package/dist/esm/lib/scope/recorders/DebugRecorder.js +4 -4
- package/dist/esm/lib/scope/recorders/MetricRecorder.js +2 -2
- package/dist/esm/lib/scope/recorders/index.js +1 -1
- package/dist/esm/lib/scope/types.js +1 -1
- package/dist/esm/recorders.js +1 -1
- package/dist/esm/trace.js +15 -2
- package/dist/index.js +3 -3
- package/dist/lib/builder/FlowChartBuilder.js +359 -70
- package/dist/lib/builder/index.js +1 -1
- package/dist/lib/builder/structure/StructureRecorder.js +159 -0
- package/dist/lib/builder/structure/StructureRecorderDispatcher.js +175 -0
- package/dist/lib/builder/types.js +1 -1
- package/dist/lib/decide/decide.js +9 -9
- package/dist/lib/decide/evidence.js +2 -2
- package/dist/lib/engine/errors/errorInfo.js +4 -3
- package/dist/lib/engine/handlers/ChildrenExecutor.js +11 -2
- package/dist/lib/engine/handlers/DeciderHandler.js +5 -7
- package/dist/lib/engine/handlers/RuntimeStructureManager.js +2 -2
- package/dist/lib/engine/handlers/SelectorHandler.js +9 -7
- package/dist/lib/engine/handlers/SubflowExecutor.js +7 -1
- package/dist/lib/engine/handlers/index.js +2 -5
- package/dist/lib/engine/handlers/types.js +2 -2
- package/dist/lib/engine/narrative/CombinedNarrativeRecorder.js +14 -4
- package/dist/lib/engine/narrative/FlowRecorderDispatcher.js +5 -5
- package/dist/lib/engine/narrative/NarrativeFlowRecorder.js +8 -1
- package/dist/lib/engine/narrative/types.js +1 -1
- package/dist/lib/engine/runtimeStageId.js +66 -3
- package/dist/lib/engine/traversal/FlowchartTraverser.js +30 -56
- package/dist/lib/engine/types.js +1 -1
- package/dist/lib/engine/walkSubflowSpec.js +148 -0
- package/dist/lib/memory/StageContext.js +3 -3
- package/dist/lib/memory/backtrack.js +1 -1
- package/dist/lib/reactive/createTypedScope.js +5 -5
- package/dist/lib/reactive/types.js +7 -7
- package/dist/lib/recorder/BoundaryStateStore.js +171 -0
- package/dist/lib/recorder/BoundaryStateTracker.js +4 -4
- package/dist/lib/recorder/CombinedRecorder.js +9 -9
- package/dist/lib/recorder/CommitRangeIndex.js +211 -0
- package/dist/lib/recorder/CompositeRecorder.js +6 -6
- package/dist/lib/recorder/EmitRecorder.js +3 -3
- package/dist/lib/recorder/InOutRecorder.js +1 -1
- package/dist/lib/recorder/KeyedStore.js +117 -0
- package/dist/lib/recorder/QualityRecorder.js +2 -2
- package/dist/lib/recorder/SequenceRecorder.js +2 -2
- package/dist/lib/recorder/SequenceStore.js +199 -0
- package/dist/lib/recorder/TopologyRecorder.js +1 -1
- package/dist/lib/recorder/index.js +19 -6
- package/dist/lib/runner/ExecutionRuntime.js +1 -1
- package/dist/lib/runner/FlowChartExecutor.js +75 -44
- package/dist/lib/runner/RunContext.js +2 -2
- package/dist/lib/runner/RunnableChart.js +1 -1
- package/dist/lib/runner/getSubtreeSnapshot.js +3 -2
- package/dist/lib/runner/runId.js +70 -0
- package/dist/lib/scope/ScopeFacade.js +6 -6
- package/dist/lib/scope/index.js +1 -1
- package/dist/lib/scope/recorders/DebugRecorder.js +4 -4
- package/dist/lib/scope/recorders/MetricRecorder.js +2 -2
- package/dist/lib/scope/recorders/index.js +1 -1
- package/dist/lib/scope/types.js +1 -1
- package/dist/recorders.js +1 -1
- package/dist/trace.js +21 -2
- package/dist/types/advanced.d.ts +4 -4
- package/dist/types/index.d.ts +6 -4
- package/dist/types/lib/builder/FlowChartBuilder.d.ts +110 -14
- package/dist/types/lib/builder/index.d.ts +2 -1
- package/dist/types/lib/builder/structure/StructureRecorder.d.ts +349 -0
- package/dist/types/lib/builder/structure/StructureRecorderDispatcher.d.ts +77 -0
- package/dist/types/lib/builder/types.d.ts +20 -6
- package/dist/types/lib/decide/evidence.d.ts +3 -3
- package/dist/types/lib/engine/errors/errorInfo.d.ts +3 -2
- package/dist/types/lib/engine/handlers/DeciderHandler.d.ts +3 -3
- package/dist/types/lib/engine/handlers/RuntimeStructureManager.d.ts +1 -1
- package/dist/types/lib/engine/handlers/SelectorHandler.d.ts +2 -2
- package/dist/types/lib/engine/handlers/index.d.ts +1 -2
- package/dist/types/lib/engine/handlers/types.d.ts +2 -9
- package/dist/types/lib/engine/narrative/CombinedNarrativeRecorder.d.ts +1 -1
- package/dist/types/lib/engine/narrative/FlowRecorderDispatcher.d.ts +4 -4
- package/dist/types/lib/engine/narrative/types.d.ts +43 -4
- package/dist/types/lib/engine/runtimeStageId.d.ts +57 -1
- package/dist/types/lib/engine/traversal/FlowchartTraverser.d.ts +12 -6
- package/dist/types/lib/engine/types.d.ts +0 -26
- package/dist/types/lib/engine/walkSubflowSpec.d.ts +95 -0
- package/dist/types/lib/memory/StageContext.d.ts +2 -2
- package/dist/types/lib/memory/backtrack.d.ts +1 -1
- package/dist/types/lib/reactive/types.d.ts +7 -7
- package/dist/types/lib/recorder/BoundaryStateStore.d.ts +115 -0
- package/dist/types/lib/recorder/BoundaryStateTracker.d.ts +3 -3
- package/dist/types/lib/recorder/CombinedRecorder.d.ts +11 -11
- package/dist/types/lib/recorder/CommitRangeIndex.d.ts +147 -0
- package/dist/types/lib/recorder/CompositeRecorder.d.ts +8 -8
- package/dist/types/lib/recorder/EmitRecorder.d.ts +2 -2
- package/dist/types/lib/recorder/InOutRecorder.d.ts +1 -1
- package/dist/types/lib/recorder/KeyedStore.d.ts +70 -0
- package/dist/types/lib/recorder/QualityRecorder.d.ts +4 -4
- package/dist/types/lib/recorder/SequenceRecorder.d.ts +1 -1
- package/dist/types/lib/recorder/SequenceStore.d.ts +120 -0
- package/dist/types/lib/recorder/TopologyRecorder.d.ts +1 -1
- package/dist/types/lib/recorder/index.d.ts +5 -2
- package/dist/types/lib/runner/ExecutionRuntime.d.ts +1 -1
- package/dist/types/lib/runner/FlowChartExecutor.d.ts +40 -30
- package/dist/types/lib/runner/RunContext.d.ts +2 -2
- package/dist/types/lib/runner/RunnableChart.d.ts +2 -2
- package/dist/types/lib/runner/runId.d.ts +45 -0
- package/dist/types/lib/scope/ScopeFacade.d.ts +4 -4
- package/dist/types/lib/scope/index.d.ts +1 -1
- package/dist/types/lib/scope/recorders/DebugRecorder.d.ts +4 -4
- package/dist/types/lib/scope/recorders/MetricRecorder.d.ts +4 -4
- package/dist/types/lib/scope/recorders/index.d.ts +1 -1
- package/dist/types/lib/scope/types.d.ts +1 -1
- package/dist/types/recorders.d.ts +1 -1
- package/dist/types/trace.d.ts +8 -1
- package/package.json +1 -1
- package/dist/esm/lib/engine/handlers/ExtractorRunner.js +0 -122
- package/dist/lib/engine/handlers/ExtractorRunner.js +0 -126
- package/dist/types/lib/engine/handlers/ExtractorRunner.d.ts +0 -41
|
@@ -13,9 +13,25 @@ import type { StructuredErrorInfo } from '../errors/errorInfo.js';
|
|
|
13
13
|
* Uses Null Object pattern: NullControlFlowNarrativeGenerator satisfies this
|
|
14
14
|
* interface with empty methods for zero-cost disabled path.
|
|
15
15
|
*/
|
|
16
|
+
/**
|
|
17
|
+
* The kind of stage that completed. Lets consumers route uniform
|
|
18
|
+
* "did this stage execute" handling without a side-table lookup into
|
|
19
|
+
* the chart spec. Required on every `onStageExecuted` event since
|
|
20
|
+
* proposal #003 unified the event to fire for ALL stage kinds.
|
|
21
|
+
*/
|
|
22
|
+
export type StageType = 'linear' | 'decider' | 'fork' | 'selector' | 'subflow-mount';
|
|
16
23
|
export interface IControlFlowNarrative {
|
|
17
|
-
/**
|
|
18
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Called when a stage completes its main work. Fires for ALL stage
|
|
26
|
+
* kinds (`'linear'`, `'decider'`, `'fork'`, `'selector'`, `'subflow-mount'`)
|
|
27
|
+
* AFTER the corresponding specialized event (`onDecision`, `onFork`,
|
|
28
|
+
* `onSelected`, `onSubflowEntry`). For linear stages, fires after
|
|
29
|
+
* the stage function returns.
|
|
30
|
+
*
|
|
31
|
+
* Consumers tracking "did this stage run?" use this event uniformly
|
|
32
|
+
* and switch on `stageType` for kind-specific work.
|
|
33
|
+
*/
|
|
34
|
+
onStageExecuted(stageName: string, description: string | undefined, traversalContext: TraversalContext | undefined, stageType: StageType): void;
|
|
19
35
|
/** Called on linear continuation from one stage to the next. */
|
|
20
36
|
onNext(fromStage: string, toStage: string, description?: string, traversalContext?: TraversalContext): void;
|
|
21
37
|
/** Called when a decider selects a branch. Most valuable for LLM context. */
|
|
@@ -73,9 +89,26 @@ export interface IControlFlowNarrative {
|
|
|
73
89
|
* Like OpenTelemetry's span context: stageId + parentStageId form a tree.
|
|
74
90
|
*/
|
|
75
91
|
export interface TraversalContext {
|
|
92
|
+
/**
|
|
93
|
+
* Per-`executor.run()` identifier. Generated once at the start of every
|
|
94
|
+
* `run()` (and again on `resume()`); shared by every event of that run;
|
|
95
|
+
* differs across consecutive runs of the same executor.
|
|
96
|
+
*
|
|
97
|
+
* Format: `${Date.now()}-${counter}` — sortable lexicographically (==
|
|
98
|
+
* chronologically for runs > 1ms apart). Process-local — for cross-
|
|
99
|
+
* process correlation use `getEnv().traceId` (consumer-supplied).
|
|
100
|
+
*
|
|
101
|
+
* Recorders that accumulate state across runs (fork bookkeeping,
|
|
102
|
+
* sibling-handoff state, etc.) detect "new run" via
|
|
103
|
+
* `event.traversalContext.runId !== this.lastRunId` and reset
|
|
104
|
+
* transient bookkeeping. Recorders that don't care about scoping
|
|
105
|
+
* ignore the field.
|
|
106
|
+
*/
|
|
107
|
+
readonly runId: string;
|
|
76
108
|
/** Stable stage identifier from the builder (matches spec node id). */
|
|
77
109
|
readonly stageId: string;
|
|
78
|
-
/** Unique per-execution-step identifier. Format: [subflowPath/]stageId#executionIndex
|
|
110
|
+
/** Unique per-execution-step identifier. Format: [subflowPath/]stageId#executionIndex.
|
|
111
|
+
* Counter resets per executor — combine with `runId` for globally unique step keys. */
|
|
79
112
|
readonly runtimeStageId: string;
|
|
80
113
|
/** Human-readable stage name. */
|
|
81
114
|
readonly stageName: string;
|
|
@@ -98,6 +131,12 @@ export interface FlowStageEvent {
|
|
|
98
131
|
description?: string;
|
|
99
132
|
/** Traversal context from the engine — read-only, set by traverser. */
|
|
100
133
|
traversalContext?: TraversalContext;
|
|
134
|
+
/**
|
|
135
|
+
* Which kind of stage completed. The engine fires `onStageExecuted`
|
|
136
|
+
* uniformly for every stage kind (proposal #003); consumers route by
|
|
137
|
+
* `stageType` without a chart-spec lookup.
|
|
138
|
+
*/
|
|
139
|
+
stageType: StageType;
|
|
101
140
|
}
|
|
102
141
|
/** Event passed to FlowRecorder.onNext. */
|
|
103
142
|
export interface FlowNextEvent {
|
|
@@ -228,7 +267,7 @@ export interface FlowRunEvent {
|
|
|
228
267
|
/**
|
|
229
268
|
* FlowRecorder — Pluggable observer for control flow events.
|
|
230
269
|
*
|
|
231
|
-
* Mirrors the scope-level
|
|
270
|
+
* Mirrors the scope-level ScopeRecorder pattern for the engine layer.
|
|
232
271
|
* All methods are optional — implement only the hooks you need.
|
|
233
272
|
* Recorders are invoked synchronously in attachment order.
|
|
234
273
|
* If a recorder throws, the error is caught and swallowed; execution continues.
|
|
@@ -14,6 +14,15 @@
|
|
|
14
14
|
* - Human-readable ('sf-tools/execute-tool-calls#8')
|
|
15
15
|
* - Parseable (split on '#' for stageId and index, split stageId on '/' for subflow path)
|
|
16
16
|
*
|
|
17
|
+
* Naming-collision warning
|
|
18
|
+
* ────────────────────────
|
|
19
|
+
* The parsed-output `.stageId` field below is the LOCAL form (segment
|
|
20
|
+
* after the last '/'). This is NOT the same as `spec.id` / `node.id`
|
|
21
|
+
* for subflow-nested stages, which carry the FULL prefixed form
|
|
22
|
+
* (`'sf-tools/execute-tool-calls'`). To compare safely, use
|
|
23
|
+
* `splitStageId(spec.id)` to decompose the prefixed form the same
|
|
24
|
+
* way `parseRuntimeStageId` decomposes a runtimeStageId.
|
|
25
|
+
*
|
|
17
26
|
* @example
|
|
18
27
|
* ```
|
|
19
28
|
* buildRuntimeStageId('call-llm', 5) // 'call-llm#5'
|
|
@@ -29,12 +38,59 @@
|
|
|
29
38
|
* consumers constructing IDs from parsed components (round-trip via parseRuntimeStageId).
|
|
30
39
|
*/
|
|
31
40
|
export declare function buildRuntimeStageId(stageId: string, executionIndex: number, subflowPath?: string): string;
|
|
32
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* Parse a runtimeStageId into its components.
|
|
43
|
+
*
|
|
44
|
+
* IMPORTANT — naming collision: the returned `stageId` is the LOCAL
|
|
45
|
+
* form (the segment between the last '/' and the '#'). This is NOT
|
|
46
|
+
* the same as `spec.id` or `node.id` for subflow-nested stages,
|
|
47
|
+
* which contain the FULL prefixed form.
|
|
48
|
+
*
|
|
49
|
+
* parseRuntimeStageId('sf-tools/execute-tool-calls#8').stageId
|
|
50
|
+
* // → 'execute-tool-calls' (LOCAL)
|
|
51
|
+
*
|
|
52
|
+
* node.id // (post-mount, in a spec that contains subflows)
|
|
53
|
+
* // → 'sf-tools/execute-tool-calls' (FULL prefixed)
|
|
54
|
+
*
|
|
55
|
+
* To compare these two safely, use `splitStageId(node.id)` to get
|
|
56
|
+
* the local form, OR reconstruct the full form via
|
|
57
|
+
* `(subflowPath ? subflowPath + '/' : '') + stageId`.
|
|
58
|
+
*/
|
|
33
59
|
export declare function parseRuntimeStageId(runtimeStageId: string): {
|
|
34
60
|
stageId: string;
|
|
35
61
|
executionIndex: number;
|
|
36
62
|
subflowPath: string | undefined;
|
|
37
63
|
};
|
|
64
|
+
/**
|
|
65
|
+
* Decompose a (possibly prefixed) stage id into its components.
|
|
66
|
+
*
|
|
67
|
+
* Use this when you have an id WITHOUT the `#N` execution suffix and
|
|
68
|
+
* need the local stage name and/or the subflow path. Common sources
|
|
69
|
+
* of such ids:
|
|
70
|
+
* - `spec.id` (post-mount the id includes any subflow prefix)
|
|
71
|
+
* - `CommitBundle.stageId` (post-mount id)
|
|
72
|
+
* - `node.id` from xyflow nodes built off the spec
|
|
73
|
+
* - the segment of `runtimeStageId` BEFORE the `#` (use
|
|
74
|
+
* `parseRuntimeStageId` directly for full runtimeStageId strings)
|
|
75
|
+
*
|
|
76
|
+
* Mirrors the decomposition `parseRuntimeStageId` performs on the
|
|
77
|
+
* stageId portion of a runtimeStageId, so the two helpers stay in
|
|
78
|
+
* lockstep on naming and behavior.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* splitStageId('sf-tools/execute-tool-calls')
|
|
82
|
+
* // → { localStageId: 'execute-tool-calls', subflowPath: 'sf-tools' }
|
|
83
|
+
*
|
|
84
|
+
* splitStageId('execute-tool-calls')
|
|
85
|
+
* // → { localStageId: 'execute-tool-calls', subflowPath: undefined }
|
|
86
|
+
*
|
|
87
|
+
* splitStageId('sf-outer/sf-inner/validate')
|
|
88
|
+
* // → { localStageId: 'validate', subflowPath: 'sf-outer/sf-inner' }
|
|
89
|
+
*/
|
|
90
|
+
export declare function splitStageId(prefixedStageId: string): {
|
|
91
|
+
localStageId: string;
|
|
92
|
+
subflowPath: string | undefined;
|
|
93
|
+
};
|
|
38
94
|
/**
|
|
39
95
|
* Shared mutable counter for execution index.
|
|
40
96
|
* Passed by reference to child traversers (subflows) so they
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
import type { ScopeProtectionMode } from '../../scope/protection/types.js';
|
|
22
22
|
import { FlowRecorderDispatcher } from '../narrative/FlowRecorderDispatcher.js';
|
|
23
23
|
import type { FlowRecorder, IControlFlowNarrative } from '../narrative/types.js';
|
|
24
|
-
import type {
|
|
24
|
+
import type { IExecutionRuntime, ILogger, ScopeFactory, SerializedPipelineStructure, StageFunction, StageNode, StreamHandlers, SubflowResult, TraversalResult } from '../types.js';
|
|
25
25
|
export interface TraverserOptions<TOut = any, TScope = any> {
|
|
26
26
|
root: StageNode<TOut, TScope>;
|
|
27
27
|
stageMap: Map<string, StageFunction<TOut, TScope>>;
|
|
@@ -32,12 +32,10 @@ export interface TraverserOptions<TOut = any, TScope = any> {
|
|
|
32
32
|
executionEnv?: import('../../engine/types').ExecutionEnv;
|
|
33
33
|
throttlingErrorChecker?: (error: unknown) => boolean;
|
|
34
34
|
streamHandlers?: StreamHandlers;
|
|
35
|
-
extractor?: TraversalExtractor;
|
|
36
35
|
scopeProtectionMode?: ScopeProtectionMode;
|
|
37
36
|
subflows?: Record<string, {
|
|
38
37
|
root: StageNode<TOut, TScope>;
|
|
39
38
|
}>;
|
|
40
|
-
enrichSnapshots?: boolean;
|
|
41
39
|
narrativeEnabled?: boolean;
|
|
42
40
|
buildTimeStructure?: SerializedPipelineStructure;
|
|
43
41
|
logger: ILogger;
|
|
@@ -71,6 +69,14 @@ export interface TraverserOptions<TOut = any, TScope = any> {
|
|
|
71
69
|
* the inputMapper. Undefined on normal `run()` paths.
|
|
72
70
|
*/
|
|
73
71
|
subflowStatesForResume?: Record<string, Record<string, unknown>>;
|
|
72
|
+
/**
|
|
73
|
+
* Per-`executor.run()` identifier. Threaded into every TraversalContext
|
|
74
|
+
* this traverser produces so recorders can scope state to a single run.
|
|
75
|
+
* Subflow traversers inherit the parent's runId (the subflow is part of
|
|
76
|
+
* the same run from the consumer's POV). Required field — every event
|
|
77
|
+
* needs it. See `runner/runId.ts`.
|
|
78
|
+
*/
|
|
79
|
+
runId: string;
|
|
74
80
|
}
|
|
75
81
|
export declare class FlowchartTraverser<TOut = any, TScope = any> {
|
|
76
82
|
private readonly root;
|
|
@@ -84,6 +90,9 @@ export declare class FlowchartTraverser<TOut = any, TScope = any> {
|
|
|
84
90
|
* top-level traversal so consumers (e.g. `InOutRecorder`) can bracket
|
|
85
91
|
* the run with the same payload shape that subflows already have. */
|
|
86
92
|
private readonly readOnlyContext?;
|
|
93
|
+
/** Per-`executor.run()` identifier. Stamped onto every TraversalContext.
|
|
94
|
+
* Inherited by subflow traversers so all events of one run share one runId. */
|
|
95
|
+
private readonly runId;
|
|
87
96
|
private readonly nodeResolver;
|
|
88
97
|
private readonly childrenExecutor;
|
|
89
98
|
private readonly subflowExecutor;
|
|
@@ -92,7 +101,6 @@ export declare class FlowchartTraverser<TOut = any, TScope = any> {
|
|
|
92
101
|
private readonly deciderHandler;
|
|
93
102
|
private readonly selectorHandler;
|
|
94
103
|
private readonly structureManager;
|
|
95
|
-
private readonly extractorRunner;
|
|
96
104
|
private readonly narrativeGenerator;
|
|
97
105
|
private readonly flowRecorderDispatcher;
|
|
98
106
|
private subflowResults;
|
|
@@ -183,8 +191,6 @@ export declare class FlowchartTraverser<TOut = any, TScope = any> {
|
|
|
183
191
|
getBranchIds(): string[];
|
|
184
192
|
getRuntimeRoot(): StageNode;
|
|
185
193
|
getSubflowResults(): Map<string, SubflowResult>;
|
|
186
|
-
getExtractedResults<TResult = unknown>(): Map<string, TResult>;
|
|
187
|
-
getExtractorErrors(): ExtractorError[];
|
|
188
194
|
getNarrative(): string[];
|
|
189
195
|
/** Returns the FlowRecorderDispatcher, or undefined if narrative is disabled. */
|
|
190
196
|
getFlowRecorderDispatcher(): FlowRecorderDispatcher | undefined;
|
|
@@ -323,32 +323,6 @@ export interface RuntimeStructureMetadata {
|
|
|
323
323
|
isLoopReference?: boolean;
|
|
324
324
|
streamId?: string;
|
|
325
325
|
}
|
|
326
|
-
export interface StageSnapshot<TOut = any, TScope = any> {
|
|
327
|
-
node: StageNode<TOut, TScope>;
|
|
328
|
-
context: StageContext;
|
|
329
|
-
stepNumber: number;
|
|
330
|
-
structureMetadata: RuntimeStructureMetadata;
|
|
331
|
-
scopeState?: Record<string, unknown>;
|
|
332
|
-
debugInfo?: {
|
|
333
|
-
logs: Record<string, unknown>;
|
|
334
|
-
errors: Record<string, unknown>;
|
|
335
|
-
metrics: Record<string, unknown>;
|
|
336
|
-
evals: Record<string, unknown>;
|
|
337
|
-
flowMessages?: FlowMessage[];
|
|
338
|
-
};
|
|
339
|
-
stageOutput?: unknown;
|
|
340
|
-
errorInfo?: {
|
|
341
|
-
type: string;
|
|
342
|
-
message: string;
|
|
343
|
-
};
|
|
344
|
-
historyIndex?: number;
|
|
345
|
-
}
|
|
346
|
-
export type TraversalExtractor<TResult = unknown> = (snapshot: StageSnapshot) => TResult | undefined | null;
|
|
347
|
-
export interface ExtractorError {
|
|
348
|
-
stagePath: string;
|
|
349
|
-
message: string;
|
|
350
|
-
error: unknown;
|
|
351
|
-
}
|
|
352
326
|
export type NodeResultType = {
|
|
353
327
|
id: string;
|
|
354
328
|
result: unknown;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* walkSubflowSpec — yield the structural shape of a subflow spec as a
|
|
3
|
+
* flat ordered stream of items, with `subflowPath` already composed
|
|
4
|
+
* for nested subflows.
|
|
5
|
+
*
|
|
6
|
+
* This is the public contract for traversing the structure delivered
|
|
7
|
+
* via `StructureSubflowMountedEvent.subflowSpec`. Item shapes mirror
|
|
8
|
+
* the corresponding Structure event payloads so consumers can route
|
|
9
|
+
* walker items through the same handlers they use for live events.
|
|
10
|
+
*
|
|
11
|
+
* Walker contract (LOCKED):
|
|
12
|
+
* 1. AUTO-RECURSE by default into nested subflows, with composed
|
|
13
|
+
* paths (`parent/child/...`). Pass `{ recurse: false }` to walk
|
|
14
|
+
* only one level.
|
|
15
|
+
* 2. ENTRY-STAGE MARKER FIRST: for each subflow (top-level and
|
|
16
|
+
* nested), yields a `{ kind: 'subflow-start', ... }` item BEFORE
|
|
17
|
+
* any stage/edge items from that subflow. Lets consumers draw the
|
|
18
|
+
* boundary edge from the mount node to the entry stage.
|
|
19
|
+
* 3. COMPOSED PATHS: nested subflows get `parentPath + '/' + localId`.
|
|
20
|
+
* Top-level mount paths are local-only (`'auth'`, NOT `'__root__/auth'`).
|
|
21
|
+
* 4. SHAPE MIRRORING: stage/edge/loop items have the same payload
|
|
22
|
+
* shape as Structure events, with `subflowPath` added.
|
|
23
|
+
* 5. SOURCE DISCRIMINATOR: every walker item carries `source: 'walker'`
|
|
24
|
+
* (Structure events do NOT). Lets consumers distinguish event vs
|
|
25
|
+
* walker in logs/debuggers while still sharing handler code paths.
|
|
26
|
+
* 6. STAGE-ID PREFIXING: stage IDs in nested subflows are already
|
|
27
|
+
* prefixed by the spec (e.g. `'auth/verify/check'`). Walker
|
|
28
|
+
* preserves this; `subflowPath` field is redundant-but-explicit.
|
|
29
|
+
*/
|
|
30
|
+
import type { SerializedPipelineStructure } from '../builder/types.js';
|
|
31
|
+
export interface WalkerOptions {
|
|
32
|
+
/** Auto-recurse into nested subflows (default: true). When false,
|
|
33
|
+
* nested subflow items are yielded but their internals are not
|
|
34
|
+
* traversed. */
|
|
35
|
+
recurse?: boolean;
|
|
36
|
+
}
|
|
37
|
+
export type WalkerItem = {
|
|
38
|
+
kind: 'subflow-start';
|
|
39
|
+
stageId: string;
|
|
40
|
+
subflowPath: string;
|
|
41
|
+
source: 'walker';
|
|
42
|
+
} | {
|
|
43
|
+
kind: 'stage';
|
|
44
|
+
stageId: string;
|
|
45
|
+
name: string;
|
|
46
|
+
type: NonNullable<SerializedPipelineStructure['type']>;
|
|
47
|
+
isPausable?: boolean;
|
|
48
|
+
spec: SerializedPipelineStructure;
|
|
49
|
+
subflowPath: string;
|
|
50
|
+
source: 'walker';
|
|
51
|
+
} | {
|
|
52
|
+
kind: 'edge';
|
|
53
|
+
from: string;
|
|
54
|
+
to: string;
|
|
55
|
+
edgeKind: 'next' | 'fork-branch' | 'decision-branch';
|
|
56
|
+
label?: string;
|
|
57
|
+
subflowPath: string;
|
|
58
|
+
source: 'walker';
|
|
59
|
+
} | {
|
|
60
|
+
kind: 'loop';
|
|
61
|
+
from: string;
|
|
62
|
+
to: string;
|
|
63
|
+
subflowPath: string;
|
|
64
|
+
source: 'walker';
|
|
65
|
+
} | {
|
|
66
|
+
kind: 'subflow';
|
|
67
|
+
mountStageId: string;
|
|
68
|
+
subflowId: string;
|
|
69
|
+
subflowName: string;
|
|
70
|
+
subflowSpec: SerializedPipelineStructure;
|
|
71
|
+
subflowPath: string;
|
|
72
|
+
source: 'walker';
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Walk a subflow spec, yielding its structure as flat ordered items.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* import { walkSubflowSpec } from 'footprintjs/trace';
|
|
80
|
+
*
|
|
81
|
+
* onSubflowMounted(event) {
|
|
82
|
+
* if (!event.subflowSpec) return; // lazy mount — no spec yet
|
|
83
|
+
* for (const item of walkSubflowSpec(event.subflowSpec, event.subflowPath)) {
|
|
84
|
+
* switch (item.kind) {
|
|
85
|
+
* case 'subflow-start': break; // entry boundary
|
|
86
|
+
* case 'stage': break; // inner stage
|
|
87
|
+
* case 'edge': break; // inner edge
|
|
88
|
+
* case 'loop': break; // inner loop back-edge
|
|
89
|
+
* case 'subflow': break; // nested mount marker
|
|
90
|
+
* }
|
|
91
|
+
* }
|
|
92
|
+
* }
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export declare function walkSubflowSpec(spec: SerializedPipelineStructure, subflowPath: string, options?: WalkerOptions): Generator<WalkerItem, void, void>;
|
|
@@ -50,7 +50,7 @@ export declare class StageContext {
|
|
|
50
50
|
private _stageWrites;
|
|
51
51
|
/** Tracks user-level reads (pre-namespace) for the memory view. */
|
|
52
52
|
private _stageReads;
|
|
53
|
-
/** Observer called after commit() — used by ScopeFacade to fire
|
|
53
|
+
/** Observer called after commit() — used by ScopeFacade to fire ScopeRecorder.onCommit. */
|
|
54
54
|
private _commitObserver?;
|
|
55
55
|
constructor(runId: string, name: string, stageId: string, sharedMemory: SharedMemory, branchId?: string, eventLog?: EventLog, isDecider?: boolean);
|
|
56
56
|
/** Returns the SharedMemory instance (needed by scope layer). */
|
|
@@ -89,7 +89,7 @@ export declare class StageContext {
|
|
|
89
89
|
getScope(): Record<string, unknown>;
|
|
90
90
|
getRunId(): string;
|
|
91
91
|
/** Register an observer that fires after commit() applies patches.
|
|
92
|
-
* Used by ScopeFacade to dispatch
|
|
92
|
+
* Used by ScopeFacade to dispatch ScopeRecorder.onCommit events. */
|
|
93
93
|
setCommitObserver(observer: (mutations: Record<string, {
|
|
94
94
|
value: unknown;
|
|
95
95
|
operation: 'set' | 'update' | 'delete';
|
|
@@ -84,7 +84,7 @@ export interface CausalChainOptions {
|
|
|
84
84
|
* which read→write edges to follow.
|
|
85
85
|
*
|
|
86
86
|
* Implementors: QualityRecorder tracks keysRead per step,
|
|
87
|
-
* or build a Map<runtimeStageId, string[]> from
|
|
87
|
+
* or build a Map<runtimeStageId, string[]> from ScopeRecorder.onRead events.
|
|
88
88
|
*/
|
|
89
89
|
export type KeysReadLookup = (runtimeStageId: string) => string[];
|
|
90
90
|
/**
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Dependency: type-only imports from engine/ and scope/ (zero runtime cost).
|
|
9
9
|
*/
|
|
10
10
|
import type { ExecutionEnv } from '../engine/types.js';
|
|
11
|
-
import type {
|
|
11
|
+
import type { ScopeRecorder } from '../scope/types.js';
|
|
12
12
|
export interface ReactiveTarget {
|
|
13
13
|
getValue(key?: string): unknown;
|
|
14
14
|
setValue(key: string, value: unknown, shouldRedact?: boolean, description?: string): void;
|
|
@@ -22,9 +22,9 @@ export interface ReactiveTarget {
|
|
|
22
22
|
getValueSilent?(key?: string): unknown;
|
|
23
23
|
getArgs<T = Record<string, unknown>>(): T;
|
|
24
24
|
getEnv(): Readonly<ExecutionEnv>;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
attachScopeRecorder(recorder: ScopeRecorder): void;
|
|
26
|
+
detachScopeRecorder(recorderId: string): void;
|
|
27
|
+
getScopeRecorders(): ScopeRecorder[];
|
|
28
28
|
addDebugInfo(key: string, value: unknown): void;
|
|
29
29
|
addDebugMessage(value: unknown): void;
|
|
30
30
|
addErrorInfo(key: string, value: unknown): void;
|
|
@@ -48,9 +48,9 @@ export interface ScopeMethods {
|
|
|
48
48
|
$error(key: string, value: unknown): void;
|
|
49
49
|
$metric(name: string, value: unknown): void;
|
|
50
50
|
$eval(name: string, value: unknown): void;
|
|
51
|
-
$
|
|
52
|
-
$
|
|
53
|
-
$
|
|
51
|
+
$attachScopeRecorder(recorder: ScopeRecorder): void;
|
|
52
|
+
$detachScopeRecorder(recorderId: string): void;
|
|
53
|
+
$getScopeRecorders(): ScopeRecorder[];
|
|
54
54
|
/**
|
|
55
55
|
* Batch-mutate an array key in a single clone+write cycle.
|
|
56
56
|
*
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BoundaryStateStore<TState> — concrete, composable per-boundary
|
|
3
|
+
* transient state storage.
|
|
4
|
+
*
|
|
5
|
+
* Pattern: COMPOSITION primitive. Concrete class — instantiate with
|
|
6
|
+
* `new BoundaryStateStore<TState>()` and own it as a field
|
|
7
|
+
* on your recorder. Replaces the abstract
|
|
8
|
+
* `BoundaryStateTracker<TState>` base class for the v5
|
|
9
|
+
* "one purpose per recorder" rule.
|
|
10
|
+
* Role: "What's the LIVE transient state of every currently-active
|
|
11
|
+
* boundary?" A boundary is a matched event pair `[start, stop]`
|
|
12
|
+
* bracketing an interval (e.g., `(llm_start, llm_end)`).
|
|
13
|
+
* Between the brackets, intermediate events evolve the
|
|
14
|
+
* boundary's state. On `stop`, the state clears.
|
|
15
|
+
*
|
|
16
|
+
* Algorithmically this is the **DFS bracket-sequence pattern** — the
|
|
17
|
+
* `active` map is the open-brackets stack at any moment.
|
|
18
|
+
*
|
|
19
|
+
* **Lifecycle contract — STRICT:** every `start(key, ...)` call MUST
|
|
20
|
+
* be paired with a `stop(key)` call. Failure to wire stop produces a
|
|
21
|
+
* memory leak: the active map grows without bound.
|
|
22
|
+
*
|
|
23
|
+
* **Concurrency / nesting:** concurrent boundaries (parallel branches
|
|
24
|
+
* with two LLM calls active at once) work correctly — each is keyed
|
|
25
|
+
* independently. Nested boundaries of DIFFERENT KINDS require
|
|
26
|
+
* SEPARATE store instances — one per kind.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* import { BoundaryStateStore } from 'footprintjs/trace';
|
|
31
|
+
* import type { CombinedRecorder, EmitEvent } from 'footprintjs';
|
|
32
|
+
*
|
|
33
|
+
* interface LLMLiveState { partial: string; tokens: number; }
|
|
34
|
+
*
|
|
35
|
+
* class LiveLLMTracker implements CombinedRecorder {
|
|
36
|
+
* readonly id = 'live-llm';
|
|
37
|
+
* private readonly store = new BoundaryStateStore<LLMLiveState>();
|
|
38
|
+
*
|
|
39
|
+
* onEmit(event: EmitEvent): void {
|
|
40
|
+
* if (event.name === 'agentfootprint.stream.llm_start') {
|
|
41
|
+
* this.store.start(event.runtimeStageId, { partial: '', tokens: 0 });
|
|
42
|
+
* } else if (event.name === 'agentfootprint.stream.llm_end') {
|
|
43
|
+
* this.store.stop(event.runtimeStageId);
|
|
44
|
+
* } else if (event.name === 'agentfootprint.stream.token') {
|
|
45
|
+
* this.store.update(event.runtimeStageId, (s) => ({
|
|
46
|
+
* partial: s.partial + (event.payload as { content: string }).content,
|
|
47
|
+
* tokens: s.tokens + 1,
|
|
48
|
+
* }));
|
|
49
|
+
* }
|
|
50
|
+
* }
|
|
51
|
+
*
|
|
52
|
+
* isInFlight(): boolean { return this.store.hasActive; }
|
|
53
|
+
* getPartial(rid: string): string {
|
|
54
|
+
* return this.store.get(rid)?.partial ?? '';
|
|
55
|
+
* }
|
|
56
|
+
*
|
|
57
|
+
* clear() { this.store.clear(); }
|
|
58
|
+
* }
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export declare class BoundaryStateStore<TState> {
|
|
62
|
+
/** Open-brackets stack: key → current transient state. */
|
|
63
|
+
private readonly active;
|
|
64
|
+
/** Per-key count of `update` calls that landed without a matching
|
|
65
|
+
* active boundary. Drives rate-limited dev-mode warnings so a
|
|
66
|
+
* stuck loop doesn't spam the console. */
|
|
67
|
+
private readonly missedUpdates;
|
|
68
|
+
/** Optional id for diagnostics — passed to dev-mode warnings so the
|
|
69
|
+
* source of the leak is easy to find when multiple stores coexist. */
|
|
70
|
+
private readonly diagnosticId;
|
|
71
|
+
constructor(diagnosticId?: string);
|
|
72
|
+
/**
|
|
73
|
+
* Open a new boundary with initial transient state. If a boundary
|
|
74
|
+
* with the same `key` is already active, the prior state is
|
|
75
|
+
* overwritten (last-writer-wins). Dev mode warns — usually
|
|
76
|
+
* indicates a missed `stop` upstream.
|
|
77
|
+
*/
|
|
78
|
+
start(key: string, initial: TState): void;
|
|
79
|
+
/**
|
|
80
|
+
* Evolve the transient state of an active boundary using a pure
|
|
81
|
+
* updater function. Silent no-op if no boundary is active for `key`
|
|
82
|
+
* (defensive against out-of-order events). Dev mode logs a rate-
|
|
83
|
+
* limited warning.
|
|
84
|
+
*/
|
|
85
|
+
update(key: string, updater: (prev: TState) => TState): void;
|
|
86
|
+
/**
|
|
87
|
+
* Close the boundary identified by `key` and return its FINAL
|
|
88
|
+
* transient state (so the consumer can do any cleanup — e.g., emit
|
|
89
|
+
* a snapshot to a SequenceStore for durable storage).
|
|
90
|
+
*/
|
|
91
|
+
stop(key: string): TState | undefined;
|
|
92
|
+
/** Current transient state of ONE active boundary. `undefined` if no
|
|
93
|
+
* boundary is active for `key`. */
|
|
94
|
+
get(key: string): TState | undefined;
|
|
95
|
+
/** All currently-active boundaries.
|
|
96
|
+
*
|
|
97
|
+
* **Type-only readonly:** TypeScript prevents mutation through
|
|
98
|
+
* `ReadonlyMap`, but a runtime cast or non-TS consumer can mutate
|
|
99
|
+
* the underlying Map and corrupt state. Don't. */
|
|
100
|
+
getAll(): ReadonlyMap<string, TState>;
|
|
101
|
+
/** True if any boundary is currently active. O(1). */
|
|
102
|
+
get hasActive(): boolean;
|
|
103
|
+
/** Number of currently-active boundaries. O(1). */
|
|
104
|
+
get activeCount(): number;
|
|
105
|
+
/**
|
|
106
|
+
* Reset all transient state. Called by recorder composers from
|
|
107
|
+
* their own `clear()` method, which the executor invokes before
|
|
108
|
+
* each run.
|
|
109
|
+
*
|
|
110
|
+
* Dev mode warns if any boundaries are still active when called —
|
|
111
|
+
* likely indicates a missed `stop` upstream. Leaked keys are listed
|
|
112
|
+
* (truncated to 10) so the wiring bug is findable.
|
|
113
|
+
*/
|
|
114
|
+
clear(): void;
|
|
115
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* **Mental model — observers vs. bookkeepers:**
|
|
6
6
|
*
|
|
7
|
-
* `
|
|
7
|
+
* `ScopeRecorder` / `FlowRecorder` / `EmitRecorder` / `CombinedRecorder`
|
|
8
8
|
* are OBSERVER interfaces — they describe how a recorder hears
|
|
9
9
|
* events from the executor.
|
|
10
10
|
*
|
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
* - Aggregations across the whole run (totals, counts) — those are
|
|
58
58
|
* `SequenceRecorder.aggregate()` / `KeyedRecorder.aggregate()`.
|
|
59
59
|
*
|
|
60
|
-
* - Stage-level concerns — those use `
|
|
61
|
-
* `
|
|
60
|
+
* - Stage-level concerns — those use `ScopeRecorder.onStageStart` /
|
|
61
|
+
* `ScopeRecorder.onStageEnd`. This primitive operates at finer
|
|
62
62
|
* granularity (events emitted DURING a stage execution).
|
|
63
63
|
*
|
|
64
64
|
* **Lifecycle contract — STRICT:**
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Before `CombinedRecorder`, a consumer who wanted to observe both streams
|
|
9
9
|
* had to:
|
|
10
|
-
* 1. Implement both `
|
|
10
|
+
* 1. Implement both `ScopeRecorder` (8 methods) and `FlowRecorder` (12 methods)
|
|
11
11
|
* fully — stubbing every event they didn't care about.
|
|
12
|
-
* 2. Remember to call BOTH `
|
|
12
|
+
* 2. Remember to call BOTH `attachScopeRecorder(r)` AND `attachFlowRecorder(r)`.
|
|
13
13
|
* Forgetting the second call silently dropped half their events — no
|
|
14
14
|
* warning, no runtime error.
|
|
15
15
|
* 3. Re-implement coordination logic (buffering, ordering) themselves.
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
*
|
|
37
37
|
* const audit: CombinedRecorder = {
|
|
38
38
|
* id: 'audit',
|
|
39
|
-
* onWrite: (e) => logWrite(e.key, e.value), //
|
|
39
|
+
* onWrite: (e) => logWrite(e.key, e.value), // ScopeRecorder method
|
|
40
40
|
* onDecision: (e) => logDecision(e.chosen), // FlowRecorder method
|
|
41
41
|
* };
|
|
42
42
|
*
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
*
|
|
47
47
|
* ## Contract with existing APIs
|
|
48
48
|
*
|
|
49
|
-
* - `
|
|
49
|
+
* - `attachScopeRecorder(r)` and `attachFlowRecorder(r)` remain unchanged.
|
|
50
50
|
* Consumers who want only ONE channel keep using them — explicit is good.
|
|
51
51
|
* - `attachCombinedRecorder(r)` is the ONLY way to guarantee an object is
|
|
52
52
|
* hooked into every stream it has methods for, without maintaining two
|
|
@@ -55,10 +55,10 @@
|
|
|
55
55
|
* same `id` replaces the previous instance on BOTH channels, not just one.
|
|
56
56
|
*/
|
|
57
57
|
import type { FlowErrorEvent, FlowPauseEvent, FlowRecorder, FlowResumeEvent } from '../engine/narrative/types.js';
|
|
58
|
-
import type { ErrorEvent, PauseEvent,
|
|
58
|
+
import type { ErrorEvent, PauseEvent, ResumeEvent, ScopeRecorder } from '../scope/types.js';
|
|
59
59
|
import type { EmitRecorder } from './EmitRecorder.js';
|
|
60
60
|
/**
|
|
61
|
-
* Method names that appear on BOTH `
|
|
61
|
+
* Method names that appear on BOTH `ScopeRecorder` and `FlowRecorder` but with
|
|
62
62
|
* different event payload types. For these, a `CombinedRecorder` declares
|
|
63
63
|
* ONE handler that receives the union of both payloads — consumers
|
|
64
64
|
* discriminate on `traversalContext`, which only control-flow events carry.
|
|
@@ -70,7 +70,7 @@ type SharedLifecycle = 'id' | 'clear' | 'toSnapshot';
|
|
|
70
70
|
* A recorder that MAY observe any combination of supported event streams.
|
|
71
71
|
*
|
|
72
72
|
* Today's streams:
|
|
73
|
-
* - Scope data-flow (`
|
|
73
|
+
* - Scope data-flow (`ScopeRecorder`: onRead/onWrite/onCommit/onStageStart/…)
|
|
74
74
|
* - Control-flow (`FlowRecorder`: onDecision/onSubflowEntry/onLoop/…)
|
|
75
75
|
*
|
|
76
76
|
* All event handlers are optional — implement only what you care about.
|
|
@@ -78,7 +78,7 @@ type SharedLifecycle = 'id' | 'clear' | 'toSnapshot';
|
|
|
78
78
|
*
|
|
79
79
|
* ## Shared method names (onError / onPause / onResume)
|
|
80
80
|
*
|
|
81
|
-
* Both `
|
|
81
|
+
* Both `ScopeRecorder` and `FlowRecorder` declare these with DIFFERENT payload
|
|
82
82
|
* shapes. In a combined recorder, each such handler is called by BOTH
|
|
83
83
|
* channels with its own variant. The parameter type is a union — consumers
|
|
84
84
|
* can either handle both variants uniformly, or discriminate (control-flow
|
|
@@ -90,7 +90,7 @@ type SharedLifecycle = 'id' | 'clear' | 'toSnapshot';
|
|
|
90
90
|
* gains another `& Partial<…>` clause. Because every clause is `Partial`,
|
|
91
91
|
* existing `CombinedRecorder` implementations remain type-valid.
|
|
92
92
|
*/
|
|
93
|
-
export type CombinedRecorder = Partial<Omit<
|
|
93
|
+
export type CombinedRecorder = Partial<Omit<ScopeRecorder, SharedLifecycleOverlap | SharedLifecycle>> & Partial<Omit<FlowRecorder, SharedLifecycleOverlap | SharedLifecycle>> & Partial<Omit<EmitRecorder, SharedLifecycle>> & {
|
|
94
94
|
readonly id: string;
|
|
95
95
|
clear?(): void;
|
|
96
96
|
toSnapshot?(): {
|
|
@@ -151,10 +151,10 @@ export declare function isFlowEvent<T>(event: T): event is Exclude<T, {
|
|
|
151
151
|
*
|
|
152
152
|
* ## Return type
|
|
153
153
|
*
|
|
154
|
-
* Returns plain `boolean` (not a type predicate). The full `
|
|
154
|
+
* Returns plain `boolean` (not a type predicate). The full `ScopeRecorder`
|
|
155
155
|
* interface has payload types that diverge from `CombinedRecorder`'s union
|
|
156
156
|
* variants for shared methods, so a narrowing predicate would be unsound.
|
|
157
|
-
* Callers that need to treat the recorder as a `
|
|
157
|
+
* Callers that need to treat the recorder as a `ScopeRecorder` do so explicitly
|
|
158
158
|
* at the attach site — the cast is the contract that each channel passes
|
|
159
159
|
* its own payload variant.
|
|
160
160
|
*/
|