neoagent 2.3.1-beta.0 → 2.3.1-beta.100

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 (406) hide show
  1. package/.env.example +63 -5
  2. package/README.md +16 -5
  3. package/com.neoagent.plist +1 -1
  4. package/docs/automation.md +82 -41
  5. package/docs/capabilities.md +85 -89
  6. package/docs/configuration.md +89 -87
  7. package/docs/getting-started.md +46 -27
  8. package/docs/hardware.md +1 -1
  9. package/docs/index.md +24 -30
  10. package/docs/integrations.md +46 -60
  11. package/docs/migration.md +41 -146
  12. package/docs/operations.md +23 -20
  13. package/docs/skills.md +20 -20
  14. package/docs/why-neoagent.md +15 -13
  15. package/extensions/chrome-browser/background.mjs +10 -2
  16. package/flutter_app/.metadata +42 -0
  17. package/flutter_app/DESIGN_SYSTEM.md +166 -0
  18. package/flutter_app/README.md +21 -0
  19. package/flutter_app/analysis_options.yaml +32 -0
  20. package/flutter_app/android/app/build.gradle.kts +111 -0
  21. package/flutter_app/android/app/src/debug/AndroidManifest.xml +7 -0
  22. package/flutter_app/android/app/src/main/AndroidManifest.xml +185 -0
  23. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/MainActivity.kt +888 -0
  24. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/auto/NeoAgentCarAppService.kt +114 -0
  25. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthConnectGateway.kt +280 -0
  26. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncNotifications.kt +113 -0
  27. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncPayload.kt +57 -0
  28. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncScheduler.kt +78 -0
  29. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncWorker.kt +253 -0
  30. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/PermissionsRationaleActivity.kt +46 -0
  31. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingBootReceiver.kt +21 -0
  32. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingForegroundService.kt +586 -0
  33. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingStateStore.kt +78 -0
  34. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingUploadClient.kt +104 -0
  35. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/telecom/NeoAgentConnection.kt +118 -0
  36. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/telecom/NeoAgentConnectionService.kt +86 -0
  37. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiHomeWidgetProvider.kt +457 -0
  38. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiWidgetStore.kt +194 -0
  39. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/VoiceLaunchWidgetProvider.kt +67 -0
  40. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetConfigActivity.kt +228 -0
  41. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncScheduler.kt +72 -0
  42. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncWorker.kt +186 -0
  43. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetTaskRunWorker.kt +210 -0
  44. package/flutter_app/android/app/src/main/res/drawable/launch_background.xml +12 -0
  45. package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_bg.xml +11 -0
  46. package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_task_bg.xml +8 -0
  47. package/flutter_app/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
  48. package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget.xml +138 -0
  49. package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget_task_row.xml +52 -0
  50. package/flutter_app/android/app/src/main/res/layout/neoagent_voice_widget.xml +49 -0
  51. package/flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  52. package/flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  53. package/flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  54. package/flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  55. package/flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  56. package/flutter_app/android/app/src/main/res/values/arrays.xml +8 -0
  57. package/flutter_app/android/app/src/main/res/values/strings.xml +12 -0
  58. package/flutter_app/android/app/src/main/res/values/styles.xml +18 -0
  59. package/flutter_app/android/app/src/main/res/values-night/styles.xml +18 -0
  60. package/flutter_app/android/app/src/main/res/xml/automotive_app_desc.xml +4 -0
  61. package/flutter_app/android/app/src/main/res/xml/file_paths.xml +6 -0
  62. package/flutter_app/android/app/src/main/res/xml/neoagent_ai_widget_info.xml +12 -0
  63. package/flutter_app/android/app/src/main/res/xml/neoagent_voice_widget_info.xml +12 -0
  64. package/flutter_app/android/app/src/profile/AndroidManifest.xml +7 -0
  65. package/flutter_app/android/build.gradle.kts +24 -0
  66. package/flutter_app/android/ci-release.keystore +0 -0
  67. package/flutter_app/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  68. package/flutter_app/android/gradle.properties +3 -0
  69. package/flutter_app/android/key.properties +4 -0
  70. package/flutter_app/android/settings.gradle.kts +26 -0
  71. package/flutter_app/assets/branding/app_icon_1024.png +0 -0
  72. package/flutter_app/assets/branding/app_icon_128.png +0 -0
  73. package/flutter_app/assets/branding/app_icon_192.png +0 -0
  74. package/flutter_app/assets/branding/app_icon_256.png +0 -0
  75. package/flutter_app/assets/branding/app_icon_32.png +0 -0
  76. package/flutter_app/assets/branding/app_icon_512.png +0 -0
  77. package/flutter_app/assets/branding/app_icon_64.png +0 -0
  78. package/flutter_app/assets/branding/onboarding_intro.mp4 +0 -0
  79. package/flutter_app/assets/branding/tray_icon_template.png +0 -0
  80. package/flutter_app/lib/features/location/location_service.dart +117 -0
  81. package/flutter_app/lib/features/notifications/notification_interceptor.dart +73 -0
  82. package/flutter_app/lib/features/onboarding/onboarding_chrome.dart +571 -0
  83. package/flutter_app/lib/features/onboarding/onboarding_messaging_step.dart +275 -0
  84. package/flutter_app/lib/features/onboarding/onboarding_model_step.dart +366 -0
  85. package/flutter_app/lib/features/onboarding/onboarding_shell.dart +57 -0
  86. package/flutter_app/lib/features/onboarding/onboarding_video_step.dart +218 -0
  87. package/flutter_app/lib/features/onboarding/onboarding_welcome_step.dart +46 -0
  88. package/flutter_app/lib/main.dart +107 -0
  89. package/flutter_app/lib/main_account_settings.dart +1340 -0
  90. package/flutter_app/lib/main_admin.dart +900 -0
  91. package/flutter_app/lib/main_app_shell.dart +2162 -0
  92. package/flutter_app/lib/main_chat.dart +3902 -0
  93. package/flutter_app/lib/main_controller.dart +7229 -0
  94. package/flutter_app/lib/main_devices.dart +1738 -0
  95. package/flutter_app/lib/main_integrations.dart +917 -0
  96. package/flutter_app/lib/main_launcher.dart +959 -0
  97. package/flutter_app/lib/main_launcher_entry.dart +5 -0
  98. package/flutter_app/lib/main_models.dart +3807 -0
  99. package/flutter_app/lib/main_navigation.dart +206 -0
  100. package/flutter_app/lib/main_operations.dart +5249 -0
  101. package/flutter_app/lib/main_recordings.dart +920 -0
  102. package/flutter_app/lib/main_runtime.dart +869 -0
  103. package/flutter_app/lib/main_settings.dart +2288 -0
  104. package/flutter_app/lib/main_shared.dart +3393 -0
  105. package/flutter_app/lib/main_spacing.dart +33 -0
  106. package/flutter_app/lib/main_theme.dart +276 -0
  107. package/flutter_app/lib/main_unified.dart +388 -0
  108. package/flutter_app/lib/main_voice_assistant.dart +957 -0
  109. package/flutter_app/lib/src/analytics_service.dart +294 -0
  110. package/flutter_app/lib/src/android_apk_drop_zone.dart +32 -0
  111. package/flutter_app/lib/src/android_apk_drop_zone_stub.dart +16 -0
  112. package/flutter_app/lib/src/android_apk_drop_zone_web.dart +348 -0
  113. package/flutter_app/lib/src/android_app_installer.dart +22 -0
  114. package/flutter_app/lib/src/android_app_installer_io.dart +122 -0
  115. package/flutter_app/lib/src/android_app_installer_stub.dart +21 -0
  116. package/flutter_app/lib/src/android_auto_bridge.dart +59 -0
  117. package/flutter_app/lib/src/android_launcher_bridge.dart +239 -0
  118. package/flutter_app/lib/src/app_launch_bridge.dart +58 -0
  119. package/flutter_app/lib/src/app_release_updater.dart +511 -0
  120. package/flutter_app/lib/src/backend_client.dart +1870 -0
  121. package/flutter_app/lib/src/desktop_companion.dart +2 -0
  122. package/flutter_app/lib/src/desktop_companion_actions.dart +586 -0
  123. package/flutter_app/lib/src/desktop_companion_io.dart +538 -0
  124. package/flutter_app/lib/src/desktop_companion_stub.dart +59 -0
  125. package/flutter_app/lib/src/desktop_native_bridge.dart +91 -0
  126. package/flutter_app/lib/src/desktop_screen_capture.dart +21 -0
  127. package/flutter_app/lib/src/desktop_screen_capture_io.dart +142 -0
  128. package/flutter_app/lib/src/desktop_screen_capture_stub.dart +12 -0
  129. package/flutter_app/lib/src/diagnostics_logger.dart +119 -0
  130. package/flutter_app/lib/src/health_bridge.dart +136 -0
  131. package/flutter_app/lib/src/live_voice_capture.dart +85 -0
  132. package/flutter_app/lib/src/messaging_access_summary.dart +46 -0
  133. package/flutter_app/lib/src/network/app_http_client.dart +53 -0
  134. package/flutter_app/lib/src/network/app_http_client_factory.dart +6 -0
  135. package/flutter_app/lib/src/network/app_http_client_io.dart +138 -0
  136. package/flutter_app/lib/src/network/app_http_client_stub.dart +3 -0
  137. package/flutter_app/lib/src/network/app_http_client_web.dart +94 -0
  138. package/flutter_app/lib/src/oauth_launcher.dart +33 -0
  139. package/flutter_app/lib/src/oauth_launcher_io.dart +95 -0
  140. package/flutter_app/lib/src/oauth_launcher_stub.dart +33 -0
  141. package/flutter_app/lib/src/oauth_launcher_web.dart +107 -0
  142. package/flutter_app/lib/src/recording_bridge.dart +232 -0
  143. package/flutter_app/lib/src/recording_bridge_io.dart +1019 -0
  144. package/flutter_app/lib/src/recording_bridge_stub.dart +120 -0
  145. package/flutter_app/lib/src/recording_bridge_web.dart +689 -0
  146. package/flutter_app/lib/src/recording_payloads.dart +86 -0
  147. package/flutter_app/lib/src/theme/palette.dart +81 -0
  148. package/flutter_app/lib/src/web_app_update_monitor.dart +17 -0
  149. package/flutter_app/lib/src/web_app_update_monitor_stub.dart +24 -0
  150. package/flutter_app/lib/src/web_app_update_monitor_web.dart +123 -0
  151. package/flutter_app/lib/src/widget_bridge.dart +49 -0
  152. package/flutter_app/linux/CMakeLists.txt +128 -0
  153. package/flutter_app/linux/flutter/CMakeLists.txt +88 -0
  154. package/flutter_app/linux/flutter/generated_plugin_registrant.cc +47 -0
  155. package/flutter_app/linux/flutter/generated_plugin_registrant.h +15 -0
  156. package/flutter_app/linux/flutter/generated_plugins.cmake +32 -0
  157. package/flutter_app/linux/runner/CMakeLists.txt +26 -0
  158. package/flutter_app/linux/runner/main.cc +6 -0
  159. package/flutter_app/linux/runner/my_application.cc +144 -0
  160. package/flutter_app/linux/runner/my_application.h +18 -0
  161. package/flutter_app/linux/runner/resources/app_icon.png +0 -0
  162. package/flutter_app/macos/Flutter/Flutter-Debug.xcconfig +2 -0
  163. package/flutter_app/macos/Flutter/Flutter-Release.xcconfig +2 -0
  164. package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +48 -0
  165. package/flutter_app/macos/Podfile +42 -0
  166. package/flutter_app/macos/Podfile.lock +87 -0
  167. package/flutter_app/macos/Runner/AppDelegate.swift +576 -0
  168. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +68 -0
  169. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
  170. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
  171. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
  172. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
  173. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
  174. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
  175. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
  176. package/flutter_app/macos/Runner/Base.lproj/MainMenu.xib +342 -0
  177. package/flutter_app/macos/Runner/Configs/AppInfo.xcconfig +14 -0
  178. package/flutter_app/macos/Runner/Configs/Debug.xcconfig +2 -0
  179. package/flutter_app/macos/Runner/Configs/Release.xcconfig +2 -0
  180. package/flutter_app/macos/Runner/Configs/Warnings.xcconfig +13 -0
  181. package/flutter_app/macos/Runner/DebugProfile.entitlements +16 -0
  182. package/flutter_app/macos/Runner/Info.plist +36 -0
  183. package/flutter_app/macos/Runner/MainFlutterWindow.swift +19 -0
  184. package/flutter_app/macos/Runner/Release.entitlements +12 -0
  185. package/flutter_app/macos/Runner.xcodeproj/project.pbxproj +801 -0
  186. package/flutter_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  187. package/flutter_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +99 -0
  188. package/flutter_app/macos/Runner.xcworkspace/contents.xcworkspacedata +10 -0
  189. package/flutter_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  190. package/flutter_app/macos/RunnerTests/RunnerTests.swift +12 -0
  191. package/flutter_app/patch_strings.py +12 -0
  192. package/flutter_app/pubspec.lock +1256 -0
  193. package/flutter_app/pubspec.yaml +59 -0
  194. package/flutter_app/third_party/desktop_audio_capture/LICENSE +21 -0
  195. package/flutter_app/third_party/desktop_audio_capture/README.md +262 -0
  196. package/flutter_app/third_party/desktop_audio_capture/lib/audio_capture.dart +65 -0
  197. package/flutter_app/third_party/desktop_audio_capture/lib/config/mic_audio_config.dart +153 -0
  198. package/flutter_app/third_party/desktop_audio_capture/lib/config/system_adudio_config.dart +110 -0
  199. package/flutter_app/third_party/desktop_audio_capture/lib/mic/mic_audio_capture.dart +461 -0
  200. package/flutter_app/third_party/desktop_audio_capture/lib/model/audio_status.dart +91 -0
  201. package/flutter_app/third_party/desktop_audio_capture/lib/model/decibel_data.dart +106 -0
  202. package/flutter_app/third_party/desktop_audio_capture/lib/model/input_device_type.dart +219 -0
  203. package/flutter_app/third_party/desktop_audio_capture/lib/system/system_audio_capture.dart +336 -0
  204. package/flutter_app/third_party/desktop_audio_capture/linux/CMakeLists.txt +101 -0
  205. package/flutter_app/third_party/desktop_audio_capture/linux/audio_capture_plugin.cc +692 -0
  206. package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/audio_capture_plugin.h +35 -0
  207. package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/mic_capture_plugin.h +36 -0
  208. package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/audio_capture_plugin.h +32 -0
  209. package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/mic_capture_plugin.h +32 -0
  210. package/flutter_app/third_party/desktop_audio_capture/linux/mic_capture_plugin.cc +878 -0
  211. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/AudioCapturePlugin.swift +27 -0
  212. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/MicCapturePlugin.swift +1172 -0
  213. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/SystemCapturePlugin.swift +655 -0
  214. package/flutter_app/third_party/desktop_audio_capture/macos/Resources/PrivacyInfo.xcprivacy +12 -0
  215. package/flutter_app/third_party/desktop_audio_capture/macos/desktop_audio_capture.podspec +30 -0
  216. package/flutter_app/third_party/desktop_audio_capture/pubspec.yaml +87 -0
  217. package/flutter_app/third_party/desktop_audio_capture/windows/CMakeLists.txt +105 -0
  218. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.cpp +80 -0
  219. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.h +31 -0
  220. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin_c_api.cpp +12 -0
  221. package/flutter_app/third_party/desktop_audio_capture/windows/include/audio_capture/audio_capture_plugin_c_api.h +23 -0
  222. package/flutter_app/third_party/desktop_audio_capture/windows/include/desktop_audio_capture/audio_capture_plugin.h +25 -0
  223. package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.cpp +1117 -0
  224. package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.h +115 -0
  225. package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.cpp +777 -0
  226. package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.h +87 -0
  227. package/flutter_app/third_party/flutter_secure_storage_linux/linux/CMakeLists.txt +30 -0
  228. package/flutter_app/third_party/flutter_secure_storage_linux/linux/flutter_secure_storage_linux_plugin.cc +215 -0
  229. package/flutter_app/third_party/flutter_secure_storage_linux/linux/include/flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h +27 -0
  230. package/flutter_app/third_party/flutter_secure_storage_linux/pubspec.yaml +20 -0
  231. package/flutter_app/tool/generate_desktop_branding.py +219 -0
  232. package/flutter_app/web/favicon.png +0 -0
  233. package/flutter_app/web/favicon.svg +12 -0
  234. package/flutter_app/web/icons/Icon-192.png +0 -0
  235. package/flutter_app/web/icons/Icon-512.png +0 -0
  236. package/flutter_app/web/icons/Icon-maskable-192.png +0 -0
  237. package/flutter_app/web/icons/Icon-maskable-512.png +0 -0
  238. package/flutter_app/web/index.html +39 -0
  239. package/flutter_app/web/manifest.json +35 -0
  240. package/flutter_app/windows/CMakeLists.txt +108 -0
  241. package/flutter_app/windows/flutter/CMakeLists.txt +109 -0
  242. package/flutter_app/windows/flutter/generated_plugin_registrant.cc +50 -0
  243. package/flutter_app/windows/flutter/generated_plugin_registrant.h +15 -0
  244. package/flutter_app/windows/flutter/generated_plugins.cmake +36 -0
  245. package/flutter_app/windows/runner/CMakeLists.txt +41 -0
  246. package/flutter_app/windows/runner/Runner.rc +121 -0
  247. package/flutter_app/windows/runner/flutter_window.cpp +533 -0
  248. package/flutter_app/windows/runner/flutter_window.h +37 -0
  249. package/flutter_app/windows/runner/main.cpp +53 -0
  250. package/flutter_app/windows/runner/resource.h +16 -0
  251. package/flutter_app/windows/runner/resources/app_icon.ico +0 -0
  252. package/flutter_app/windows/runner/runner.exe.manifest +14 -0
  253. package/flutter_app/windows/runner/utils.cpp +65 -0
  254. package/flutter_app/windows/runner/utils.h +19 -0
  255. package/flutter_app/windows/runner/win32_window.cpp +299 -0
  256. package/flutter_app/windows/runner/win32_window.h +102 -0
  257. package/lib/install_helpers.js +31 -0
  258. package/lib/manager.js +465 -43
  259. package/package.json +11 -3
  260. package/runtime/paths.js +137 -0
  261. package/server/config/analytics.js +30 -0
  262. package/server/db/database.js +363 -21
  263. package/server/guest-agent.android.package.json +13 -0
  264. package/server/guest-agent.browser.package.json +14 -0
  265. package/server/guest_agent.js +98 -52
  266. package/server/http/middleware.js +55 -2
  267. package/server/http/routes.js +6 -1
  268. package/server/http/static.js +41 -1
  269. package/server/index.js +16 -16
  270. package/server/public/.last_build_id +1 -1
  271. package/server/public/assets/AssetManifest.bin +1 -1
  272. package/server/public/assets/AssetManifest.bin.json +1 -1
  273. package/server/public/assets/AssetManifest.json +1 -1
  274. package/server/public/assets/NOTICES +679 -129
  275. package/server/public/assets/assets/branding/onboarding_intro.mp4 +0 -0
  276. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  277. package/server/public/assets/packages/mixpanel_flutter/assets/mixpanel.js +3 -0
  278. package/server/public/canvaskit/wimp.wasm +0 -0
  279. package/server/public/flutter_bootstrap.js +2 -2
  280. package/server/public/main.dart.js +95866 -87333
  281. package/server/routes/account.js +26 -1
  282. package/server/routes/agents.js +6 -2
  283. package/server/routes/android.js +22 -6
  284. package/server/routes/auth.js +25 -5
  285. package/server/routes/browser.js +47 -13
  286. package/server/routes/integrations.js +119 -9
  287. package/server/routes/memory.js +179 -8
  288. package/server/routes/runtime.js +14 -0
  289. package/server/routes/screenHistory.js +46 -0
  290. package/server/routes/settings.js +102 -24
  291. package/server/routes/skills.js +12 -0
  292. package/server/routes/social_video.js +66 -0
  293. package/server/routes/tasks.js +22 -2
  294. package/server/routes/triggers.js +90 -0
  295. package/server/routes/wearable.js +67 -0
  296. package/server/routes/widgets.js +4 -4
  297. package/server/services/ai/capabilityHealth.js +23 -51
  298. package/server/services/ai/compaction.js +1 -1
  299. package/server/services/ai/deliverables/artifact_helpers.js +190 -0
  300. package/server/services/ai/deliverables/contracts.js +113 -0
  301. package/server/services/ai/deliverables/index.js +20 -0
  302. package/server/services/ai/deliverables/selector.js +94 -0
  303. package/server/services/ai/deliverables/validator.js +63 -0
  304. package/server/services/ai/deliverables/workflows.js +207 -0
  305. package/server/services/ai/engine.js +372 -27
  306. package/server/services/ai/hooks.js +127 -0
  307. package/server/services/ai/integrated_tools/index.js +98 -0
  308. package/server/services/ai/integrated_tools/remotion.js +373 -0
  309. package/server/services/ai/integrated_tools/shared.js +204 -0
  310. package/server/services/ai/integrated_tools/slidev.js +207 -0
  311. package/server/services/ai/loopPolicy.js +146 -0
  312. package/server/services/ai/models.js +36 -0
  313. package/server/services/ai/providers/githubCopilot.js +97 -0
  314. package/server/services/ai/providers/openai.js +2 -1
  315. package/server/services/ai/providers/openaiCodex.js +384 -0
  316. package/server/services/ai/recordingInsights.js +11 -13
  317. package/server/services/ai/runEvents.js +100 -0
  318. package/server/services/ai/settings.js +20 -0
  319. package/server/services/ai/systemPrompt.js +16 -2
  320. package/server/services/ai/taskAnalysis.js +2 -0
  321. package/server/services/ai/toolResult.js +20 -0
  322. package/server/services/ai/toolRunner.js +46 -6
  323. package/server/services/ai/toolSelector.js +54 -1
  324. package/server/services/ai/tools.js +360 -127
  325. package/server/services/android/android_bootstrap_worker.js +63 -0
  326. package/server/services/android/controller.js +442 -1760
  327. package/server/services/bootstrap_helpers.js +0 -34
  328. package/server/services/browser/controller.js +326 -52
  329. package/server/services/cli/executor.js +40 -1
  330. package/server/services/desktop/screenRecorder.js +182 -0
  331. package/server/services/integrations/env.js +10 -0
  332. package/server/services/integrations/figma/provider.js +1 -0
  333. package/server/services/integrations/github/common.js +106 -0
  334. package/server/services/integrations/github/provider.js +499 -0
  335. package/server/services/integrations/github/repos.js +1124 -0
  336. package/server/services/integrations/google/provider.js +1 -0
  337. package/server/services/integrations/manager.js +88 -12
  338. package/server/services/integrations/microsoft/provider.js +1 -0
  339. package/server/services/integrations/oauth_provider.js +25 -8
  340. package/server/services/integrations/provider_config_store.js +85 -0
  341. package/server/services/integrations/registry.js +8 -2
  342. package/server/services/integrations/spotify/provider.js +488 -0
  343. package/server/services/integrations/trello/provider.js +842 -0
  344. package/server/services/integrations/weather/provider.js +559 -0
  345. package/server/services/integrations/whatsapp/provider.js +6 -2
  346. package/server/services/manager.js +109 -154
  347. package/server/services/mcp/client.js +120 -23
  348. package/server/services/memory/llm_transfer.js +218 -0
  349. package/server/services/memory/manager.js +299 -35
  350. package/server/services/messaging/access_policy.js +10 -0
  351. package/server/services/messaging/automation.js +1 -1
  352. package/server/services/messaging/manager.js +93 -8
  353. package/server/services/messaging/meshtastic.js +277 -0
  354. package/server/services/messaging/meshtastic_env.js +40 -0
  355. package/server/services/messaging/meshtastic_protocol.js +530 -0
  356. package/server/services/messaging/meshtastic_tcp_transport.js +25 -0
  357. package/server/services/runtime/backends/local-vm.js +221 -158
  358. package/server/services/runtime/docker-vm-manager.js +392 -0
  359. package/server/services/runtime/guest_bootstrap.js +654 -0
  360. package/server/services/runtime/manager.js +85 -29
  361. package/server/services/runtime/settings.js +22 -31
  362. package/server/services/runtime/validation.js +16 -31
  363. package/server/services/skills/base_catalog.js +33 -0
  364. package/server/services/social_video/adapters/base.js +26 -0
  365. package/server/services/social_video/adapters/index.js +27 -0
  366. package/server/services/social_video/adapters/instagram.js +17 -0
  367. package/server/services/social_video/adapters/tiktok.js +17 -0
  368. package/server/services/social_video/adapters/x.js +17 -0
  369. package/server/services/social_video/adapters/youtube.js +17 -0
  370. package/server/services/social_video/captions.js +187 -0
  371. package/server/services/social_video/frame.js +42 -0
  372. package/server/services/social_video/index.js +7 -0
  373. package/server/services/social_video/metadata.js +63 -0
  374. package/server/services/social_video/result.js +63 -0
  375. package/server/services/social_video/service.js +720 -0
  376. package/server/services/social_video/url.js +83 -0
  377. package/server/services/tasks/adapters/index.js +2 -0
  378. package/server/services/tasks/adapters/manual.js +12 -0
  379. package/server/services/tasks/adapters/schedule.js +33 -5
  380. package/server/services/tasks/adapters/weather_event.js +84 -0
  381. package/server/services/tasks/integration_runtime.js +86 -1
  382. package/server/services/tasks/runtime.js +3 -3
  383. package/server/services/tasks/task_repository.js +3 -6
  384. package/server/services/voice/agentBridge.js +20 -4
  385. package/server/services/voice/message.js +3 -0
  386. package/server/services/voice/openaiClient.js +4 -1
  387. package/server/services/voice/openaiSpeech.js +6 -1
  388. package/server/services/voice/providers.js +144 -15
  389. package/server/services/voice/runtime.js +8 -8
  390. package/server/services/voice/runtimeManager.js +292 -20
  391. package/server/services/voice/turnRunner.js +29 -9
  392. package/server/services/wearable/firmware_manifest.js +353 -0
  393. package/server/services/wearable/gateway.js +350 -0
  394. package/server/services/wearable/protocol.js +45 -0
  395. package/server/services/wearable/service.js +240 -0
  396. package/server/services/websocket.js +3 -1
  397. package/server/services/widgets/focus_widget.js +137 -0
  398. package/server/services/widgets/service.js +182 -6
  399. package/server/services/workspace/manager.js +301 -0
  400. package/server/utils/deployment.js +16 -9
  401. package/server/utils/local_secrets.js +56 -0
  402. package/server/utils/logger.js +37 -9
  403. package/server/services/integrations/home_assistant/provider.js +0 -350
  404. package/server/services/messaging/access_policy.test.js +0 -228
  405. package/server/services/runtime/backends/host.js +0 -37
  406. package/server/services/runtime/qemu.js +0 -391
