footprintjs 4.16.0 → 4.17.2
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/AGENTS.md +567 -82
- package/CLAUDE.md +57 -0
- package/README.md +2 -0
- package/dist/advanced.js +4 -2
- package/dist/detach.js +78 -0
- package/dist/esm/advanced.js +2 -1
- package/dist/esm/detach.js +57 -0
- package/dist/esm/lib/builder/FlowChartBuilder.js +171 -61
- package/dist/esm/lib/contract/openapi.js +4 -5
- package/dist/esm/lib/contract/schema.js +115 -4
- package/dist/esm/lib/decide/decide.js +4 -5
- package/dist/esm/lib/decide/evidence.js +3 -2
- package/dist/esm/lib/detach/drivers/immediate.js +66 -0
- package/dist/esm/lib/detach/drivers/microtaskBatch.js +113 -0
- package/dist/esm/lib/detach/drivers/sendBeacon.js +78 -0
- package/dist/esm/lib/detach/drivers/setImmediate.js +81 -0
- package/dist/esm/lib/detach/drivers/setTimeout.js +69 -0
- package/dist/esm/lib/detach/drivers/workerThread.js +117 -0
- package/dist/esm/lib/detach/flush.js +91 -0
- package/dist/esm/lib/detach/handle.js +134 -0
- package/dist/esm/lib/detach/registry.js +97 -0
- package/dist/esm/lib/detach/runChild.js +40 -0
- package/dist/esm/lib/detach/spawn.js +86 -0
- package/dist/esm/lib/detach/types.js +37 -0
- package/dist/esm/lib/engine/errors/errorInfo.js +4 -4
- package/dist/esm/lib/engine/graph/StageNode.js +2 -2
- package/dist/esm/lib/engine/handlers/ChildrenExecutor.js +9 -8
- package/dist/esm/lib/engine/handlers/ContinuationResolver.js +12 -9
- package/dist/esm/lib/engine/handlers/DeciderHandler.js +7 -8
- package/dist/esm/lib/engine/handlers/ExtractorRunner.js +14 -8
- package/dist/esm/lib/engine/handlers/NodeResolver.js +5 -3
- package/dist/esm/lib/engine/handlers/RuntimeStructureManager.js +11 -14
- package/dist/esm/lib/engine/handlers/SelectorHandler.js +9 -9
- package/dist/esm/lib/engine/handlers/StageRunner.js +9 -11
- package/dist/esm/lib/engine/handlers/SubflowExecutor.js +13 -12
- package/dist/esm/lib/engine/handlers/SubflowInputMapper.js +4 -4
- package/dist/esm/lib/engine/narrative/CombinedNarrativeRecorder.js +85 -96
- package/dist/esm/lib/engine/narrative/FlowRecorderDispatcher.js +18 -36
- package/dist/esm/lib/engine/narrative/NarrativeFlowRecorder.js +6 -5
- package/dist/esm/lib/engine/narrative/recorders/AdaptiveNarrativeFlowRecorder.js +7 -6
- package/dist/esm/lib/engine/narrative/recorders/ManifestFlowRecorder.js +10 -10
- package/dist/esm/lib/engine/narrative/recorders/MilestoneNarrativeFlowRecorder.js +5 -3
- package/dist/esm/lib/engine/narrative/recorders/ProgressiveNarrativeFlowRecorder.js +4 -3
- package/dist/esm/lib/engine/narrative/recorders/RLENarrativeFlowRecorder.js +4 -4
- package/dist/esm/lib/engine/narrative/recorders/SeparateNarrativeFlowRecorder.js +5 -6
- package/dist/esm/lib/engine/narrative/recorders/SilentNarrativeFlowRecorder.js +5 -6
- package/dist/esm/lib/engine/narrative/recorders/WindowedNarrativeFlowRecorder.js +5 -3
- package/dist/esm/lib/engine/traversal/FlowchartTraverser.js +97 -71
- package/dist/esm/lib/memory/DiagnosticCollector.js +6 -8
- package/dist/esm/lib/memory/EventLog.js +5 -3
- package/dist/esm/lib/memory/SharedMemory.js +3 -2
- package/dist/esm/lib/memory/StageContext.js +44 -14
- package/dist/esm/lib/memory/TransactionBuffer.js +9 -8
- package/dist/esm/lib/memory/backtrack.js +3 -4
- package/dist/esm/lib/memory/commitLogUtils.js +2 -2
- package/dist/esm/lib/memory/utils.js +2 -3
- package/dist/esm/lib/pause/types.js +33 -14
- package/dist/esm/lib/reactive/createTypedScope.js +10 -8
- package/dist/esm/lib/reactive/types.js +3 -1
- package/dist/esm/lib/recorder/BoundaryStateTracker.js +263 -0
- package/dist/esm/lib/recorder/CompositeRecorder.js +3 -1
- package/dist/esm/lib/recorder/InOutRecorder.js +5 -6
- package/dist/esm/lib/recorder/KeyedRecorder.js +2 -4
- package/dist/esm/lib/recorder/QualityRecorder.js +15 -14
- package/dist/esm/lib/recorder/SequenceRecorder.js +11 -12
- package/dist/esm/lib/recorder/TopologyRecorder.js +36 -40
- package/dist/esm/lib/recorder/index.js +2 -1
- package/dist/esm/lib/recorder/qualityTrace.js +4 -5
- package/dist/esm/lib/runner/ExecutionRuntime.js +20 -4
- package/dist/esm/lib/runner/FlowChartExecutor.js +99 -55
- package/dist/esm/lib/runner/RunContext.js +5 -3
- package/dist/esm/lib/runner/RunnableChart.js +7 -9
- package/dist/esm/lib/runner/getSubtreeSnapshot.js +4 -5
- package/dist/esm/lib/schema/errors.js +4 -3
- package/dist/esm/lib/schema/validate.js +4 -5
- package/dist/esm/lib/scope/ScopeFacade.js +52 -35
- package/dist/esm/lib/scope/providers/baseStateCompatible.js +9 -9
- package/dist/esm/lib/scope/providers/guards.js +2 -2
- package/dist/esm/lib/scope/recorders/DebugRecorder.js +9 -7
- package/dist/esm/lib/scope/recorders/MetricRecorder.js +10 -8
- package/dist/esm/lib/scope/state/zod/defineScopeFromZod.js +2 -3
- package/dist/esm/lib/scope/state/zod/resolver.js +2 -3
- package/dist/esm/lib/scope/state/zod/scopeFactory.js +16 -20
- package/dist/esm/lib/scope/state/zod/utils/validateHelper.js +57 -14
- package/dist/esm/trace.js +4 -1
- package/dist/lib/builder/FlowChartBuilder.js +171 -61
- package/dist/lib/contract/openapi.js +4 -5
- package/dist/lib/contract/schema.js +115 -4
- package/dist/lib/decide/decide.js +4 -5
- package/dist/lib/decide/evidence.js +3 -2
- package/dist/lib/detach/drivers/immediate.js +70 -0
- package/dist/lib/detach/drivers/microtaskBatch.js +117 -0
- package/dist/lib/detach/drivers/sendBeacon.js +82 -0
- package/dist/lib/detach/drivers/setImmediate.js +85 -0
- package/dist/lib/detach/drivers/setTimeout.js +73 -0
- package/dist/lib/detach/drivers/workerThread.js +121 -0
- package/dist/lib/detach/flush.js +95 -0
- package/dist/lib/detach/handle.js +140 -0
- package/dist/lib/detach/registry.js +106 -0
- package/dist/lib/detach/runChild.js +67 -0
- package/dist/lib/detach/spawn.js +92 -0
- package/dist/lib/detach/types.js +38 -0
- package/dist/lib/engine/errors/errorInfo.js +4 -4
- package/dist/lib/engine/graph/StageNode.js +2 -2
- package/dist/lib/engine/handlers/ChildrenExecutor.js +9 -8
- package/dist/lib/engine/handlers/ContinuationResolver.js +12 -9
- package/dist/lib/engine/handlers/DeciderHandler.js +7 -8
- package/dist/lib/engine/handlers/ExtractorRunner.js +14 -8
- package/dist/lib/engine/handlers/NodeResolver.js +5 -3
- package/dist/lib/engine/handlers/RuntimeStructureManager.js +11 -14
- package/dist/lib/engine/handlers/SelectorHandler.js +9 -9
- package/dist/lib/engine/handlers/StageRunner.js +9 -11
- package/dist/lib/engine/handlers/SubflowExecutor.js +13 -12
- package/dist/lib/engine/handlers/SubflowInputMapper.js +4 -4
- package/dist/lib/engine/narrative/CombinedNarrativeRecorder.js +85 -96
- package/dist/lib/engine/narrative/FlowRecorderDispatcher.js +18 -36
- package/dist/lib/engine/narrative/NarrativeFlowRecorder.js +6 -5
- package/dist/lib/engine/narrative/recorders/AdaptiveNarrativeFlowRecorder.js +7 -6
- package/dist/lib/engine/narrative/recorders/ManifestFlowRecorder.js +10 -10
- package/dist/lib/engine/narrative/recorders/MilestoneNarrativeFlowRecorder.js +5 -3
- package/dist/lib/engine/narrative/recorders/ProgressiveNarrativeFlowRecorder.js +4 -3
- package/dist/lib/engine/narrative/recorders/RLENarrativeFlowRecorder.js +4 -4
- package/dist/lib/engine/narrative/recorders/SeparateNarrativeFlowRecorder.js +5 -6
- package/dist/lib/engine/narrative/recorders/SilentNarrativeFlowRecorder.js +5 -6
- package/dist/lib/engine/narrative/recorders/WindowedNarrativeFlowRecorder.js +5 -3
- package/dist/lib/engine/traversal/FlowchartTraverser.js +97 -71
- package/dist/lib/memory/DiagnosticCollector.js +6 -8
- package/dist/lib/memory/EventLog.js +5 -3
- package/dist/lib/memory/SharedMemory.js +3 -2
- package/dist/lib/memory/StageContext.js +44 -14
- package/dist/lib/memory/TransactionBuffer.js +9 -8
- package/dist/lib/memory/backtrack.js +3 -4
- package/dist/lib/memory/commitLogUtils.js +2 -2
- package/dist/lib/memory/utils.js +2 -3
- package/dist/lib/pause/types.js +33 -14
- package/dist/lib/reactive/createTypedScope.js +10 -8
- package/dist/lib/reactive/types.js +3 -1
- package/dist/lib/recorder/BoundaryStateTracker.js +267 -0
- package/dist/lib/recorder/CompositeRecorder.js +3 -1
- package/dist/lib/recorder/InOutRecorder.js +5 -6
- package/dist/lib/recorder/KeyedRecorder.js +2 -4
- package/dist/lib/recorder/QualityRecorder.js +15 -14
- package/dist/lib/recorder/SequenceRecorder.js +11 -12
- package/dist/lib/recorder/TopologyRecorder.js +36 -40
- package/dist/lib/recorder/index.js +4 -2
- package/dist/lib/recorder/qualityTrace.js +4 -5
- package/dist/lib/runner/ExecutionRuntime.js +20 -4
- package/dist/lib/runner/FlowChartExecutor.js +99 -55
- package/dist/lib/runner/RunContext.js +5 -3
- package/dist/lib/runner/RunnableChart.js +7 -9
- package/dist/lib/runner/getSubtreeSnapshot.js +4 -5
- package/dist/lib/schema/errors.js +4 -3
- package/dist/lib/schema/validate.js +4 -5
- package/dist/lib/scope/ScopeFacade.js +52 -35
- package/dist/lib/scope/providers/baseStateCompatible.js +9 -9
- package/dist/lib/scope/providers/guards.js +2 -2
- package/dist/lib/scope/recorders/DebugRecorder.js +9 -7
- package/dist/lib/scope/recorders/MetricRecorder.js +10 -8
- package/dist/lib/scope/state/zod/defineScopeFromZod.js +2 -3
- package/dist/lib/scope/state/zod/resolver.js +2 -3
- package/dist/lib/scope/state/zod/scopeFactory.js +16 -20
- package/dist/lib/scope/state/zod/utils/validateHelper.js +57 -14
- package/dist/trace.js +6 -2
- package/dist/types/advanced.d.ts +1 -0
- package/dist/types/detach.d.ts +59 -0
- package/dist/types/lib/builder/FlowChartBuilder.d.ts +81 -0
- package/dist/types/lib/detach/drivers/immediate.d.ts +39 -0
- package/dist/types/lib/detach/drivers/microtaskBatch.d.ts +57 -0
- package/dist/types/lib/detach/drivers/sendBeacon.d.ts +38 -0
- package/dist/types/lib/detach/drivers/setImmediate.d.ts +32 -0
- package/dist/types/lib/detach/drivers/setTimeout.d.ts +34 -0
- package/dist/types/lib/detach/drivers/workerThread.d.ts +50 -0
- package/dist/types/lib/detach/flush.d.ts +62 -0
- package/dist/types/lib/detach/handle.d.ts +83 -0
- package/dist/types/lib/detach/registry.d.ts +82 -0
- package/dist/types/lib/detach/runChild.d.ts +41 -0
- package/dist/types/lib/detach/spawn.d.ts +64 -0
- package/dist/types/lib/detach/types.d.ts +200 -0
- package/dist/types/lib/engine/traversal/FlowchartTraverser.d.ts +0 -1
- package/dist/types/lib/engine/types.d.ts +0 -1
- package/dist/types/lib/reactive/types.d.ts +4 -0
- package/dist/types/lib/recorder/BoundaryStateTracker.d.ts +215 -0
- package/dist/types/lib/recorder/index.d.ts +1 -0
- package/dist/types/lib/runner/FlowChartExecutor.d.ts +28 -0
- package/dist/types/lib/scope/ScopeFacade.d.ts +4 -0
- package/dist/types/lib/scope/state/zod/utils/validateHelper.d.ts +13 -1
- package/dist/types/trace.d.ts +1 -0
- package/package.json +6 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* detach/runChild.ts — The "how do I actually run a child flowchart?" hook.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: Strategy (GoF). Drivers are decoupled from the FlowChartExecutor
|
|
5
|
+
* — each driver is created with a `ChildRunner` it calls to
|
|
6
|
+
* materialize the work. Default implementation imports the
|
|
7
|
+
* executor lazily so drivers can be picked up by tree-shakers
|
|
8
|
+
* that don't pull the runner module into the bundle.
|
|
9
|
+
* Role: Glue between drivers and the engine. Without this seam, every
|
|
10
|
+
* driver would have to import `FlowChartExecutor` directly,
|
|
11
|
+
* creating circular import risk and bundle bloat.
|
|
12
|
+
*
|
|
13
|
+
* Why a separate module (not inlined in each driver):
|
|
14
|
+
* - DRY — every driver shares the same "instantiate executor, run, return
|
|
15
|
+
* result" sequence
|
|
16
|
+
* - Allows test code to swap the runner via factory injection without
|
|
17
|
+
* having to rebuild the whole driver
|
|
18
|
+
* - Future-proofs for the case where we want to inject env/recorders
|
|
19
|
+
* into the child (the runner is the natural place to do that)
|
|
20
|
+
*/
|
|
21
|
+
import type { FlowChart } from '../builder/types.js';
|
|
22
|
+
/**
|
|
23
|
+
* Function the driver calls to actually execute the child flowchart.
|
|
24
|
+
* Returns a Promise resolving to the chart's terminal value (whatever
|
|
25
|
+
* `FlowChartExecutor.run()` resolves with), or rejects on failure.
|
|
26
|
+
*
|
|
27
|
+
* Drivers wrap this in their own try/catch so the rejection routes to
|
|
28
|
+
* `handle._markFailed()` instead of escaping into the parent context
|
|
29
|
+
* (passive-recorder rule).
|
|
30
|
+
*/
|
|
31
|
+
export type ChildRunner = (child: FlowChart, input: unknown) => Promise<unknown>;
|
|
32
|
+
/**
|
|
33
|
+
* Default runner — instantiates a fresh `FlowChartExecutor` for each
|
|
34
|
+
* child and awaits its run. Returns the executor's traversal result.
|
|
35
|
+
*
|
|
36
|
+
* Lazy-imports `FlowChartExecutor` so drivers that consumers create with
|
|
37
|
+
* their own runner don't pull the engine into their bundle.
|
|
38
|
+
*
|
|
39
|
+
* Uses dynamic import — see https://v8.dev/features/dynamic-import.
|
|
40
|
+
*/
|
|
41
|
+
export declare const defaultRunChild: ChildRunner;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* detach/spawn.ts — One-call detach primitive used by both scope and
|
|
3
|
+
* executor surfaces.
|
|
4
|
+
*
|
|
5
|
+
* Pattern: Facade (GoF). Hides driver invocation + refId minting +
|
|
6
|
+
* registry registration behind two named functions
|
|
7
|
+
* (`detachAndJoinLater`, `detachAndForget`). Same helper is
|
|
8
|
+
* called from `scope.$detachAndJoinLater(...)` and from
|
|
9
|
+
* `executor.detachAndJoinLater(...)` — single source of truth.
|
|
10
|
+
*
|
|
11
|
+
* Why a separate module:
|
|
12
|
+
* - Avoids duplicating the "validate driver, mint refId, call schedule"
|
|
13
|
+
* sequence in both scope and executor entry points
|
|
14
|
+
* - Keeps the scope/executor files free of driver knowledge — they
|
|
15
|
+
* just call this and forward the result
|
|
16
|
+
*
|
|
17
|
+
* refId scheme:
|
|
18
|
+
* - When the caller is a stage (scope path): refId = `${runtimeStageId}:detach:${counter}`
|
|
19
|
+
* — the runtimeStageId prefix lets diagnostics correlate the handle
|
|
20
|
+
* back to the source stage
|
|
21
|
+
* - When the caller is bare executor (executor path):
|
|
22
|
+
* refId = `__executor__:detach:${counter}` — uniform "no source stage"
|
|
23
|
+
* marker
|
|
24
|
+
* - Counter is module-private + monotonic for the process lifetime —
|
|
25
|
+
* safe across re-entrant detach calls
|
|
26
|
+
*/
|
|
27
|
+
import type { FlowChart } from '../builder/types.js';
|
|
28
|
+
import type { DetachDriver, DetachHandle } from './types.js';
|
|
29
|
+
/** Reset the counter for tests — never call from production code. */
|
|
30
|
+
export declare function _resetSpawnCounterForTests(): void;
|
|
31
|
+
/**
|
|
32
|
+
* Schedule `child` on the given driver, with the consumer's `input`,
|
|
33
|
+
* and return the resulting `DetachHandle`. Callers can `wait()` on it,
|
|
34
|
+
* read its `.status` property, or just hold the reference for later.
|
|
35
|
+
*
|
|
36
|
+
* **Joinable variant** — the caller wants to be able to await the result
|
|
37
|
+
* (or check its status). The `forget` variant simply discards the handle.
|
|
38
|
+
*
|
|
39
|
+
* @param driver - The driver implementation to use. Required (no
|
|
40
|
+
* library-default — passing it explicitly avoids global state and
|
|
41
|
+
* keeps the engine free of driver imports).
|
|
42
|
+
* @param child - The child flowchart to run.
|
|
43
|
+
* @param input - The input to hand to the child's run() call.
|
|
44
|
+
* @param sourcePrefix - Refix prefix for the minted refId; pass the
|
|
45
|
+
* parent's `runtimeStageId` from a stage caller, or `'__executor__'`
|
|
46
|
+
* from a bare-executor caller.
|
|
47
|
+
*/
|
|
48
|
+
export declare function detachAndJoinLater(driver: DetachDriver, child: FlowChart, input: unknown, sourcePrefix: string): DetachHandle;
|
|
49
|
+
/**
|
|
50
|
+
* Same as `detachAndJoinLater` but discards the handle. Use when the
|
|
51
|
+
* caller doesn't care about the result and doesn't need to await — e.g.,
|
|
52
|
+
* fire-and-forget telemetry exports.
|
|
53
|
+
*
|
|
54
|
+
* The handle still exists internally (driver creates it, registry holds
|
|
55
|
+
* it briefly) — but the caller cannot reference it. This is intentional:
|
|
56
|
+
* having no handle reference is what gives "forget" its semantic — there
|
|
57
|
+
* is no chance of the caller accidentally awaiting it.
|
|
58
|
+
*
|
|
59
|
+
* Errors raised by the child are STILL routed to the handle's failed
|
|
60
|
+
* state (the driver does that). They just go unobserved unless something
|
|
61
|
+
* else (a recorder, logging) is wired to surface them. See the docs in
|
|
62
|
+
* T7 for recommended observability patterns for "forget" detach.
|
|
63
|
+
*/
|
|
64
|
+
export declare function detachAndForget(driver: DetachDriver, child: FlowChart, input: unknown, sourcePrefix: string): void;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* detach/types.ts — Type definitions for the fire-and-forget primitive.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: Strategy + Bridge (GoF). Same shape as cache strategies in
|
|
5
|
+
* agentfootprint v2.6 — ONE interface, N concrete drivers,
|
|
6
|
+
* consumer picks via explicit import.
|
|
7
|
+
* Role: Foundation. Every other detach file imports from here.
|
|
8
|
+
* This is the lockable contract; downstream files implement it.
|
|
9
|
+
*
|
|
10
|
+
* Two sibling concepts:
|
|
11
|
+
*
|
|
12
|
+
* 1. `DetachDriver` — the algorithm that decides WHEN/HOW the work runs.
|
|
13
|
+
* Six built-in algorithms ship as algorithm-named exports
|
|
14
|
+
* (`microtaskBatchDriver`, `setImmediateDriver`, `sendBeaconDriver`,
|
|
15
|
+
* `setTimeoutDriver`, `immediateDriver`, `workerThreadDriver`).
|
|
16
|
+
* Consumers can implement their own (BYOS — bring your own driver).
|
|
17
|
+
*
|
|
18
|
+
* 2. `DetachHandle` — the consumer-facing handle returned by
|
|
19
|
+
* `detachAndJoinLater`. Exposes status as PROPERTIES (sync read)
|
|
20
|
+
* and `wait()` (Promise — opt-in async join). Style 2 from the
|
|
21
|
+
* panel review: properties for sync, single method for async.
|
|
22
|
+
*
|
|
23
|
+
* Naming policy (locked from naming review):
|
|
24
|
+
* - Public API uses simple verbs / properties (product-engineer friendly)
|
|
25
|
+
* - Internal class names CAN use CS terms (Scheduler, Continuation, etc.)
|
|
26
|
+
* - Drivers are algorithm-named (semantic at the algorithm level)
|
|
27
|
+
*
|
|
28
|
+
* Locked design decisions (panel review captured in
|
|
29
|
+
* `docs/inspiration/detach-primitive.md`):
|
|
30
|
+
* - Sync hot path; async only at flush boundaries
|
|
31
|
+
* - Errors → commitLog + typed event, NEVER thrown to parent
|
|
32
|
+
* - Scope isolation between parent and detached child
|
|
33
|
+
* - Lifecycle tied to executor disposal
|
|
34
|
+
* - Type-level rejection of `outputMapper` on detach options
|
|
35
|
+
*/
|
|
36
|
+
import type { FlowChart } from '../builder/types.js';
|
|
37
|
+
/**
|
|
38
|
+
* Snapshot of a detached handle's state. Returned by `handle.poll()`
|
|
39
|
+
* (when added in a follow-up; v1 exposes properties directly on
|
|
40
|
+
* `DetachHandle`).
|
|
41
|
+
*/
|
|
42
|
+
export interface DetachPollResult {
|
|
43
|
+
readonly status: DetachHandle['status'];
|
|
44
|
+
/** Present iff status === 'done'. */
|
|
45
|
+
readonly result?: unknown;
|
|
46
|
+
/** Present iff status === 'failed'. */
|
|
47
|
+
readonly error?: Error;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Result delivered when the handle's `wait()` Promise resolves
|
|
51
|
+
* successfully (status reached 'done'). Rejection delivers the
|
|
52
|
+
* native `Error` directly — no wrapping shape.
|
|
53
|
+
*/
|
|
54
|
+
export interface DetachWaitResult {
|
|
55
|
+
readonly result: unknown;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Handle returned by `chart.detachAndJoinLater(...)` and
|
|
59
|
+
* `executor.detachAndJoinLater(...)`. Exposes status as PROPERTIES so
|
|
60
|
+
* sync access is property-read (cheap, no allocation). The single
|
|
61
|
+
* method `wait()` returns a Promise for opt-in async join.
|
|
62
|
+
*
|
|
63
|
+
* The handle is **not** Promise-shaped (no `.then()`) — that would
|
|
64
|
+
* make it accidentally awaitable, defeating the fire-and-forget
|
|
65
|
+
* semantics. To await, the consumer must call `.wait()` explicitly.
|
|
66
|
+
*
|
|
67
|
+
* Lifecycle:
|
|
68
|
+
*
|
|
69
|
+
* queued → driver received the work, hasn't started it
|
|
70
|
+
* running → driver started the work
|
|
71
|
+
* done → terminal: result available
|
|
72
|
+
* failed → terminal: error available
|
|
73
|
+
*
|
|
74
|
+
* `done` and `failed` are TERMINAL — once reached, status never
|
|
75
|
+
* changes again.
|
|
76
|
+
*/
|
|
77
|
+
export interface DetachHandle {
|
|
78
|
+
/** Stable id assigned at detach time. Used as the lookup key in
|
|
79
|
+
* `detachRegistry` and as the scope-storage key prefix. */
|
|
80
|
+
readonly id: string;
|
|
81
|
+
/** Current state. Read directly for sync access. */
|
|
82
|
+
readonly status: 'queued' | 'running' | 'done' | 'failed';
|
|
83
|
+
/** The work's result. Present iff `status === 'done'`. Reading
|
|
84
|
+
* before terminal returns `undefined`. */
|
|
85
|
+
readonly result?: unknown;
|
|
86
|
+
/** The work's error. Present iff `status === 'failed'`. Reading
|
|
87
|
+
* before terminal returns `undefined`. */
|
|
88
|
+
readonly error?: Error;
|
|
89
|
+
/**
|
|
90
|
+
* Opt-in async join. Returns a Promise that:
|
|
91
|
+
* - resolves with `{ result }` when status becomes 'done'
|
|
92
|
+
* - rejects with the captured `Error` when status becomes 'failed'
|
|
93
|
+
* - resolves/rejects IMMEDIATELY if status is already terminal
|
|
94
|
+
* - returns the SAME cached Promise on repeated calls (no
|
|
95
|
+
* re-running, no duplicated I/O)
|
|
96
|
+
*
|
|
97
|
+
* Calling `wait()` does NOT change the handle's lifecycle — it's
|
|
98
|
+
* passive observation. If never called, the work still completes;
|
|
99
|
+
* the handle just goes uncollected (parent execution unaffected).
|
|
100
|
+
*
|
|
101
|
+
* Use when:
|
|
102
|
+
* - You actually need the result and want to await it
|
|
103
|
+
* - Coordinating multiple handles via Promise.all / Promise.race
|
|
104
|
+
* - Backpressure ("don't fire more than N in flight")
|
|
105
|
+
*
|
|
106
|
+
* Don't use when:
|
|
107
|
+
* - Pure fire-and-forget (use `detachAndForget` — no handle returned)
|
|
108
|
+
* - You just want to check status (read `handle.status` directly)
|
|
109
|
+
*/
|
|
110
|
+
wait(): Promise<DetachWaitResult>;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Capabilities a driver declares. Drives the runtime decision of
|
|
114
|
+
* "is this driver appropriate for the current environment?" Consumers
|
|
115
|
+
* inspect at construction; the framework doesn't enforce.
|
|
116
|
+
*
|
|
117
|
+
* All capabilities are optional flags — false / undefined means "not
|
|
118
|
+
* supported / no claim." A driver that supports everything sets all
|
|
119
|
+
* to true.
|
|
120
|
+
*/
|
|
121
|
+
export interface DriverCapabilities {
|
|
122
|
+
/** Driver works in browser environments (window, document, etc.). */
|
|
123
|
+
readonly browserSafe?: boolean;
|
|
124
|
+
/** Driver works in Node.js environments. */
|
|
125
|
+
readonly nodeSafe?: boolean;
|
|
126
|
+
/** Driver works in edge runtimes (Cloudflare Workers, Deno Deploy,
|
|
127
|
+
* Bun edge, etc. — restricted environments). */
|
|
128
|
+
readonly edgeSafe?: boolean;
|
|
129
|
+
/** Work survives page-unload / process-exit. e.g.,
|
|
130
|
+
* `sendBeaconDriver` schedules via `navigator.sendBeacon` which
|
|
131
|
+
* ships even on tab close. */
|
|
132
|
+
readonly survivesUnload?: boolean;
|
|
133
|
+
/** Work runs on a separate OS thread (no event-loop block).
|
|
134
|
+
* e.g., `workerThreadDriver`. */
|
|
135
|
+
readonly cpuIsolated?: boolean;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* A driver is the WHEN/HOW of the detach. Maps `(child, input, refId)`
|
|
139
|
+
* to a `DetachHandle` whose lifecycle the driver owns.
|
|
140
|
+
*
|
|
141
|
+
* Drivers are themselves footprintjs primitives — internally they may
|
|
142
|
+
* be a single function or a multi-stage flowChart. Either way, the
|
|
143
|
+
* interface they expose to consumers is `schedule(...)`.
|
|
144
|
+
*
|
|
145
|
+
* Drivers MUST:
|
|
146
|
+
* - Return synchronously (the agent loop never blocks on schedule)
|
|
147
|
+
* - Not throw — errors during scheduling route through the handle
|
|
148
|
+
* (`handle.status = 'failed'`, `handle.error = ...`)
|
|
149
|
+
* - Honor the passive-recorder rule: the parent's `detach*` call
|
|
150
|
+
* never waits for the driver's deferred work to complete
|
|
151
|
+
*
|
|
152
|
+
* Drivers MAY:
|
|
153
|
+
* - Implement `validate()` for one-time configuration checks at
|
|
154
|
+
* registration / use time (e.g., assert `navigator.sendBeacon` exists)
|
|
155
|
+
* - Build their internal pipeline as a footprintjs flowChart for
|
|
156
|
+
* observability — driver implementation detail, not consumer-facing
|
|
157
|
+
*
|
|
158
|
+
* @example Built-in
|
|
159
|
+
* import { microtaskBatchDriver } from 'footprintjs/detach';
|
|
160
|
+
* const handle = driver.schedule(child, input, refId);
|
|
161
|
+
*
|
|
162
|
+
* @example Custom (BYOS)
|
|
163
|
+
* const lambdaExtensionDriver: DetachDriver = {
|
|
164
|
+
* name: 'lambda-extension',
|
|
165
|
+
* capabilities: { nodeSafe: true, survivesUnload: true },
|
|
166
|
+
* schedule(child, input, refId) {
|
|
167
|
+
* sharedBuffer.push({ refId, child, input });
|
|
168
|
+
* return createHandle(refId, 'queued');
|
|
169
|
+
* },
|
|
170
|
+
* };
|
|
171
|
+
*/
|
|
172
|
+
export interface DetachDriver {
|
|
173
|
+
/** Stable name for diagnostics + registry lookup. Conventionally
|
|
174
|
+
* algorithm-named (e.g. `'microtask-batch'`, `'send-beacon'`). */
|
|
175
|
+
readonly name: string;
|
|
176
|
+
/** What this driver supports. Used by consumers to pick the right
|
|
177
|
+
* driver for their environment. */
|
|
178
|
+
readonly capabilities: DriverCapabilities;
|
|
179
|
+
/**
|
|
180
|
+
* Hand the work to the driver's scheduling mechanism. MUST return
|
|
181
|
+
* synchronously with a fresh `DetachHandle`. The actual work may
|
|
182
|
+
* run later (next microtask / setImmediate / browser-beacon-flush /
|
|
183
|
+
* worker-thread / etc.) on the driver's chosen mechanism.
|
|
184
|
+
*/
|
|
185
|
+
schedule(child: FlowChart, input: unknown, refId: string): DetachHandle;
|
|
186
|
+
/**
|
|
187
|
+
* Optional one-time validation hook. Called at first use (or
|
|
188
|
+
* registration time, depending on driver) — drivers throw if their
|
|
189
|
+
* configuration is invalid (missing peer dep, unreachable endpoint,
|
|
190
|
+
* wrong API key shape, etc.).
|
|
191
|
+
*
|
|
192
|
+
* Example: `sendBeaconDriver.validate()` checks
|
|
193
|
+
* `typeof navigator?.sendBeacon === 'function'` and throws with a
|
|
194
|
+
* helpful message if absent (e.g., in Node).
|
|
195
|
+
*
|
|
196
|
+
* Per the New Relic panel review: early-fail-with-useful-message
|
|
197
|
+
* beats silent zero-emission.
|
|
198
|
+
*/
|
|
199
|
+
validate?(): void;
|
|
200
|
+
}
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
* Break semantics: If a stage calls breakFn(), commit and STOP.
|
|
19
19
|
* Patch model: Stage writes into local patch; commitPatch() after return or throw.
|
|
20
20
|
*/
|
|
21
|
-
/// <reference types="node" />
|
|
22
21
|
import type { ScopeProtectionMode } from '../../scope/protection/types.js';
|
|
23
22
|
import { FlowRecorderDispatcher } from '../narrative/FlowRecorderDispatcher.js';
|
|
24
23
|
import type { FlowRecorder, IControlFlowNarrative } from '../narrative/types.js';
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* Centralizes type definitions to avoid circular dependencies.
|
|
5
5
|
* Every handler receives HandlerDeps (the DI bag) instead of importing the traverser.
|
|
6
6
|
*/
|
|
7
|
-
/// <reference types="node" />
|
|
8
7
|
import type { StageContext } from '../memory/StageContext.js';
|
|
9
8
|
import type { FlowControlType, FlowMessage } from '../memory/types.js';
|
|
10
9
|
import type { ScopeProtectionMode } from '../scope/protection/types.js';
|
|
@@ -31,6 +31,8 @@ export interface ReactiveTarget {
|
|
|
31
31
|
addMetric(name: string, value: unknown): void;
|
|
32
32
|
addEval(name: string, value: unknown): void;
|
|
33
33
|
emitEvent(name: string, payload?: unknown): void;
|
|
34
|
+
detachAndJoinLater(driver: import('../detach/types.js').DetachDriver, child: import('../builder/types.js').FlowChart, input?: unknown): import('../detach/types.js').DetachHandle;
|
|
35
|
+
detachAndForget(driver: import('../detach/types.js').DetachDriver, child: import('../builder/types.js').FlowChart, input?: unknown): void;
|
|
34
36
|
}
|
|
35
37
|
export interface ScopeMethods {
|
|
36
38
|
$getValue(key: string): unknown;
|
|
@@ -122,6 +124,8 @@ export interface ScopeMethods {
|
|
|
122
124
|
* ```
|
|
123
125
|
*/
|
|
124
126
|
$emit(name: string, payload?: unknown): void;
|
|
127
|
+
$detachAndJoinLater(driver: import('../detach/types.js').DetachDriver, child: import('../builder/types.js').FlowChart, input?: unknown): import('../detach/types.js').DetachHandle;
|
|
128
|
+
$detachAndForget(driver: import('../detach/types.js').DetachDriver, child: import('../builder/types.js').FlowChart, input?: unknown): void;
|
|
125
129
|
/**
|
|
126
130
|
* Stop the current execution context.
|
|
127
131
|
*
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BoundaryStateTracker<TState> — third storage primitive on the recorder
|
|
3
|
+
* shelf, alongside `SequenceRecorder<T>` and `KeyedRecorder<T>`.
|
|
4
|
+
*
|
|
5
|
+
* **Mental model — observers vs. bookkeepers:**
|
|
6
|
+
*
|
|
7
|
+
* `Recorder` / `FlowRecorder` / `EmitRecorder` / `CombinedRecorder`
|
|
8
|
+
* are OBSERVER interfaces — they describe how a recorder hears
|
|
9
|
+
* events from the executor.
|
|
10
|
+
*
|
|
11
|
+
* `SequenceRecorder<T>` / `KeyedRecorder<T>` / `BoundaryStateTracker<TState>`
|
|
12
|
+
* are STORAGE primitives — three different bookkeeping shelves with
|
|
13
|
+
* different durability and indexing properties. A real recorder
|
|
14
|
+
* class typically picks ONE observer interface AND ONE storage
|
|
15
|
+
* shelf, combining them via `extends + implements`.
|
|
16
|
+
*
|
|
17
|
+
* **What this primitive answers:** "At any moment during the run, what
|
|
18
|
+
* is the LIVE transient state of every currently-active boundary?"
|
|
19
|
+
*
|
|
20
|
+
* A "boundary" is a matched event pair `[start, stop]` bracketing an
|
|
21
|
+
* interval — for example, `(llm_start, llm_end)` for an LLM call,
|
|
22
|
+
* `(tool_start, tool_end)` for tool execution, `(turn_start, turn_end)`
|
|
23
|
+
* for an agent turn. Between the brackets, intermediate events evolve
|
|
24
|
+
* the boundary's state (token chunks accumulating into a partial
|
|
25
|
+
* answer, args streaming in, etc.). On `stop`, the state clears.
|
|
26
|
+
*
|
|
27
|
+
* Algorithmically this is the **DFS bracket-sequence pattern** —
|
|
28
|
+
* stack-frame state during a graph-traversal interval. Same shape used
|
|
29
|
+
* by Tarjan's SCC algorithm, tree decomposition, and push-down
|
|
30
|
+
* automata. The `active` map is the open-brackets stack at any moment.
|
|
31
|
+
*
|
|
32
|
+
* **Comparison with the other storage primitives:**
|
|
33
|
+
*
|
|
34
|
+
* | Primitive | Stores | Time scope | Memory |
|
|
35
|
+
* |---------------------------------|--------------------------------------|---------------------|-------------|
|
|
36
|
+
* | `SequenceRecorder<T>` | append-only ordered + keyed entries | durable across run | O(N events) |
|
|
37
|
+
* | `KeyedRecorder<T>` | 1:1 entry per `runtimeStageId` | durable across run | O(N steps) |
|
|
38
|
+
* | `BoundaryStateTracker<TState>` | transient bracket-scoped state | live; clears on stop | O(K active) |
|
|
39
|
+
*
|
|
40
|
+
* **When to pick which:**
|
|
41
|
+
*
|
|
42
|
+
* - "I need to keep a permanent log of every event for time-travel"
|
|
43
|
+
* → `SequenceRecorder<T>`
|
|
44
|
+
* - "I want one durable record per stage execution (e.g., metrics)"
|
|
45
|
+
* → `KeyedRecorder<T>`
|
|
46
|
+
* - "I need to know what's happening RIGHT NOW inside an in-flight
|
|
47
|
+
* boundary (e.g., partial LLM stream, partial tool args)"
|
|
48
|
+
* → `BoundaryStateTracker<TState>` (this class)
|
|
49
|
+
*
|
|
50
|
+
* **What this is NOT for:**
|
|
51
|
+
*
|
|
52
|
+
* - Time-travel queries ("what was the state at past slider step N?")
|
|
53
|
+
* — transient state clears on stop. For time-travel, snapshot the
|
|
54
|
+
* state at each emit into a separate `SequenceRecorder<TState>`,
|
|
55
|
+
* or wait for a future `BoundarySnapshotRecorder<TState>` primitive.
|
|
56
|
+
*
|
|
57
|
+
* - Aggregations across the whole run (totals, counts) — those are
|
|
58
|
+
* `SequenceRecorder.aggregate()` / `KeyedRecorder.aggregate()`.
|
|
59
|
+
*
|
|
60
|
+
* - Stage-level concerns — those use `Recorder.onStageStart` /
|
|
61
|
+
* `Recorder.onStageEnd`. This primitive operates at finer
|
|
62
|
+
* granularity (events emitted DURING a stage execution).
|
|
63
|
+
*
|
|
64
|
+
* **Lifecycle contract — STRICT:**
|
|
65
|
+
*
|
|
66
|
+
* Every `startBoundary(key, ...)` call MUST be paired with a
|
|
67
|
+
* `stopBoundary(key)` call. Failure to wire the stop side produces a
|
|
68
|
+
* memory leak: the active map grows without bound, and `getAllActive()`
|
|
69
|
+
* returns stale entries that look in-flight but aren't. Common cause:
|
|
70
|
+
* subclass wires `start` to one event handler and forgets to wire
|
|
71
|
+
* `stop`. **Always wire both at the same time.**
|
|
72
|
+
*
|
|
73
|
+
* Dev mode (`enableDevMode()`) detects leaks at `clear()` time —
|
|
74
|
+
* warning includes the leaked keys so you can find the missing wiring.
|
|
75
|
+
*
|
|
76
|
+
* **Concurrency / nesting:**
|
|
77
|
+
*
|
|
78
|
+
* - Concurrent boundaries (parallel branches with two LLM calls
|
|
79
|
+
* active at once) work correctly: each is keyed independently in
|
|
80
|
+
* the active map.
|
|
81
|
+
* - Nested boundaries of DIFFERENT KINDS (Agent boundary contains
|
|
82
|
+
* LLM boundary) require SEPARATE tracker instances — one per kind.
|
|
83
|
+
* The base class tracks one boundary kind per instance.
|
|
84
|
+
*
|
|
85
|
+
* **Key convention:**
|
|
86
|
+
*
|
|
87
|
+
* The `key: string` is whatever your subclass picks. Convention:
|
|
88
|
+
* use `runtimeStageId` when the boundary maps 1:1 to a stage
|
|
89
|
+
* execution — this gives free interop with `SequenceRecorder
|
|
90
|
+
* .getEntriesForStep`, `KeyedRecorder.getByKey`, `findCommit` /
|
|
91
|
+
* `findLastWriter`, and the rest of the trace ecosystem. Use a more
|
|
92
|
+
* granular key (e.g., `toolCallId`) only when there are multiple
|
|
93
|
+
* concurrent boundaries WITHIN one stage execution.
|
|
94
|
+
*
|
|
95
|
+
* @example Build a live LLM tracker (combining storage + observer):
|
|
96
|
+
*
|
|
97
|
+
* ```typescript
|
|
98
|
+
* import {
|
|
99
|
+
* BoundaryStateTracker,
|
|
100
|
+
* type CombinedRecorder,
|
|
101
|
+
* type EmitEvent,
|
|
102
|
+
* } from 'footprintjs';
|
|
103
|
+
*
|
|
104
|
+
* interface LLMLiveState {
|
|
105
|
+
* readonly partial: string;
|
|
106
|
+
* readonly tokens: number;
|
|
107
|
+
* }
|
|
108
|
+
*
|
|
109
|
+
* class LiveLLMTracker
|
|
110
|
+
* extends BoundaryStateTracker<LLMLiveState> // STORAGE shelf
|
|
111
|
+
* implements CombinedRecorder // OBSERVER interface
|
|
112
|
+
* {
|
|
113
|
+
* readonly id = 'live-llm';
|
|
114
|
+
*
|
|
115
|
+
* // Observer half — translate events into bracket mutations.
|
|
116
|
+
* onEmit(event: EmitEvent): void {
|
|
117
|
+
* if (event.name === 'agentfootprint.stream.llm_start') {
|
|
118
|
+
* this.startBoundary(event.runtimeStageId, { partial: '', tokens: 0 });
|
|
119
|
+
* } else if (event.name === 'agentfootprint.stream.llm_end') {
|
|
120
|
+
* this.stopBoundary(event.runtimeStageId);
|
|
121
|
+
* } else if (event.name === 'agentfootprint.stream.token') {
|
|
122
|
+
* this.updateBoundary(event.runtimeStageId, (s) => ({
|
|
123
|
+
* partial: s.partial + (event.payload as { content: string }).content,
|
|
124
|
+
* tokens: s.tokens + 1,
|
|
125
|
+
* }));
|
|
126
|
+
* }
|
|
127
|
+
* }
|
|
128
|
+
*
|
|
129
|
+
* // Public read API — O(1) at any moment.
|
|
130
|
+
* isInFlight(): boolean { return this.hasActive; }
|
|
131
|
+
* getPartial(stageId: string): string {
|
|
132
|
+
* return this.getActive(stageId)?.partial ?? '';
|
|
133
|
+
* }
|
|
134
|
+
* }
|
|
135
|
+
*
|
|
136
|
+
* // Attached the same way as any other CombinedRecorder.
|
|
137
|
+
* const tracker = new LiveLLMTracker();
|
|
138
|
+
* executor.attachCombinedRecorder(tracker);
|
|
139
|
+
* await executor.run({ input });
|
|
140
|
+
*
|
|
141
|
+
* // Read live state at any time during or after the run.
|
|
142
|
+
* tracker.isInFlight();
|
|
143
|
+
* tracker.getPartial(rid);
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
export declare abstract class BoundaryStateTracker<TState> {
|
|
147
|
+
/** Stable id — same idempotency contract as other recorders. */
|
|
148
|
+
abstract readonly id: string;
|
|
149
|
+
/** Open-brackets stack: key → current transient state. */
|
|
150
|
+
private readonly active;
|
|
151
|
+
/** Per-key count of `updateBoundary` calls that landed without a
|
|
152
|
+
* matching active boundary. Drives rate-limited dev-mode warnings
|
|
153
|
+
* so a stuck loop doesn't spam the console. */
|
|
154
|
+
private readonly missedUpdates;
|
|
155
|
+
/**
|
|
156
|
+
* Open a new boundary with initial transient state.
|
|
157
|
+
*
|
|
158
|
+
* If a boundary with the same `key` is already active, the prior
|
|
159
|
+
* state is overwritten (last-writer-wins). In dev mode, a warning
|
|
160
|
+
* is logged because re-starting an active key usually indicates a
|
|
161
|
+
* missed `stopBoundary` call upstream.
|
|
162
|
+
*
|
|
163
|
+
* @param key Boundary identifier — by convention, `runtimeStageId`
|
|
164
|
+
* for 1:1-with-stage boundaries, or a more granular id
|
|
165
|
+
* (e.g., `toolCallId`) when multiple concurrent boundaries
|
|
166
|
+
* run within one stage execution.
|
|
167
|
+
* @param initial Initial state — typed by `TState`.
|
|
168
|
+
*/
|
|
169
|
+
protected startBoundary(key: string, initial: TState): void;
|
|
170
|
+
/**
|
|
171
|
+
* Evolve the transient state of an active boundary using an updater
|
|
172
|
+
* function. Silent no-op if no boundary is active for `key`
|
|
173
|
+
* (defensive against out-of-order events). In dev mode, a rate-limited
|
|
174
|
+
* warning is logged on the 1st, 10th, and 100th missed update per key.
|
|
175
|
+
*
|
|
176
|
+
* @param key Boundary identifier (must match a prior `startBoundary`).
|
|
177
|
+
* @param updater Pure function: previous state → next state.
|
|
178
|
+
*/
|
|
179
|
+
protected updateBoundary(key: string, updater: (prev: TState) => TState): void;
|
|
180
|
+
/**
|
|
181
|
+
* Close the boundary identified by `key` and return its FINAL
|
|
182
|
+
* transient state (for any cleanup the subclass wants — e.g., emit a
|
|
183
|
+
* snapshot to a SequenceRecorder for durable storage).
|
|
184
|
+
*
|
|
185
|
+
* @param key Boundary identifier.
|
|
186
|
+
* @returns The final state, or `undefined` if no boundary was active.
|
|
187
|
+
*/
|
|
188
|
+
protected stopBoundary(key: string): TState | undefined;
|
|
189
|
+
/** Current transient state of ONE active boundary. `undefined` if no
|
|
190
|
+
* boundary is active for `key`. */
|
|
191
|
+
getActive(key: string): TState | undefined;
|
|
192
|
+
/**
|
|
193
|
+
* All currently-active boundaries.
|
|
194
|
+
*
|
|
195
|
+
* **Type-only readonly:** the returned reference IS the internal Map.
|
|
196
|
+
* TypeScript prevents mutation through the `ReadonlyMap` type, but a
|
|
197
|
+
* runtime cast or non-TS consumer can mutate it and corrupt internal
|
|
198
|
+
* state. **Do not mutate.**
|
|
199
|
+
*/
|
|
200
|
+
getAllActive(): ReadonlyMap<string, TState>;
|
|
201
|
+
/** True if any boundary is currently active. O(1). */
|
|
202
|
+
get hasActive(): boolean;
|
|
203
|
+
/** Number of currently-active boundaries. O(1). */
|
|
204
|
+
get activeCount(): number;
|
|
205
|
+
/**
|
|
206
|
+
* Reset all transient state. Called by executors before each `run()`
|
|
207
|
+
* so consumers get a clean slate per run — same lifecycle contract
|
|
208
|
+
* as `SequenceRecorder.clear()`.
|
|
209
|
+
*
|
|
210
|
+
* In dev mode, warns if any boundaries are still active when called
|
|
211
|
+
* — likely indicates a missed `stopBoundary` upstream. The leaked
|
|
212
|
+
* keys are listed (truncated to 10) so the wiring bug is findable.
|
|
213
|
+
*/
|
|
214
|
+
clear(): void;
|
|
215
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { BoundaryStateTracker } from './BoundaryStateTracker.js';
|
|
1
2
|
export type { CombinedRecorder } from './CombinedRecorder.js';
|
|
2
3
|
export { hasEmitRecorderMethods, hasFlowRecorderMethods, hasRecorderMethods, isFlowEvent } from './CombinedRecorder.js';
|
|
3
4
|
export type { CompositeSnapshot } from './CompositeRecorder.js';
|
|
@@ -228,6 +228,34 @@ export declare class FlowChartExecutor<TOut = any, TScope = any> {
|
|
|
228
228
|
* ```
|
|
229
229
|
*/
|
|
230
230
|
attachRecorder(recorder: Recorder): void;
|
|
231
|
+
/**
|
|
232
|
+
* Detach a child flowchart on the given driver and return a `DetachHandle`
|
|
233
|
+
* the caller can `wait()` on (Promise) or read `.status` from (sync).
|
|
234
|
+
*
|
|
235
|
+
* The driver is a REQUIRED first argument — there is no library-default,
|
|
236
|
+
* to keep the engine free of driver imports and to make the choice of
|
|
237
|
+
* scheduling algorithm explicit at the call site.
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```typescript
|
|
241
|
+
* import { microtaskBatchDriver } from 'footprintjs/detach';
|
|
242
|
+
*
|
|
243
|
+
* const exec = new FlowChartExecutor(parentChart);
|
|
244
|
+
* const handle = exec.detachAndJoinLater(microtaskBatchDriver, telemetryChart, { event: 'x' });
|
|
245
|
+
* await handle.wait(); // optional
|
|
246
|
+
* ```
|
|
247
|
+
*/
|
|
248
|
+
detachAndJoinLater(driver: import('../detach/types.js').DetachDriver, child: import('../builder/types.js').FlowChart, input?: unknown): import('../detach/types.js').DetachHandle;
|
|
249
|
+
/**
|
|
250
|
+
* Detach a child flowchart on the given driver and DISCARD the handle.
|
|
251
|
+
* Use for telemetry exports / fire-and-forget side effects where the
|
|
252
|
+
* caller doesn't care about the result.
|
|
253
|
+
*
|
|
254
|
+
* Errors raised by the child still land on the (discarded) handle — they
|
|
255
|
+
* go silent unless surfaced through a recorder. For observable detach,
|
|
256
|
+
* prefer `detachAndJoinLater` and surface failures via `.wait().catch()`.
|
|
257
|
+
*/
|
|
258
|
+
detachAndForget(driver: import('../detach/types.js').DetachDriver, child: import('../builder/types.js').FlowChart, input?: unknown): void;
|
|
231
259
|
/** Detach all scope Recorders with the given ID. */
|
|
232
260
|
detachRecorder(id: string): void;
|
|
233
261
|
/** Returns a defensive copy of attached scope Recorders. */
|
|
@@ -100,6 +100,10 @@ export declare class ScopeFacade {
|
|
|
100
100
|
* the method routes here via `createTypedScope`.
|
|
101
101
|
*/
|
|
102
102
|
emitEvent(name: string, payload: unknown): void;
|
|
103
|
+
/** See `ScopeMethods.$detachAndJoinLater`. */
|
|
104
|
+
detachAndJoinLater(driver: import('../detach/types.js').DetachDriver, child: import('../builder/types.js').FlowChart, input?: unknown): import('../detach/types.js').DetachHandle;
|
|
105
|
+
/** See `ScopeMethods.$detachAndForget`. */
|
|
106
|
+
detachAndForget(driver: import('../detach/types.js').DetachDriver, child: import('../builder/types.js').FlowChart, input?: unknown): void;
|
|
103
107
|
/**
|
|
104
108
|
* Build the subflowPath (outer → inner) for event enrichment.
|
|
105
109
|
*
|
|
@@ -6,7 +6,19 @@
|
|
|
6
6
|
import { type ZodRecord, type ZodTypeAny } from 'zod';
|
|
7
7
|
/** Check if the value is a Zod schema node. */
|
|
8
8
|
export declare function isZodNode(x: unknown): x is ZodTypeAny;
|
|
9
|
-
/** Peel wrappers; returns the underlying base Zod node (or null).
|
|
9
|
+
/** Peel wrappers; returns the underlying base Zod node (or null).
|
|
10
|
+
*
|
|
11
|
+
* Wrapper-aware: only descends through fields that are KNOWN to hold
|
|
12
|
+
* the inner schema for wrapper Zod types (Optional, Default, Nullable,
|
|
13
|
+
* Effects/Pipeline). Notably, `_def.type` is treated as the inner
|
|
14
|
+
* schema ONLY for v3 Effects/Pipeline — it is NOT the inner schema
|
|
15
|
+
* for ZodArray (where `_def.type` holds the ELEMENT schema, which is
|
|
16
|
+
* a separate concern from wrapper unwrapping).
|
|
17
|
+
*
|
|
18
|
+
* Without this gate, `unwrap(z.array(z.string()))` would incorrectly
|
|
19
|
+
* follow `_def.type` and return `ZodString`, breaking array detection
|
|
20
|
+
* in `scopeFactory.analyze()`.
|
|
21
|
+
*/
|
|
10
22
|
export declare function unwrap(schema: ZodTypeAny | null | undefined): ZodTypeAny | null;
|
|
11
23
|
/** Version-tolerant access to ZodRecord value schema. */
|
|
12
24
|
export declare function getRecordValueType(rec: ZodRecord<any, any>): ZodTypeAny | null;
|
package/dist/types/trace.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export type { CausalChainOptions, CausalNode, KeysReadLookup } from './lib/memor
|
|
|
27
27
|
export { causalChain, flattenCausalDAG, formatCausalChain } from './lib/memory/backtrack.js';
|
|
28
28
|
export { KeyedRecorder } from './lib/recorder/KeyedRecorder.js';
|
|
29
29
|
export { SequenceRecorder } from './lib/recorder/SequenceRecorder.js';
|
|
30
|
+
export { BoundaryStateTracker } from './lib/recorder/BoundaryStateTracker.js';
|
|
30
31
|
export type { Topology, TopologyEdge, TopologyIncomingKind, TopologyNode, TopologyRecorderOptions, } from './lib/recorder/TopologyRecorder.js';
|
|
31
32
|
export { TopologyRecorder, topologyRecorder } from './lib/recorder/TopologyRecorder.js';
|
|
32
33
|
export type { InOutEntry, InOutPhase, InOutRecorderOptions } from './lib/recorder/InOutRecorder.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "footprintjs",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.17.2",
|
|
4
4
|
"description": "Explainable backend flows — automatic causal traces, decision evidence, and MCP tool generation for AI agents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Sanjay Krishna Anbalagan",
|
|
@@ -88,6 +88,11 @@
|
|
|
88
88
|
"types": "./dist/types/trace.d.ts",
|
|
89
89
|
"import": "./dist/esm/trace.js",
|
|
90
90
|
"require": "./dist/trace.js"
|
|
91
|
+
},
|
|
92
|
+
"./detach": {
|
|
93
|
+
"types": "./dist/types/detach.d.ts",
|
|
94
|
+
"import": "./dist/esm/detach.js",
|
|
95
|
+
"require": "./dist/detach.js"
|
|
91
96
|
}
|
|
92
97
|
},
|
|
93
98
|
"lint-staged": {
|