neoagent 2.3.1-beta.4 → 2.3.1-beta.41

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 (290) hide show
  1. package/.env.example +45 -0
  2. package/docs/capabilities.md +2 -2
  3. package/docs/configuration.md +12 -5
  4. package/docs/hardware.md +1 -1
  5. package/docs/integrations.md +2 -3
  6. package/flutter_app/.metadata +42 -0
  7. package/flutter_app/README.md +21 -0
  8. package/flutter_app/analysis_options.yaml +32 -0
  9. package/flutter_app/android/app/build.gradle.kts +109 -0
  10. package/flutter_app/android/app/src/debug/AndroidManifest.xml +7 -0
  11. package/flutter_app/android/app/src/main/AndroidManifest.xml +147 -0
  12. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/MainActivity.kt +747 -0
  13. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthConnectGateway.kt +280 -0
  14. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncNotifications.kt +113 -0
  15. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncPayload.kt +57 -0
  16. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncScheduler.kt +78 -0
  17. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncWorker.kt +253 -0
  18. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/PermissionsRationaleActivity.kt +46 -0
  19. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingBootReceiver.kt +21 -0
  20. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingForegroundService.kt +586 -0
  21. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingStateStore.kt +78 -0
  22. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingUploadClient.kt +104 -0
  23. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiHomeWidgetProvider.kt +457 -0
  24. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiWidgetStore.kt +194 -0
  25. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/VoiceLaunchWidgetProvider.kt +67 -0
  26. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetConfigActivity.kt +228 -0
  27. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncScheduler.kt +72 -0
  28. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncWorker.kt +186 -0
  29. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetTaskRunWorker.kt +210 -0
  30. package/flutter_app/android/app/src/main/res/drawable/launch_background.xml +12 -0
  31. package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_bg.xml +11 -0
  32. package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_task_bg.xml +8 -0
  33. package/flutter_app/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
  34. package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget.xml +138 -0
  35. package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget_task_row.xml +52 -0
  36. package/flutter_app/android/app/src/main/res/layout/neoagent_voice_widget.xml +49 -0
  37. package/flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  38. package/flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  39. package/flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  40. package/flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  41. package/flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  42. package/flutter_app/android/app/src/main/res/values/strings.xml +12 -0
  43. package/flutter_app/android/app/src/main/res/values/styles.xml +18 -0
  44. package/flutter_app/android/app/src/main/res/values-night/styles.xml +18 -0
  45. package/flutter_app/android/app/src/main/res/xml/file_paths.xml +6 -0
  46. package/flutter_app/android/app/src/main/res/xml/neoagent_ai_widget_info.xml +12 -0
  47. package/flutter_app/android/app/src/main/res/xml/neoagent_voice_widget_info.xml +12 -0
  48. package/flutter_app/android/app/src/profile/AndroidManifest.xml +7 -0
  49. package/flutter_app/android/build.gradle.kts +24 -0
  50. package/flutter_app/android/ci-release.keystore +0 -0
  51. package/flutter_app/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  52. package/flutter_app/android/gradle.properties +3 -0
  53. package/flutter_app/android/key.properties +4 -0
  54. package/flutter_app/android/settings.gradle.kts +26 -0
  55. package/flutter_app/assets/branding/app_icon_1024.png +0 -0
  56. package/flutter_app/assets/branding/app_icon_128.png +0 -0
  57. package/flutter_app/assets/branding/app_icon_192.png +0 -0
  58. package/flutter_app/assets/branding/app_icon_256.png +0 -0
  59. package/flutter_app/assets/branding/app_icon_32.png +0 -0
  60. package/flutter_app/assets/branding/app_icon_512.png +0 -0
  61. package/flutter_app/assets/branding/app_icon_64.png +0 -0
  62. package/flutter_app/assets/branding/tray_icon_template.png +0 -0
  63. package/flutter_app/lib/features/location/location_service.dart +119 -0
  64. package/flutter_app/lib/features/notifications/notification_interceptor.dart +97 -0
  65. package/flutter_app/lib/main.dart +99 -0
  66. package/flutter_app/lib/main_account_settings.dart +1250 -0
  67. package/flutter_app/lib/main_admin.dart +886 -0
  68. package/flutter_app/lib/main_app_shell.dart +1682 -0
  69. package/flutter_app/lib/main_chat.dart +3352 -0
  70. package/flutter_app/lib/main_controller.dart +6781 -0
  71. package/flutter_app/lib/main_devices.dart +2301 -0
  72. package/flutter_app/lib/main_integrations.dart +1129 -0
  73. package/flutter_app/lib/main_launcher.dart +959 -0
  74. package/flutter_app/lib/main_launcher_entry.dart +5 -0
  75. package/flutter_app/lib/main_models.dart +3546 -0
  76. package/flutter_app/lib/main_navigation.dart +193 -0
  77. package/flutter_app/lib/main_operations.dart +4851 -0
  78. package/flutter_app/lib/main_recordings.dart +870 -0
  79. package/flutter_app/lib/main_runtime.dart +806 -0
  80. package/flutter_app/lib/main_settings.dart +2024 -0
  81. package/flutter_app/lib/main_shared.dart +2861 -0
  82. package/flutter_app/lib/main_theme.dart +204 -0
  83. package/flutter_app/lib/main_voice_assistant.dart +957 -0
  84. package/flutter_app/lib/src/android_apk_drop_zone.dart +32 -0
  85. package/flutter_app/lib/src/android_apk_drop_zone_stub.dart +16 -0
  86. package/flutter_app/lib/src/android_apk_drop_zone_web.dart +348 -0
  87. package/flutter_app/lib/src/android_app_installer.dart +22 -0
  88. package/flutter_app/lib/src/android_app_installer_io.dart +122 -0
  89. package/flutter_app/lib/src/android_app_installer_stub.dart +21 -0
  90. package/flutter_app/lib/src/android_launcher_bridge.dart +239 -0
  91. package/flutter_app/lib/src/app_launch_bridge.dart +29 -0
  92. package/flutter_app/lib/src/app_release_updater.dart +511 -0
  93. package/flutter_app/lib/src/backend_client.dart +1833 -0
  94. package/flutter_app/lib/src/desktop_companion.dart +2 -0
  95. package/flutter_app/lib/src/desktop_companion_actions.dart +586 -0
  96. package/flutter_app/lib/src/desktop_companion_io.dart +538 -0
  97. package/flutter_app/lib/src/desktop_companion_stub.dart +59 -0
  98. package/flutter_app/lib/src/desktop_native_bridge.dart +91 -0
  99. package/flutter_app/lib/src/desktop_screen_capture.dart +21 -0
  100. package/flutter_app/lib/src/desktop_screen_capture_io.dart +142 -0
  101. package/flutter_app/lib/src/desktop_screen_capture_stub.dart +12 -0
  102. package/flutter_app/lib/src/diagnostics_logger.dart +119 -0
  103. package/flutter_app/lib/src/health_bridge.dart +136 -0
  104. package/flutter_app/lib/src/live_voice_capture.dart +85 -0
  105. package/flutter_app/lib/src/messaging_access_summary.dart +46 -0
  106. package/flutter_app/lib/src/network/app_http_client.dart +53 -0
  107. package/flutter_app/lib/src/network/app_http_client_factory.dart +6 -0
  108. package/flutter_app/lib/src/network/app_http_client_io.dart +138 -0
  109. package/flutter_app/lib/src/network/app_http_client_stub.dart +3 -0
  110. package/flutter_app/lib/src/network/app_http_client_web.dart +94 -0
  111. package/flutter_app/lib/src/oauth_launcher.dart +33 -0
  112. package/flutter_app/lib/src/oauth_launcher_io.dart +77 -0
  113. package/flutter_app/lib/src/oauth_launcher_stub.dart +33 -0
  114. package/flutter_app/lib/src/oauth_launcher_web.dart +107 -0
  115. package/flutter_app/lib/src/recording_bridge.dart +232 -0
  116. package/flutter_app/lib/src/recording_bridge_io.dart +1019 -0
  117. package/flutter_app/lib/src/recording_bridge_stub.dart +120 -0
  118. package/flutter_app/lib/src/recording_bridge_web.dart +689 -0
  119. package/flutter_app/lib/src/recording_payloads.dart +86 -0
  120. package/flutter_app/lib/src/theme/palette.dart +81 -0
  121. package/flutter_app/lib/src/widget_bridge.dart +49 -0
  122. package/flutter_app/linux/CMakeLists.txt +128 -0
  123. package/flutter_app/linux/flutter/CMakeLists.txt +88 -0
  124. package/flutter_app/linux/flutter/generated_plugin_registrant.cc +43 -0
  125. package/flutter_app/linux/flutter/generated_plugin_registrant.h +15 -0
  126. package/flutter_app/linux/flutter/generated_plugins.cmake +31 -0
  127. package/flutter_app/linux/runner/CMakeLists.txt +26 -0
  128. package/flutter_app/linux/runner/main.cc +6 -0
  129. package/flutter_app/linux/runner/my_application.cc +144 -0
  130. package/flutter_app/linux/runner/my_application.h +18 -0
  131. package/flutter_app/linux/runner/resources/app_icon.png +0 -0
  132. package/flutter_app/macos/Flutter/Flutter-Debug.xcconfig +2 -0
  133. package/flutter_app/macos/Flutter/Flutter-Release.xcconfig +2 -0
  134. package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +40 -0
  135. package/flutter_app/macos/Podfile +42 -0
  136. package/flutter_app/macos/Podfile.lock +87 -0
  137. package/flutter_app/macos/Runner/AppDelegate.swift +576 -0
  138. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +68 -0
  139. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
  140. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
  141. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
  142. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
  143. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
  144. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
  145. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
  146. package/flutter_app/macos/Runner/Base.lproj/MainMenu.xib +342 -0
  147. package/flutter_app/macos/Runner/Configs/AppInfo.xcconfig +14 -0
  148. package/flutter_app/macos/Runner/Configs/Debug.xcconfig +2 -0
  149. package/flutter_app/macos/Runner/Configs/Release.xcconfig +2 -0
  150. package/flutter_app/macos/Runner/Configs/Warnings.xcconfig +13 -0
  151. package/flutter_app/macos/Runner/DebugProfile.entitlements +16 -0
  152. package/flutter_app/macos/Runner/Info.plist +36 -0
  153. package/flutter_app/macos/Runner/MainFlutterWindow.swift +19 -0
  154. package/flutter_app/macos/Runner/Release.entitlements +12 -0
  155. package/flutter_app/macos/Runner.xcodeproj/project.pbxproj +801 -0
  156. package/flutter_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  157. package/flutter_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +99 -0
  158. package/flutter_app/macos/Runner.xcworkspace/contents.xcworkspacedata +10 -0
  159. package/flutter_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  160. package/flutter_app/macos/RunnerTests/RunnerTests.swift +12 -0
  161. package/flutter_app/patch_strings.py +12 -0
  162. package/flutter_app/pubspec.lock +1088 -0
  163. package/flutter_app/pubspec.yaml +53 -0
  164. package/flutter_app/test/messaging_access_summary_test.dart +22 -0
  165. package/flutter_app/test/recording_payloads_test.dart +53 -0
  166. package/flutter_app/third_party/desktop_audio_capture/LICENSE +21 -0
  167. package/flutter_app/third_party/desktop_audio_capture/README.md +262 -0
  168. package/flutter_app/third_party/desktop_audio_capture/lib/audio_capture.dart +65 -0
  169. package/flutter_app/third_party/desktop_audio_capture/lib/config/mic_audio_config.dart +153 -0
  170. package/flutter_app/third_party/desktop_audio_capture/lib/config/system_adudio_config.dart +110 -0
  171. package/flutter_app/third_party/desktop_audio_capture/lib/mic/mic_audio_capture.dart +461 -0
  172. package/flutter_app/third_party/desktop_audio_capture/lib/model/audio_status.dart +91 -0
  173. package/flutter_app/third_party/desktop_audio_capture/lib/model/decibel_data.dart +106 -0
  174. package/flutter_app/third_party/desktop_audio_capture/lib/model/input_device_type.dart +219 -0
  175. package/flutter_app/third_party/desktop_audio_capture/lib/system/system_audio_capture.dart +336 -0
  176. package/flutter_app/third_party/desktop_audio_capture/linux/CMakeLists.txt +101 -0
  177. package/flutter_app/third_party/desktop_audio_capture/linux/audio_capture_plugin.cc +692 -0
  178. package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/audio_capture_plugin.h +35 -0
  179. package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/mic_capture_plugin.h +36 -0
  180. package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/audio_capture_plugin.h +32 -0
  181. package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/mic_capture_plugin.h +32 -0
  182. package/flutter_app/third_party/desktop_audio_capture/linux/mic_capture_plugin.cc +878 -0
  183. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/AudioCapturePlugin.swift +27 -0
  184. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/MicCapturePlugin.swift +1172 -0
  185. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/SystemCapturePlugin.swift +655 -0
  186. package/flutter_app/third_party/desktop_audio_capture/macos/Resources/PrivacyInfo.xcprivacy +12 -0
  187. package/flutter_app/third_party/desktop_audio_capture/macos/desktop_audio_capture.podspec +30 -0
  188. package/flutter_app/third_party/desktop_audio_capture/pubspec.yaml +87 -0
  189. package/flutter_app/third_party/desktop_audio_capture/windows/CMakeLists.txt +105 -0
  190. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.cpp +80 -0
  191. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.h +31 -0
  192. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin_c_api.cpp +12 -0
  193. package/flutter_app/third_party/desktop_audio_capture/windows/include/audio_capture/audio_capture_plugin_c_api.h +23 -0
  194. package/flutter_app/third_party/desktop_audio_capture/windows/include/desktop_audio_capture/audio_capture_plugin.h +25 -0
  195. package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.cpp +1117 -0
  196. package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.h +115 -0
  197. package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.cpp +777 -0
  198. package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.h +87 -0
  199. package/flutter_app/third_party/flutter_secure_storage_linux/linux/CMakeLists.txt +30 -0
  200. package/flutter_app/third_party/flutter_secure_storage_linux/linux/flutter_secure_storage_linux_plugin.cc +215 -0
  201. package/flutter_app/third_party/flutter_secure_storage_linux/linux/include/flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h +27 -0
  202. package/flutter_app/third_party/flutter_secure_storage_linux/pubspec.yaml +20 -0
  203. package/flutter_app/tool/generate_desktop_branding.py +219 -0
  204. package/flutter_app/web/favicon.png +0 -0
  205. package/flutter_app/web/favicon.svg +12 -0
  206. package/flutter_app/web/icons/Icon-192.png +0 -0
  207. package/flutter_app/web/icons/Icon-512.png +0 -0
  208. package/flutter_app/web/icons/Icon-maskable-192.png +0 -0
  209. package/flutter_app/web/icons/Icon-maskable-512.png +0 -0
  210. package/flutter_app/web/index.html +39 -0
  211. package/flutter_app/web/manifest.json +35 -0
  212. package/flutter_app/windows/CMakeLists.txt +108 -0
  213. package/flutter_app/windows/flutter/CMakeLists.txt +109 -0
  214. package/flutter_app/windows/flutter/generated_plugin_registrant.cc +47 -0
  215. package/flutter_app/windows/flutter/generated_plugin_registrant.h +15 -0
  216. package/flutter_app/windows/flutter/generated_plugins.cmake +35 -0
  217. package/flutter_app/windows/runner/CMakeLists.txt +41 -0
  218. package/flutter_app/windows/runner/Runner.rc +121 -0
  219. package/flutter_app/windows/runner/flutter_window.cpp +533 -0
  220. package/flutter_app/windows/runner/flutter_window.h +37 -0
  221. package/flutter_app/windows/runner/main.cpp +53 -0
  222. package/flutter_app/windows/runner/resource.h +16 -0
  223. package/flutter_app/windows/runner/resources/app_icon.ico +0 -0
  224. package/flutter_app/windows/runner/runner.exe.manifest +14 -0
  225. package/flutter_app/windows/runner/utils.cpp +65 -0
  226. package/flutter_app/windows/runner/utils.h +19 -0
  227. package/flutter_app/windows/runner/win32_window.cpp +299 -0
  228. package/flutter_app/windows/runner/win32_window.h +102 -0
  229. package/lib/install_helpers.js +31 -0
  230. package/lib/manager.js +227 -6
  231. package/package.json +3 -1
  232. package/server/db/database.js +110 -0
  233. package/server/http/middleware.js +55 -2
  234. package/server/http/routes.js +1 -0
  235. package/server/index.js +3 -0
  236. package/server/public/.last_build_id +1 -1
  237. package/server/public/assets/NOTICES +1 -1
  238. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  239. package/server/public/canvaskit/wimp.wasm +0 -0
  240. package/server/public/flutter_bootstrap.js +2 -2
  241. package/server/public/main.dart.js +74324 -73132
  242. package/server/routes/integrations.js +108 -1
  243. package/server/routes/memory.js +11 -2
  244. package/server/{http/routes → routes}/screenHistory.js +2 -2
  245. package/server/routes/settings.js +75 -2
  246. package/server/{http/routes → routes}/triggers.js +2 -2
  247. package/server/routes/wearable.js +67 -0
  248. package/server/services/ai/models.js +30 -0
  249. package/server/services/ai/providers/githubCopilot.js +97 -0
  250. package/server/services/ai/providers/openai.js +2 -1
  251. package/server/services/ai/providers/openaiCodex.js +31 -0
  252. package/server/services/ai/settings.js +20 -0
  253. package/server/services/ai/toolSelector.js +14 -1
  254. package/server/services/ai/tools.js +77 -4
  255. package/server/services/desktop/screenRecorder.js +65 -9
  256. package/server/services/integrations/env.js +5 -0
  257. package/server/services/integrations/figma/provider.js +1 -0
  258. package/server/services/integrations/github/common.js +106 -0
  259. package/server/services/integrations/github/provider.js +499 -0
  260. package/server/services/integrations/github/repos.js +1124 -0
  261. package/server/services/integrations/google/provider.js +1 -0
  262. package/server/services/integrations/home_assistant/provider.js +325 -26
  263. package/server/services/integrations/manager.js +88 -12
  264. package/server/services/integrations/microsoft/provider.js +1 -0
  265. package/server/services/integrations/oauth_provider.js +25 -8
  266. package/server/services/integrations/provider_config_store.js +85 -0
  267. package/server/services/integrations/registry.js +4 -0
  268. package/server/services/integrations/spotify/provider.js +1 -0
  269. package/server/services/integrations/trello/provider.js +842 -0
  270. package/server/services/manager.js +46 -1
  271. package/server/services/mcp/client.js +120 -23
  272. package/server/services/memory/manager.js +39 -2
  273. package/server/services/messaging/access_policy.js +10 -0
  274. package/server/services/messaging/manager.js +49 -0
  275. package/server/services/messaging/meshtastic.js +260 -0
  276. package/server/services/messaging/meshtastic_env.js +100 -0
  277. package/server/services/messaging/meshtastic_protocol.js +476 -0
  278. package/server/services/messaging/meshtastic_tcp_transport.js +25 -0
  279. package/server/services/tasks/runtime.js +1 -1
  280. package/server/services/voice/openaiClient.js +4 -1
  281. package/server/services/voice/openaiSpeech.js +6 -1
  282. package/server/services/voice/providers.js +52 -12
  283. package/server/services/voice/runtimeManager.js +136 -19
  284. package/server/services/voice/turnRunner.js +29 -9
  285. package/server/services/wearable/firmware_manifest.js +370 -0
  286. package/server/services/wearable/gateway.js +350 -0
  287. package/server/services/wearable/protocol.js +45 -0
  288. package/server/services/wearable/service.js +244 -0
  289. package/server/utils/local_secrets.js +56 -0
  290. package/server/utils/logger.js +37 -9