@@ -3,8 +3,6 @@
3
3
  const db = require('../db/database');
4
4
  const { MemoryManager } = require('./memory/manager');
5
5
  const { MCPClient } = require('./mcp/client');
6
- const { BrowserController } = require('./browser/controller');
7
- const { AndroidController } = require('./android/controller');
8
6
  const { AgentEngine } = require('./ai/engine');
9
7
  const { MultiStepOrchestrator } = require('./ai/multiStep');
10
8
  const { SkillRunner } = require('./ai/toolRunner');
@@ -15,19 +13,21 @@ const { WidgetService } = require('./widgets/service');
15
13
  const { setupWebSocket } = require('./websocket');
16
14
  const { registerMessagingAutomation } = require('./messaging/automation');
17
15
  const { RecordingManager } = require('./recordings/manager');
16
+ const { SocialVideoService } = require('./social_video');
18
17
  const { VoiceRuntimeManager } = require('./voice/runtimeManager');
19
- const { CLIExecutor } = require('./cli/executor');
20
18
  const { AuthProviderManager } = require('./account/auth_provider_manager');
21
19
  const { IntegrationManager } = require('./integrations/manager');
22
20
  const { ArtifactStore } = require('./artifacts/store');
23
21
  const { RuntimeManager } = require('./runtime/manager');
