@mobileai/react-native 0.9.16 → 0.9.18

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 (214) hide show
  1. package/README.md +2 -2
  2. package/package.json +5 -8
  3. package/lib/module/__cli_tmp__.js.map +0 -1
  4. package/lib/module/components/AIAgent.js.map +0 -1
  5. package/lib/module/components/AIZone.js.map +0 -1
  6. package/lib/module/components/AgentChatBar.js.map +0 -1
  7. package/lib/module/components/AgentErrorBoundary.js.map +0 -1
  8. package/lib/module/components/AgentOverlay.js.map +0 -1
  9. package/lib/module/components/DiscoveryTooltip.js.map +0 -1
  10. package/lib/module/components/HighlightOverlay.js.map +0 -1
  11. package/lib/module/components/Icons.js.map +0 -1
  12. package/lib/module/components/ProactiveHint.js.map +0 -1
  13. package/lib/module/components/cards/InfoCard.js.map +0 -1
  14. package/lib/module/components/cards/ReviewSummary.js.map +0 -1
  15. package/lib/module/config/endpoints.js.map +0 -1
  16. package/lib/module/core/ActionRegistry.js.map +0 -1
  17. package/lib/module/core/AgentRuntime.js.map +0 -1
  18. package/lib/module/core/FiberTreeWalker.js.map +0 -1
  19. package/lib/module/core/IdleDetector.js.map +0 -1
  20. package/lib/module/core/MCPBridge.js.map +0 -1
  21. package/lib/module/core/ScreenDehydrator.js.map +0 -1
  22. package/lib/module/core/ZoneRegistry.js.map +0 -1
  23. package/lib/module/core/systemPrompt.js.map +0 -1
  24. package/lib/module/core/types.js.map +0 -1
  25. package/lib/module/hooks/useAction.js.map +0 -1
  26. package/lib/module/index.js.map +0 -1
  27. package/lib/module/plugin/withAppIntents.js.map +0 -1
  28. package/lib/module/providers/GeminiProvider.js.map +0 -1
  29. package/lib/module/providers/OpenAIProvider.js.map +0 -1
  30. package/lib/module/providers/ProviderFactory.js.map +0 -1
  31. package/lib/module/services/AudioInputService.js.map +0 -1
  32. package/lib/module/services/AudioOutputService.js.map +0 -1
  33. package/lib/module/services/KnowledgeBaseService.js.map +0 -1
  34. package/lib/module/services/VoiceService.js.map +0 -1
  35. package/lib/module/services/flags/FlagService.js.map +0 -1
  36. package/lib/module/services/telemetry/MobileAI.js.map +0 -1
  37. package/lib/module/services/telemetry/PiiScrubber.js.map +0 -1
  38. package/lib/module/services/telemetry/TelemetryService.js.map +0 -1
  39. package/lib/module/services/telemetry/TouchAutoCapture.js.map +0 -1
  40. package/lib/module/services/telemetry/device.js.map +0 -1
  41. package/lib/module/services/telemetry/deviceMetadata.js.map +0 -1
  42. package/lib/module/services/telemetry/index.js.map +0 -1
  43. package/lib/module/services/telemetry/types.js.map +0 -1
  44. package/lib/module/support/CSATSurvey.js.map +0 -1
  45. package/lib/module/support/EscalationEventSource.js.map +0 -1
  46. package/lib/module/support/EscalationSocket.js.map +0 -1
  47. package/lib/module/support/SupportChatModal.js.map +0 -1
  48. package/lib/module/support/SupportGreeting.js.map +0 -1
  49. package/lib/module/support/TicketStore.js.map +0 -1
  50. package/lib/module/support/escalateTool.js.map +0 -1
  51. package/lib/module/support/index.js.map +0 -1
  52. package/lib/module/support/supportPrompt.js.map +0 -1
  53. package/lib/module/support/types.js.map +0 -1
  54. package/lib/module/tools/datePickerTool.js.map +0 -1
  55. package/lib/module/tools/guideTool.js.map +0 -1
  56. package/lib/module/tools/index.js.map +0 -1
  57. package/lib/module/tools/keyboardTool.js.map +0 -1
  58. package/lib/module/tools/longPressTool.js.map +0 -1
  59. package/lib/module/tools/pickerTool.js.map +0 -1
  60. package/lib/module/tools/restoreTool.js.map +0 -1
  61. package/lib/module/tools/scrollTool.js.map +0 -1
  62. package/lib/module/tools/simplifyTool.js.map +0 -1
  63. package/lib/module/tools/sliderTool.js.map +0 -1
  64. package/lib/module/tools/tapTool.js.map +0 -1
  65. package/lib/module/tools/typeTool.js.map +0 -1
  66. package/lib/module/tools/types.js.map +0 -1
  67. package/lib/module/types/jsx.d.js.map +0 -1
  68. package/lib/module/utils/audioUtils.js.map +0 -1
  69. package/lib/module/utils/logger.js.map +0 -1
  70. package/lib/typescript/babel.config.d.ts.map +0 -1
  71. package/lib/typescript/bin/generate-map.d.cts.map +0 -1
  72. package/lib/typescript/eslint.config.d.mts.map +0 -1
  73. package/lib/typescript/generate-map.d.ts.map +0 -1
  74. package/lib/typescript/src/__cli_tmp__.d.ts.map +0 -1
  75. package/lib/typescript/src/components/AIAgent.d.ts.map +0 -1
  76. package/lib/typescript/src/components/AIZone.d.ts.map +0 -1
  77. package/lib/typescript/src/components/AgentChatBar.d.ts.map +0 -1
  78. package/lib/typescript/src/components/AgentErrorBoundary.d.ts.map +0 -1
  79. package/lib/typescript/src/components/AgentOverlay.d.ts.map +0 -1
  80. package/lib/typescript/src/components/DiscoveryTooltip.d.ts.map +0 -1
  81. package/lib/typescript/src/components/HighlightOverlay.d.ts.map +0 -1
  82. package/lib/typescript/src/components/Icons.d.ts.map +0 -1
  83. package/lib/typescript/src/components/ProactiveHint.d.ts.map +0 -1
  84. package/lib/typescript/src/components/cards/InfoCard.d.ts.map +0 -1
  85. package/lib/typescript/src/components/cards/ReviewSummary.d.ts.map +0 -1
  86. package/lib/typescript/src/config/endpoints.d.ts.map +0 -1
  87. package/lib/typescript/src/core/ActionRegistry.d.ts.map +0 -1
  88. package/lib/typescript/src/core/AgentRuntime.d.ts.map +0 -1
  89. package/lib/typescript/src/core/FiberTreeWalker.d.ts.map +0 -1
  90. package/lib/typescript/src/core/IdleDetector.d.ts.map +0 -1
  91. package/lib/typescript/src/core/MCPBridge.d.ts.map +0 -1
  92. package/lib/typescript/src/core/ScreenDehydrator.d.ts.map +0 -1
  93. package/lib/typescript/src/core/ZoneRegistry.d.ts.map +0 -1
  94. package/lib/typescript/src/core/systemPrompt.d.ts.map +0 -1
  95. package/lib/typescript/src/core/types.d.ts.map +0 -1
  96. package/lib/typescript/src/hooks/useAction.d.ts.map +0 -1
  97. package/lib/typescript/src/index.d.ts.map +0 -1
  98. package/lib/typescript/src/plugin/withAppIntents.d.ts.map +0 -1
  99. package/lib/typescript/src/providers/GeminiProvider.d.ts.map +0 -1
  100. package/lib/typescript/src/providers/OpenAIProvider.d.ts.map +0 -1
  101. package/lib/typescript/src/providers/ProviderFactory.d.ts.map +0 -1
  102. package/lib/typescript/src/services/AudioInputService.d.ts.map +0 -1
  103. package/lib/typescript/src/services/AudioOutputService.d.ts.map +0 -1
  104. package/lib/typescript/src/services/KnowledgeBaseService.d.ts.map +0 -1
  105. package/lib/typescript/src/services/VoiceService.d.ts.map +0 -1
  106. package/lib/typescript/src/services/flags/FlagService.d.ts.map +0 -1
  107. package/lib/typescript/src/services/telemetry/MobileAI.d.ts.map +0 -1
  108. package/lib/typescript/src/services/telemetry/PiiScrubber.d.ts.map +0 -1
  109. package/lib/typescript/src/services/telemetry/TelemetryService.d.ts.map +0 -1
  110. package/lib/typescript/src/services/telemetry/TouchAutoCapture.d.ts.map +0 -1
  111. package/lib/typescript/src/services/telemetry/device.d.ts.map +0 -1
  112. package/lib/typescript/src/services/telemetry/deviceMetadata.d.ts.map +0 -1
  113. package/lib/typescript/src/services/telemetry/index.d.ts.map +0 -1
  114. package/lib/typescript/src/services/telemetry/types.d.ts.map +0 -1
  115. package/lib/typescript/src/support/CSATSurvey.d.ts.map +0 -1
  116. package/lib/typescript/src/support/EscalationEventSource.d.ts.map +0 -1
  117. package/lib/typescript/src/support/EscalationSocket.d.ts.map +0 -1
  118. package/lib/typescript/src/support/SupportChatModal.d.ts.map +0 -1
  119. package/lib/typescript/src/support/SupportGreeting.d.ts.map +0 -1
  120. package/lib/typescript/src/support/TicketStore.d.ts.map +0 -1
  121. package/lib/typescript/src/support/escalateTool.d.ts.map +0 -1
  122. package/lib/typescript/src/support/index.d.ts.map +0 -1
  123. package/lib/typescript/src/support/supportPrompt.d.ts.map +0 -1
  124. package/lib/typescript/src/support/types.d.ts.map +0 -1
  125. package/lib/typescript/src/tools/datePickerTool.d.ts.map +0 -1
  126. package/lib/typescript/src/tools/guideTool.d.ts.map +0 -1
  127. package/lib/typescript/src/tools/index.d.ts.map +0 -1
  128. package/lib/typescript/src/tools/keyboardTool.d.ts.map +0 -1
  129. package/lib/typescript/src/tools/longPressTool.d.ts.map +0 -1
  130. package/lib/typescript/src/tools/pickerTool.d.ts.map +0 -1
  131. package/lib/typescript/src/tools/restoreTool.d.ts.map +0 -1
  132. package/lib/typescript/src/tools/scrollTool.d.ts.map +0 -1
  133. package/lib/typescript/src/tools/simplifyTool.d.ts.map +0 -1
  134. package/lib/typescript/src/tools/sliderTool.d.ts.map +0 -1
  135. package/lib/typescript/src/tools/tapTool.d.ts.map +0 -1
  136. package/lib/typescript/src/tools/typeTool.d.ts.map +0 -1
  137. package/lib/typescript/src/tools/types.d.ts.map +0 -1
  138. package/lib/typescript/src/utils/audioUtils.d.ts.map +0 -1
  139. package/lib/typescript/src/utils/logger.d.ts.map +0 -1
  140. package/src/__cli_tmp__.tsx +0 -9
  141. package/src/cli/analyzers/chain-analyzer.ts +0 -183
  142. package/src/cli/extractors/ai-extractor.ts +0 -6
  143. package/src/cli/extractors/ast-extractor.ts +0 -551
  144. package/src/cli/generate-intents.ts +0 -140
  145. package/src/cli/generate-map.ts +0 -121
  146. package/src/cli/generate-swift.ts +0 -116
  147. package/src/cli/scanners/expo-scanner.ts +0 -203
  148. package/src/cli/scanners/rn-scanner.ts +0 -445
  149. package/src/components/AIAgent.tsx +0 -1716
  150. package/src/components/AIZone.tsx +0 -147
  151. package/src/components/AgentChatBar.tsx +0 -1143
  152. package/src/components/AgentErrorBoundary.tsx +0 -78
  153. package/src/components/AgentOverlay.tsx +0 -73
  154. package/src/components/DiscoveryTooltip.tsx +0 -148
  155. package/src/components/HighlightOverlay.tsx +0 -136
  156. package/src/components/Icons.tsx +0 -253
  157. package/src/components/ProactiveHint.tsx +0 -145
  158. package/src/components/cards/InfoCard.tsx +0 -58
  159. package/src/components/cards/ReviewSummary.tsx +0 -76
  160. package/src/config/endpoints.ts +0 -22
  161. package/src/core/ActionRegistry.ts +0 -105
  162. package/src/core/AgentRuntime.ts +0 -1471
  163. package/src/core/FiberTreeWalker.ts +0 -930
  164. package/src/core/IdleDetector.ts +0 -72
  165. package/src/core/MCPBridge.ts +0 -163
  166. package/src/core/ScreenDehydrator.ts +0 -53
  167. package/src/core/ZoneRegistry.ts +0 -44
  168. package/src/core/systemPrompt.ts +0 -431
  169. package/src/core/types.ts +0 -521
  170. package/src/hooks/useAction.ts +0 -182
  171. package/src/index.ts +0 -83
  172. package/src/plugin/withAppIntents.ts +0 -98
  173. package/src/providers/GeminiProvider.ts +0 -357
  174. package/src/providers/OpenAIProvider.ts +0 -379
  175. package/src/providers/ProviderFactory.ts +0 -36
  176. package/src/services/AudioInputService.ts +0 -226
  177. package/src/services/AudioOutputService.ts +0 -236
  178. package/src/services/KnowledgeBaseService.ts +0 -156
  179. package/src/services/VoiceService.ts +0 -451
  180. package/src/services/flags/FlagService.ts +0 -137
  181. package/src/services/telemetry/MobileAI.ts +0 -66
  182. package/src/services/telemetry/PiiScrubber.ts +0 -17
  183. package/src/services/telemetry/TelemetryService.ts +0 -323
  184. package/src/services/telemetry/TouchAutoCapture.ts +0 -165
  185. package/src/services/telemetry/device.ts +0 -93
  186. package/src/services/telemetry/deviceMetadata.ts +0 -13
  187. package/src/services/telemetry/index.ts +0 -13
  188. package/src/services/telemetry/types.ts +0 -75
  189. package/src/support/CSATSurvey.tsx +0 -304
  190. package/src/support/EscalationEventSource.ts +0 -190
  191. package/src/support/EscalationSocket.ts +0 -152
  192. package/src/support/SupportChatModal.tsx +0 -563
  193. package/src/support/SupportGreeting.tsx +0 -161
  194. package/src/support/TicketStore.ts +0 -100
  195. package/src/support/escalateTool.ts +0 -174
  196. package/src/support/index.ts +0 -29
  197. package/src/support/supportPrompt.ts +0 -55
  198. package/src/support/types.ts +0 -155
  199. package/src/tools/datePickerTool.ts +0 -60
  200. package/src/tools/guideTool.ts +0 -76
  201. package/src/tools/index.ts +0 -20
  202. package/src/tools/keyboardTool.ts +0 -30
  203. package/src/tools/longPressTool.ts +0 -61
  204. package/src/tools/pickerTool.ts +0 -115
  205. package/src/tools/restoreTool.ts +0 -33
  206. package/src/tools/scrollTool.ts +0 -156
  207. package/src/tools/simplifyTool.ts +0 -33
  208. package/src/tools/sliderTool.ts +0 -65
  209. package/src/tools/tapTool.ts +0 -93
  210. package/src/tools/typeTool.ts +0 -113
  211. package/src/tools/types.ts +0 -58
  212. package/src/types/jsx.d.ts +0 -20
  213. package/src/utils/audioUtils.ts +0 -54
  214. package/src/utils/logger.ts +0 -38
