neoagent 2.3.1-beta.11 → 2.3.1-beta.111

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 (390) hide show
  1. package/.env.example +53 -5
  2. package/README.md +16 -7
  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 +87 -94
  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 -64
  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 +579 -122
  259. package/package.json +10 -3
  260. package/runtime/paths.js +137 -0
  261. package/server/config/analytics.js +30 -0
  262. package/server/db/database.js +296 -22
  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 +5 -2
  267. package/server/http/routes.js +3 -0
  268. package/server/http/static.js +41 -1
  269. package/server/index.js +15 -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 +644 -155
  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 +96067 -88139
  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 +22 -1
  287. package/server/routes/memory.js +168 -6
  288. package/server/routes/runtime.js +14 -0
  289. package/server/routes/settings.js +102 -24
  290. package/server/routes/skills.js +12 -0
  291. package/server/routes/social_video.js +66 -0
  292. package/server/routes/tasks.js +22 -2
  293. package/server/routes/triggers.js +44 -35
  294. package/server/routes/wearable.js +67 -0
  295. package/server/routes/widgets.js +4 -4
  296. package/server/services/ai/capabilityHealth.js +23 -51
  297. package/server/services/ai/compaction.js +1 -1
  298. package/server/services/ai/deliverables/artifact_helpers.js +190 -0
  299. package/server/services/ai/deliverables/contracts.js +113 -0
  300. package/server/services/ai/deliverables/index.js +20 -0
  301. package/server/services/ai/deliverables/selector.js +94 -0
  302. package/server/services/ai/deliverables/validator.js +63 -0
  303. package/server/services/ai/deliverables/workflows.js +207 -0
  304. package/server/services/ai/engine.js +363 -27
  305. package/server/services/ai/hooks.js +127 -0
  306. package/server/services/ai/integrated_tools/index.js +98 -0
  307. package/server/services/ai/integrated_tools/remotion.js +373 -0
  308. package/server/services/ai/integrated_tools/shared.js +204 -0
  309. package/server/services/ai/integrated_tools/slidev.js +207 -0
  310. package/server/services/ai/loopPolicy.js +146 -0
  311. package/server/services/ai/models.js +31 -4
  312. package/server/services/ai/providers/claudeCode.js +49 -0
  313. package/server/services/ai/providers/openai.js +2 -1
  314. package/server/services/ai/providers/openaiCodex.js +422 -15
  315. package/server/services/ai/recordingInsights.js +11 -13
  316. package/server/services/ai/runEvents.js +100 -0
  317. package/server/services/ai/settings.js +12 -2
  318. package/server/services/ai/systemPrompt.js +15 -1
  319. package/server/services/ai/taskAnalysis.js +2 -0
  320. package/server/services/ai/toolResult.js +20 -0
  321. package/server/services/ai/toolRunner.js +46 -6
  322. package/server/services/ai/toolSelector.js +54 -1
  323. package/server/services/ai/tools.js +210 -116
  324. package/server/services/android/android_bootstrap_worker.js +63 -0
  325. package/server/services/android/controller.js +442 -1760
  326. package/server/services/bootstrap_helpers.js +0 -34
  327. package/server/services/browser/controller.js +279 -49
  328. package/server/services/cli/executor.js +40 -1
  329. package/server/services/desktop/screenRecorder.js +65 -9
  330. package/server/services/integrations/figma/provider.js +1 -0
  331. package/server/services/integrations/github/provider.js +3 -3
  332. package/server/services/integrations/google/provider.js +1 -0
  333. package/server/services/integrations/manager.js +25 -5
  334. package/server/services/integrations/microsoft/provider.js +1 -0
  335. package/server/services/integrations/oauth_provider.js +15 -5
  336. package/server/services/integrations/provider_config_store.js +22 -13
  337. package/server/services/integrations/registry.js +2 -4
  338. package/server/services/integrations/spotify/provider.js +1 -0
  339. package/server/services/integrations/trello/provider.js +842 -0
  340. package/server/services/manager.js +88 -155
  341. package/server/services/mcp/client.js +120 -23
  342. package/server/services/memory/llm_transfer.js +218 -0
  343. package/server/services/memory/manager.js +260 -33
  344. package/server/services/messaging/access_policy.js +10 -0
  345. package/server/services/messaging/automation.js +1 -1
  346. package/server/services/messaging/manager.js +64 -1
  347. package/server/services/messaging/meshtastic.js +277 -0
  348. package/server/services/messaging/meshtastic_env.js +40 -0
  349. package/server/services/messaging/meshtastic_protocol.js +530 -0
  350. package/server/services/messaging/meshtastic_tcp_transport.js +25 -0
  351. package/server/services/runtime/backends/local-vm.js +221 -158
  352. package/server/services/runtime/docker-vm-manager.js +392 -0
  353. package/server/services/runtime/guest_bootstrap.js +654 -0
  354. package/server/services/runtime/manager.js +85 -29
  355. package/server/services/runtime/settings.js +22 -31
  356. package/server/services/runtime/validation.js +16 -31
  357. package/server/services/social_video/adapters/base.js +26 -0
  358. package/server/services/social_video/adapters/index.js +27 -0
  359. package/server/services/social_video/adapters/instagram.js +17 -0
  360. package/server/services/social_video/adapters/tiktok.js +17 -0
  361. package/server/services/social_video/adapters/x.js +17 -0
  362. package/server/services/social_video/adapters/youtube.js +17 -0
  363. package/server/services/social_video/captions.js +187 -0
  364. package/server/services/social_video/frame.js +42 -0
  365. package/server/services/social_video/index.js +7 -0
  366. package/server/services/social_video/metadata.js +63 -0
  367. package/server/services/social_video/result.js +63 -0
  368. package/server/services/social_video/service.js +720 -0
  369. package/server/services/social_video/url.js +83 -0
  370. package/server/services/tasks/integration_runtime.js +1 -1
  371. package/server/services/tasks/runtime.js +1 -1
  372. package/server/services/tasks/task_repository.js +3 -6
  373. package/server/services/voice/openaiSpeech.js +6 -1
  374. package/server/services/voice/providers.js +142 -14
  375. package/server/services/voice/runtime.js +8 -8
  376. package/server/services/voice/runtimeManager.js +156 -19
  377. package/server/services/voice/turnRunner.js +29 -9
  378. package/server/services/wearable/firmware_manifest.js +353 -0
  379. package/server/services/wearable/gateway.js +350 -0
  380. package/server/services/wearable/protocol.js +45 -0
  381. package/server/services/wearable/service.js +240 -0
  382. package/server/services/websocket.js +3 -1
  383. package/server/services/widgets/focus_widget.js +137 -0
  384. package/server/services/widgets/service.js +133 -2
  385. package/server/services/workspace/manager.js +301 -0
  386. package/server/utils/deployment.js +16 -9
  387. package/server/services/integrations/home_assistant/provider.js +0 -630
  388. package/server/services/messaging/access_policy.test.js +0 -228
  389. package/server/services/runtime/backends/host.js +0 -37
  390. package/server/services/runtime/qemu.js +0 -391