22
+ const { WorkspaceManager } = require('./workspace/manager');
24
23
  const { BrowserExtensionRegistry } = require('./browser/extension/registry');
25
24
  const { DesktopCompanionRegistry } = require('./desktop/registry');
26
25
  const { DesktopProvider } = require('./desktop/provider');
27
- const { assertRuntimeValidation, getRuntimeValidation } = require('./runtime/validation');
26
+ const { ScreenRecorder } = require('./desktop/screenRecorder');
27
+ const { WearableService } = require('./wearable/service');
28
+ const { getRuntimeValidation } = require('./runtime/validation');
28
29
  const {
29
30
  getErrorMessage,
30
- restoreBrowserHeadlessPreference,
31
31
  runBackgroundTask,
32
32
  } = require('./bootstrap_helpers');
33
33
 
@@ -40,18 +40,18 @@ function logServiceReady(message) {
40
40
  console.log(`[Services] ${message}`);
41
41
  }
42
42
 
43
- function createCliExecutor(app) {
44
- const cliExecutor = registerLocal(app, 'cliExecutor', new CLIExecutor());
45
- logServiceReady('CLI executor ready');
46
- return cliExecutor;
47
- }
48
-
49
43
  function createArtifactStore(app) {
50
44
  const artifactStore = registerLocal(app, 'artifactStore', new ArtifactStore());
51
45
  logServiceReady('Artifact store ready');
52
46
  return artifactStore;
53
47
  }