@@ -1,236 +0,0 @@
1
- /**
2
- * AudioOutputService — AI speech playback for voice mode.
3
- *
4
- * Uses react-native-audio-api (Software Mansion) for gapless, low-latency
5
- * PCM playback. Decodes base64 PCM from Gemini Live API and queues it via
6
- * AudioBufferQueueSourceNode for seamless streaming.
7
- *
8
- * Requires: react-native-audio-api (development build only, not Expo Go)
9
- */
10
-
11
- import { logger } from '../utils/logger';
12
- import { base64ToFloat32 } from '../utils/audioUtils';
13
- import { NativeModules } from 'react-native';
14
-
15
- // ─── Types ─────────────────────────────────────────────────────
16
-
17
- /** Gemini Live API outputs 24kHz 16-bit mono PCM */
18
- const GEMINI_OUTPUT_SAMPLE_RATE = 24000;
19
-
20
- export interface AudioOutputConfig {
21
- sampleRate?: number;
22
- onPlaybackStart?: () => void;
23
- onPlaybackEnd?: () => void;
24
- onError?: (error: string) => void;
25
- }
26
-
27
- // ─── Service ───────────────────────────────────────────────────
28
-
29
- export class AudioOutputService {
30
- private config: AudioOutputConfig;
31
- private audioContext: any = null;
32
- private queueSourceNode: any = null;
33
- private gainNode: any = null;
34
- private muted = false;
35
- private isStarted = false;
36
- private chunkCount = 0;
37
-
38
- constructor(config: AudioOutputConfig = {}) {
39
- this.config = config;
40
- }
41
-
42
- // ─── Lifecycle ─────────────────────────────────────────────
43
-
44
- async initialize(): Promise<boolean> {
45
- try {
46
- let audioApi: any;
47
- try {
48
- // Guard: NativeModules.AudioApiModule is only present in dev/prod builds.
49
- // In Expo Go it is undefined, and require() throws a native bridge error
50
- // that cannot be caught by a standard try/catch.
51
- if (!NativeModules.AudioApiModule) {
52
- const msg =
53
- '[mobileai] react-native-audio-api native module not found. '
54
- + 'Voice audio output requires a development build (not Expo Go). '
55
- + 'Run: npx expo run:ios';
56
- logger.warn('AudioOutput', msg);
57
- this.config.onError?.(msg);
58
- return false;
59
- }
60
- // Static require — Metro needs a literal string.
61
- // The NativeModules guard above already prevents this from running in Expo Go.
62
- audioApi = require('react-native-audio-api');
63
- } catch {
64
- const msg =
65
- 'react-native-audio-api is required for audio output. Install with: npm install react-native-audio-api';
66
- logger.error('AudioOutput', msg);
67
- this.config.onError?.(msg);
68
- return false;
69
- }
70
-
71
- const sampleRate = this.config.sampleRate || GEMINI_OUTPUT_SAMPLE_RATE;
72
-
73
- // Configure audio session for duplex audio (simultaneous mic + speaker)
74
- // BEFORE creating AudioContext. This enables hardware-level AEC, AGC,
75
- // and noise suppression through the OS — no extra library needed.
76
- try {
77
- const { AudioManager } = audioApi;
78
- AudioManager.setAudioSessionOptions({
79
- iosCategory: 'playAndRecord',
80
- iosMode: 'voiceChat',
81
- iosOptions: ['defaultToSpeaker', 'allowBluetoothHFP'],
82
- });
83
- logger.info('AudioOutput', '🔊 Audio session configured: playAndRecord + voiceChat (hardware AEC enabled)');
84
- } catch (sessionErr: any) {
85
- logger.warn('AudioOutput', `⚠️ AudioManager setup failed: ${sessionErr?.message || sessionErr} — continuing with default session`);
86
- }
87
-
88
- // Create AudioContext at Gemini's output sample rate
89
- this.audioContext = new audioApi.AudioContext({ sampleRate });
90
-
91
- // Create GainNode for mute control
92
- this.gainNode = this.audioContext.createGain();
93
- this.gainNode.gain.value = 1.0;
94
- this.gainNode.connect(this.audioContext.destination);
95
-
96
- // Create AudioBufferQueueSourceNode for gapless streaming
97
- this.queueSourceNode = this.audioContext.createBufferQueueSource();
98
- this.queueSourceNode.connect(this.gainNode);
99
-
100
- logger.info('AudioOutput', `Initialized (${sampleRate}Hz, AudioBufferQueueSourceNode)`);
101
-
102
- // CRITICAL: Resume AudioContext — it starts in 'suspended' state.
103
- // Per Web Audio API spec, audio won't render until context is 'running'.
104
- if (this.audioContext.state === 'suspended') {
105
- await this.audioContext.resume();
106
- logger.info('AudioOutput', `AudioContext resumed: state=${this.audioContext.state}`);
107
- }
108
-
109
- return true;
110
- } catch (error: any) {
111
- logger.error('AudioOutput', `Failed to initialize: ${error.message}`);
112
- this.config.onError?.(error.message);
113
- return false;
114
- }
115
- }
116
-
117
- // ─── Enqueue Audio ─────────────────────────────────────────
118
-
119
- /** Add a base64-encoded PCM chunk from Gemini to the playback queue */
120
- enqueue(base64Audio: string): void {
121
- // LOG EVERY CALL — we need full visibility
122
- if (this.chunkCount < 5 || this.chunkCount % 50 === 0) {
123
- logger.info('AudioOutput', `📥 enqueue() called #${this.chunkCount + 1}: b64len=${base64Audio.length}, muted=${this.muted}, ctx=${!!this.audioContext}, ctxState=${this.audioContext?.state || 'null'}, queue=${!!this.queueSourceNode}, started=${this.isStarted}, gain=${this.gainNode?.gain?.value}`);
124
- }
125
-
126
- if (this.muted || !this.audioContext || !this.queueSourceNode) {
127
- logger.warn('AudioOutput', `⚠️ enqueue() SKIPPED #${this.chunkCount}: muted=${this.muted}, ctx=${!!this.audioContext}, queue=${!!this.queueSourceNode}`);
128
- return;
129
- }
130
-
131
- try {
132
- this.chunkCount++;
133
-
134
- // Decode base64 Int16 PCM → Float32
135
- const float32Data = base64ToFloat32(base64Audio);
136
- const sampleRate = this.config.sampleRate || GEMINI_OUTPUT_SAMPLE_RATE;
137
-
138
- // Diagnostic on first 3 chunks
139
- if (this.chunkCount <= 3) {
140
- let peakAmp = 0;
141
- for (let i = 0; i < float32Data.length; i++) {
142
- const abs = Math.abs(float32Data[i] || 0);
143
- if (abs > peakAmp) peakAmp = abs;
144
- }
145
- logger.info('AudioOutput', `🔍 Chunk #${this.chunkCount}: ${base64Audio.length} b64 → ${float32Data.length} samples, peakAmp=${peakAmp.toFixed(4)}, rate=${sampleRate}`);
146
- }
147
-
148
- // Create an AudioBuffer and fill it with PCM data
149
- const audioBuffer = this.audioContext.createBuffer(1, float32Data.length, sampleRate);
150
- audioBuffer.copyToChannel(float32Data, 0);
151
-
152
- // Enqueue the buffer for gapless playback
153
- this.queueSourceNode.enqueueBuffer(audioBuffer);
154
- if (this.chunkCount <= 3) {
155
- logger.info('AudioOutput', `✅ Buffer enqueued #${this.chunkCount}`);
156
- }
157
-
158
- // Start playback on first enqueue
159
- if (!this.isStarted) {
160
- this.queueSourceNode.start();
161
- this.isStarted = true;
162
- this.config.onPlaybackStart?.();
163
- logger.info('AudioOutput', `▶️ Playback started — ctxState=${this.audioContext?.state}`);
164
- }
165
-
166
- if (this.chunkCount % 20 === 0) {
167
- logger.info('AudioOutput', `Queued chunk #${this.chunkCount}`);
168
- }
169
- } catch (error: any) {
170
- logger.error('AudioOutput', `❌ Enqueue error #${this.chunkCount}: ${error.message}\n${error.stack?.substring(0, 300)}`);
171
- }
172
- }
173
-
174
- // ─── Mute/Unmute ──────────────────────────────────────────
175
-
176
- mute(): void {
177
- this.muted = true;
178
- if (this.gainNode) {
179
- this.gainNode.gain.value = 0;
180
- }
181
- logger.info('AudioOutput', 'Speaker muted');
182
- }
183
-
184
- unmute(): void {
185
- this.muted = false;
186
- if (this.gainNode) {
187
- this.gainNode.gain.value = 1.0;
188
- }
189
- logger.info('AudioOutput', 'Speaker unmuted');
190
- }
191
-
192
- get isMuted(): boolean {
193
- return this.muted;
194
- }
195
-
196
- // ─── Stop & Cleanup ───────────────────────────────────────
197
-
198
- async stop(): Promise<void> {
199
- try {
200
- if (this.queueSourceNode && this.isStarted) {
201
- this.queueSourceNode.stop();
202
- this.queueSourceNode.clearBuffers();
203
- }
204
- this.isStarted = false;
205
- this.chunkCount = 0;
206
-
207
- // Web Audio API: once a source node is stopped, it CANNOT be restarted.
208
- // We must create a fresh AudioBufferQueueSourceNode for the next session.
209
- if (this.audioContext && this.gainNode) {
210
- this.queueSourceNode = this.audioContext.createBufferQueueSource();
211
- this.queueSourceNode.connect(this.gainNode);
212
- logger.info('AudioOutput', 'Playback stopped — fresh queue node created for next session');
213
- } else {
214
- logger.info('AudioOutput', 'Playback stopped');
215
- }
216
-
217
- this.config.onPlaybackEnd?.();
218
- } catch (error: any) {
219
- logger.error('AudioOutput', `Stop error: ${error.message}`);
220
- }
221
- }
222
-
223
- async cleanup(): Promise<void> {
224
- await this.stop();
225
- try {
226
- if (this.audioContext) {
227
- await this.audioContext.close();
228
- }
229
- } catch {
230
- // Non-critical
231
- }
232
- this.audioContext = null;
233
- this.queueSourceNode = null;
234
- this.gainNode = null;
235
- }
236
- }
@@ -1,156 +0,0 @@
1
- /**
2
- * KnowledgeBaseService — Retrieves domain-specific knowledge for the AI agent.
3
- *
4
- * Supports two modes:
5
- * 1. Static entries: Consumer passes KnowledgeEntry[] — SDK handles keyword matching
6
- * 2. Custom retriever: Consumer passes { retrieve(query, screen) } — full control
7
- *
8
- * Results are formatted as plain text for the LLM tool response.
9
- */
10
-
11
- import { logger } from '../utils/logger';
12
- import type {
13
- KnowledgeEntry,
14
- KnowledgeRetriever,
15
- KnowledgeBaseConfig,
16
- } from '../core/types';
17
-
18
- // ─── Constants ─────────────────────────────────────────────────
19
-
20
- const DEFAULT_MAX_TOKENS = 2000;
21
- const CHARS_PER_TOKEN = 4; // Conservative estimate
22
- const DEFAULT_PRIORITY = 5;
23
-
24
- // ─── Service ───────────────────────────────────────────────────
25
-
26
- export class KnowledgeBaseService {
27
- private retriever: KnowledgeRetriever;
28
- private maxChars: number;
29
-
30
- constructor(config: KnowledgeBaseConfig, maxTokens?: number) {
31
- this.maxChars = (maxTokens ?? DEFAULT_MAX_TOKENS) * CHARS_PER_TOKEN;
32
-
33
- // Normalize: array → built-in keyword retriever, object → use as-is
34
- if (Array.isArray(config)) {
35
- const entries = config;
36
- this.retriever = {
37
- retrieve: async (query, screenName) =>
38
- this.keywordRetrieve(entries, query, screenName),
39
- };
40
- logger.info(
41
- 'KnowledgeBase',
42
- `Initialized with ${entries.length} static entries (budget: ${maxTokens ?? DEFAULT_MAX_TOKENS} tokens)`
43
- );
44
- } else {
45
- this.retriever = config;
46
- logger.info(
47
- 'KnowledgeBase',
48
- `Initialized with custom retriever (budget: ${maxTokens ?? DEFAULT_MAX_TOKENS} tokens)`
49
- );
50
- }
51
- }
52
-
53
- // ─── Public API ──────────────────────────────────────────────
54
-
55
- /**
56
- * Retrieve and format knowledge for a given query.
57
- * Returns formatted plain text for the LLM, or a "no results" message.
58
- */
59
- async retrieve(query: string, screenName: string): Promise<string> {
60
- try {
61
- const entries = await this.retriever.retrieve(query, screenName);
62
-
63
- if (!entries || entries.length === 0) {
64
- return 'No relevant knowledge found for this query. Answer based on what is visible on screen, or let the user know you don\'t have that information.';
65
- }
66
-
67
- // Apply token budget — take entries until we hit the limit
68
- const selected = this.applyTokenBudget(entries);
69
-
70
- logger.info(
71
- 'KnowledgeBase',
72
- `Retrieved ${selected.length}/${entries.length} entries for "${query}" (screen: ${screenName})`
73
- );
74
-
75
- return this.formatEntries(selected);
76
- } catch (error: any) {
77
- logger.error('KnowledgeBase', `Retrieval failed: ${error.message}`);
78
- return 'Knowledge retrieval failed. Answer based on what is visible on screen.';
79
- }
80
- }
81
-
82
- // ─── Built-in Keyword Retriever ──────────────────────────────
83
-
84
- /**
85
- * Simple keyword-based retrieval for static entries.
86
- * Scores entries by word overlap with the query, filters by screen.
87
- */
88
- private keywordRetrieve(
89
- entries: KnowledgeEntry[],
90
- query: string,
91
- screenName: string
92
- ): KnowledgeEntry[] {
93
- const queryWords = this.tokenize(query);
94
-
95
- if (queryWords.length === 0) return [];
96
-
97
- // Score each entry
98
- const scored = entries
99
- .filter((entry) => this.isScreenMatch(entry, screenName))
100
- .map((entry) => {
101
- const searchable = this.tokenize(
102
- `${entry.title} ${(entry.tags || []).join(' ')} ${entry.content}`
103
- );
104
- const matchCount = queryWords.filter((w) =>
105
- searchable.some((s) => s.includes(w) || w.includes(s))
106
- ).length;
107
- const score = matchCount * (entry.priority ?? DEFAULT_PRIORITY);
108
- return { entry, score };
109
- })
110
- .filter(({ score }) => score > 0)
111
- .sort((a, b) => b.score - a.score);
112
-
113
- return scored.map(({ entry }) => entry);
114
- }
115
-
116
- // ─── Helpers ─────────────────────────────────────────────────
117
-
118
- /** Check if an entry should be included on the current screen. */
119
- private isScreenMatch(entry: KnowledgeEntry, screenName: string): boolean {
120
- if (!entry.screens || entry.screens.length === 0) return true;
121
- return entry.screens.some(
122
- (s) => s.toLowerCase() === screenName.toLowerCase()
123
- );
124
- }
125
-
126
- /** Tokenize text into lowercase words for matching. */
127
- private tokenize(text: string): string[] {
128
- return text
129
- .toLowerCase()
130
- .replace(/[^a-z0-9\u0600-\u06FF\s]/g, ' ') // Keep alphanumeric + Arabic chars
131
- .split(/\s+/)
132
- .filter((w) => w.length > 1); // Skip single-char words
133
- }
134
-
135
- /** Take entries until the token budget is exhausted. */
136
- private applyTokenBudget(entries: KnowledgeEntry[]): KnowledgeEntry[] {
137
- const selected: KnowledgeEntry[] = [];
138
- let totalChars = 0;
139
-
140
- for (const entry of entries) {
141
- const entryChars = entry.title.length + entry.content.length + 10; // +10 for formatting
142
- if (totalChars + entryChars > this.maxChars && selected.length > 0) break;
143
- selected.push(entry);
144
- totalChars += entryChars;
145
- }
146
-
147
- return selected;
148
- }
149
-
150
- /** Format entries as readable plain text for the LLM. */
151
- private formatEntries(entries: KnowledgeEntry[]): string {
152
- return entries
153
- .map((e) => `## ${e.title}\n${e.content}`)
154
- .join('\n\n');
155
- }
156
- }