@framers/agentos 0.1.82 → 0.1.84

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.
Files changed (30) hide show
  1. package/dist/orchestration/events/GraphEvent.d.ts +39 -0
  2. package/dist/orchestration/events/GraphEvent.d.ts.map +1 -1
  3. package/dist/orchestration/events/GraphEvent.js.map +1 -1
  4. package/dist/orchestration/ir/types.d.ts +34 -1
  5. package/dist/orchestration/ir/types.d.ts.map +1 -1
  6. package/dist/orchestration/runtime/NodeExecutor.d.ts +26 -0
  7. package/dist/orchestration/runtime/NodeExecutor.d.ts.map +1 -1
  8. package/dist/orchestration/runtime/NodeExecutor.js +34 -2
  9. package/dist/orchestration/runtime/NodeExecutor.js.map +1 -1
  10. package/dist/orchestration/runtime/VoiceNodeExecutor.d.ts +105 -0
  11. package/dist/orchestration/runtime/VoiceNodeExecutor.d.ts.map +1 -0
  12. package/dist/orchestration/runtime/VoiceNodeExecutor.js +232 -0
  13. package/dist/orchestration/runtime/VoiceNodeExecutor.js.map +1 -0
  14. package/dist/orchestration/runtime/VoiceTurnCollector.d.ts +95 -0
  15. package/dist/orchestration/runtime/VoiceTurnCollector.d.ts.map +1 -0
  16. package/dist/orchestration/runtime/VoiceTurnCollector.js +148 -0
  17. package/dist/orchestration/runtime/VoiceTurnCollector.js.map +1 -0
  18. package/dist/voice-pipeline/VoiceInterruptError.d.ts +52 -0
  19. package/dist/voice-pipeline/VoiceInterruptError.d.ts.map +1 -0
  20. package/dist/voice-pipeline/VoiceInterruptError.js +43 -0
  21. package/dist/voice-pipeline/VoiceInterruptError.js.map +1 -0
  22. package/dist/voice-pipeline/VoicePipelineOrchestrator.d.ts +44 -1
  23. package/dist/voice-pipeline/VoicePipelineOrchestrator.d.ts.map +1 -1
  24. package/dist/voice-pipeline/VoicePipelineOrchestrator.js +63 -0
  25. package/dist/voice-pipeline/VoicePipelineOrchestrator.js.map +1 -1
  26. package/dist/voice-pipeline/index.d.ts +1 -0
  27. package/dist/voice-pipeline/index.d.ts.map +1 -1
  28. package/dist/voice-pipeline/index.js +1 -0
  29. package/dist/voice-pipeline/index.js.map +1 -1
  30. package/package.json +1 -1
