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
@@ -25,6 +25,7 @@ const { BrowserExtensionRegistry } = require('./browser/extension/registry');
25
25
  const { DesktopCompanionRegistry } = require('./desktop/registry');
26
26
  const { DesktopProvider } = require('./desktop/provider');
27
27
  const { ScreenRecorder } = require('./desktop/screenRecorder');
28
+ const { WearableService } = require('./wearable/service');
28
29
  const { assertRuntimeValidation, getRuntimeValidation } = require('./runtime/validation');
29
30
  const {
30
31
  getErrorMessage,
@@ -429,11 +430,46 @@ function createWidgetService(app) {
429
430
  return widgetService;
430
431
  }
431
432
 
433
+ function createWearableService(app) {
434
+ const wearableService = registerLocal(
435
+ app,
436
+ 'wearableService',
437
+ new WearableService({ app }),
438
+ );
439
+ logServiceReady('Wearable service ready');
440
+ return wearableService;
441
+ }
442
+
432
443
  function createScreenRecorder(app) {
444
+ const hasActiveRemoteCaptureSession = () => {
445
+ const desktopRegistry = app.locals.desktopCompanionRegistry;
446
+ if (desktopRegistry?.connectionsByUser instanceof Map) {
447
+ for (const userMap of desktopRegistry.connectionsByUser.values()) {
448
+ if (!(userMap instanceof Map)) continue;
449
+ for (const connection of userMap.values()) {
450
+ if (typeof connection?.isOpen === 'function' && connection.isOpen()) {
451
+ return true;
452
+ }
453
+ }
454
+ }
455
+ }
456
+
457
+ const extensionRegistry = app.locals.browserExtensionRegistry;
458
+ if (extensionRegistry?.connectionsByUser instanceof Map) {
459
+ for (const connection of extensionRegistry.connectionsByUser.values()) {
460
+ if (typeof connection?.isOpen === 'function' && connection.isOpen()) {
461
+ return true;
462
+ }
463
+ }
464
+ }
465
+
466
+ return false;
467
+ };
468
+
433
469
  const screenRecorder = registerLocal(
434
470
  app,
435
471
  'screenRecorder',
436
- new ScreenRecorder(),
472
+ new ScreenRecorder({ hasActiveRemoteCaptureSession }),
437
473
  );
438
474
  screenRecorder.start();
439
475
  logServiceReady('Screen recorder started');
@@ -525,6 +561,7 @@ async function startServices(app, io) {
525
561
  const messagingManager = createMessagingManager(app, io, agentEngine);
526
562
  const recordingManager = createRecordingManager(app, io);
527
563
  createWidgetService(app);
564
+ createWearableService(app);
528
565
  createScreenRecorder(app);
529
566
 
530
567
  restoreMessagingConnections(messagingManager);
@@ -589,6 +626,14 @@ async function stopServices(app) {
589
626
  );
590
627
  }
591
628
 
629
+ if (app.locals.wearableGateway?.close) {
630
+ tasks.push(
631
+ app.locals.wearableGateway.close().catch((err) => {
632
+ console.error('[WearableGateway] Shutdown error:', getErrorMessage(err));
633
+ }),
634
+ );
635
+ }
636
+
592
637
  if (app.locals.browserControllers instanceof Map) {
593
638
  for (const controller of app.locals.browserControllers.values()) {
594
639
  tasks.push(
@@ -1,3 +1,5 @@
1
+ 'use strict';
2
+
1
3
  const EventEmitter = require('events');
2
4
  const crypto = require('crypto');
3
5
  const db = require('../../db/database');
@@ -5,6 +7,9 @@ const { Client } = require('@modelcontextprotocol/sdk/client/index.js');
5
7
  const { SSEClientTransport } = require('@modelcontextprotocol/sdk/client/sse.js');
6
8
  const { validateRemoteMcpEndpoint } = require('../runtime/mcp');
7
9
 
10
+ const CONSECUTIVE_FAIL_LIMIT = 3;
11
+ const RECONNECT_DELAY_MS = 60_000;
12
+
8
13
  class DBAuthProvider {
9
14
  constructor(serverId, clientId, authServerUrl) {
10
15
  this.serverId = serverId;
@@ -50,7 +55,6 @@ class DBAuthProvider {
50
55
  }
51
56
 
52
57
  redirectToAuthorization(authorizationUrl) {
53
- // Throw error so the API route catches it and returns the URL to the frontend
54
58
  throw new Error(`OAUTH_REDIRECT:${authorizationUrl.toString()}`);
55
59
  }
56
60
 
@@ -66,10 +70,30 @@ class DBAuthProvider {
66
70
  }
67
71
  }
68
72
 
73
+ function extractErrorMessage(err) {
74
+ const raw = err?.message || String(err || 'Unknown error');
75
+ // Strip HTML bodies (e.g. Cloudflare 530 error pages) — keep only the first line
76
+ if (raw.includes('<!doctype') || raw.includes('<html') || raw.includes('<!DOCTYPE')) {
77
+ const httpMatch = raw.match(/HTTP (\d+)/i);
78
+ return httpMatch
79
+ ? `Server returned HTTP ${httpMatch[1]} — the MCP endpoint may be down or misconfigured`
80
+ : 'Server returned an HTML error page — the MCP endpoint may be down or misconfigured';
81
+ }
82
+ // ECONNREFUSED: pull out just the host/port
83
+ if (err?.code === 'ECONNREFUSED' || raw.includes('ECONNREFUSED')) {
84
+ const addrMatch = raw.match(/connect ECONNREFUSED ([^\s,]+)/);
85
+ return addrMatch
86
+ ? `Connection refused at ${addrMatch[1]} — is the MCP server running?`
87
+ : 'Connection refused — the MCP server is not reachable';
88
+ }
89
+ return raw.split('\n')[0].trim();
90
+ }
91
+
69
92
  class MCPClient extends EventEmitter {
70
93
  constructor() {
71
94
  super();
72
95
  this.servers = new Map();
96
+ this._reconnectTimers = new Map();
73
97
  }
74
98
 
75
99
  async startServer(serverId, url, name = '', userId = null, options = {}) {
@@ -90,13 +114,12 @@ class MCPClient extends EventEmitter {
90
114
 
91
115
  const transportOpts = {
92
116
  requestInit: { headers: {} },
93
- eventSourceInit: { headers: {} }
117
+ eventSourceInit: { headers: {} },
94
118
  };
95
119
 
96
120
  if (authObj.type === 'bearer' && authObj.token) {
97
121
  const h = `Bearer ${authObj.token}`;
98
122
  transportOpts.requestInit.headers['Authorization'] = h;
99
- // Native EventSource doesn't support headers well in browsers, but Node.js EventSource / sse.js might
100
123
  transportOpts.eventSourceInit.headers['Authorization'] = h;
101
124
  } else if (authObj.type === 'oauth') {
102
125
  transportOpts.authProvider = new DBAuthProvider(serverId, authObj.clientId, authObj.authServerUrl);
@@ -105,7 +128,7 @@ class MCPClient extends EventEmitter {
105
128
  const transport = new SSEClientTransport(new URL(endpoint), transportOpts);
106
129
  const client = new Client(
107
130
  { name: 'NeoAgent', version: '1.0.0' },
108
- { capabilities: { tools: {} } }
131
+ { capabilities: { tools: {} } },
109
132
  );
110
133
 
111
134
  const serverObj = {
@@ -119,7 +142,9 @@ class MCPClient extends EventEmitter {
119
142
  client,
120
143
  transport,
121
144
  tools: [],
122
- status: 'starting'
145
+ status: 'starting',
146
+ consecutiveFails: 0,
147
+ lastError: null,
123
148
  };
124
149
 
125
150
  this.servers.set(serverId, serverObj);
@@ -129,17 +154,24 @@ class MCPClient extends EventEmitter {
129
154
  const server = this.servers.get(serverId);
130
155
  if (server) {
131
156
  server.status = 'running';
157
+ server.consecutiveFails = 0;
158
+ server.lastError = null;
132
159
  this.emit('server_status', { serverId, status: 'running' });
133
160
  }
134
161
 
135
162
  return { status: 'running' };
136
163
  } catch (err) {
164
+ const message = extractErrorMessage(err);
137
165
  const server = this.servers.get(serverId);
138
166
  if (server) {
167
+ server.consecutiveFails = (server.consecutiveFails || 0) + 1;
168
+ server.lastError = message;
139
169
  server.status = 'error';
140
- this.emit('server_status', { serverId, status: 'error', error: err.message });
170
+ this.emit('server_status', { serverId, status: 'error', error: message });
141
171
  }
142
- throw err;
172
+ const friendlyErr = new Error(message);
173
+ friendlyErr.originalError = err;
174
+ throw friendlyErr;
143
175
  }
144
176
  }
145
177
 
@@ -149,26 +181,62 @@ class MCPClient extends EventEmitter {
149
181
  throw new Error(`Server ${serverId} transport not initialized`);
150
182
  }
151
183
  await server.transport.finishAuth(code);
152
- await server.client.connect(server.transport).catch(() => { }); // Reconnect using tokens
184
+ await server.client.connect(server.transport).catch(() => {});
153
185
 
154
186
  server.status = 'running';
187
+ server.consecutiveFails = 0;
188
+ server.lastError = null;
155
189
  this.emit('server_status', { serverId, status: 'running' });
156
190
  }
157
191
 
158
192
  async stopServer(serverId) {
193
+ const timer = this._reconnectTimers.get(serverId);
194
+ if (timer) {
195
+ clearTimeout(timer);
196
+ this._reconnectTimers.delete(serverId);
197
+ }
198
+
159
199
  const server = this.servers.get(serverId);
160
200
  if (!server) return;
161
201
 
162
202
  try {
163
203
  if (server.client) await server.client.close();
164
204
  } catch (err) {
165
- console.error(`Error closing MCP client ${serverId}:`, err);
205
+ console.error(`[MCP] Error closing client ${serverId}:`, err.message);
166
206
  }
167
207
 
168
208
  this.servers.delete(serverId);
169
209
  this.emit('server_status', { serverId, status: 'stopped' });
170
210
  }
171
211
 
212
+ _scheduleReconnect(serverId, userId, options) {
213
+ if (this._reconnectTimers.has(serverId)) return;
214
+
215
+ const timer = setTimeout(async () => {
216
+ this._reconnectTimers.delete(serverId);
217
+ const server = this.servers.get(serverId);
218
+ if (!server || server.status === 'running') return;
219
+
220
+ const row = db.prepare('SELECT * FROM mcp_servers WHERE id = ? AND enabled = 1').get(serverId);
221
+ if (!row) return;
222
+
223
+ try {
224
+ await this.startServer(serverId, row.command, row.name, userId, options);
225
+ await this.listTools(serverId, userId);
226
+ console.log(`[MCP] Reconnected to ${row.name}`);
227
+ } catch (err) {
228
+ const server = this.servers.get(serverId);
229
+ if (server && server.consecutiveFails < CONSECUTIVE_FAIL_LIMIT) {
230
+ this._scheduleReconnect(serverId, userId, options);
231
+ } else {
232
+ console.warn(`[MCP] ${row.name} disabled after ${CONSECUTIVE_FAIL_LIMIT} consecutive failures: ${err.message}`);
233
+ }
234
+ }
235
+ }, RECONNECT_DELAY_MS);
236
+
237
+ this._reconnectTimers.set(serverId, timer);
238
+ }
239
+
172
240
  _getOwnedServer(serverId, userId = null) {
173
241
  const server = this.servers.get(serverId);
174
242
  if (!server) return null;
@@ -189,14 +257,30 @@ class MCPClient extends EventEmitter {
189
257
 
190
258
  async callTool(serverId, toolName, args = {}, userId = null) {
191
259
  const server = this._getOwnedServer(serverId, userId);
192
- if (!server || server.status !== 'running') {
193
- throw new Error(`Server ${serverId} not running`);
260
+ if (!server) throw new Error(`Server ${serverId} not found`);
261
+
262
+ if (server.status !== 'running') {
263
+ const hint = server.lastError ? ` (${server.lastError})` : '';
264
+ throw new Error(`MCP server "${server.name}" is not available${hint}`);
194
265
  }
195
266
 
196
- return await server.client.callTool({
197
- name: toolName,
198
- arguments: args
199
- });
267
+ try {
268
+ const result = await server.client.callTool({ name: toolName, arguments: args });
269
+ server.consecutiveFails = 0;
270
+ return result;
271
+ } catch (err) {
272
+ const message = extractErrorMessage(err);
273
+ server.consecutiveFails = (server.consecutiveFails || 0) + 1;
274
+ server.lastError = message;
275
+
276
+ if (server.consecutiveFails >= CONSECUTIVE_FAIL_LIMIT) {
277
+ server.status = 'error';
278
+ this.emit('server_status', { serverId, status: 'error', error: message });
279
+ this._scheduleReconnect(serverId, server.userId, { agentId: server.agentId });
280
+ }
281
+
282
+ throw new Error(`MCP tool "${toolName}" failed: ${message}`);
283
+ }
200
284
  }
201
285
 
202
286
  async callToolByName(fullName, args = {}, userId = null, options = {}) {
@@ -204,10 +288,10 @@ class MCPClient extends EventEmitter {
204
288
  if (userId != null && server.userId !== userId) continue;
205
289
  if (options.agentId && server.agentId && server.agentId !== options.agentId) continue;
206
290
  const prefix = `mcp_${server.slug}_`;
207
- if (fullName.startsWith(prefix)) {
208
- const originalName = fullName.substring(prefix.length);
209
- return await this.callTool(serverId, originalName, args, userId);
210
- }
291
+ if (!fullName.startsWith(prefix)) continue;
292
+
293
+ const originalName = fullName.substring(prefix.length);
294
+ return await this.callTool(serverId, originalName, args, userId);
211
295
  }
212
296
  return null;
213
297
  }
@@ -224,7 +308,7 @@ class MCPClient extends EventEmitter {
224
308
  name: `mcp_${server.slug}_${tool.name}`,
225
309
  originalName: tool.name,
226
310
  parameters: tool.inputSchema || tool.parameters,
227
- serverId
311
+ serverId,
228
312
  });
229
313
  }
230
314
  }
@@ -242,7 +326,9 @@ class MCPClient extends EventEmitter {
242
326
  command: server.url,
243
327
  args: [],
244
328
  toolCount: server.tools.length,
245
- serverInfo: null
329
+ error: server.lastError || null,
330
+ consecutiveFails: server.consecutiveFails || 0,
331
+ serverInfo: null,
246
332
  };
247
333
  }
248
334
  return statuses;
@@ -258,8 +344,14 @@ class MCPClient extends EventEmitter {
258
344
  await this.listTools(srv.id, userId);
259
345
  results.push({ id: srv.id, name: srv.name, status: 'running' });
260
346
  } catch (err) {
261
- console.error(`Failed to start MCP server ${srv.name}:`, err.message);
262
- results.push({ id: srv.id, name: srv.name, status: 'error', error: err.message });
347
+ const message = err.message;
348
+ console.error(`[MCP] Failed to start "${srv.name}":`, message);
349
+ // Schedule a reconnect attempt for transient failures (not auth errors)
350
+ const server = this.servers.get(srv.id);
351
+ if (server) {
352
+ this._scheduleReconnect(srv.id, userId, { agentId: srv.agent_id });
353
+ }
354
+ results.push({ id: srv.id, name: srv.name, status: 'error', error: message });
263
355
  }
264
356
  }
265
357
 
@@ -267,6 +359,11 @@ class MCPClient extends EventEmitter {
267
359
  }
268
360
 
269
361
  async shutdown() {
362
+ for (const timer of this._reconnectTimers.values()) {
363
+ clearTimeout(timer);
364
+ }
365
+ this._reconnectTimers.clear();
366
+
270
367
  const promises = [];
271
368
  for (const serverId of this.servers.keys()) {
272
369
  promises.push(this.stopServer(serverId));
@@ -12,6 +12,11 @@ const {
12
12
  const { getMemoryStorageDecision } = require('./policy');
13
13
  const { AGENT_DATA_DIR } = require('../../../runtime/paths');
14
14
  const { isMainAgent, resolveAgentId } = require('../agents/manager');
15
+ const {
16
+ decryptLocalValue,
17
+ encryptLocalValue,
18
+ isLocalEncryptedValue,
19
+ } = require('../../utils/local_secrets');
15
20
 
16
21
  async function getActiveProvider(userId, agentId = null) {
17
22
  try {
@@ -516,11 +521,43 @@ class MemoryManager {
516
521
  return {};
517
522
  }
518
523
  }
519
- try { return JSON.parse(fs.readFileSync(filePath, 'utf-8')); } catch { return {}; }
524
+ try {
525
+ const parsed = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
526
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
527
+ return {};
528
+ }
529
+
530
+ let shouldMigrate = false;
531
+ const normalized = {};
532
+
533
+ for (const [service, rawValue] of Object.entries(parsed)) {
534
+ const value = String(rawValue || '');
535
+ if (!value) {
536
+ normalized[service] = '';
537
+ continue;
538
+ }
539
+
540
+ if (!isLocalEncryptedValue(value)) shouldMigrate = true;
541
+ normalized[service] = decryptLocalValue(value);
542
+ }
543
+
544
+ if (shouldMigrate) {
545
+ this.writeApiKeys(normalized, userId);
546
+ }
547
+
548
+ return normalized;
549
+ } catch {
550
+ return {};
551
+ }
520
552
  }
521
553
 
522
554
  writeApiKeys(keys, userId = null) {
523
- fs.writeFileSync(this._userApiKeysPath(userId), JSON.stringify(keys, null, 2), 'utf-8');
555
+ const encrypted = {};
556
+ for (const [service, rawValue] of Object.entries(keys || {})) {
557
+ const value = String(rawValue || '');
558
+ encrypted[service] = value ? encryptLocalValue(value) : '';
559
+ }
560
+ fs.writeFileSync(this._userApiKeysPath(userId), JSON.stringify(encrypted, null, 2), 'utf-8');
524
561
  }
525
562
 
526
563
  setApiKey(service, key, userId = null) {
@@ -165,6 +165,16 @@ const PLATFORM_CAPABILITIES = Object.freeze({
165
165
  sharedSpaceRuleScopes: Object.freeze(['channel', 'chat']),
166
166
  sharedActorRuleScopes: Object.freeze(['user']),
167
167
  }),
168
+ meshtastic: capabilityTemplate({
169
+ supportsDirectPolicy: false,
170
+ supportsSharedPolicy: true,
171
+ supportsMentionGate: false,
172
+ supportsDiscovery: false,
173
+ directRuleScopes: Object.freeze([]),
174
+ sharedSpaceRuleScopes: Object.freeze(['channel', 'chat']),
175
+ sharedActorRuleScopes: Object.freeze(['user']),
176
+ manualEntryHint: 'Add a Meshtastic node number or channel id.',
177
+ }),
168
178
  feishu: capabilityTemplate({ supportsDiscovery: false }),
169
179
  nextcloud_talk: capabilityTemplate({ supportsDiscovery: false }),
170
180
  nostr: capabilityTemplate({ supportsDiscovery: false }),
@@ -9,6 +9,7 @@ const { WhatsAppPlatform } = require('./whatsapp');
9
9
  const { TelnyxVoicePlatform } = require('./telnyx');
10
10
  const { DiscordPlatform } = require('./discord');
11
11
  const { TelegramPlatform } = require('./telegram');
12
+ const { MeshtasticPlatform } = require('./meshtastic');
12
13
  const {
13
14
  SlackPlatform,
14
15
  GoogleChatPlatform,
@@ -34,6 +35,7 @@ const {
34
35
  classifyRecentTarget,
35
36
  } = require('./access_policy');
36
37
  const { decryptValue, encryptValue } = require('../integrations/secrets');
38
+ const { readMeshtasticEnabled } = require('./meshtastic_env');
37
39
 
38
40
  const LEGACY_WHATSAPP_AUTH_DIR = path.join(DATA_DIR, 'whatsapp-auth');
39
41
 
@@ -78,6 +80,7 @@ class MessagingManager extends EventEmitter {
78
80
  feishu: createGenericPlatformClass('feishu'),
79
81
  line: LinePlatform,
80
82
  mattermost: MattermostPlatform,
83
+ meshtastic: MeshtasticPlatform,
81
84
  nextcloud_talk: createGenericPlatformClass('nextcloud_talk'),
82
85
  nostr: createGenericPlatformClass('nostr'),
83
86
  synology_chat: createGenericPlatformClass('synology_chat'),
@@ -302,6 +305,9 @@ class MessagingManager extends EventEmitter {
302
305
  config.accessPolicy = this._loadAccessPolicy(userId, agentId, platformName);
303
306
  const PlatformClass = this.platformTypes[platformName];
304
307
  if (!PlatformClass) throw new Error(`Unknown platform: ${platformName}`);
308
+ if (platformName === 'meshtastic' && !readMeshtasticEnabled()) {
309
+ throw new Error('Meshtastic is disabled by environment configuration');
310
+ }
305
311
 
306
312
  if (platformName === 'whatsapp' && !config.authDir) {
307
313
  config.authDir = this._scopedPlatformAuthDir(userId, agentId, platformName);
@@ -545,6 +551,14 @@ class MessagingManager extends EventEmitter {
545
551
 
546
552
  getPlatformStatus(userId, platformName, options = {}) {
547
553
  const agentId = this._agentId(userId, options);
554
+ if (platformName === 'meshtastic' && !readMeshtasticEnabled()) {
555
+ return {
556
+ status: 'disabled',
557
+ authInfo: {
558
+ label: 'Disabled in env',
559
+ },
560
+ };
561
+ }
548
562
  const key = this._key(userId, agentId, platformName);
549
563
  const platform = this.platforms.get(key);
550
564
  if (!platform) {
@@ -562,7 +576,21 @@ class MessagingManager extends EventEmitter {
562
576
  const connections = db.prepare('SELECT platform, status, last_connected, agent_id FROM platform_connections WHERE user_id = ? AND agent_id = ?').all(userId, agentId);
563
577
  const statuses = {};
564
578
 
579
+ if (!readMeshtasticEnabled()) {
580
+ statuses.meshtastic = {
581
+ status: 'disabled',
582
+ agentId,
583
+ lastConnected: null,
584
+ authInfo: {
585
+ label: 'Disabled in env',
586
+ },
587
+ };
588
+ }
589
+
565
590
  for (const conn of connections) {
591
+ if (conn.platform === 'meshtastic' && !readMeshtasticEnabled()) {
592
+ continue;
593
+ }
566
594
  const key = this._key(userId, agentId, conn.platform);
567
595
  const platform = this.platforms.get(key);
568
596
  statuses[conn.platform] = {
@@ -616,6 +644,11 @@ class MessagingManager extends EventEmitter {
616
644
  ).all();
617
645
  for (const row of rows) {
618
646
  try {
647
+ if (row.platform === 'meshtastic' && !readMeshtasticEnabled()) {
648
+ db.prepare("UPDATE platform_connections SET status = 'disabled' WHERE user_id = ? AND agent_id = ? AND platform = ?")
649
+ .run(row.user_id, row.agent_id, row.platform);
650
+ continue;
651
+ }
619
652
  const config = this._decodeStoredConfig(row.config);
620
653
  console.log(`[Messaging] Restoring ${row.platform} for user ${row.user_id} agent ${row.agent_id || 'main'}`);
621
654
  await this.connectPlatform(row.user_id, row.platform, config, { agentId: row.agent_id });
@@ -627,6 +660,22 @@ class MessagingManager extends EventEmitter {
627
660
  }
628
661
  }
629
662
 
663
+ async updateMeshtasticEnabled(enabled) {
664
+ if (enabled) return;
665
+ const disconnects = [];
666
+ for (const [key, platform] of this.platforms.entries()) {
667
+ if (!key.endsWith(':meshtastic')) continue;
668
+ disconnects.push(
669
+ Promise.resolve(platform.disconnect()).catch(() => {}).then(() => {
670
+ this.platforms.delete(key);
671
+ })
672
+ );
673
+ }
674
+ await Promise.all(disconnects);
675
+ db.prepare("UPDATE platform_connections SET status = 'disabled' WHERE platform = 'meshtastic'")
676
+ .run();
677
+ }
678
+
630
679
  async shutdown() {
631
680
  this.isShuttingDown = true;
632
681