@@ -6,6 +6,7 @@ const { AGENT_DATA_DIR } = require('../../../runtime/paths');
6
6
  const { getOpenAiClient } = require('./openaiClient');
7
7
  const { synthesizeSpeechBuffer } = require('./openaiSpeech');
8
8
  const { transcribeChunkWithDeepgram } = require('../recordings/deepgram');
9
+ const { decryptLocalValue } = require('../../utils/local_secrets');
9
10
 
10
11
  const DEFAULT_STT_PROVIDER = 'openai';
11
12
  const DEFAULT_TTS_PROVIDER = 'openai';
@@ -36,6 +37,12 @@ const DEFAULT_GEMINI_TRANSCRIPTION_PROMPT =
36
37
  'Transcribe this audio verbatim. Return only the transcript text.';
37
38
  const EMOJI_SPEECH_REGEX =
38
39
  /[\p{Extended_Pictographic}\p{Emoji_Presentation}\p{Regional_Indicator}\u200D\uFE0F\u20E3]/gu;
40
+ const WEARABLE_SAFE_AUDIO_FORMAT = Object.freeze({
41
+ responseFormat: 'wav',
42
+ mimeType: 'audio/wav',
43
+ deepgramEncoding: 'linear16',
44
+ deepgramContainer: 'wav',
45
+ });
39
46
 