54
48
 
49
+ function createWorkspaceManager(app) {
50
+ const workspaceManager = registerLocal(app, 'workspaceManager', new WorkspaceManager());
51
+ logServiceReady('Workspace manager ready');
52
+ return workspaceManager;
53
+ }
54
+
55
55
  function createBrowserExtensionRegistry(app) {
56
56
  const registry = registerLocal(app, 'browserExtensionRegistry', new BrowserExtensionRegistry());
57
57
  logServiceReady('Browser extension registry ready');
@@ -197,131 +197,32 @@ function createUserScopedControllerPool(app, {
197
197
  }
198
198
 
199
199
  function createBrowserController(app, artifactStore) {
200
- function getUserHeadlessPreference(userId) {
201
- try {
202
- const row = db
203
- .prepare('SELECT value FROM user_settings WHERE user_id = ? AND key = ?')
204
- .get(userId, 'headless_browser');
205
- if (!row) return true;
206
- return row.value !== 'false' && row.value !== false && row.value !== '0';
207
- } catch (err) {
208
- console.warn('[Services] Failed to read user headless_browser setting, defaulting to true:', getErrorMessage(err));
209
- return true;
210
- }
211
- }
212
-
213
- createUserScopedControllerPool(app, {
214
- controllersKey: 'browserControllers',
215
- creationPromisesKey: 'browserControllerCreationPromises',
216
- lastAccessKey: 'browserControllerLastAccess',
217
- resolverKey: 'getBrowserControllerForUser',
218
- defaultControllerKey: 'browserController',
219
- createController: async (userId) => {
220
- const controller = new BrowserController({
221
- userId,
222
- artifactStore,
223
- runtimeBackend: 'host',
224
- });
225
- controller.headless = getUserHeadlessPreference(userId);
226
- return controller;
227
- },
228
- closeController: async (controller) => {
229
- if (typeof controller.closeBrowser === 'function') {
230
- await controller.closeBrowser();
231
- }
232
- },
233
- closeErrorLabel: '[Browser] Failed to close stale browser controller',
200
+ registerLocal(app, 'getBrowserControllerForUser', async () => {
201
+ throw new Error('Host browser controller is disabled. Use the VM browser backend or a paired extension.');
234
202
  });
235
-
236
- const browserController = registerLocal(
237
- app,
238
- 'browserController',
239
- new BrowserController({
240
- artifactStore,
241
- runtimeBackend: 'host',
242
- }),
243
- );
244
- const { restored, userCount, headless } = restoreBrowserHeadlessPreference(
245
- browserController,
246
- db,
247
- );
248
-
249
- if (restored) {
250
- logServiceReady(`Browser headless setting restored to ${headless}`);
251
- }
252
-
253
- logServiceReady(`Browser controller ready for ${userCount} user(s)`);
254
- return browserController;
203
+ registerLocal(app, 'browserController', null);
204
+ logServiceReady('Browser controller disabled in VM-only mode');
205
+ return null;
255
206
  }
256
207
 
257
- function createAndroidController(app, artifactStore) {
258
- createUserScopedControllerPool(app, {
259
- controllersKey: 'androidControllers',
260
- creationPromisesKey: 'androidControllerCreationPromises',
261
- lastAccessKey: 'androidControllerLastAccess',
262
- resolverKey: 'getAndroidControllerForUser',
263
- defaultControllerKey: 'androidController',
264
- createController: async (userId) => {
265
- const controller = new AndroidController({
266
- userId,
267
- artifactStore,
268
- runtimeBackend: 'host',
269
- });
270
- return controller;
271
- },
272
- closeController: async (controller) => {
273
- if (typeof controller.close === 'function') {
274
- await controller.close();
275
- }
276
- },
277
- closeErrorLabel: '[Android] Failed to close stale Android controller',
278
- });
279
-
280
- const androidController = registerLocal(
281
- app,
282
- 'androidController',
283
- new AndroidController({
284
- artifactStore,
285
- runtimeBackend: 'host',
286
- }),
287
- );
288
- logServiceReady('Android controller ready');
289
- return androidController;
290
- }
291
-
292
- function createRuntimeManager(app, cliExecutor) {
208
+ function createRuntimeManager(app) {
293
209
  const runtimeManager = registerLocal(
294
210
  app,
295
211
  'runtimeManager',
296
212
  new RuntimeManager({
297
- cliExecutor,
298
213
  artifactStore: app.locals.artifactStore,
299
214
  browserExtensionRegistry: app.locals.browserExtensionRegistry,
300
- getHostBrowserProvider: (userId) => {
301
- const resolver = app.locals.getBrowserControllerForUser;
302
- if (typeof resolver === 'function') {
303
- return resolver(userId);
304
- }
305
- return app.locals.browserController;
306
- },
307
- getHostAndroidProvider: (userId) => {
308
- const resolver = app.locals.getAndroidControllerForUser;
309
- if (typeof resolver === 'function') {
310
- return resolver(userId);
311
- }
312
- return app.locals.androidController;
313
- },
314
215
  }),
315
216
  );
316
217
  logServiceReady('Runtime manager ready');
317
218
  return runtimeManager;
318
219
  }
319
220
 
320
- async function createSkillRunner(app, cliExecutor, runtimeManager) {
221
+ async function createSkillRunner(app, runtimeManager) {
321
222
  const skillRunner = registerLocal(
322
223
  app,
323
224
  'skillRunner',
324
- new SkillRunner({ executor: cliExecutor, runtimeManager }),
225
+ new SkillRunner({ runtimeManager }),
325
226
  );
326
227
  await skillRunner.loadSkills();
327
228
  logServiceReady('Skills loaded');
@@ -332,13 +233,13 @@ function createAgentEngine(
332
233
  app,
333
234
  io,
334
235
  {
335
- cliExecutor,
336
236
  memoryManager,
337
237
  mcpClient,
338
238
  browserController,
339
239
  androidController,
340
240
  runtimeManager,
341
241
  skillRunner,
242
+ workspaceManager,
342
243
  },
343
244
  ) {
344
245
  const agentEngine = registerLocal(
@@ -346,12 +247,12 @@ function createAgentEngine(
346
247
  'agentEngine',
347
248
  new AgentEngine(io, {
348
249
  app,
349
- cliExecutor,
350
250
  memoryManager,
351
251
  mcpClient,
352
252
  browserController,
353
253
  androidController,
354
254
  runtimeManager,
255
+ workspaceManager,
355
256
  messagingManager: null,
356
257
  skillRunner,
357
258
  }),
@@ -418,6 +319,19 @@ function createRecordingManager(app, io) {
418
319
  return recordingManager;
419
320
  }
420
321
 
322
+ function createSocialVideoService(app) {
323
+ const socialVideoService = registerLocal(
324
+ app,
325
+ 'socialVideoService',
326
+ new SocialVideoService({
327
+ artifactStore: app.locals.artifactStore,
328
+ runtimeManager: app.locals.runtimeManager,
329
+ }),
330
+ );
331
+ logServiceReady('Social video service ready');
332
+ return socialVideoService;
333
+ }
334
+
421
335
  function createWidgetService(app) {
422
336
  const widgetService = registerLocal(
423
337
  app,
@@ -428,6 +342,52 @@ function createWidgetService(app) {
428
342
  return widgetService;
429
343
  }
430
344
 
345
+ function createWearableService(app) {
346
+ const wearableService = registerLocal(
347
+ app,
348
+ 'wearableService',
349
+ new WearableService({ app }),
350
+ );
351
+ logServiceReady('Wearable service ready');
352
+ return wearableService;
353
+ }
354
+
355
+ function createScreenRecorder(app) {
356
+ const hasActiveRemoteCaptureSession = () => {
357
+ const desktopRegistry = app.locals.desktopCompanionRegistry;
358
+ if (desktopRegistry?.connectionsByUser instanceof Map) {
359
+ for (const userMap of desktopRegistry.connectionsByUser.values()) {
360
+ if (!(userMap instanceof Map)) continue;
361
+ for (const connection of userMap.values()) {
362
+ if (typeof connection?.isOpen === 'function' && connection.isOpen()) {
363
+ return true;
364
+ }
365
+ }
366
+ }
367
+ }
368
+
369
+ const extensionRegistry = app.locals.browserExtensionRegistry;
370
+ if (extensionRegistry?.connectionsByUser instanceof Map) {
371
+ for (const connection of extensionRegistry.connectionsByUser.values()) {
372
+ if (typeof connection?.isOpen === 'function' && connection.isOpen()) {
373
+ return true;
374
+ }
375
+ }
376
+ }
377
+
378
+ return false;
379
+ };
380
+
381
+ const screenRecorder = registerLocal(
382
+ app,
383
+ 'screenRecorder',
384
+ new ScreenRecorder({ hasActiveRemoteCaptureSession }),
385
+ );
386
+ screenRecorder.start();
387
+ logServiceReady('Screen recorder started');
388
+ return screenRecorder;
389
+ }
390
+
431
391
  function restoreMessagingConnections(messagingManager) {
432
392
  void runBackgroundTask('[Messaging] Restore error:', () =>
433
393
  messagingManager.restoreConnections(),
@@ -479,8 +439,8 @@ async function startServices(app, io) {
479
439
  console.log('[Services] Starting service initialization');
480
440
 
481
441
  try {
482
- const cliExecutor = createCliExecutor(app);
483
442
  const artifactStore = createArtifactStore(app);
443
+ createWorkspaceManager(app);
484
444
  createBrowserExtensionRegistry(app);
485
445
  createDesktopCompanionRegistry(app);
486
446
  const memoryManager = createMemoryManager(app);
@@ -488,19 +448,21 @@ async function startServices(app, io) {
488
448
  createAuthProviderManager(app);
489
449
  const integrationManager = createIntegrationManager(app);
490
450
  const browserController = createBrowserController(app, artifactStore);
491
- const androidController = createAndroidController(app, artifactStore);
492
- const runtimeManager = createRuntimeManager(app, cliExecutor);
493
- registerLocal(app, 'runtimeValidation', getRuntimeValidation(runtimeManager));
494
- assertRuntimeValidation(runtimeManager);
495
- const skillRunner = await createSkillRunner(app, cliExecutor, runtimeManager);
451
+ const runtimeManager = createRuntimeManager(app);
452
+ const runtimeValidation = getRuntimeValidation(runtimeManager);
453
+ registerLocal(app, 'runtimeValidation', runtimeValidation);
454
+ if (!runtimeValidation.ready) {
455
+ console.warn('[Services] Runtime validation is degraded:', runtimeValidation.issues.join(' '));
456
+ }
457
+ const skillRunner = await createSkillRunner(app, runtimeManager);
496
458
  const agentEngine = createAgentEngine(app, io, {
497
- cliExecutor,
498
459
  memoryManager,
499
460
  mcpClient,
500
461
  browserController,
501
- androidController,
462
+ androidController: null,
502
463
  runtimeManager,
503
464
  skillRunner,
465
+ workspaceManager: app.locals.workspaceManager,
504
466
  });
505
467
 
506
468
  createMultiStep(app, agentEngine, io);
@@ -512,7 +474,10 @@ async function startServices(app, io) {
512
474
 
513
475
  const messagingManager = createMessagingManager(app, io, agentEngine);
514
476
  const recordingManager = createRecordingManager(app, io);
477
+ createSocialVideoService(app);
515
478
  createWidgetService(app);
479
+ createWearableService(app);
480
+ createScreenRecorder(app);
516
481
 
517
482
  restoreMessagingConnections(messagingManager);
518
483
  restoreMcpClients(mcpClient);
@@ -576,29 +541,19 @@ async function stopServices(app) {
576
541
  );
577
542
  }
578
543
 
579
- if (app.locals.browserControllers instanceof Map) {
580
- for (const controller of app.locals.browserControllers.values()) {
581
- tasks.push(
582
- controller.closeBrowser().catch((err) => {
583
- console.error('[Browser] User-scoped shutdown error:', getErrorMessage(err));
584
- }),
585
- );
586
- }
587
- }
588
-
589
- if (app.locals.androidController) {
544
+ if (app.locals.wearableGateway?.close) {
590
545
  tasks.push(
591
- app.locals.androidController.close().catch((err) => {
592
- console.error('[Android] Shutdown error:', getErrorMessage(err));
546
+ app.locals.wearableGateway.close().catch((err) => {
547
+ console.error('[WearableGateway] Shutdown error:', getErrorMessage(err));
593
548
  }),
594
549
  );
595
550
  }
596
551
 
597
- if (app.locals.androidControllers instanceof Map) {
598
- for (const controller of app.locals.androidControllers.values()) {
552
+ if (app.locals.browserControllers instanceof Map) {
553
+ for (const controller of app.locals.browserControllers.values()) {
599
554
  tasks.push(
600
- controller.close().catch((err) => {
601
- console.error('[Android] User-scoped shutdown error:', getErrorMessage(err));
555
+ controller.closeBrowser().catch((err) => {
556
+ console.error('[Browser] User-scoped shutdown error:', getErrorMessage(err));
602
557
  }),
603
558
  );
604
559
  }
@@ -639,6 +594,15 @@ async function stopServices(app) {
639
594
  }
640
595
  }
641
596
 
597
+ if (app.locals.screenRecorder) {
598
+ try {
599
+ app.locals.screenRecorder.stop();
600
+ logServiceReady('Screen recorder stopped');
601
+ } catch (err) {
602
+ console.error('[ScreenRecorder] Stop error:', getErrorMessage(err));
603
+ }
604
+ }
605
+
642
606
  if (app.locals.browserExtensionRegistry) {
643
607
  try {
644
608
  app.locals.browserExtensionRegistry.closeAll();
@@ -657,15 +621,6 @@ async function stopServices(app) {
657
621
  }
658
622
  }
659
623
 
660
- if (app.locals.cliExecutor) {
661
- try {
662
- app.locals.cliExecutor.killAll('shutdown');
663
- logServiceReady('CLI executor processes terminated');
664
- } catch (err) {
665
- console.error('[CLI] Shutdown error:', getErrorMessage(err));
666
- }
667
- }
668
-
669
624
  await Promise.allSettled(tasks);
670
625
  logServiceReady('Shutdown tasks settled');
671
626
  }
@@ -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));