@framers/agentos 0.1.112 → 0.1.113
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/dist/api/strategies/debate.d.ts +12 -1
- package/dist/api/strategies/debate.d.ts.map +1 -1
- package/dist/api/strategies/debate.js +41 -5
- package/dist/api/strategies/debate.js.map +1 -1
- package/dist/api/strategies/hierarchical.d.ts +15 -1
- package/dist/api/strategies/hierarchical.d.ts.map +1 -1
- package/dist/api/strategies/hierarchical.js +51 -7
- package/dist/api/strategies/hierarchical.js.map +1 -1
- package/dist/api/strategies/index.d.ts +26 -4
- package/dist/api/strategies/index.d.ts.map +1 -1
- package/dist/api/strategies/index.js +26 -4
- package/dist/api/strategies/index.js.map +1 -1
- package/dist/api/strategies/parallel.d.ts +15 -4
- package/dist/api/strategies/parallel.d.ts.map +1 -1
- package/dist/api/strategies/parallel.js +53 -16
- package/dist/api/strategies/parallel.js.map +1 -1
- package/dist/api/strategies/review-loop.d.ts +15 -1
- package/dist/api/strategies/review-loop.d.ts.map +1 -1
- package/dist/api/strategies/review-loop.js +36 -10
- package/dist/api/strategies/review-loop.js.map +1 -1
- package/dist/api/strategies/sequential.d.ts +11 -1
- package/dist/api/strategies/sequential.d.ts.map +1 -1
- package/dist/api/strategies/sequential.js +39 -8
- package/dist/api/strategies/sequential.js.map +1 -1
- package/dist/api/strategies/shared.d.ts +71 -7
- package/dist/api/strategies/shared.d.ts.map +1 -1
- package/dist/api/strategies/shared.js +89 -10
- package/dist/api/strategies/shared.js.map +1 -1
- package/dist/api/types.d.ts +54 -1
- package/dist/api/types.d.ts.map +1 -1
- package/dist/api/types.js.map +1 -1
- package/dist/memory/facade/Memory.d.ts.map +1 -1
- package/dist/memory/facade/Memory.js +8 -0
- package/dist/memory/facade/Memory.js.map +1 -1
- package/dist/memory/facade/types.d.ts +10 -0
- package/dist/memory/facade/types.d.ts.map +1 -1
- package/dist/memory/index.d.ts +6 -0
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +5 -0
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/observation/MemoryObserver.d.ts +63 -1
- package/dist/memory/observation/MemoryObserver.d.ts.map +1 -1
- package/dist/memory/observation/MemoryObserver.js +115 -4
- package/dist/memory/observation/MemoryObserver.js.map +1 -1
- package/dist/memory/observation/ObservationCompressor.d.ts +88 -0
- package/dist/memory/observation/ObservationCompressor.d.ts.map +1 -0
- package/dist/memory/observation/ObservationCompressor.js +207 -0
- package/dist/memory/observation/ObservationCompressor.js.map +1 -0
- package/dist/memory/observation/ObservationReflector.d.ts +82 -0
- package/dist/memory/observation/ObservationReflector.d.ts.map +1 -0
- package/dist/memory/observation/ObservationReflector.js +212 -0
- package/dist/memory/observation/ObservationReflector.js.map +1 -0
- package/dist/memory/observation/temporal.d.ts +54 -0
- package/dist/memory/observation/temporal.d.ts.map +1 -0
- package/dist/memory/observation/temporal.js +115 -0
- package/dist/memory/observation/temporal.js.map +1 -0
- package/dist/orchestration/builders/VoiceNodeBuilder.d.ts +82 -25
- package/dist/orchestration/builders/VoiceNodeBuilder.d.ts.map +1 -1
- package/dist/orchestration/builders/VoiceNodeBuilder.js +86 -26
- package/dist/orchestration/builders/VoiceNodeBuilder.js.map +1 -1
- package/dist/orchestration/events/GraphEvent.d.ts +67 -5
- package/dist/orchestration/events/GraphEvent.d.ts.map +1 -1
- package/dist/orchestration/events/GraphEvent.js.map +1 -1
- package/dist/orchestration/runtime/VoiceNodeExecutor.d.ts +102 -25
- package/dist/orchestration/runtime/VoiceNodeExecutor.d.ts.map +1 -1
- package/dist/orchestration/runtime/VoiceNodeExecutor.js +133 -38
- package/dist/orchestration/runtime/VoiceNodeExecutor.js.map +1 -1
- package/dist/orchestration/runtime/VoiceTransportAdapter.d.ts +94 -32
- package/dist/orchestration/runtime/VoiceTransportAdapter.d.ts.map +1 -1
- package/dist/orchestration/runtime/VoiceTransportAdapter.js +82 -28
- package/dist/orchestration/runtime/VoiceTransportAdapter.js.map +1 -1
- package/dist/orchestration/runtime/VoiceTurnCollector.d.ts +73 -20
- package/dist/orchestration/runtime/VoiceTurnCollector.d.ts.map +1 -1
- package/dist/orchestration/runtime/VoiceTurnCollector.js +84 -23
- package/dist/orchestration/runtime/VoiceTurnCollector.js.map +1 -1
- package/package.json +1 -1
|
@@ -5,18 +5,31 @@
|
|
|
5
5
|
* multiple exit conditions (hangup, turns exhausted, keyword, silence timeout,
|
|
6
6
|
* barge-in abort) to determine when the voice node completes.
|
|
7
7
|
*
|
|
8
|
+
* ## Design rationale
|
|
9
|
+
*
|
|
8
10
|
* The executor follows the standard 2-arg `execute(node, state)` contract used by
|
|
9
|
-
* {@link NodeExecutor}.
|
|
11
|
+
* {@link NodeExecutor}. Internally it creates an `AbortController` for barge-in
|
|
10
12
|
* support and optionally merges a parent abort signal from `state.scratch.abortSignal`.
|
|
11
13
|
*
|
|
14
|
+
* Exit conditions are modelled as a **single `Promise.race`** rather than a state
|
|
15
|
+
* machine because the conditions are orthogonal and any one of them can fire at any
|
|
16
|
+
* time. The `settled` flag inside {@link raceExitConditions} guards against
|
|
17
|
+
* double-resolution when two conditions fire within the same microtask.
|
|
18
|
+
*
|
|
19
|
+
* ## State contract
|
|
20
|
+
*
|
|
12
21
|
* Voice transport and session references are expected in `state.scratch`:
|
|
13
|
-
* - `voiceTransport`
|
|
14
|
-
* - `voiceTransport._voiceSession`
|
|
22
|
+
* - `voiceTransport` -- the bidirectional transport EventEmitter (emits `close` / `disconnected`).
|
|
23
|
+
* - `voiceTransport._voiceSession` -- the voice pipeline session EventEmitter that fires
|
|
15
24
|
* `final_transcript`, `turn_complete`, `speech_start`, and `barge_in` events.
|
|
16
25
|
*
|
|
17
26
|
* Checkpoint data is stored in `state.scratch[nodeId]` as a {@link VoiceNodeCheckpoint},
|
|
18
27
|
* enabling the graph runtime to resume a voice session from the exact turn index where
|
|
19
28
|
* it was previously suspended.
|
|
29
|
+
*
|
|
30
|
+
* @see {@link VoiceTurnCollector} for transcript buffering and event bridging.
|
|
31
|
+
* @see {@link VoiceTransportAdapter} for how graph I/O is wrapped at the transport level.
|
|
32
|
+
* @see {@link VoiceInterruptError} for the structured barge-in error type.
|
|
20
33
|
*/
|
|
21
34
|
import type { GraphNode, GraphState, VoiceNodeConfig } from '../ir/types.js';
|
|
22
35
|
import type { GraphEvent } from '../events/GraphEvent.js';
|
|
@@ -27,21 +40,44 @@ import type { NodeExecutionResult } from './NodeExecutor.js';
|
|
|
27
40
|
* The graph runtime persists this structure so that a subsequent invocation of the
|
|
28
41
|
* same voice node (e.g. after a graph loop or checkpoint restore) can continue the
|
|
29
42
|
* conversation from `turnIndex` rather than resetting to zero.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* // Restoring from a checkpoint:
|
|
47
|
+
* const checkpoint = state.scratch['voice-1'] as VoiceNodeCheckpoint;
|
|
48
|
+
* const resumedTurnIndex = checkpoint.turnIndex; // e.g. 5
|
|
49
|
+
* ```
|
|
30
50
|
*/
|
|
31
51
|
export interface VoiceNodeCheckpoint {
|
|
32
52
|
/** Number of turns completed when the checkpoint was captured. */
|
|
33
53
|
turnIndex: number;
|
|
34
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* Full transcript buffer at the time of checkpoint.
|
|
56
|
+
*
|
|
57
|
+
* Each entry records a confirmed (final) utterance with its speaker label
|
|
58
|
+
* and wall-clock timestamp, preserving the full conversation history for
|
|
59
|
+
* downstream summarisation or analytics.
|
|
60
|
+
*/
|
|
35
61
|
transcript: Array<{
|
|
36
62
|
speaker: string;
|
|
37
63
|
text: string;
|
|
38
64
|
timestamp: number;
|
|
39
65
|
}>;
|
|
40
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* Exit reason that caused the voice node to complete.
|
|
68
|
+
* `null` when the checkpoint was captured mid-session (e.g. process crash).
|
|
69
|
+
*/
|
|
41
70
|
lastExitReason: string | null;
|
|
42
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* Maps diarization speaker labels to human-readable names.
|
|
73
|
+
* Reserved for future use -- populated as an empty object today.
|
|
74
|
+
*/
|
|
43
75
|
speakerMap: Record<string, string>;
|
|
44
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* The voice config that was active when this checkpoint was created.
|
|
78
|
+
* Stored so that a resumed session can verify config compatibility before
|
|
79
|
+
* continuing from the persisted turn index.
|
|
80
|
+
*/
|
|
45
81
|
sessionConfig: VoiceNodeConfig;
|
|
46
82
|
}
|
|
47
83
|
/**
|
|
@@ -49,55 +85,96 @@ export interface VoiceNodeCheckpoint {
|
|
|
49
85
|
* multiple exit conditions to determine when the node is done.
|
|
50
86
|
*
|
|
51
87
|
* Exit conditions are evaluated concurrently via a single `Promise` race:
|
|
52
|
-
* - **Hangup**
|
|
53
|
-
* - **Turns exhausted**
|
|
88
|
+
* - **Hangup** -- transport emits `close` or `disconnected`.
|
|
89
|
+
* - **Turns exhausted** -- session emits `turn_complete` and the collector's count
|
|
54
90
|
* reaches `config.maxTurns`.
|
|
55
|
-
* - **Keyword**
|
|
56
|
-
* - **Silence timeout**
|
|
57
|
-
* - **Abort/barge-in**
|
|
91
|
+
* - **Keyword** -- a `final_transcript` event contains one of `config.exitKeywords`.
|
|
92
|
+
* - **Silence timeout** -- no speech activity for 30 seconds (when `exitOn: 'silence-timeout'`).
|
|
93
|
+
* - **Abort/barge-in** -- the internal `AbortController` is signalled, either by a
|
|
58
94
|
* parent abort signal or a `VoiceInterruptError`.
|
|
59
95
|
*
|
|
60
96
|
* @example
|
|
61
97
|
* ```ts
|
|
62
98
|
* const executor = new VoiceNodeExecutor((event) => emitter.emit(event));
|
|
63
99
|
* const result = await executor.execute(voiceNode, graphState);
|
|
64
|
-
* console.log(result.output.exitReason);
|
|
100
|
+
* console.log(result.output.exitReason);
|
|
101
|
+
* // 'turns-exhausted' | 'hangup' | 'keyword:goodbye' | 'silence-timeout' | 'interrupted'
|
|
65
102
|
* ```
|
|
103
|
+
*
|
|
104
|
+
* @see {@link VoiceTurnCollector} -- subscribes to session events and buffers transcript.
|
|
105
|
+
* @see {@link VoiceInterruptError} -- structured barge-in error that triggers the `interrupted` path.
|
|
66
106
|
*/
|
|
67
107
|
export declare class VoiceNodeExecutor {
|
|
68
108
|
private readonly eventSink;
|
|
69
109
|
/**
|
|
110
|
+
* Creates a new VoiceNodeExecutor.
|
|
111
|
+
*
|
|
70
112
|
* @param eventSink - Callback invoked synchronously for every emitted {@link GraphEvent}.
|
|
71
|
-
* Typically bound to the graph runtime's event emitter
|
|
113
|
+
* Typically bound to the graph runtime's event emitter so that
|
|
114
|
+
* voice lifecycle events (`voice_session`, `voice_transcript`, etc.)
|
|
115
|
+
* are visible to all graph event consumers.
|
|
72
116
|
*/
|
|
73
117
|
constructor(eventSink: (event: GraphEvent) => void);
|
|
74
118
|
/**
|
|
75
119
|
* Execute a voice node. Matches the standard 2-arg `execute(node, state)` signature
|
|
76
120
|
* used throughout the orchestration runtime.
|
|
77
121
|
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
122
|
+
* ## Lifecycle
|
|
123
|
+
*
|
|
124
|
+
* 1. Validates that `node.executorConfig.type` is `'voice'`.
|
|
125
|
+
* 2. Creates an internal `AbortController` for barge-in, wiring it to any parent
|
|
126
|
+
* abort signal in `state.scratch.abortSignal`.
|
|
127
|
+
* 3. Extracts the `voiceTransport` from `state.scratch` (must be pre-placed by
|
|
128
|
+
* the graph runtime or {@link VoiceTransportAdapter}).
|
|
129
|
+
* 4. Checks for a {@link VoiceNodeCheckpoint} to resume from.
|
|
130
|
+
* 5. Emits a `voice_session` started event.
|
|
131
|
+
* 6. Wires a {@link VoiceTurnCollector} onto the session and races exit conditions.
|
|
132
|
+
* 7. Resolves the exit reason to a route target via the node's edge map.
|
|
133
|
+
* 8. Returns a {@link NodeExecutionResult} with transcript, exit reason, checkpoint,
|
|
134
|
+
* and optional route target.
|
|
81
135
|
*
|
|
82
136
|
* @param node - Immutable voice node descriptor from the compiled graph IR.
|
|
137
|
+
* Must have `executorConfig.type === 'voice'`.
|
|
83
138
|
* @param state - Current (partial) graph state threaded from the runtime.
|
|
139
|
+
* Must contain `scratch.voiceTransport` for the voice session.
|
|
84
140
|
* @returns A {@link NodeExecutionResult} with transcript, exit reason, and optional route target.
|
|
141
|
+
* On success, `output` contains `{ transcript, turns, exitReason, lastSpeaker, interruptedText }`.
|
|
142
|
+
* `scratchUpdate` carries the {@link VoiceNodeCheckpoint} keyed by node id.
|
|
143
|
+
* @throws Never -- all errors are caught and returned as `{ success: false, error }`.
|
|
144
|
+
* {@link VoiceInterruptError} is caught and mapped to `exitReason: 'interrupted'`.
|
|
145
|
+
*
|
|
146
|
+
* @see {@link raceExitConditions} for the concurrent exit condition implementation.
|
|
85
147
|
*/
|
|
86
148
|
execute(node: GraphNode, state: Partial<GraphState>): Promise<NodeExecutionResult>;
|
|
87
149
|
/**
|
|
88
150
|
* Races all configured exit conditions against each other and resolves with
|
|
89
151
|
* the first one that fires.
|
|
90
152
|
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
153
|
+
* ## How it works
|
|
154
|
+
*
|
|
155
|
+
* Each exit condition is wired as a listener on either the `session` or
|
|
156
|
+
* `transport` EventEmitter. All listeners call a shared `settleWith()` helper
|
|
157
|
+
* that resolves the outer Promise exactly once (guarded by a `settled` boolean).
|
|
158
|
+
*
|
|
159
|
+
* The `AbortController` signal is also monitored -- if it fires with a
|
|
160
|
+
* {@link VoiceInterruptError} the Promise rejects (handled by the caller's
|
|
161
|
+
* catch block), otherwise it resolves with `{ reason: 'interrupted' }`.
|
|
162
|
+
*
|
|
163
|
+
* ## Why a Promise race instead of a state machine?
|
|
164
|
+
*
|
|
165
|
+
* The exit conditions are independent and asynchronous. A Promise-based race
|
|
166
|
+
* avoids complex state transitions and lets each condition be a simple
|
|
167
|
+
* event listener. The `settled` guard handles the only tricky case: two
|
|
168
|
+
* conditions firing in the same microtask.
|
|
95
169
|
*
|
|
96
|
-
* @param session - Voice pipeline session EventEmitter
|
|
97
|
-
*
|
|
98
|
-
* @param
|
|
170
|
+
* @param session - Voice pipeline session EventEmitter that fires
|
|
171
|
+
* `turn_complete`, `final_transcript`, `speech_start`, `barge_in`.
|
|
172
|
+
* @param collector - Active turn collector tracking turn count and transcript.
|
|
173
|
+
* @param config - Voice node configuration with exit settings (`maxTurns`,
|
|
174
|
+
* `exitOn`, `exitKeywords`).
|
|
99
175
|
* @param controller - Internal AbortController for barge-in signalling.
|
|
100
|
-
* @param transport - Bidirectional transport EventEmitter
|
|
176
|
+
* @param transport - Bidirectional transport EventEmitter that fires `close`
|
|
177
|
+
* and `disconnected` on hangup.
|
|
101
178
|
* @returns The winning exit condition's reason string and optional interrupted text.
|
|
102
179
|
*/
|
|
103
180
|
private raceExitConditions;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VoiceNodeExecutor.d.ts","sourceRoot":"","sources":["../../../src/orchestration/runtime/VoiceNodeExecutor.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"VoiceNodeExecutor.d.ts","sourceRoot":"","sources":["../../../src/orchestration/runtime/VoiceNodeExecutor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC7E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAQ7D;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,mBAAmB;IAClC,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;IAElB;;;;;;OAMG;IACH,UAAU,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAExE;;;OAGG;IACH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAE9B;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEnC;;;;OAIG;IACH,aAAa,EAAE,eAAe,CAAC;CAChC;AAMD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,iBAAiB;IAU1B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAT5B;;;;;;;OAOG;gBAEgB,SAAS,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI;IAGzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACG,OAAO,CACX,IAAI,EAAE,SAAS,EACf,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,GACzB,OAAO,CAAC,mBAAmB,CAAC;IAiI/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;YACW,kBAAkB;CA+FjC"}
|
|
@@ -5,18 +5,31 @@
|
|
|
5
5
|
* multiple exit conditions (hangup, turns exhausted, keyword, silence timeout,
|
|
6
6
|
* barge-in abort) to determine when the voice node completes.
|
|
7
7
|
*
|
|
8
|
+
* ## Design rationale
|
|
9
|
+
*
|
|
8
10
|
* The executor follows the standard 2-arg `execute(node, state)` contract used by
|
|
9
|
-
* {@link NodeExecutor}.
|
|
11
|
+
* {@link NodeExecutor}. Internally it creates an `AbortController` for barge-in
|
|
10
12
|
* support and optionally merges a parent abort signal from `state.scratch.abortSignal`.
|
|
11
13
|
*
|
|
14
|
+
* Exit conditions are modelled as a **single `Promise.race`** rather than a state
|
|
15
|
+
* machine because the conditions are orthogonal and any one of them can fire at any
|
|
16
|
+
* time. The `settled` flag inside {@link raceExitConditions} guards against
|
|
17
|
+
* double-resolution when two conditions fire within the same microtask.
|
|
18
|
+
*
|
|
19
|
+
* ## State contract
|
|
20
|
+
*
|
|
12
21
|
* Voice transport and session references are expected in `state.scratch`:
|
|
13
|
-
* - `voiceTransport`
|
|
14
|
-
* - `voiceTransport._voiceSession`
|
|
22
|
+
* - `voiceTransport` -- the bidirectional transport EventEmitter (emits `close` / `disconnected`).
|
|
23
|
+
* - `voiceTransport._voiceSession` -- the voice pipeline session EventEmitter that fires
|
|
15
24
|
* `final_transcript`, `turn_complete`, `speech_start`, and `barge_in` events.
|
|
16
25
|
*
|
|
17
26
|
* Checkpoint data is stored in `state.scratch[nodeId]` as a {@link VoiceNodeCheckpoint},
|
|
18
27
|
* enabling the graph runtime to resume a voice session from the exact turn index where
|
|
19
28
|
* it was previously suspended.
|
|
29
|
+
*
|
|
30
|
+
* @see {@link VoiceTurnCollector} for transcript buffering and event bridging.
|
|
31
|
+
* @see {@link VoiceTransportAdapter} for how graph I/O is wrapped at the transport level.
|
|
32
|
+
* @see {@link VoiceInterruptError} for the structured barge-in error type.
|
|
20
33
|
*/
|
|
21
34
|
import { EventEmitter } from 'events';
|
|
22
35
|
import { VoiceTurnCollector } from './VoiceTurnCollector.js';
|
|
@@ -29,25 +42,33 @@ import { VoiceInterruptError } from '../../voice-pipeline/VoiceInterruptError.js
|
|
|
29
42
|
* multiple exit conditions to determine when the node is done.
|
|
30
43
|
*
|
|
31
44
|
* Exit conditions are evaluated concurrently via a single `Promise` race:
|
|
32
|
-
* - **Hangup**
|
|
33
|
-
* - **Turns exhausted**
|
|
45
|
+
* - **Hangup** -- transport emits `close` or `disconnected`.
|
|
46
|
+
* - **Turns exhausted** -- session emits `turn_complete` and the collector's count
|
|
34
47
|
* reaches `config.maxTurns`.
|
|
35
|
-
* - **Keyword**
|
|
36
|
-
* - **Silence timeout**
|
|
37
|
-
* - **Abort/barge-in**
|
|
48
|
+
* - **Keyword** -- a `final_transcript` event contains one of `config.exitKeywords`.
|
|
49
|
+
* - **Silence timeout** -- no speech activity for 30 seconds (when `exitOn: 'silence-timeout'`).
|
|
50
|
+
* - **Abort/barge-in** -- the internal `AbortController` is signalled, either by a
|
|
38
51
|
* parent abort signal or a `VoiceInterruptError`.
|
|
39
52
|
*
|
|
40
53
|
* @example
|
|
41
54
|
* ```ts
|
|
42
55
|
* const executor = new VoiceNodeExecutor((event) => emitter.emit(event));
|
|
43
56
|
* const result = await executor.execute(voiceNode, graphState);
|
|
44
|
-
* console.log(result.output.exitReason);
|
|
57
|
+
* console.log(result.output.exitReason);
|
|
58
|
+
* // 'turns-exhausted' | 'hangup' | 'keyword:goodbye' | 'silence-timeout' | 'interrupted'
|
|
45
59
|
* ```
|
|
60
|
+
*
|
|
61
|
+
* @see {@link VoiceTurnCollector} -- subscribes to session events and buffers transcript.
|
|
62
|
+
* @see {@link VoiceInterruptError} -- structured barge-in error that triggers the `interrupted` path.
|
|
46
63
|
*/
|
|
47
64
|
export class VoiceNodeExecutor {
|
|
48
65
|
/**
|
|
66
|
+
* Creates a new VoiceNodeExecutor.
|
|
67
|
+
*
|
|
49
68
|
* @param eventSink - Callback invoked synchronously for every emitted {@link GraphEvent}.
|
|
50
|
-
* Typically bound to the graph runtime's event emitter
|
|
69
|
+
* Typically bound to the graph runtime's event emitter so that
|
|
70
|
+
* voice lifecycle events (`voice_session`, `voice_transcript`, etc.)
|
|
71
|
+
* are visible to all graph event consumers.
|
|
51
72
|
*/
|
|
52
73
|
constructor(eventSink) {
|
|
53
74
|
this.eventSink = eventSink;
|
|
@@ -56,49 +77,85 @@ export class VoiceNodeExecutor {
|
|
|
56
77
|
* Execute a voice node. Matches the standard 2-arg `execute(node, state)` signature
|
|
57
78
|
* used throughout the orchestration runtime.
|
|
58
79
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
80
|
+
* ## Lifecycle
|
|
81
|
+
*
|
|
82
|
+
* 1. Validates that `node.executorConfig.type` is `'voice'`.
|
|
83
|
+
* 2. Creates an internal `AbortController` for barge-in, wiring it to any parent
|
|
84
|
+
* abort signal in `state.scratch.abortSignal`.
|
|
85
|
+
* 3. Extracts the `voiceTransport` from `state.scratch` (must be pre-placed by
|
|
86
|
+
* the graph runtime or {@link VoiceTransportAdapter}).
|
|
87
|
+
* 4. Checks for a {@link VoiceNodeCheckpoint} to resume from.
|
|
88
|
+
* 5. Emits a `voice_session` started event.
|
|
89
|
+
* 6. Wires a {@link VoiceTurnCollector} onto the session and races exit conditions.
|
|
90
|
+
* 7. Resolves the exit reason to a route target via the node's edge map.
|
|
91
|
+
* 8. Returns a {@link NodeExecutionResult} with transcript, exit reason, checkpoint,
|
|
92
|
+
* and optional route target.
|
|
62
93
|
*
|
|
63
94
|
* @param node - Immutable voice node descriptor from the compiled graph IR.
|
|
95
|
+
* Must have `executorConfig.type === 'voice'`.
|
|
64
96
|
* @param state - Current (partial) graph state threaded from the runtime.
|
|
97
|
+
* Must contain `scratch.voiceTransport` for the voice session.
|
|
65
98
|
* @returns A {@link NodeExecutionResult} with transcript, exit reason, and optional route target.
|
|
99
|
+
* On success, `output` contains `{ transcript, turns, exitReason, lastSpeaker, interruptedText }`.
|
|
100
|
+
* `scratchUpdate` carries the {@link VoiceNodeCheckpoint} keyed by node id.
|
|
101
|
+
* @throws Never -- all errors are caught and returned as `{ success: false, error }`.
|
|
102
|
+
* {@link VoiceInterruptError} is caught and mapped to `exitReason: 'interrupted'`.
|
|
103
|
+
*
|
|
104
|
+
* @see {@link raceExitConditions} for the concurrent exit condition implementation.
|
|
66
105
|
*/
|
|
67
106
|
async execute(node, state) {
|
|
68
107
|
const config = node.executorConfig;
|
|
108
|
+
// Guard: only voice nodes should reach this executor.
|
|
69
109
|
if (config.type !== 'voice') {
|
|
70
110
|
return { success: false, error: 'VoiceNodeExecutor received non-voice node' };
|
|
71
111
|
}
|
|
72
112
|
const voiceConfig = config.voiceConfig;
|
|
73
|
-
//
|
|
113
|
+
// Create an internal AbortController so barge-in events or parent cancellation
|
|
114
|
+
// can terminate the exit condition race without waiting for a session event.
|
|
74
115
|
const controller = new AbortController();
|
|
75
|
-
// If a parent abort signal exists in scratch
|
|
116
|
+
// If a parent abort signal exists in scratch (e.g. from a graph-level timeout
|
|
117
|
+
// or manual cancellation), forward its abort to our internal controller so that
|
|
118
|
+
// the voice session is cancelled when the parent cancels.
|
|
76
119
|
const parentSignal = state?.scratch?.abortSignal;
|
|
77
120
|
if (parentSignal) {
|
|
78
121
|
parentSignal.addEventListener('abort', () => controller.abort(parentSignal.reason), { once: true });
|
|
79
122
|
}
|
|
80
|
-
//
|
|
123
|
+
// The voice transport must be pre-placed in state.scratch by the graph runtime
|
|
124
|
+
// or VoiceTransportAdapter before executing a voice node. Without it we cannot
|
|
125
|
+
// receive session events or detect hangup.
|
|
81
126
|
const transport = state?.scratch?.voiceTransport;
|
|
82
127
|
if (!transport) {
|
|
83
128
|
return { success: false, error: 'Voice node requires voiceTransport in state.scratch' };
|
|
84
129
|
}
|
|
85
|
-
// Check for checkpoint restore
|
|
130
|
+
// Check for checkpoint restore -- if the node was previously executed and the
|
|
131
|
+
// graph was suspended/restored, the prior turn count is in the checkpoint.
|
|
132
|
+
// This lets the turn counter continue from where it left off rather than
|
|
133
|
+
// resetting to zero, which would cause premature exits on maxTurns.
|
|
86
134
|
const checkpoint = state?.scratch?.[node.id];
|
|
87
135
|
const initialTurnCount = checkpoint?.turnIndex ?? 0;
|
|
88
|
-
//
|
|
136
|
+
// Signal that the voice session is now active for this node.
|
|
89
137
|
this.eventSink({ type: 'voice_session', nodeId: node.id, action: 'started' });
|
|
90
138
|
try {
|
|
91
139
|
// The voice session EventEmitter is expected on transport._voiceSession.
|
|
92
|
-
// In production this is the VoicePipelineSession; in tests it can be a
|
|
140
|
+
// In production this is the VoicePipelineSession; in tests it can be a
|
|
141
|
+
// plain EventEmitter. Fallback to a fresh emitter avoids null dereferences
|
|
142
|
+
// when the transport doesn't have an attached session.
|
|
93
143
|
const session = transport._voiceSession ?? new EventEmitter();
|
|
94
|
-
// Create the turn collector
|
|
144
|
+
// Create the turn collector -- it subscribes to session events (interim_transcript,
|
|
145
|
+
// final_transcript, turn_complete, barge_in) and bridges them into GraphEvents
|
|
146
|
+
// while maintaining a running transcript buffer and turn counter.
|
|
95
147
|
const collector = new VoiceTurnCollector(session, this.eventSink, node.id, initialTurnCount);
|
|
96
|
-
// Race all exit conditions against each other.
|
|
148
|
+
// Race all exit conditions against each other. The first condition to fire
|
|
149
|
+
// determines exitReason and ends the voice node.
|
|
97
150
|
const result = await this.raceExitConditions(session, collector, voiceConfig, controller, transport);
|
|
98
|
-
//
|
|
151
|
+
// Map the exitReason string to a target node id using the edge map.
|
|
152
|
+
// This is how voice nodes implement conditional routing: different exit
|
|
153
|
+
// conditions route to different downstream nodes.
|
|
99
154
|
const edges = node.edges ?? {};
|
|
100
155
|
const routeTarget = typeof edges === 'object' ? edges[result.reason] : undefined;
|
|
101
|
-
// Build checkpoint
|
|
156
|
+
// Build the checkpoint so the runtime can persist and restore later.
|
|
157
|
+
// This is written into scratchUpdate and merged back into state.scratch
|
|
158
|
+
// by the graph runtime after execution completes.
|
|
102
159
|
const voiceCheckpoint = {
|
|
103
160
|
turnIndex: collector.getTurnCount(),
|
|
104
161
|
transcript: collector.getTranscript(),
|
|
@@ -106,7 +163,7 @@ export class VoiceNodeExecutor {
|
|
|
106
163
|
speakerMap: {},
|
|
107
164
|
sessionConfig: voiceConfig,
|
|
108
165
|
};
|
|
109
|
-
//
|
|
166
|
+
// Signal that the voice session has ended for this node.
|
|
110
167
|
this.eventSink({ type: 'voice_session', nodeId: node.id, action: 'ended', exitReason: result.reason });
|
|
111
168
|
return {
|
|
112
169
|
success: true,
|
|
@@ -122,8 +179,10 @@ export class VoiceNodeExecutor {
|
|
|
122
179
|
};
|
|
123
180
|
}
|
|
124
181
|
catch (err) {
|
|
125
|
-
// VoiceInterruptError is a structured barge-in
|
|
126
|
-
//
|
|
182
|
+
// VoiceInterruptError is a structured barge-in -- the user spoke over the
|
|
183
|
+
// agent. This is not an error condition; it's a valid exit path that the
|
|
184
|
+
// graph should be able to route on. We convert it to a successful result
|
|
185
|
+
// with exitReason: 'interrupted' so edge routing works as expected.
|
|
127
186
|
if (err instanceof VoiceInterruptError) {
|
|
128
187
|
const edges = node.edges ?? {};
|
|
129
188
|
const routeTarget = edges['interrupted'];
|
|
@@ -140,7 +199,8 @@ export class VoiceNodeExecutor {
|
|
|
140
199
|
routeTarget,
|
|
141
200
|
};
|
|
142
201
|
}
|
|
143
|
-
// Unhandled error
|
|
202
|
+
// Unhandled error -- surface as a failed result so the graph runtime can
|
|
203
|
+
// decide whether to retry, reroute, or halt.
|
|
144
204
|
this.eventSink({ type: 'voice_session', nodeId: node.id, action: 'ended', exitReason: 'error' });
|
|
145
205
|
return { success: false, error: String(err) };
|
|
146
206
|
}
|
|
@@ -152,25 +212,42 @@ export class VoiceNodeExecutor {
|
|
|
152
212
|
* Races all configured exit conditions against each other and resolves with
|
|
153
213
|
* the first one that fires.
|
|
154
214
|
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
215
|
+
* ## How it works
|
|
216
|
+
*
|
|
217
|
+
* Each exit condition is wired as a listener on either the `session` or
|
|
218
|
+
* `transport` EventEmitter. All listeners call a shared `settleWith()` helper
|
|
219
|
+
* that resolves the outer Promise exactly once (guarded by a `settled` boolean).
|
|
220
|
+
*
|
|
221
|
+
* The `AbortController` signal is also monitored -- if it fires with a
|
|
222
|
+
* {@link VoiceInterruptError} the Promise rejects (handled by the caller's
|
|
223
|
+
* catch block), otherwise it resolves with `{ reason: 'interrupted' }`.
|
|
224
|
+
*
|
|
225
|
+
* ## Why a Promise race instead of a state machine?
|
|
226
|
+
*
|
|
227
|
+
* The exit conditions are independent and asynchronous. A Promise-based race
|
|
228
|
+
* avoids complex state transitions and lets each condition be a simple
|
|
229
|
+
* event listener. The `settled` guard handles the only tricky case: two
|
|
230
|
+
* conditions firing in the same microtask.
|
|
159
231
|
*
|
|
160
|
-
* @param session - Voice pipeline session EventEmitter
|
|
161
|
-
*
|
|
162
|
-
* @param
|
|
232
|
+
* @param session - Voice pipeline session EventEmitter that fires
|
|
233
|
+
* `turn_complete`, `final_transcript`, `speech_start`, `barge_in`.
|
|
234
|
+
* @param collector - Active turn collector tracking turn count and transcript.
|
|
235
|
+
* @param config - Voice node configuration with exit settings (`maxTurns`,
|
|
236
|
+
* `exitOn`, `exitKeywords`).
|
|
163
237
|
* @param controller - Internal AbortController for barge-in signalling.
|
|
164
|
-
* @param transport - Bidirectional transport EventEmitter
|
|
238
|
+
* @param transport - Bidirectional transport EventEmitter that fires `close`
|
|
239
|
+
* and `disconnected` on hangup.
|
|
165
240
|
* @returns The winning exit condition's reason string and optional interrupted text.
|
|
166
241
|
*/
|
|
167
242
|
async raceExitConditions(session, collector, config, controller, transport) {
|
|
168
243
|
return new Promise((resolve, reject) => {
|
|
169
|
-
/**
|
|
244
|
+
/** Prevents double-resolution when multiple conditions fire simultaneously. */
|
|
170
245
|
let settled = false;
|
|
171
246
|
/**
|
|
172
247
|
* Settle the promise with a resolve value, guarding against double-settle.
|
|
173
|
-
*
|
|
248
|
+
* Every exit condition calls this instead of `resolve()` directly.
|
|
249
|
+
*
|
|
250
|
+
* @param result - The exit condition result containing the reason string.
|
|
174
251
|
*/
|
|
175
252
|
const settleWith = (result) => {
|
|
176
253
|
if (settled)
|
|
@@ -179,10 +256,16 @@ export class VoiceNodeExecutor {
|
|
|
179
256
|
resolve(result);
|
|
180
257
|
};
|
|
181
258
|
// -- Hangup: transport disconnects -----------------------------------
|
|
259
|
+
// Both `close` and `disconnected` events indicate the transport is gone.
|
|
260
|
+
// We listen for both because different transport implementations emit
|
|
261
|
+
// different event names (WebSocket uses `close`, telephony uses `disconnected`).
|
|
182
262
|
const onDisconnect = () => settleWith({ reason: 'hangup' });
|
|
183
263
|
transport.on('close', onDisconnect);
|
|
184
264
|
transport.on('disconnected', onDisconnect);
|
|
185
265
|
// -- Turns exhausted -------------------------------------------------
|
|
266
|
+
// Only armed when maxTurns is a positive number. We check the collector's
|
|
267
|
+
// count (not a local counter) because the collector may have been seeded
|
|
268
|
+
// with an initialTurnCount from a checkpoint restore.
|
|
186
269
|
if (config.maxTurns && config.maxTurns > 0) {
|
|
187
270
|
session.on('turn_complete', () => {
|
|
188
271
|
if (collector.getTurnCount() >= config.maxTurns) {
|
|
@@ -191,6 +274,9 @@ export class VoiceNodeExecutor {
|
|
|
191
274
|
});
|
|
192
275
|
}
|
|
193
276
|
// -- Keyword detection -----------------------------------------------
|
|
277
|
+
// Only armed when exitOn is 'keyword' and at least one keyword is provided.
|
|
278
|
+
// The keyword check is case-insensitive and uses substring matching so that
|
|
279
|
+
// "goodbye" matches "okay goodbye then".
|
|
194
280
|
if (config.exitOn === 'keyword' && config.exitKeywords?.length) {
|
|
195
281
|
session.on('final_transcript', (evt) => {
|
|
196
282
|
const text = (evt.text ?? '').toLowerCase();
|
|
@@ -203,20 +289,29 @@ export class VoiceNodeExecutor {
|
|
|
203
289
|
});
|
|
204
290
|
}
|
|
205
291
|
// -- Silence timeout (default 30 s) ----------------------------------
|
|
292
|
+
// Only armed when exitOn is 'silence-timeout'. A watchdog timer is reset
|
|
293
|
+
// on every speech activity event. If the timer fires without being reset,
|
|
294
|
+
// the user has been silent for too long and the session ends.
|
|
206
295
|
if (config.exitOn === 'silence-timeout') {
|
|
207
296
|
let silenceTimer = null;
|
|
208
297
|
const timeoutMs = 30000;
|
|
209
|
-
/** Reset the silence watchdog
|
|
298
|
+
/** Reset the silence watchdog -- called on any speech activity. */
|
|
210
299
|
const resetTimer = () => {
|
|
211
300
|
if (silenceTimer)
|
|
212
301
|
clearTimeout(silenceTimer);
|
|
213
302
|
silenceTimer = setTimeout(() => settleWith({ reason: 'silence-timeout' }), timeoutMs);
|
|
214
303
|
};
|
|
304
|
+
// Reset on both speech_start (user began talking) and turn_complete
|
|
305
|
+
// (user finished a turn) to cover all speech activity signals.
|
|
215
306
|
session.on('speech_start', resetTimer);
|
|
216
307
|
session.on('turn_complete', resetTimer);
|
|
217
308
|
resetTimer(); // Start the initial timer immediately.
|
|
218
309
|
}
|
|
219
310
|
// -- Abort signal (barge-in or parent cancellation) ------------------
|
|
311
|
+
// If the abort reason is a VoiceInterruptError, we reject the Promise
|
|
312
|
+
// (the caller's catch block converts it to exitReason: 'interrupted').
|
|
313
|
+
// For any other abort reason (e.g. parent timeout), we resolve normally
|
|
314
|
+
// with reason: 'interrupted'.
|
|
220
315
|
controller.signal.addEventListener('abort', () => {
|
|
221
316
|
const reason = controller.signal.reason;
|
|
222
317
|
if (reason instanceof VoiceInterruptError) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VoiceNodeExecutor.js","sourceRoot":"","sources":["../../../src/orchestration/runtime/VoiceNodeExecutor.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"VoiceNodeExecutor.js","sourceRoot":"","sources":["../../../src/orchestration/runtime/VoiceNodeExecutor.ts"],"names":[],"mappings":"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"}
|