@@ -0,0 +1,232 @@
1
+ /**
2
+ * @file VoiceNodeExecutor.ts
3
+ * @description Executes voice nodes in the orchestration graph by managing a voice
4
+ * pipeline session, collecting turns via {@link VoiceTurnCollector}, and racing
5
+ * multiple exit conditions (hangup, turns exhausted, keyword, silence timeout,
6
+ * barge-in abort) to determine when the voice node completes.
7
+ *
8
+ * The executor follows the standard 2-arg `execute(node, state)` contract used by
9
+ * {@link NodeExecutor}. It creates an internal `AbortController` for barge-in
10
+ * support and optionally merges a parent abort signal from `state.scratch.abortSignal`.
11
+ *
12
+ * Voice transport and session references are expected in `state.scratch`:
13
+ * - `voiceTransport` — the bidirectional transport EventEmitter (emits `close` / `disconnected`).
14
+ * - `voiceTransport._voiceSession` — the voice pipeline session EventEmitter that fires
15
+ * `final_transcript`, `turn_complete`, `speech_start`, and `barge_in` events.
16
+ *
17
+ * Checkpoint data is stored in `state.scratch[nodeId]` as a {@link VoiceNodeCheckpoint},
18
+ * enabling the graph runtime to resume a voice session from the exact turn index where
19
+ * it was previously suspended.
20
+ */
21
+ import { EventEmitter } from 'events';
22
+ import { VoiceTurnCollector } from './VoiceTurnCollector.js';
23
+ import { VoiceInterruptError } from '../../voice-pipeline/VoiceInterruptError.js';
24
+ // ---------------------------------------------------------------------------
25
+ // VoiceNodeExecutor
26
+ // ---------------------------------------------------------------------------
27
+ /**
28
+ * Executes voice-type graph nodes by running a voice pipeline session and racing
29
+ * multiple exit conditions to determine when the node is done.
30
+ *
31
+ * Exit conditions are evaluated concurrently via a single `Promise` race:
32
+ * - **Hangup** — transport emits `close` or `disconnected`.
33
+ * - **Turns exhausted** — session emits `turn_complete` and the collector's count
34
+ * reaches `config.maxTurns`.
35
+ * - **Keyword** — a `final_transcript` event contains one of `config.exitKeywords`.
36
+ * - **Silence timeout** — no speech activity for 30 seconds (when `exitOn: 'silence-timeout'`).
37
+ * - **Abort/barge-in** — the internal `AbortController` is signalled, either by a
38
+ * parent abort signal or a `VoiceInterruptError`.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * const executor = new VoiceNodeExecutor((event) => emitter.emit(event));
43
+ * const result = await executor.execute(voiceNode, graphState);
44
+ * console.log(result.output.exitReason); // 'turns-exhausted' | 'hangup' | 'keyword:goodbye' | ...
45
+ * ```
46
+ */
47
+ export class VoiceNodeExecutor {
48
+ /**
49
+ * @param eventSink - Callback invoked synchronously for every emitted {@link GraphEvent}.
50
+ * Typically bound to the graph runtime's event emitter.
51
+ */
52
+ constructor(eventSink) {
53
+ this.eventSink = eventSink;
54
+ }
55
+ /**
56
+ * Execute a voice node. Matches the standard 2-arg `execute(node, state)` signature
57
+ * used throughout the orchestration runtime.
58
+ *
59
+ * Creates an internal `AbortController` for barge-in, wires up a
60
+ * {@link VoiceTurnCollector} on the session, and races exit conditions to
61
+ * determine when the node completes.
62
+ *
63
+ * @param node - Immutable voice node descriptor from the compiled graph IR.
64
+ * @param state - Current (partial) graph state threaded from the runtime.
65
+ * @returns A {@link NodeExecutionResult} with transcript, exit reason, and optional route target.
66
+ */
67
+ async execute(node, state) {
68
+ const config = node.executorConfig;
69
+ if (config.type !== 'voice') {
70
+ return { success: false, error: 'VoiceNodeExecutor received non-voice node' };
71
+ }
72
+ const voiceConfig = config.voiceConfig;
73
+ // Internal AbortController for barge-in or parent cancellation.
74
+ const controller = new AbortController();
75
+ // If a parent abort signal exists in scratch, forward its abort to ours.
76
+ const parentSignal = state?.scratch?.abortSignal;
77
+ if (parentSignal) {
78
+ parentSignal.addEventListener('abort', () => controller.abort(parentSignal.reason), { once: true });
79
+ }
80
+ // Voice transport must be pre-placed in state.scratch by the graph runtime.
81
+ const transport = state?.scratch?.voiceTransport;
82
+ if (!transport) {
83
+ return { success: false, error: 'Voice node requires voiceTransport in state.scratch' };
84
+ }
85
+ // Check for checkpoint restore — continue from a prior turn index.
86
+ const checkpoint = state?.scratch?.[node.id];
87
+ const initialTurnCount = checkpoint?.turnIndex ?? 0;
88
+ // Emit session lifecycle event: started.
89
+ this.eventSink({ type: 'voice_session', nodeId: node.id, action: 'started' });
90
+ try {
91
+ // The voice session EventEmitter is expected on transport._voiceSession.
92
+ // In production this is the VoicePipelineSession; in tests it can be a plain EventEmitter.
93
+ const session = transport._voiceSession ?? new EventEmitter();
94
+ // Create the turn collector — it subscribes to session events and buffers transcript.
95
+ const collector = new VoiceTurnCollector(session, this.eventSink, node.id, initialTurnCount);
96
+ // Race all exit conditions against each other.
97
+ const result = await this.raceExitConditions(session, collector, voiceConfig, controller, transport);
98
+ // Resolve exitReason → routeTarget from node edges.
99
+ const edges = node.edges ?? {};
100
+ const routeTarget = typeof edges === 'object' ? edges[result.reason] : undefined;
101
+ // Build checkpoint for scratch so the runtime can persist/restore later.
102
+ const voiceCheckpoint = {
103
+ turnIndex: collector.getTurnCount(),
104
+ transcript: collector.getTranscript(),
105
+ lastExitReason: result.reason,
106
+ speakerMap: {},
107
+ sessionConfig: voiceConfig,
108
+ };
109
+ // Emit session lifecycle event: ended.
110
+ this.eventSink({ type: 'voice_session', nodeId: node.id, action: 'ended', exitReason: result.reason });
111
+ return {
112
+ success: true,
113
+ output: {
114
+ transcript: collector.getTranscript(),
115
+ turns: collector.getTurnCount(),
116
+ exitReason: result.reason,
117
+ lastSpeaker: collector.getLastSpeaker(),
118
+ interruptedText: result.interruptedText,
119
+ },
120
+ routeTarget,
121
+ scratchUpdate: { [node.id]: voiceCheckpoint },
122
+ };
123
+ }
124
+ catch (err) {
125
+ // VoiceInterruptError is a structured barge-in — treat as a successful exit
126
+ // with exitReason: 'interrupted' so the graph can route accordingly.
127
+ if (err instanceof VoiceInterruptError) {
128
+ const edges = node.edges ?? {};
129
+ const routeTarget = edges['interrupted'];
130
+ this.eventSink({ type: 'voice_session', nodeId: node.id, action: 'ended', exitReason: 'interrupted' });
131
+ return {
132
+ success: true,
133
+ output: {
134
+ transcript: [],
135
+ turns: 0,
136
+ exitReason: 'interrupted',
137
+ interruptedText: err.interruptedText,
138
+ userSpeech: err.userSpeech,
139
+ },
140
+ routeTarget,
141
+ };
142
+ }
143
+ // Unhandled error — surface as a failed result.
144
+ this.eventSink({ type: 'voice_session', nodeId: node.id, action: 'ended', exitReason: 'error' });
145
+ return { success: false, error: String(err) };
146
+ }
147
+ }
148
+ // ---------------------------------------------------------------------------
149
+ // Private helpers
150
+ // ---------------------------------------------------------------------------
151
+ /**
152
+ * Races all configured exit conditions against each other and resolves with
153
+ * the first one that fires.
154
+ *
155
+ * Listeners are attached to the session and transport EventEmitters. The
156
+ * `AbortController` signal is also monitored — if it fires with a
157
+ * {@link VoiceInterruptError} the Promise rejects (handled by the caller),
158
+ * otherwise it resolves with `{ reason: 'interrupted' }`.
159
+ *
160
+ * @param session - Voice pipeline session EventEmitter.
161
+ * @param collector - Active turn collector tracking turn count.
162
+ * @param config - Voice node configuration with exit settings.
163
+ * @param controller - Internal AbortController for barge-in signalling.
164
+ * @param transport - Bidirectional transport EventEmitter.
165
+ * @returns The winning exit condition's reason string and optional interrupted text.
166
+ */
167
+ async raceExitConditions(session, collector, config, controller, transport) {
168
+ return new Promise((resolve, reject) => {
169
+ /** Prevent double-resolution from multiple conditions firing simultaneously. */
170
+ let settled = false;
171
+ /**
172
+ * Settle the promise with a resolve value, guarding against double-settle.
173
+ * @param result - The exit condition result.
174
+ */
175
+ const settleWith = (result) => {
176
+ if (settled)
177
+ return;
178
+ settled = true;
179
+ resolve(result);
180
+ };
181
+ // -- Hangup: transport disconnects -----------------------------------
182
+ const onDisconnect = () => settleWith({ reason: 'hangup' });
183
+ transport.on('close', onDisconnect);
184
+ transport.on('disconnected', onDisconnect);
185
+ // -- Turns exhausted -------------------------------------------------
186
+ if (config.maxTurns && config.maxTurns > 0) {
187
+ session.on('turn_complete', () => {
188
+ if (collector.getTurnCount() >= config.maxTurns) {
189
+ settleWith({ reason: 'turns-exhausted' });
190
+ }
191
+ });
192
+ }
193
+ // -- Keyword detection -----------------------------------------------
194
+ if (config.exitOn === 'keyword' && config.exitKeywords?.length) {
195
+ session.on('final_transcript', (evt) => {
196
+ const text = (evt.text ?? '').toLowerCase();
197
+ for (const kw of config.exitKeywords) {
198
+ if (text.includes(kw.toLowerCase())) {
199
+ settleWith({ reason: `keyword:${kw}` });
200
+ return;
201
+ }
202
+ }
203
+ });
204
+ }
205
+ // -- Silence timeout (default 30 s) ----------------------------------
206
+ if (config.exitOn === 'silence-timeout') {
207
+ let silenceTimer = null;
208
+ const timeoutMs = 30000;
209
+ /** Reset the silence watchdog — called on any speech activity. */
210
+ const resetTimer = () => {
211
+ if (silenceTimer)
212
+ clearTimeout(silenceTimer);
213
+ silenceTimer = setTimeout(() => settleWith({ reason: 'silence-timeout' }), timeoutMs);
214
+ };
215
+ session.on('speech_start', resetTimer);
216
+ session.on('turn_complete', resetTimer);
217
+ resetTimer(); // Start the initial timer immediately.
218
+ }
219
+ // -- Abort signal (barge-in or parent cancellation) ------------------
220
+ controller.signal.addEventListener('abort', () => {
221
+ const reason = controller.signal.reason;
222
+ if (reason instanceof VoiceInterruptError) {
223
+ reject(reason);
224
+ }
225
+ else {
226
+ settleWith({ reason: 'interrupted' });
227
+ }
228
+ }, { once: true });
229
+ });
230
+ }
231
+ }
232
+ //# sourceMappingURL=VoiceNodeExecutor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VoiceNodeExecutor.js","sourceRoot":"","sources":["../../../src/orchestration/runtime/VoiceNodeExecutor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * @file VoiceTurnCollector.ts
3
+ * @description Subscribes to voice pipeline session events and maintains a
4
+ * running transcript buffer, turn counter, and last-speaker tracker.
5
+ *
6
+ * The collector bridges the raw EventEmitter-based voice pipeline session into
7
+ * the typed `GraphEvent` stream consumed by the graph runtime. Four session
8
+ * events are handled:
9
+ *
10
+ * - `interim_transcript` — partial STT result; forwarded as a non-final
11
+ * `voice_transcript` GraphEvent but **not** buffered (too noisy).
12
+ * - `final_transcript` — confirmed STT result; buffered in `transcript` and
13
+ * forwarded as a final `voice_transcript` GraphEvent.
14
+ * - `turn_complete` — endpoint detection fired; increments `turnCount` and
15
+ * emits a `voice_turn_complete` GraphEvent.
16
+ * - `barge_in` — user interrupted the agent mid-speech; emits a
17
+ * `voice_barge_in` GraphEvent.
18
+ *
19
+ * The `initialTurnCount` constructor parameter enables checkpoint restore:
20
+ * pass the previously persisted count so that `turnIndex` values continue
21
+ * from where the session left off rather than resetting to zero.
22
+ */
23
+ import { EventEmitter } from 'events';
24
+ import type { GraphEvent } from '../events/GraphEvent.js';
25
+ /**
26
+ * A single confirmed (final) utterance captured from the voice pipeline.
27
+ *
28
+ * Only `final_transcript` events populate this buffer — interim partials are
29
+ * discarded to keep the transcript clean and avoid duplicate entries.
30
+ */
31
+ export interface TranscriptEntry {
32
+ /** Speaker identifier as reported by the STT service (e.g. `"Speaker_0"`). */
33
+ speaker: string;
34
+ /** Recognised text for this utterance. */
35
+ text: string;
36
+ /**
37
+ * Wall-clock timestamp (milliseconds since Unix epoch) recorded at the
38
+ * moment the `final_transcript` event was processed.
39
+ */
40
+ timestamp: number;
41
+ }
42
+ /**
43
+ * Stateful collector that subscribes to a voice pipeline session and routes
44
+ * session events into the AgentOS `GraphEvent` stream.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * const collector = new VoiceTurnCollector(
49
+ * session,
50
+ * (evt) => graphEventEmitter.emit(evt),
51
+ * 'voice-node-1',
52
+ * );
53
+ *
54
+ * // After the conversation:
55
+ * console.log(collector.getTurnCount()); // number of completed turns
56
+ * console.log(collector.getTranscript()); // full buffered transcript
57
+ * console.log(collector.getLastSpeaker()); // last identified speaker
58
+ * ```
59
+ */
60
+ export declare class VoiceTurnCollector {
61
+ private readonly eventSink;
62
+ private readonly nodeId;
63
+ /** Buffered confirmed utterances in chronological order. */
64
+ private transcript;
65
+ /** Running count of completed turns (endpoint-detected). */
66
+ private turnCount;
67
+ /** Speaker identifier from the most recent `final_transcript` event. */
68
+ private lastSpeaker;
69
+ /**
70
+ * @param session - The voice pipeline `EventEmitter` to subscribe to.
71
+ * @param eventSink - Callback invoked synchronously for every emitted `GraphEvent`.
72
+ * @param nodeId - Identifies the owning graph node in every emitted event.
73
+ * @param initialTurnCount - Seed value for `turnCount`; pass a persisted value to
74
+ * resume from a checkpoint rather than starting at zero.
75
+ */
76
+ constructor(session: EventEmitter, eventSink: (event: GraphEvent) => void, nodeId: string, initialTurnCount?: number);
77
+ /**
78
+ * Returns the total number of completed turns since construction (or since the
79
+ * provided `initialTurnCount` when restoring from a checkpoint).
80
+ */
81
+ getTurnCount(): number;
82
+ /**
83
+ * Returns a shallow copy of the buffered transcript entries.
84
+ *
85
+ * A copy is returned to prevent external callers from mutating the internal
86
+ * buffer — entries are append-only and must remain ordered.
87
+ */
88
+ getTranscript(): TranscriptEntry[];
89
+ /**
90
+ * Returns the speaker identifier from the most recent `final_transcript` event,
91
+ * or an empty string if no final transcript has been received yet.
92
+ */
93
+ getLastSpeaker(): string;
94
+ }
95
+ //# sourceMappingURL=VoiceTurnCollector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VoiceTurnCollector.d.ts","sourceRoot":"","sources":["../../../src/orchestration/runtime/VoiceTurnCollector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAM1D;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;CACnB;AAMD;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,kBAAkB;IAmB3B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAnBzB,4DAA4D;IAC5D,OAAO,CAAC,UAAU,CAAyB;IAE3C,4DAA4D;IAC5D,OAAO,CAAC,SAAS,CAAS;IAE1B,wEAAwE;IACxE,OAAO,CAAC,WAAW,CAAM;IAEzB;;;;;;OAMG;gBAED,OAAO,EAAE,YAAY,EACJ,SAAS,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,EACtC,MAAM,EAAE,MAAM,EAC/B,gBAAgB,SAAI;IA6EtB;;;OAGG;IACH,YAAY,IAAI,MAAM;IAItB;;;;;OAKG;IACH,aAAa,IAAI,eAAe,EAAE;IAIlC;;;OAGG;IACH,cAAc,IAAI,MAAM;CAGzB"}
@@ -0,0 +1,148 @@
1
+ /**
2
+ * @file VoiceTurnCollector.ts
3
+ * @description Subscribes to voice pipeline session events and maintains a
4
+ * running transcript buffer, turn counter, and last-speaker tracker.
5
+ *
6
+ * The collector bridges the raw EventEmitter-based voice pipeline session into
7
+ * the typed `GraphEvent` stream consumed by the graph runtime. Four session
8
+ * events are handled:
9
+ *
10
+ * - `interim_transcript` — partial STT result; forwarded as a non-final
11
+ * `voice_transcript` GraphEvent but **not** buffered (too noisy).
12
+ * - `final_transcript` — confirmed STT result; buffered in `transcript` and
13
+ * forwarded as a final `voice_transcript` GraphEvent.
14
+ * - `turn_complete` — endpoint detection fired; increments `turnCount` and
15
+ * emits a `voice_turn_complete` GraphEvent.
16
+ * - `barge_in` — user interrupted the agent mid-speech; emits a
17
+ * `voice_barge_in` GraphEvent.
18
+ *
19
+ * The `initialTurnCount` constructor parameter enables checkpoint restore:
20
+ * pass the previously persisted count so that `turnIndex` values continue
21
+ * from where the session left off rather than resetting to zero.
22
+ */
23
+ // ---------------------------------------------------------------------------
24
+ // VoiceTurnCollector
25
+ // ---------------------------------------------------------------------------
26
+ /**
27
+ * Stateful collector that subscribes to a voice pipeline session and routes
28
+ * session events into the AgentOS `GraphEvent` stream.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * const collector = new VoiceTurnCollector(
33
+ * session,
34
+ * (evt) => graphEventEmitter.emit(evt),
35
+ * 'voice-node-1',
36
+ * );
37
+ *
38
+ * // After the conversation:
39
+ * console.log(collector.getTurnCount()); // number of completed turns
40
+ * console.log(collector.getTranscript()); // full buffered transcript
41
+ * console.log(collector.getLastSpeaker()); // last identified speaker
42
+ * ```
43
+ */
44
+ export class VoiceTurnCollector {
45
+ /**
46
+ * @param session - The voice pipeline `EventEmitter` to subscribe to.
47
+ * @param eventSink - Callback invoked synchronously for every emitted `GraphEvent`.
48
+ * @param nodeId - Identifies the owning graph node in every emitted event.
49
+ * @param initialTurnCount - Seed value for `turnCount`; pass a persisted value to
50
+ * resume from a checkpoint rather than starting at zero.
51
+ */
52
+ constructor(session, eventSink, nodeId, initialTurnCount = 0) {
53
+ this.eventSink = eventSink;
54
+ this.nodeId = nodeId;
55
+ /** Buffered confirmed utterances in chronological order. */
56
+ this.transcript = [];
57
+ /** Speaker identifier from the most recent `final_transcript` event. */
58
+ this.lastSpeaker = '';
59
+ this.turnCount = initialTurnCount;
60
+ // ------------------------------------------------------------------
61
+ // interim_transcript — partial STT result, forwarded but not buffered
62
+ // ------------------------------------------------------------------
63
+ session.on('interim_transcript', (evt) => {
64
+ this.eventSink({
65
+ type: 'voice_transcript',
66
+ nodeId: this.nodeId,
67
+ text: evt.text ?? '',
68
+ isFinal: false,
69
+ speaker: evt.speaker,
70
+ confidence: evt.confidence ?? 0,
71
+ });
72
+ });
73
+ // ------------------------------------------------------------------
74
+ // final_transcript — confirmed utterance, buffered and forwarded
75
+ // ------------------------------------------------------------------
76
+ session.on('final_transcript', (evt) => {
77
+ const speaker = evt.speaker ?? 'user';
78
+ // Buffer the confirmed entry for downstream consumers.
79
+ this.transcript.push({
80
+ speaker,
81
+ text: evt.text ?? '',
82
+ timestamp: Date.now(),
83
+ });
84
+ // Track the most recent speaker for quick access without iterating the buffer.
85
+ this.lastSpeaker = speaker;
86
+ this.eventSink({
87
+ type: 'voice_transcript',
88
+ nodeId: this.nodeId,
89
+ text: evt.text ?? '',
90
+ isFinal: true,
91
+ speaker,
92
+ confidence: evt.confidence ?? 0,
93
+ });
94
+ });
95
+ // ------------------------------------------------------------------
96
+ // turn_complete — endpoint detection fired; advance the turn counter
97
+ // ------------------------------------------------------------------
98
+ session.on('turn_complete', (evt) => {
99
+ // Increment before emitting so that turnIndex reflects the new count.
100
+ this.turnCount++;
101
+ this.eventSink({
102
+ type: 'voice_turn_complete',
103
+ nodeId: this.nodeId,
104
+ transcript: evt.transcript ?? '',
105
+ turnIndex: this.turnCount,
106
+ endpointReason: evt.reason ?? 'unknown',
107
+ });
108
+ });
109
+ // ------------------------------------------------------------------
110
+ // barge_in — user interrupted agent mid-speech
111
+ // ------------------------------------------------------------------
112
+ session.on('barge_in', (evt) => {
113
+ this.eventSink({
114
+ type: 'voice_barge_in',
115
+ nodeId: this.nodeId,
116
+ interruptedText: evt.interruptedText ?? '',
117
+ userSpeech: evt.userSpeech ?? '',
118
+ });
119
+ });
120
+ }
121
+ // ---------------------------------------------------------------------------
122
+ // Accessors
123
+ // ---------------------------------------------------------------------------
124
+ /**
125
+ * Returns the total number of completed turns since construction (or since the
126
+ * provided `initialTurnCount` when restoring from a checkpoint).
127
+ */
128
+ getTurnCount() {
129
+ return this.turnCount;
130
+ }
131
+ /**
132
+ * Returns a shallow copy of the buffered transcript entries.
133
+ *
134
+ * A copy is returned to prevent external callers from mutating the internal
135
+ * buffer — entries are append-only and must remain ordered.
136
+ */
137
+ getTranscript() {
138
+ return [...this.transcript];
139
+ }
140
+ /**
141
+ * Returns the speaker identifier from the most recent `final_transcript` event,
142
+ * or an empty string if no final transcript has been received yet.
143
+ */
144
+ getLastSpeaker() {
145
+ return this.lastSpeaker;
146
+ }
147
+ }
148
+ //# sourceMappingURL=VoiceTurnCollector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VoiceTurnCollector.js","sourceRoot":"","sources":["../../../src/orchestration/runtime/VoiceTurnCollector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @module voice-pipeline/VoiceInterruptError
3
+ *
4
+ * Typed error thrown when a voice session is interrupted by user barge-in.
5
+ * Used by VoiceNodeExecutor to catch barge-in AbortSignal and return
6
+ * a structured result instead of propagating the error.
7
+ */
8
+ /**
9
+ * Structured error representing a user barge-in interruption during TTS playback.
10
+ *
11
+ * Thrown (or returned as a typed sentinel) when the user speaks over the agent.
12
+ * Consumers can inspect `interruptedText`, `userSpeech`, and `playedDurationMs`
13
+ * to decide how to resume or branch the conversation graph.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * try {
18
+ * await voiceNode.run(context);
19
+ * } catch (err) {
20
+ * if (err instanceof VoiceInterruptError) {
21
+ * console.log(`Interrupted after ${err.playedDurationMs}ms`);
22
+ * await handleBargein(err.userSpeech);
23
+ * }
24
+ * }
25
+ * ```
26
+ */
27
+ export declare class VoiceInterruptError extends Error {
28
+ /** Discriminant name — always `'VoiceInterruptError'` for `instanceof`-free checks. */
29
+ readonly name = "VoiceInterruptError";
30
+ /** The text the agent was speaking when interrupted. */
31
+ readonly interruptedText: string;
32
+ /** What the user said that caused the interruption. */
33
+ readonly userSpeech: string;
34
+ /**
35
+ * How much of the agent's response was already played (ms).
36
+ * Derived from the cumulative `durationMs` of `EncodedAudioChunk`s sent
37
+ * to the transport before the barge-in was detected.
38
+ */
39
+ readonly playedDurationMs: number;
40
+ /**
41
+ * @param context - Barge-in context captured at the moment of interruption.
42
+ * @param context.interruptedText - Full TTS text the agent was speaking.
43
+ * @param context.userSpeech - Transcript of the user's barge-in utterance.
44
+ * @param context.playedDurationMs - Milliseconds of audio already played.
45
+ */
46
+ constructor(context: {
47
+ interruptedText: string;
48
+ userSpeech: string;
49
+ playedDurationMs: number;
50
+ });
51
+ }
52
+ //# sourceMappingURL=VoiceInterruptError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VoiceInterruptError.d.ts","sourceRoot":"","sources":["../../src/voice-pipeline/VoiceInterruptError.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,uFAAuF;IACvF,QAAQ,CAAC,IAAI,yBAAyB;IAEtC,wDAAwD;IACxD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IAEjC,uDAAuD;IACvD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAE5B;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAElC;;;;;OAKG;gBACS,OAAO,EAAE;QACnB,eAAe,EAAE,MAAM,CAAC;QACxB,UAAU,EAAE,MAAM,CAAC;QACnB,gBAAgB,EAAE,MAAM,CAAC;KAC1B;CAMF"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @module voice-pipeline/VoiceInterruptError
3
+ *
4
+ * Typed error thrown when a voice session is interrupted by user barge-in.
5
+ * Used by VoiceNodeExecutor to catch barge-in AbortSignal and return
6
+ * a structured result instead of propagating the error.
7
+ */
8
+ /**
9
+ * Structured error representing a user barge-in interruption during TTS playback.
10
+ *
11
+ * Thrown (or returned as a typed sentinel) when the user speaks over the agent.
12
+ * Consumers can inspect `interruptedText`, `userSpeech`, and `playedDurationMs`
13
+ * to decide how to resume or branch the conversation graph.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * try {
18
+ * await voiceNode.run(context);
19
+ * } catch (err) {
20
+ * if (err instanceof VoiceInterruptError) {
21
+ * console.log(`Interrupted after ${err.playedDurationMs}ms`);
22
+ * await handleBargein(err.userSpeech);
23
+ * }
24
+ * }
25
+ * ```
26
+ */
27
+ export class VoiceInterruptError extends Error {
28
+ /**
29
+ * @param context - Barge-in context captured at the moment of interruption.
30
+ * @param context.interruptedText - Full TTS text the agent was speaking.
31
+ * @param context.userSpeech - Transcript of the user's barge-in utterance.
32
+ * @param context.playedDurationMs - Milliseconds of audio already played.
33
+ */
34
+ constructor(context) {
35
+ super('Voice session interrupted by user');
36
+ /** Discriminant name — always `'VoiceInterruptError'` for `instanceof`-free checks. */
37
+ this.name = 'VoiceInterruptError';
38
+ this.interruptedText = context.interruptedText;
39
+ this.userSpeech = context.userSpeech;
40
+ this.playedDurationMs = context.playedDurationMs;
41
+ }
42
+ }
43
+ //# sourceMappingURL=VoiceInterruptError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VoiceInterruptError.js","sourceRoot":"","sources":["../../src/voice-pipeline/VoiceInterruptError.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAiB5C;;;;;OAKG;IACH,YAAY,OAIX;QACC,KAAK,CAAC,mCAAmC,CAAC,CAAC;QA3B7C,uFAAuF;QAC9E,SAAI,GAAG,qBAAqB,CAAC;QA2BpC,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC;QAC/C,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACrC,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IACnD,CAAC;CACF"}
@@ -16,7 +16,7 @@
16
16
  * ```
17
17
  */
18
18
  import { EventEmitter } from 'node:events';
19
- import type { IBargeinHandler, IDiarizationEngine, IEndpointDetector, IStreamTransport, IStreamingSTT, IStreamingTTS, IVoicePipelineAgentSession, PipelineState, VoicePipelineConfig, VoicePipelineSession } from './types.js';
19
+ import type { IBargeinHandler, IDiarizationEngine, IEndpointDetector, IStreamTransport, IStreamingSTT, IStreamingTTS, IVoicePipelineAgentSession, PipelineState, TurnCompleteEvent, VoicePipelineConfig, VoicePipelineSession } from './types.js';
20
20
  /**
21
21
  * Overrides for injecting pre-built components (primarily for testing).
22
22
  * In production, components would be resolved from ExtensionManager.
@@ -78,6 +78,49 @@ export declare class VoicePipelineOrchestrator extends EventEmitter {
78
78
  * @param reason - Optional human-readable reason for diagnostics.
79
79
  */
80
80
  stopSession(reason?: string): Promise<void>;
81
+ /**
82
+ * Wait for the next user turn to complete.
83
+ *
84
+ * Wraps the internal `'turn_complete'` event in a one-shot Promise so that
85
+ * graph nodes (via VoiceTransportAdapter) can `await` user input without
86
+ * having to manage raw EventEmitter subscriptions themselves.
87
+ *
88
+ * Resolves with the first `TurnCompleteEvent` fired after this call.
89
+ * If the session is closed before a turn completes, the Promise will never
90
+ * resolve — callers should race it against a session-close signal if needed.
91
+ *
92
+ * @returns A Promise that resolves with the completed turn event.
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * const turn = await orchestrator.waitForUserTurn();
97
+ * console.log('User said:', turn.transcript);
98
+ * ```
99
+ */
100
+ waitForUserTurn(): Promise<TurnCompleteEvent>;
101
+ /**
102
+ * Push text to the active TTS session.
103
+ *
104
+ * Accepts either a plain string or an `AsyncIterable<string>` of token chunks
105
+ * (e.g. a streaming LLM response). Calls `pushTokens()` on the active TTS
106
+ * session for each token, then calls `flush()` to signal end-of-utterance.
107
+ *
108
+ * Used by VoiceTransportAdapter to deliver graph node output as speech
109
+ * without the caller needing a direct reference to the TTS session.
110
+ *
111
+ * @param text - A complete string, or an async iterable of string tokens.
112
+ * @throws {Error} If there is no active TTS session (i.e. session not started).
113
+ *
114
+ * @example
115
+ * ```typescript
116
+ * // Plain string
117
+ * await orchestrator.pushToTTS('Hello, how can I help?');
118
+ *
119
+ * // Streaming tokens from an LLM
120
+ * await orchestrator.pushToTTS(llm.streamTokens(prompt));
121
+ * ```
122
+ */
123
+ pushToTTS(text: string | AsyncIterable<string>): Promise<void>;
81
124
  /**
82
125
  * Forward audio frames from transport to STT session.
83
126
  */