@@ -7,6 +7,11 @@ const APK_UPLOAD_ROOT = path.resolve(
7
7
  || path.join(DATA_DIR, 'uploads', 'android-apks'),
8
8
  );
9
9
  const MAX_APK_BYTES = Number(process.env.NEOAGENT_ANDROID_APK_MAX_BYTES || 512 * 1024 * 1024);
10
+ const IDLE_TIMEOUT_MS = Number(process.env.NEOAGENT_VM_IDLE_TIMEOUT_MS || 10 * 60 * 1000);
11
+
12
+ function sleep(ms) {
13
+ return new Promise((resolve) => setTimeout(resolve, ms));
14
+ }
10
15
 
11
16
  function assertPathInside(baseDir, candidatePath, label) {
12
17
  const resolvedBase = path.resolve(baseDir);
@@ -22,27 +27,49 @@ function assertPathInside(baseDir, candidatePath, label) {
22
27
  return resolvedCandidate;
23
28
  }
24
29
 
30
+ function isPidAlive(pid) {
31
+ if (!Number.isInteger(pid) || pid <= 0) {
32
+ return false;
33
+ }
34
+ try {
35
+ process.kill(pid, 0);
36
+ return true;
37
+ } catch {
38
+ return false;
39
+ }
40
+ }
41
+
25
42
  class RuntimeHttpClient {
26
- constructor(baseUrl, token = '') {
43
+ constructor(baseUrl, token = '', options = {}) {
27
44
  this.baseUrl = String(baseUrl || '').replace(/\/+$/, '');
28
45
  this.token = String(token || '').trim();
46
+ this.onActivity = options.onActivity || null;
29
47
  }
30
48
 
31
49
  async waitForHealth(options = {}) {
32
- const timeoutMs = Number(options.timeoutMs || 120000);
50
+ const timeoutMs = Number(options.timeoutMs || 600000); // Increased from 120s to 10m for bootstrap
33
51
  const intervalMs = Number(options.intervalMs || 1000);
52
+ const checkLiveness = options.checkLiveness || (() => true);
34
53
  const startedAt = Date.now();
35
54
  let lastError = null;
36
55
 
37
56
  while (Date.now() - startedAt < timeoutMs) {
57
+ if (!checkLiveness()) {
58
+ throw new Error('Guest runtime process exited unexpectedly during bootstrap.');
59
+ }
60
+ const elapsed = Math.round((Date.now() - startedAt) / 1000);
38
61
  try {
39
- const health = await this.request('GET', '/health');
62
+ const health = await this.request('GET', '/health', undefined, { timeoutMs: 2000 });
40
63
  if (health?.status === 'ok') {
64
+ console.log(`[Runtime] Guest agent ready after ${elapsed}s`);
41
65
  return health;
42
66
  }
43
67
  lastError = new Error('Guest agent health check returned a non-ok status.');
44
68
  } catch (error) {
45
69
  lastError = error;
70
+ if (elapsed % 10 === 0) {
71
+ console.log(`[Runtime] Waiting for guest agent health... (${elapsed}s elapsed, last error: ${error.message})`);
72
+ }
46
73
  }
47
74
  await new Promise((resolve) => setTimeout(resolve, intervalMs));
48
75
  }
@@ -53,26 +80,54 @@ class RuntimeHttpClient {
53
80
  throw new Error('Timed out waiting for the guest runtime to become ready.');
54
81
  }
55
82
 
56
- async request(method, pathname, body) {
57
- const response = await fetch(`${this.baseUrl}${pathname}`, {
58
- method,
59
- headers: {
60
- 'content-type': 'application/json',
61
- ...(this.token ? { authorization: `Bearer ${this.token}` } : {}),
62
- },
63
- body: body === undefined ? undefined : JSON.stringify(body),
64
- });
83
+ async request(method, pathname, body, options = {}) {
84
+ const retryCount = Math.max(0, Number(options.retryCount ?? 6));
85
+ const retryDelayMs = Math.max(100, Number(options.retryDelayMs ?? 1000));
86
+ let lastError = null;
65
87
 
66
- const contentType = response.headers.get('content-type') || '';
67
- const payload = contentType.includes('application/json')
68
- ? await response.json().catch(() => ({}))
69
- : { text: await response.text().catch(() => '') };
88
+ for (let attempt = 0; attempt <= retryCount; attempt += 1) {
89
+ const controller = options.timeoutMs ? new AbortController() : null;
90
+ const timer = controller ? setTimeout(() => controller.abort(new Error(`Request timed out after ${options.timeoutMs} ms.`)), options.timeoutMs) : null;
91
+ try {
92
+ const response = await fetch(`${this.baseUrl}${pathname}`, {
93
+ method,
94
+ headers: {
95
+ 'content-type': 'application/json',
96
+ ...(this.token ? { authorization: `Bearer ${this.token}` } : {}),
97
+ },
98
+ body: body === undefined ? undefined : JSON.stringify(body),
99
+ signal: controller?.signal,
100
+ });
70
101
 
71
- if (!response.ok) {
72
- const errorMessage = payload?.error || payload?.text || `Runtime request failed: ${response.status}`;
73
- throw new Error(errorMessage);
102
+ const contentType = response.headers.get('content-type') || '';
103
+ const payload = contentType.includes('application/json')
104
+ ? await response.json().catch(() => ({}))
105
+ : { text: await response.text().catch(() => '') };
106
+
107
+ if (!response.ok) {
108
+ const errorMessage = payload?.error || payload?.text || `Runtime request failed: ${response.status}`;
109
+ throw new Error(errorMessage);
110
+ }
111
+ if (typeof this.onActivity === 'function') {
112
+ this.onActivity();
113
+ }
114
+ return payload;
115
+ } catch (error) {
116
+ lastError = error;
117
+ const message = String(error?.message || error);
118
+ const retryable = /fetch failed|ECONNREFUSED|ECONNRESET|socket hang up|timed out/i.test(message);
119
+ if (!retryable || attempt === retryCount) {
120
+ throw error;
121
+ }
122
+ await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
123
+ } finally {
124
+ if (timer) {
125
+ clearTimeout(timer);
126
+ }
127
+ }
74
128
  }
75
- return payload;
129
+
130
+ throw lastError || new Error('Runtime request failed.');
76
131
  }
77
132
 
78
133
  async requestStream(method, pathname, stream, options = {}) {
@@ -88,6 +143,10 @@ class RuntimeHttpClient {
88
143
  duplex: 'half',
89
144
  });
90
145
 
146
+ if (response.ok && typeof this.onActivity === 'function') {
147
+ this.onActivity();
148
+ }
149
+
91
150
  const contentType = response.headers.get('content-type') || '';
92
151
  const payload = contentType.includes('application/json')
93
152
  ? await response.json().catch(() => ({}))
@@ -110,13 +169,63 @@ class VmBrowserProvider {
110
169
  }
111
170
 
112
171
  async #materialize(result) {
113
- if (!result || !result.fullPath || !this.artifactStore || this.userId == null) {
172
+ if (!result || !this.artifactStore || this.userId == null) {
114
173
  return result;
115
174
  }
116
- const file = await this.client.request('POST', '/files/read', {
117
- path: result.fullPath,
118
- encoding: 'base64',
119
- });
175
+
176
+ const readablePathCandidates = [];
177
+ if (result.fullPath) {
178
+ readablePathCandidates.push(String(result.fullPath));
179
+ }
180
+ if (typeof result.screenshotPath === 'string' && result.screenshotPath.trim() !== '') {
181
+ readablePathCandidates.push(result.screenshotPath);
182
+ }
183
+ if (readablePathCandidates.length === 0) {
184
+ return result;
185
+ }
186
+
187
+ let file = null;
188
+ const maxAttempts = 20;
189
+ for (let attempt = 0; attempt < maxAttempts && !file?.content; attempt += 1) {
190
+ for (const candidate of readablePathCandidates) {
191
+ try {
192
+ file = await this.client.request('POST', '/files/read', {
193
+ path: candidate,
194
+ encoding: 'base64',
195
+ });
196
+ if (file?.content) {
197
+ break;
198
+ }
199
+ } catch (error) {
200
+ if (attempt === maxAttempts - 1) {
201
+ console.warn('[Runtime:browser_vm] screenshot materialization read failed', {
202
+ userId: this.userId,
203
+ candidate,
204
+ error: String(error?.message || error),
205
+ });
206
+ }
207
+ }
208
+ }
209
+ if (!file?.content) {
210
+ await sleep(250);
211
+ }
212
+ }
213
+ if (!file?.content) {
214
+ if (typeof result.screenshotPath === 'string' && result.screenshotPath.trim() !== '') {
215
+ console.warn('[Runtime:browser_vm] unresolved VM screenshot path suppressed', {
216
+ userId: this.userId,
217
+ screenshotPath: result.screenshotPath,
218
+ });
219
+ return {
220
+ ...result,
221
+ screenshotPath: null,
222
+ artifactId: result.artifactId || null,
223
+ fullPath: result.fullPath || null,
224
+ };
225
+ }
226
+ return result;
227
+ }
228
+
120
229
  const allocation = this.artifactStore.allocateFile(this.userId, {
121
230
  kind: 'browser-screenshot',
122
231
  backend: 'vm',
@@ -163,143 +272,52 @@ class VmBrowserProvider {
163
272
  const status = await this.client.request('GET', '/browser/status');
164
273
  return Number(status?.pages || 0);
165
274
  }
275
+ async getCookies() {
276
+ return this.client.request('GET', '/browser/cookies');
277
+ }
166
278
  async setHeadless(value) {
167
- this.headless = value !== false && value !== 'false';
168
- return { success: false, unsupported: true };
279
+ this.headless = true;
280
+ return { success: true };
169
281
  }
170
282
  }
171
283
 
172
- class VmAndroidProvider {
173
- constructor(client, options = {}) {
174
- this.client = client;
175
- this.userId = options.userId;
284
+ class LocalVmExecutionBackend {
285
+ constructor(options = {}) {
286
+ this.vmManager = options.vmManager;
287
+ this.runtimeProfile = options.runtimeProfile === 'android' ? 'android' : 'browser_cli';
288
+ this.token = options.token || process.env.NEOAGENT_VM_GUEST_TOKEN || '';
176
289
  this.artifactStore = options.artifactStore || null;
177
- }
178
-
179
- async #promoteBinary(pathname, kind, contentType, extension) {
180
- if (!pathname || !this.artifactStore || this.userId == null || /^\/api\/artifacts\//.test(pathname)) {
181
- return { url: pathname, artifactId: null, fullPath: null };
182
- }
183
- const file = await this.client.request('POST', '/files/read', {
184
- path: pathname,
185
- encoding: 'base64',
186
- });
187
- const allocation = this.artifactStore.allocateFile(this.userId, {
188
- kind,
189
- backend: 'vm',
190
- extension,
191
- contentType,
192
- filenameBase: kind,
193
- });
194
- fs.writeFileSync(allocation.storagePath, Buffer.from(String(file.content || ''), 'base64'));
195
- this.artifactStore.finalizeFile(allocation.artifactId, allocation.storagePath);
196
- return {
197
- url: allocation.url,
198
- artifactId: allocation.artifactId,
199
- fullPath: allocation.storagePath,
200
- };
201
- }
290
+ this.lastActivity = new Map();
291
+ this.reaperInterval = null;
202
292
 
203
- async #promoteText(pathname, kind, contentType, extension) {
204
- if (!pathname || !this.artifactStore || this.userId == null || /^\/api\/artifacts\//.test(pathname)) {
205
- return { url: pathname, artifactId: null };
293
+ if (IDLE_TIMEOUT_MS > 0) {
294
+ this.#startIdleReaper();
206
295
  }
207
- const file = await this.client.request('POST', '/files/read', {
208
- path: pathname,
209
- encoding: 'utf8',
210
- });
211
- const artifact = this.artifactStore.createTextArtifact(this.userId, {
212
- kind,
213
- backend: 'vm',
214
- extension,
215
- contentType,
216
- filenameBase: kind,
217
- content: String(file.content || ''),
218
- });
219
- return {
220
- url: artifact.url,
221
- artifactId: artifact.artifactId,
222
- };
223
296
  }
224
297
 
225
- async #materializeObservation(result = {}) {
226
- if (!result || typeof result !== 'object') return result;
227
- let next = { ...result };
228
- if (result.fullPath) {
229
- const screenshot = await this.#promoteBinary(result.fullPath, 'android-screenshot', 'image/png', 'png');
230
- next = {
231
- ...next,
232
- screenshotPath: screenshot.url,
233
- artifactId: screenshot.artifactId || next.artifactId || null,
234
- fullPath: screenshot.fullPath,
235
- };
298
+ #touch(userId) {
299
+ const key = String(userId || '').trim();
300
+ if (key) {
301
+ this.lastActivity.set(key, Date.now());
236
302
  }
237
- if (result.uiDumpPath && !/^https?:|^\/api\/artifacts\//.test(String(result.uiDumpPath))) {
238
- const dump = await this.#promoteText(result.uiDumpPath, 'android-ui-dump', 'application/xml', 'xml');
239
- next = {
240
- ...next,
241
- uiDumpPath: dump.url,
242
- uiDumpArtifactId: dump.artifactId || next.uiDumpArtifactId || null,
243
- };
244
- }
245
- return next;
246
303
  }
247
304
 
248
- getStatus() { return this.client.request('GET', '/android/status'); }
249
- requestStartEmulator(options = {}) { return this.client.request('POST', '/android/start', options); }
250
- startEmulator(options = {}) { return this.requestStartEmulator(options); }
251
- stopEmulator() { return this.client.request('POST', '/android/stop'); }
252
- listDevices() { return this.client.request('GET', '/android/devices').then((result) => result.devices || []); }
253
- async screenshot(options = {}) { return this.#materializeObservation(await this.client.request('POST', '/android/screenshot', options)); }
254
- async observe(options = {}) { return this.#materializeObservation(await this.client.request('POST', '/android/observe', options)); }
255
- async dumpUi(options = {}) { return this.#materializeObservation(await this.client.request('POST', '/android/ui-dump', options)); }
256
- listApps(options = {}) {
257
- const query = options.includeSystem === true ? '?includeSystem=true' : '';
258
- return this.client.request('GET', `/android/apps${query}`);
259
- }
260
- async openApp(options = {}) { return this.#materializeObservation(await this.client.request('POST', '/android/open-app', options)); }
261
- async openIntent(options = {}) { return this.#materializeObservation(await this.client.request('POST', '/android/open-intent', options)); }
262
- async tap(options = {}) { return this.#materializeObservation(await this.client.request('POST', '/android/tap', options)); }
263
- async longPress(options = {}) { return this.#materializeObservation(await this.client.request('POST', '/android/long-press', options)); }
264
- async type(options = {}) { return this.#materializeObservation(await this.client.request('POST', '/android/type', options)); }
265
- async swipe(options = {}) { return this.#materializeObservation(await this.client.request('POST', '/android/swipe', options)); }
266
- async pressKey(options = {}) { return this.#materializeObservation(await this.client.request('POST', '/android/press-key', options)); }
267
- async waitFor(options = {}) { return this.#materializeObservation(await this.client.request('POST', '/android/wait-for', options)); }
268
- async shell(options = {}) { return this.#materializeObservation(await this.client.request('POST', '/android/shell', options)); }
269
- async installApk(options = {}) {
270
- const apkPath = assertPathInside(
271
- APK_UPLOAD_ROOT,
272
- String(options.apkPath || ''),
273
- 'APK path',
274
- );
275
- const stat = await fs.promises.stat(apkPath).catch(() => null);
276
- if (!stat || !stat.isFile()) {
277
- throw new Error(`APK not found: ${apkPath}`);
278
- }
279
- if (stat.size > MAX_APK_BYTES) {
280
- throw new Error(`APK is too large: ${stat.size} bytes (limit ${MAX_APK_BYTES}).`);
281
- }
282
- return this.client.requestStream(
283
- 'POST',
284
- '/android/install-apk-stream',
285
- fs.createReadStream(apkPath),
286
- {
287
- contentType: 'application/octet-stream',
288
- contentLength: stat.size,
289
- headers: {
290
- 'x-neoagent-filename': encodeURIComponent(path.basename(apkPath)),
291
- },
292
- },
293
- );
294
- }
295
- close() { return Promise.resolve(); }
296
- }
297
-
298
- class LocalVmExecutionBackend {
299
- constructor(options = {}) {
300
- this.vmManager = options.vmManager;
301
- this.token = options.token || process.env.NEOAGENT_VM_GUEST_TOKEN || '';
302
- this.artifactStore = options.artifactStore || null;
305
+ #startIdleReaper() {
306
+ if (this.reaperInterval) return;
307
+ this.reaperInterval = setInterval(async () => {
308
+ const now = Date.now();
309
+ for (const [userId, lastUsed] of this.lastActivity.entries()) {
310
+ if (now - lastUsed > IDLE_TIMEOUT_MS) {
311
+ console.log(`[Runtime:${this.runtimeProfile}] User ${userId} runtime idle for ${Math.round((now - lastUsed) / 1000)}s, shutting down VM.`);
312
+ this.lastActivity.delete(userId);
313
+ try {
314
+ await this.vmManager?.killVm?.(userId);
315
+ } catch (err) {
316
+ console.error(`[Runtime:${this.runtimeProfile}] Failed to shut down idle VM for user ${userId}:`, err.message);
317
+ }
318
+ }
319
+ }
320
+ }, Math.min(IDLE_TIMEOUT_MS, 60 * 1000));
303
321
  }
304
322
 
305
323
  async #clientForUser(userId) {
@@ -307,10 +325,18 @@ class LocalVmExecutionBackend {
307
325
  throw new Error('Local VM manager is not available.');
308
326
  }
309
327
  const session = await this.vmManager.ensureVm(userId);
310
- const client = new RuntimeHttpClient(session.baseUrl, this.token);
328
+ this.#touch(userId);
329
+ const client = new RuntimeHttpClient(session.baseUrl, session.guestToken || this.token, {
330
+ onActivity: () => this.#touch(userId),
331
+ });
311
332
  try {
312
333
  await client.waitForHealth({
313
- timeoutMs: Number(process.env.NEOAGENT_VM_BOOT_TIMEOUT_MS || 120000),
334
+ timeoutMs: Number(process.env.NEOAGENT_VM_BOOT_TIMEOUT_MS || 20 * 60 * 1000),
335
+ checkLiveness: () => {
336
+ const key = String(userId || '').trim();
337
+ const session = this.vmManager.instances.get(key);
338
+ return Boolean(session && session.process && isPidAlive(session.process.pid));
339
+ },
314
340
  });
315
341
  } catch (error) {
316
342
  const runtimeError = typeof session.getLastError === 'function' ? session.getLastError() : '';
@@ -332,6 +358,14 @@ class LocalVmExecutionBackend {
332
358
  });
333
359
  }
334
360
 
361
+ async killCommand(userId, pid, reason = 'aborted') {
362
+ const client = await this.#clientForUser(userId);
363
+ return client.request('POST', '/exec/kill', {
364
+ pid,
365
+ reason,
366
+ });
367
+ }
368
+
335
369
  async getBrowserProviderForUser(userId) {
336
370
  return new VmBrowserProvider(await this.#clientForUser(userId), {
337
371
  userId,
@@ -339,14 +373,44 @@ class LocalVmExecutionBackend {
339
373
  });
340
374
  }
341
375
 
342
- async getAndroidProviderForUser(userId) {
343
- return new VmAndroidProvider(await this.#clientForUser(userId), {
344
- userId,
345
- artifactStore: this.artifactStore,
346
- });
376
+ async getCommandExecutorForUser(userId) {
377
+ return {
378
+ execute: (command, options = {}) => this.executeCommand(userId, command, options),
379
+ executeInteractive: (command, inputs = [], options = {}) => this.executeCommand(userId, command, {
380
+ ...options,
381
+ pty: true,
382
+ inputs,
383
+ }),
384
+ kill: (pid, reason = 'aborted') => this.killCommand(userId, pid, reason),
385
+ };
386
+ }
387
+
388
+ async isGuestAgentReadyForUser(userId, timeoutMs = 1000) {
389
+ if (!this.vmManager) {
390
+ return false;
391
+ }
392
+ const key = String(userId || '').trim();
393
+ if (!key) {
394
+ return false;
395
+ }
396
+ const session = this.vmManager.instances?.get?.(key);
397
+ if (!session?.baseUrl) {
398
+ return false;
399
+ }
400
+ const client = new RuntimeHttpClient(session.baseUrl, this.token);
401
+ try {
402
+ await client.request('GET', '/health', undefined, { timeoutMs });
403
+ return true;
404
+ } catch {
405
+ return false;
406
+ }
347
407
  }
348
408
 
349
409
  async shutdown() {
410
+ if (this.reaperInterval) {
411
+ clearInterval(this.reaperInterval);
412
+ this.reaperInterval = null;
413
+ }
350
414
  await this.vmManager?.shutdown?.();
351
415
  }
352
416
  }
@@ -354,6 +418,5 @@ class LocalVmExecutionBackend {
354
418
  module.exports = {
355
419
  LocalVmExecutionBackend,
356
420
  RuntimeHttpClient,
357
- VmAndroidProvider,
358
421
  VmBrowserProvider,
359
422
  };