40
47
  function withTimeout(promise, timeoutMs, label) {
41
48
  const normalizedTimeout = Number(timeoutMs);
@@ -94,7 +101,7 @@ function resolveApiKey(candidates = []) {
94
101
  const snake = lower.replace(/[^a-z0-9]+/g, '_');
95
102
  const variants = [key, lower, snake];
96
103
  for (const variant of variants) {
97
- const value = keys[variant];
104
+ const value = decryptLocalValue(keys[variant]);
98
105
  if (typeof value === 'string' && value.trim()) {
99
106
  return value.trim();
100
107
  }
@@ -138,9 +145,16 @@ function normalizeVoiceSynthesisOptions(options = {}) {
138
145
  provider,
139
146
  model: resolveTtsModel(provider, options.model),
140
147
  voice: resolveTtsVoice(provider, options.voice),
148
+ responseFormat: String(options.responseFormat || '').trim().toLowerCase(),
149
+ transport: String(options.transport || '').trim().toLowerCase(),
141
150
  };
142
151
  }
143
152
 
153
+ function resolveWearableSafeAudioOptions(options = {}) {
154
+ return String(options.transport || '').trim().toLowerCase() === 'wearable'
155
+ || String(options.responseFormat || '').trim().toLowerCase() === 'wav';
156
+ }
157
+
144
158
  function requireApiKey(settingLabel, candidates = []) {
145
159
  const apiKey = resolveApiKey(candidates);
146
160
  if (!apiKey) {
@@ -341,10 +355,15 @@ async function synthesizeWithOpenAi(text, model, voice, options = {}) {
341
355
  if (!client) {
342
356
  throw new Error('OpenAI TTS is selected but OPENAI_API_KEY is not configured.');
343
357
  }
344
- const audioBytes = await synthesizeSpeechBuffer(client, text, { model, voice });
358
+ const useWearableSafeAudio = resolveWearableSafeAudioOptions(options);
359
+ const audioBytes = await synthesizeSpeechBuffer(client, text, {
360
+ model,
361
+ voice,
362
+ responseFormat: useWearableSafeAudio ? WEARABLE_SAFE_AUDIO_FORMAT.responseFormat : 'mp3',
363
+ });
345
364
  return {
346
365
  audioBytes,
347
- mimeType: 'audio/mpeg',
366
+ mimeType: useWearableSafeAudio ? WEARABLE_SAFE_AUDIO_FORMAT.mimeType : 'audio/mpeg',
348
367
  };
349
368
  }
350
369
 
@@ -356,25 +375,37 @@ async function streamWithOpenAi(text, model, voice, options = {}, onChunk) {
356
375
  if (!client) {
357
376
  throw new Error('OpenAI TTS is selected but OPENAI_API_KEY is not configured.');
358
377
  }
378
+ const useWearableSafeAudio = resolveWearableSafeAudioOptions(options);
359
379
  const response = await client.audio.speech.create({
360
380
  model: String(model || 'gpt-4o-mini-tts').trim() || 'gpt-4o-mini-tts',
361
381
  voice: String(voice || 'alloy').trim() || 'alloy',
362
382
  input: text,
363
- response_format: 'mp3',
383
+ response_format: useWearableSafeAudio ? WEARABLE_SAFE_AUDIO_FORMAT.responseFormat : 'mp3',
364
384
  });
365
385
  const chunks = [];
366
386
  for await (const chunk of response.body) {
367
387
  chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
368
388
  }
369
389
  const audioBytes = Buffer.concat(chunks);
370
- await onChunk({ audioBytes, mimeType: 'audio/mpeg' });
390
+ await onChunk({
391
+ audioBytes,
392
+ mimeType: useWearableSafeAudio ? WEARABLE_SAFE_AUDIO_FORMAT.mimeType : 'audio/mpeg',
393
+ });
371
394
  }
372
395
 
373
- async function synthesizeWithDeepgram(text, model) {
396
+ async function synthesizeWithDeepgram(text, model, options = {}) {
374
397
  const apiKey = requireApiKey('Deepgram TTS', ['DEEPGRAM_API_KEY']);
398
+ const useWearableSafeAudio = resolveWearableSafeAudioOptions(options);
399
+ const searchParams = new URLSearchParams({
400
+ model,
401
+ });
402
+ if (useWearableSafeAudio) {
403
+ searchParams.set('encoding', WEARABLE_SAFE_AUDIO_FORMAT.deepgramEncoding);
404
+ searchParams.set('container', WEARABLE_SAFE_AUDIO_FORMAT.deepgramContainer);
405
+ }
375
406
 
376
407
  return fetchAudioOrThrow(
377
- `https://api.deepgram.com/v1/speak?model=${encodeURIComponent(model)}`,
408
+ `https://api.deepgram.com/v1/speak?${searchParams.toString()}`,
378
409
  {
379
410
  method: 'POST',
380
411
  headers: {
@@ -384,13 +415,22 @@ async function synthesizeWithDeepgram(text, model) {
384
415
  body: JSON.stringify({ text }),
385
416
  },
386
417
  'Deepgram TTS request failed',
418
+ useWearableSafeAudio ? WEARABLE_SAFE_AUDIO_FORMAT.mimeType : 'audio/mpeg',
387
419
  );
388
420
  }
389
421
 
390
- async function streamWithDeepgram(text, model, onChunk) {
422
+ async function streamWithDeepgram(text, model, options = {}, onChunk) {
391
423
  const apiKey = requireApiKey('Deepgram TTS', ['DEEPGRAM_API_KEY']);
424
+ const useWearableSafeAudio = resolveWearableSafeAudioOptions(options);
425
+ const searchParams = new URLSearchParams({
426
+ model,
427
+ });
428
+ if (useWearableSafeAudio) {
429
+ searchParams.set('encoding', WEARABLE_SAFE_AUDIO_FORMAT.deepgramEncoding);
430
+ searchParams.set('container', WEARABLE_SAFE_AUDIO_FORMAT.deepgramContainer);
431
+ }
392
432
  await fetchAudioStreamOrThrow(
393
- `https://api.deepgram.com/v1/speak?model=${encodeURIComponent(model)}`,
433
+ `https://api.deepgram.com/v1/speak?${searchParams.toString()}`,
394
434
  {
395
435
  method: 'POST',
396
436
  headers: {
@@ -400,7 +440,7 @@ async function streamWithDeepgram(text, model, onChunk) {
400
440
  body: JSON.stringify({ text }),
401
441
  },
402
442
  'Deepgram TTS stream failed',
403
- 'audio/mpeg',
443
+ useWearableSafeAudio ? WEARABLE_SAFE_AUDIO_FORMAT.mimeType : 'audio/mpeg',
404
444
  onChunk,
405
445
  );
406
446
  }
@@ -586,7 +626,7 @@ async function synthesizeVoiceReply(text, options = {}) {
586
626
  if (provider === 'openai') {
587
627
  request = synthesizeWithOpenAi(content, model, voice, options);
588
628
  } else if (provider === 'deepgram') {
589
- request = synthesizeWithDeepgram(content, model);
629
+ request = synthesizeWithDeepgram(content, model, options);
590
630
  } else {
591
631
  request = synthesizeWithGemini(content, model, voice, options);
592
632
  }
@@ -668,7 +708,7 @@ async function synthesizeVoiceReplyStream(text, options = {}, onChunk) {
668
708
  if (provider === 'openai') {
669
709
  await streamWithOpenAi(chunk, model, voice, options, onChunk);
670
710
  } else if (provider === 'deepgram') {
671
- await streamWithDeepgram(chunk, model, onChunk);
711
+ await streamWithDeepgram(chunk, model, options, onChunk);
672
712
  } else {
673
713
  await streamWithGemini(chunk, model, voice, options, onChunk);
674
714
  }
@@ -34,11 +34,17 @@ class VoiceRuntimeManager {
34
34
  return this.sessions.get(String(sessionId || '').trim()) || null;
35
35
  }
36
36
 
37
- async openFlutterSession({ userId, agentId = null, socket, sessionId = null } = {}) {
38
- if (!socket) {
39
- throw new Error('Socket is required to open a Flutter voice session.');
37
+ async openSession({
38
+ userId,
39
+ agentId = null,
40
+ sessionId = null,
41
+ platform = 'voice_live',
42
+ sink,
43
+ outputMode = 'audio_and_text',
44
+ } = {}) {
45
+ if (!sink) {
46
+ throw new Error('A voice session sink is required.');
40
47
  }
41
-
42
48
  const voiceSettings = getVoiceRuntimeSettings(userId, agentId);
43
49
  const liveProviderRuntime = this.#getProviderRuntime(
44
50
  userId,
@@ -53,16 +59,37 @@ class VoiceRuntimeManager {
53
59
  id: resolvedSessionId,
54
60
  userId,
55
61
  agentId,
56
- platform: 'voice_live',
62
+ platform,
57
63
  voiceSettings: {
58
64
  ...voiceSettings,
59
65
  liveApiKey: liveProviderRuntime.apiKey,
60
66
  liveBaseUrl: liveProviderRuntime.baseUrl,
61
67
  },
68
+ sink,
69
+ outputMode,
70
+ });
71
+
72
+ session.adapter = adapter;
73
+ this.sessions.set(resolvedSessionId, session);
74
+ await session.publishReady();
75
+ return session;
76
+ }
77
+
78
+ async openFlutterSession({ userId, agentId = null, socket, sessionId = null } = {}) {
79
+ if (!socket) {
80
+ throw new Error('Socket is required to open a Flutter voice session.');
81
+ }
82
+ return this.openSession({
83
+ userId,
84
+ agentId,
85
+ sessionId,
86
+ platform: 'voice_live',
87
+ outputMode: 'audio_and_text',
62
88
  sink: {
63
89
  publishReady: async (_session, extra = {}) => {
90
+ const voiceSettings = getVoiceRuntimeSettings(userId, agentId);
64
91
  socket.emit('voice:session_ready', {
65
- sessionId: resolvedSessionId,
92
+ sessionId: String(sessionId || _session.id).trim(),
66
93
  runtimeMode: voiceSettings.runtimeMode,
67
94
  provider: voiceSettings.liveProvider,
68
95
  model: voiceSettings.liveModel,
@@ -72,52 +99,58 @@ class VoiceRuntimeManager {
72
99
  },
73
100
  setState: async (_session, state, extra = {}) => {
74
101
  socket.emit('voice:assistant_state', {
75
- sessionId: resolvedSessionId,
102
+ sessionId: _session.id,
76
103
  state,
77
104
  ...extra,
78
105
  });
79
106
  },
80
107
  publishTranscriptPartial: async (_session, content) => {
81
108
  socket.emit('voice:transcript_partial', {
82
- sessionId: resolvedSessionId,
109
+ sessionId: _session.id,
83
110
  content,
84
111
  });
85
112
  },
86
113
  publishTranscriptFinal: async (_session, content) => {
87
114
  socket.emit('voice:transcript_final', {
88
- sessionId: resolvedSessionId,
115
+ sessionId: _session.id,
89
116
  content,
90
117
  });
91
118
  },
92
119
  publishAssistantOutput: async (_session, content, options = {}) => {
93
- await this.#deliverFlutterAssistantOutput(socket, resolvedSessionId, session, content, options);
120
+ await this.#deliverFlutterAssistantOutput(socket, _session.id, _session, content, options);
94
121
  },
95
- interruptOutput: async () => {
122
+ interruptOutput: async (_session) => {
96
123
  socket.emit('voice:assistant_state', {
97
- sessionId: resolvedSessionId,
124
+ sessionId: _session.id,
98
125
  state: 'interrupted',
99
126
  });
100
127
  },
101
128
  publishError: async (_session, message, extra = {}) => {
102
129
  socket.emit('voice:error', {
103
- sessionId: resolvedSessionId,
130
+ sessionId: _session.id,
104
131
  error: message,
105
132
  ...extra,
106
133
  });
107
134
  },
108
- close: async () => {
135
+ close: async (_session) => {
109
136
  socket.emit('voice:assistant_state', {
110
- sessionId: resolvedSessionId,
137
+ sessionId: _session.id,
111
138
  state: 'closed',
112
139
  });
113
140
  },
114
141
  },
115
142
  });
143
+ }
116
144
 
117
- session.adapter = adapter;
118
- this.sessions.set(resolvedSessionId, session);
119
- await session.publishReady();
120
- return session;
145
+ async openWearableSession({ userId, agentId = null, sessionId = null, sink } = {}) {
146
+ return this.openSession({
147
+ userId,
148
+ agentId,
149
+ sessionId,
150
+ platform: 'wearable_live',
151
+ outputMode: 'audio_and_text',
152
+ sink,
153
+ });
121
154
  }
122
155
 
123
156
  async closeSession(sessionId, reason = 'closed') {
@@ -435,6 +468,8 @@ class VoiceRuntimeManager {
435
468
  provider: session.voiceSettings?.liveProvider,
436
469
  model: session.voiceSettings?.liveTtsModel,
437
470
  voice: session.voiceSettings?.liveVoice,
471
+ transport: 'wearable',
472
+ responseFormat: 'wav',
438
473
  });
439
474
  const spokenContent = sanitizeSpeechText(content);
440
475
 
@@ -490,6 +525,88 @@ class VoiceRuntimeManager {
490
525
  }
491
526
  }
492
527
 
528
+ async deliverWearableAssistantOutput(ws, sessionId, content, options = {}) {
529
+ const session = this.getSession(sessionId);
530
+ if (!session) {
531
+ return;
532
+ }
533
+ const kind = String(options.kind || 'final').trim() || 'final';
534
+ ws.send(JSON.stringify({
535
+ type: 'voice:assistant_text',
536
+ sessionId,
537
+ content,
538
+ kind,
539
+ }));
540
+
541
+ if (kind === 'final') {
542
+ await session.setState('speaking', { kind });
543
+ }
544
+
545
+ const voiceOptions = normalizeVoiceSynthesisOptions({
546
+ provider: session.voiceSettings?.liveProvider,
547
+ model: session.voiceSettings?.liveTtsModel,
548
+ voice: session.voiceSettings?.liveVoice,
549
+ });
550
+ const spokenContent = sanitizeSpeechText(content);
551
+ let index = 0;
552
+ let streamError = null;
553
+ const ttsAttempts = this.#buildTtsAttemptOrder(session, voiceOptions);
554
+
555
+ if (spokenContent) {
556
+ try {
557
+ for (const attempt of ttsAttempts) {
558
+ index = 0;
559
+ streamError = null;
560
+ try {
561
+ await synthesizeVoiceReplyStream(
562
+ spokenContent,
563
+ attempt,
564
+ async ({ audioBytes, mimeType }) => {
565
+ if (session.closed || session.interrupted) return;
566
+ ws.send(JSON.stringify({
567
+ type: 'voice:audio_chunk',
568
+ sessionId,
569
+ kind,
570
+ index,
571
+ audioBase64: audioBytes.toString('base64'),
572
+ mimeType,
573
+ }));
574
+ index += 1;
575
+ },
576
+ );
577
+ break;
578
+ } catch (error) {
579
+ streamError = String(error?.message || error || 'Voice playback failed.');
580
+ }
581
+ }
582
+ } catch (error) {
583
+ streamError = String(error?.message || error || 'Voice playback failed.');
584
+ }
585
+ }
586
+
587
+ if (!streamError && !session.closed && !session.interrupted) {
588
+ ws.send(JSON.stringify({
589
+ type: 'voice:audio_done',
590
+ sessionId,
591
+ kind,
592
+ totalChunks: index,
593
+ }));
594
+ } else if (kind === 'final' && !session.closed && !session.interrupted) {
595
+ ws.send(JSON.stringify({
596
+ type: 'voice:error',
597
+ sessionId,
598
+ error: streamError,
599
+ recoverable: true,
600
+ phase: 'tts',
601
+ }));
602
+ await session.setState('degraded', { kind, phase: 'tts' });
603
+ }
604
+
605
+ if (kind === 'final' && !streamError) {
606
+ await session.setState('idle');
607
+ }
608
+ }
609
+
493
610
  #buildTtsAttemptOrder(session, voiceOptions) {
494
611
  const attempts = [];
495
612
  const providers = [
@@ -110,8 +110,35 @@ async function runVoiceTranscriptTurn({
110
110
  });
111
111
 
112
112
  const replyText = String(runResult?.content || '').trim();
113
+ const assistantMetadata = {
114
+ ...persistedMetadata,
115
+ platform,
116
+ tokens: runResult?.totalTokens || 0,
117
+ screenshotIncluded: Boolean(screenshotContext),
118
+ screenshotVisionProvider: screenshotContext?.provider || null,
119
+ screenshotVisionModel: screenshotContext?.model || null,
120
+ };
113
121
  if (!replyText) {
114
- throw new Error('Agent returned an empty voice reply.');
122
+ db.prepare('INSERT INTO conversation_history (user_id, agent_id, agent_run_id, role, content, metadata) VALUES (?, ?, ?, ?, ?, ?)')
123
+ .run(
124
+ userId,
125
+ agentId,
126
+ runResult?.runId || null,
127
+ 'assistant',
128
+ '',
129
+ JSON.stringify(assistantMetadata),
130
+ );
131
+ return {
132
+ runId: runResult?.runId || null,
133
+ transcript: transcriptText,
134
+ replyText: '',
135
+ ttsProvider: voiceOptions.provider,
136
+ ttsModel: voiceOptions.model,
137
+ ttsVoice: voiceOptions.voice,
138
+ audioMimeType: 'audio/mpeg',
139
+ audioBase64: '',
140
+ ttsError: null,
141
+ };
115
142
  }
116
143
 
117
144
  db.prepare('INSERT INTO conversation_history (user_id, agent_id, agent_run_id, role, content, metadata) VALUES (?, ?, ?, ?, ?, ?)')
@@ -121,14 +148,7 @@ async function runVoiceTranscriptTurn({
121
148
  runResult?.runId || null,
122
149
  'assistant',
123
150
  replyText,
124
- JSON.stringify({
125
- ...persistedMetadata,
126
- platform,
127
- tokens: runResult?.totalTokens || 0,
128
- screenshotIncluded: Boolean(screenshotContext),
129
- screenshotVisionProvider: screenshotContext?.provider || null,
130
- screenshotVisionModel: screenshotContext?.model || null,
131
- }),
151
+ JSON.stringify(assistantMetadata),
132
152
  );
133
153
 
134
154
  let synthesized;