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
@@ -9,6 +9,10 @@ const {
9
9
  normalizeOutgoingMessageForPlatform,
10
10
  } = require('../messaging/formatting_guides');
11
11
  const { INTERIM_KINDS, normalizeInterimKind } = require('./interim');
12
+ const {
13
+ executeIntegratedTool,
14
+ getIntegratedToolDefinitions,
15
+ } = require('./integrated_tools');
12
16
 
13
17
  function compactText(text, maxChars = 120) {
14
18
  const str = String(text || '').replace(/\s+/g, ' ').trim();
@@ -91,10 +95,155 @@ function compactToolDefinition(tool, options = {}) {
91
95
  return compact;
92
96
  }
93
97
 
98
+ function normalizeScheduleTriggerConfig(inputConfig = {}) {
99
+ if (!inputConfig || typeof inputConfig !== 'object' || Array.isArray(inputConfig)) {
100
+ return inputConfig;
101
+ }
102
+
103
+ const normalized = { ...inputConfig };
104
+ const schedule = (normalized.schedule && typeof normalized.schedule === 'object' && !Array.isArray(normalized.schedule))
105
+ ? normalized.schedule
106
+ : null;
107
+
108
+ const modeCandidate = String(
109
+ normalized.mode
110
+ || normalized.type
111
+ || normalized.scheduleType
112
+ || normalized.schedule_type
113
+ || (normalized.oneTime || normalized.one_time ? 'one_time' : '')
114
+ || ''
115
+ ).trim().toLowerCase();
116
+ if (modeCandidate === 'once') normalized.mode = 'one_time';
117
+ else if (modeCandidate === 'one_time' || modeCandidate === 'recurring') normalized.mode = modeCandidate;
118
+
119
+ const cronCandidate = normalized.cronExpression
120
+ || normalized.cron_expression
121
+ || normalized.cron
122
+ || schedule?.cronExpression
123
+ || schedule?.cron_expression
124
+ || schedule?.cron;
125
+ if (cronCandidate != null) {
126
+ normalized.cronExpression = String(cronCandidate).trim();
127
+ }
128
+
129
+ const runAtCandidate = normalized.runAt
130
+ || normalized.run_at
131
+ || normalized.at
132
+ || normalized.when
133
+ || schedule?.runAt
134
+ || schedule?.run_at
135
+ || schedule?.at
136
+ || schedule?.when;
137
+ if (runAtCandidate != null) {
138
+ normalized.runAt = String(runAtCandidate).trim();
139
+ }
140
+
141
+ if (!normalized.mode) {
142
+ normalized.mode = normalized.runAt ? 'one_time' : 'recurring';
143
+ }
144
+
145
+ delete normalized.schedule;
146
+ return normalized;
147
+ }
148
+
149
+ function normalizeTaskTriggerInput(triggerType, triggerConfig) {
150
+ if (String(triggerType || '').trim() !== 'schedule') {
151
+ return triggerConfig;
152
+ }
153
+ return normalizeScheduleTriggerConfig(triggerConfig || {});
154
+ }
155
+
156
+ function resolveTaskTriggerArgs(args = {}, fallbackTriggerType = null) {
157
+ let triggerType = args.trigger_type !== undefined
158
+ ? String(args.trigger_type || '').trim()
159
+ : String(fallbackTriggerType || '').trim();
160
+ let triggerConfig = args.trigger_config;
161
+ let hasType = args.trigger_type !== undefined;
162
+ let hasConfig = args.trigger_config !== undefined;
163
+
164
+ if (args.trigger && typeof args.trigger === 'object' && !Array.isArray(args.trigger)) {
165
+ const trigger = args.trigger;
166
+ const unifiedType = trigger.type ?? trigger.triggerType ?? trigger.trigger_type;
167
+ const unifiedHasType = unifiedType !== undefined && unifiedType !== null && String(unifiedType).trim().length > 0;
168
+ if (unifiedHasType) {
169
+ triggerType = String(unifiedType).trim();
170
+ hasType = true;
171
+ }
172
+
173
+ const hasUnifiedConfig = Object.prototype.hasOwnProperty.call(trigger, 'config')
174
+ || Object.prototype.hasOwnProperty.call(trigger, 'triggerConfig')
175
+ || Object.prototype.hasOwnProperty.call(trigger, 'trigger_config');
176
+ if (hasUnifiedConfig) {
177
+ triggerConfig = trigger.config ?? trigger.triggerConfig ?? trigger.trigger_config;
178
+ hasConfig = true;
179
+ }
180
+ }
181
+
182
+ const normalizedType = String(triggerType || '').trim() || null;
183
+ const normalizedConfig = hasConfig
184
+ ? normalizeTaskTriggerInput(normalizedType || fallbackTriggerType || 'schedule', triggerConfig)
185
+ : undefined;
186
+
187
+ return {
188
+ triggerType: normalizedType,
189
+ triggerConfig: normalizedConfig,
190
+ hasType,
191
+ hasConfig,
192
+ };
193
+ }
194
+
94
195
  function isProactiveTrigger(triggerSource) {
95
196
  return triggerSource === 'schedule' || triggerSource === 'tasks';
96
197
  }
97
198
 
199
+ function normalizeSendMessagePurpose(value) {
200
+ const normalized = String(value || '').trim().toLowerCase();
201
+ if (normalized === 'final_result' || normalized === 'blocker' || normalized === 'no_response') {
202
+ return normalized;
203
+ }
204
+ return '';
205
+ }
206
+
207
+ function validateProactiveSendMessageArgs({ purpose, normalizedMessage }) {
208
+ const normalizedPurpose = normalizeSendMessagePurpose(purpose);
209
+ if (!normalizedPurpose) {
210
+ return {
211
+ ok: false,
212
+ error: 'Background send_message requires purpose=final_result, blocker, or no_response.',
213
+ reason: 'Background send_message requires purpose=final_result, blocker, or no_response.',
214
+ };
215
+ }
216
+
217
+ if (normalizedPurpose === 'no_response') {
218
+ if (normalizedMessage !== '[NO RESPONSE]') {
219
+ return {
220
+ ok: false,
221
+ error: 'purpose=no_response requires content "[NO RESPONSE]".',
222
+ reason: 'purpose=no_response requires content "[NO RESPONSE]".',
223
+ };
224
+ }
225
+ return {
226
+ ok: false,
227
+ skipped: true,
228
+ suppressed: true,
229
+ reason: 'no_response',
230
+ };
231
+ }
232
+
233
+ if (normalizedMessage === '[NO RESPONSE]') {
234
+ return {
235
+ ok: false,
236
+ error: `purpose=${normalizedPurpose} cannot use content "[NO RESPONSE]".`,
237
+ reason: `purpose=${normalizedPurpose} cannot use content "[NO RESPONSE]".`,
238
+ };
239
+ }
240
+
241
+ return {
242
+ ok: true,
243
+ purpose: normalizedPurpose,
244
+ };
245
+ }
246
+
98
247
  function getRunState(engine, runId) {
99
248
  if (!engine || !runId) return null;
100
249
  return engine.activeRuns.get(runId) || null;
@@ -645,14 +794,15 @@ function getAvailableTools(app, options = {}) {
645
794
  },
646
795
  {
647
796
  name: 'send_message',
648
- description: `Send a message on a connected messaging platform. Supports WhatsApp (text/media), Telnyx Voice (phone calls — TTS), Discord, Telegram, Slack, Google Chat, Microsoft Teams, Matrix, Signal, iMessage/BlueBubbles, IRC, Feishu, LINE, Mattermost, Nextcloud Talk, Nostr, Synology Chat, Tlon, Twitch, Zalo, WeChat, WebChat, and configurable webhook bridges. ${buildSendMessageFormattingReference()} For WhatsApp: use media_path to attach files. Use content "[NO RESPONSE]" only when the user explicitly asked for silence or no reply.`,
797
+ description: `Send a message on a connected messaging platform. Supports WhatsApp (text/media), Telnyx Voice (phone calls — TTS), Discord, Telegram, Slack, Google Chat, Microsoft Teams, Matrix, Signal, iMessage/BlueBubbles, IRC, Feishu, LINE, Mattermost, Nextcloud Talk, Nostr, Synology Chat, Tlon, Twitch, Zalo, WeChat, WebChat, and configurable webhook bridges. ${buildSendMessageFormattingReference()} For WhatsApp: use media_path to attach files. Use content "[NO RESPONSE]" only when the user explicitly asked for silence or no reply. For background task or schedule runs, set purpose to final_result, blocker, or no_response.`,
649
798
  parameters: {
650
799
  type: 'object',
651
800
  properties: {
652
801
  platform: { type: 'string', description: 'Platform name, for example whatsapp, telnyx, discord, telegram, slack, google_chat, teams, matrix, signal, imessage, bluebubbles, irc, line, mattermost, or webchat' },
653
802
  to: { type: 'string', description: 'Recipient/chat ID for the connected platform, such as a WhatsApp chat ID, Telnyx call_control_id, Slack channel ID, Matrix room ID, Discord channel snowflake / "dm_<userId>", Telegram "dm_<userId>" / raw group chat ID, IRC channel, or webhook target' },
654
803
  content: { type: 'string', description: 'Message text. Write one compact natural chat reply; the runtime adapts final formatting for the destination platform.' },
655
- media_path: { type: 'string', description: 'WhatsApp only: absolute path to a local file to attach. Leave empty for text-only or Telnyx.' }
804
+ media_path: { type: 'string', description: 'WhatsApp only: absolute path to a local file to attach. Leave empty for text-only or Telnyx.' },
805
+ purpose: { type: 'string', enum: ['final_result', 'blocker', 'no_response'], description: 'For background task or schedule runs, required intent for this outbound message. Use final_result for a concrete useful outcome, blocker for a real issue the user should know about, or no_response to intentionally send nothing.' }
656
806
  },
657
807
  required: ['platform', 'to', 'content']
658
808
  }
@@ -876,15 +1026,16 @@ function getAvailableTools(app, options = {}) {
876
1026
  type: 'object',
877
1027
  properties: {
878
1028
  name: { type: 'string', description: 'Short descriptive name for the task.' },
879
- trigger_type: { type: 'string', description: 'Trigger type such as schedule, gmail_message_received, outlook_email_received, slack_message_received, teams_message_received, or whatsapp_personal_message_received.' },
880
- trigger_config: { type: 'object', description: 'Trigger-specific configuration object.' },
1029
+ trigger: { type: 'object', description: 'Unified trigger object. Prefer { type: "manual" | "schedule" | integration_trigger_type, config: {...} }.' },
1030
+ trigger_type: { type: 'string', description: 'Trigger type such as manual, schedule, gmail_message_received, outlook_email_received, slack_message_received, teams_message_received, weather_event, or whatsapp_personal_message_received.' },
1031
+ trigger_config: { type: 'object', description: 'Trigger-specific configuration object. For schedule triggers prefer { mode: "recurring", cronExpression: "m h dom mon dow" } or { mode: "one_time", runAt: ISO datetime }. 5-field cron only (seconds unsupported).' },
881
1032
  prompt: { type: 'string', description: 'The instructions the agent will run when the trigger fires.' },
882
1033
  enabled: { type: 'boolean', description: 'Whether to activate immediately.' },
883
1034
  model: { type: 'string', description: 'Optional model override.' },
884
1035
  call_to: { type: 'string', description: 'Optional E.164 phone number to call via Telnyx when this task fires.' },
885
1036
  call_greeting: { type: 'string', description: 'Optional spoken greeting hint for make_call.' }
886
1037
  },
887
- required: ['name', 'trigger_type', 'trigger_config', 'prompt']
1038
+ required: ['name', 'prompt']
888
1039
  }
889
1040
  },
890
1041
  {
@@ -911,8 +1062,9 @@ function getAvailableTools(app, options = {}) {
911
1062
  properties: {
912
1063
  task_id: { type: 'number', description: 'The numeric ID of the task to update.' },
913
1064
  name: { type: 'string', description: 'New name for the task.' },
914
- trigger_type: { type: 'string', description: 'Updated trigger type.' },
915
- trigger_config: { type: 'object', description: 'Updated trigger-specific configuration.' },
1065
+ trigger: { type: 'object', description: 'Unified trigger object. Use { type, config } to update trigger in one section.' },
1066
+ trigger_type: { type: 'string', description: 'Updated trigger type, e.g. manual, schedule, or integration trigger type.' },
1067
+ trigger_config: { type: 'object', description: 'Updated trigger-specific configuration. For schedule triggers use mode+cronExpression (recurring) or mode+runAt (one_time).' },
916
1068
  prompt: { type: 'string', description: 'Updated task prompt.' },
917
1069
  enabled: { type: 'boolean', description: 'Enable or disable the task.' },
918
1070
  model: { type: 'string', description: 'Specific AI model ID for this task. Set to empty string to clear the override.' },
@@ -1019,6 +1171,7 @@ function getAvailableTools(app, options = {}) {
1019
1171
  required: ['prompt']
1020
1172
  }
1021
1173
  },
1174
+ ...getIntegratedToolDefinitions(),
1022
1175
  {
1023
1176
  name: 'generate_table',
1024
1177
  description: 'Format data into a markdown table. The resulting markdown will be returned to you. You MUST include it in your next message to the user so they can see it.',
@@ -1053,6 +1206,17 @@ function getAvailableTools(app, options = {}) {
1053
1206
  required: ['image_path']
1054
1207
  }
1055
1208
  },
1209
+ {
1210
+ name: 'ocr_extract',
1211
+ description: 'Extract raw text from an image locally using Tesseract OCR. This is faster and completely offline compared to analyze_image.',
1212
+ parameters: {
1213
+ type: 'object',
1214
+ properties: {
1215
+ image_path: { type: 'string', description: 'Absolute path to the image file' }
1216
+ },
1217
+ required: ['image_path']
1218
+ }
1219
+ },
1056
1220
  {
1057
1221
  name: 'read_health_data',
1058
1222
  description: 'Read the user\'s synced mobile health data. Omit metric_type for a summary of all available metrics. With metric_type, returns an aggregate summary (total, avg, min, max over all stored data) plus the most recent individual records. Always report the summary figures — avoid listing every raw record.',
@@ -1103,9 +1267,46 @@ function getAvailableTools(app, options = {}) {
1103
1267
  },
1104
1268
  required: ['query']
1105
1269
  }
1270
+ },
1271
+ {
1272
+ name: 'social_video_extract',
1273
+ description: 'Extract title, description, transcript, and one representative frame image from a public social video URL (YouTube, TikTok, Instagram, or X) without social API keys.',
1274
+ parameters: {
1275
+ type: 'object',
1276
+ properties: {
1277
+ url: { type: 'string', description: 'Public social video URL.' },
1278
+ include_frame: { type: 'boolean', description: 'Whether to return one representative frame image artifact (default true).' },
1279
+ force_stt: { type: 'boolean', description: 'Force speech-to-text fallback even if captions are present.' }
1280
+ },
1281
+ required: ['url']
1282
+ }
1106
1283
  }
1107
1284
  ];
1108
1285
 
1286
+ // task_complete — always available. Lets the AI explicitly signal that
1287
+ // the task is fully done and provide the final response. This replaces
1288
+ // the opaque directAnswerEligible heuristic as the primary loop-exit
1289
+ // mechanism and gives the AI real agency over when it's finished.
1290
+ tools.push({
1291
+ name: 'task_complete',
1292
+ description: 'Signal that the task is fully complete and provide the final response. Call this exactly once when all steps are done and you have a complete answer ready. Do NOT call it if you still have work to do.',
1293
+ parameters: {
1294
+ type: 'object',
1295
+ properties: {
1296
+ message: {
1297
+ type: 'string',
1298
+ description: 'Your complete final response to the user. Write it as if it were your reply — do not summarize or reference prior steps.'
1299
+ },
1300
+ confidence: {
1301
+ type: 'string',
1302
+ enum: ['high', 'medium', 'low'],
1303
+ description: 'How confident are you the task is fully and correctly complete? Use "low" if you had to make assumptions.'
1304
+ }
1305
+ },
1306
+ required: ['message']
1307
+ }
1308
+ });
1309
+
1109
1310
  const allowInterimUpdates = (
1110
1311
  (options.triggerSource === 'web' || options.triggerSource === 'messaging' || options.triggerSource === 'voice_live')
1111
1312
  && options.triggerType !== 'subagent'
@@ -1123,7 +1324,8 @@ function getAvailableTools(app, options = {}) {
1123
1324
  properties: {
1124
1325
  content: { type: 'string', description: 'Natural assistant message derived from the current task state.' },
1125
1326
  kind: { type: 'string', enum: Array.from(INTERIM_KINDS), description: 'ack, progress, question, or blocker' },
1126
- expects_reply: { type: 'boolean', description: 'Set true only when the current run should pause for the user to answer.' }
1327
+ expects_reply: { type: 'boolean', description: 'Set true only when the current run should pause for the user to answer.' },
1328
+ defer_follow_up: { type: 'boolean', description: 'Set true when you choose to deliver the final result later via the user\'s last connected chat target.' }
1127
1329
  },
1128
1330
  required: ['content', 'kind']
1129
1331
  }
@@ -1204,23 +1406,16 @@ async function executeTool(toolName, args, context, engine) {
1204
1406
  if (manager && typeof manager.getBrowserProviderForUser === 'function') {
1205
1407
  return manager.getBrowserProviderForUser(userId);
1206
1408
  }
1207
- const scoped = app?.locals?.getBrowserControllerForUser;
1208
- if (typeof scoped === 'function') {
1209
- return scoped(userId);
1210
- }
1211
- return app?.locals?.browserController || engine.browserController;
1409
+ throw new Error('Browser provider is unavailable. VM runtime is required.');
1212
1410
  };
1213
1411
  const ac = () => {
1214
1412
  const manager = runtime();
1215
1413
  if (manager && typeof manager.getAndroidProviderForUser === 'function') {
1216
1414
  return manager.getAndroidProviderForUser(userId);
1217
1415
  }
1218
- const scoped = app?.locals?.getAndroidControllerForUser;
1219
- if (typeof scoped === 'function') {
1220
- return scoped(userId);
1221
- }
1222
- return app?.locals?.androidController || engine.androidController;
1416
+ throw new Error('Android provider is unavailable. VM runtime is required.');
1223
1417
  };
1418
+ const wc = () => app?.locals?.workspaceManager || engine.workspaceManager || null;
1224
1419
  const dc = () => {
1225
1420
  const scoped = app?.locals?.getDesktopProviderForUser;
1226
1421
  if (typeof scoped === 'function') {
@@ -1234,7 +1429,9 @@ async function executeTool(toolName, args, context, engine) {
1234
1429
  const sk = () => app?.locals?.skillRunner || engine.skillRunner;
1235
1430
  const taskRuntime = () => app?.locals?.taskRuntime || engine.taskRuntime;
1236
1431
  const rec = () => app?.locals?.recordingManager || null;
1432
+ const socialVideo = () => app?.locals?.socialVideoService || null;
1237
1433
  const widgets = () => app?.locals?.widgetService || null;
1434
+ const artifactStore = app?.locals?.artifactStore || null;
1238
1435
 
1239
1436
  const integrationManager = integrations();
1240
1437
  if (integrationManager) {
@@ -1259,35 +1456,37 @@ async function executeTool(toolName, args, context, engine) {
1259
1456
  }
1260
1457
  }
1261
1458
 
1459
+ const integratedToolResult = await executeIntegratedTool(toolName, args, {
1460
+ userId,
1461
+ agentId,
1462
+ cliExecutor: runtime() && typeof runtime().getCommandExecutorForUser === 'function'
1463
+ ? await runtime().getCommandExecutorForUser(userId)
1464
+ : null,
1465
+ workspaceManager: wc(),
1466
+ artifactStore,
1467
+ });
1468
+ if (integratedToolResult !== null) {
1469
+ return integratedToolResult;
1470
+ }
1471
+
1262
1472
  switch (toolName) {
1473
+ // task_complete is handled at the engine loop level before executeTool
1474
+ // is called. If it somehow reaches here, return a no-op success so the
1475
+ // loop-level handler can still read the args from the tool call object.
1476
+ case 'task_complete':
1477
+ return { success: true, handled_by: 'engine_loop' };
1478
+
1263
1479
  case 'execute_command': {
1264
1480
  const runtimeManager = runtime();
1265
- if (runtimeManager && typeof runtimeManager.executeCommand === 'function') {
1266
- const onSpawn = (pid) => engine.attachProcessToRun(runId, pid);
1267
- return await runtimeManager.executeCommand(userId, args.command, {
1268
- cwd: args.cwd,
1269
- timeout: args.timeout || (args.pty ? 20 * 60 * 1000 : 15 * 60 * 1000),
1270
- stdinInput: args.stdin_input,
1271
- pty: args.pty === true,
1272
- inputs: args.inputs || [],
1273
- onSpawn,
1274
- });
1275
- }
1276
- const { CLIExecutor } = require('../cli/executor');
1277
- const executor = app?.locals?.cliExecutor || engine.cliExecutor || new CLIExecutor();
1278
- const onSpawn = (pid) => engine.attachProcessToRun(runId, pid);
1279
- if (args.pty) {
1280
- return await executor.executeInteractive(args.command, args.inputs || [], {
1281
- cwd: args.cwd,
1282
- timeout: args.timeout || 20 * 60 * 1000,
1283
- onSpawn
1284
- });
1481
+ if (!runtimeManager || typeof runtimeManager.executeCommand !== 'function') {
1482
+ return { error: 'Command execution is unavailable. VM runtime is required.' };
1285
1483
  }
1286
- return await executor.execute(args.command, {
1484
+ return await runtimeManager.executeCommand(userId, args.command, {
1287
1485
  cwd: args.cwd,
1288
- timeout: args.timeout || 15 * 60 * 1000,
1486
+ timeout: args.timeout || (args.pty ? 20 * 60 * 1000 : 15 * 60 * 1000),
1289
1487
  stdinInput: args.stdin_input,
1290
- onSpawn
1488
+ pty: args.pty === true,
1489
+ inputs: args.inputs || [],
1291
1490
  });
1292
1491
  }
1293
1492
 
@@ -1795,6 +1994,22 @@ async function executeTool(toolName, args, context, engine) {
1795
1994
  };
1796
1995
  }
1797
1996
 
1997
+ case 'social_video_extract': {
1998
+ const service = socialVideo();
1999
+ if (!service || typeof service.extractFromUrl !== 'function') {
2000
+ return { error: 'Social video extraction service is unavailable.' };
2001
+ }
2002
+ const sourceUrl = String(args.url || '').trim();
2003
+ if (!sourceUrl) {
2004
+ return { error: 'url is required' };
2005
+ }
2006
+ return await service.extractFromUrl(userId, sourceUrl, {
2007
+ includeFrame: args.include_frame !== false,
2008
+ forceStt: args.force_stt === true,
2009
+ agentId,
2010
+ });
2011
+ }
2012
+
1798
2013
  case 'memory_write': {
1799
2014
  const { MemoryManager } = require('../memory/manager');
1800
2015
  const mm = new MemoryManager();
@@ -1847,6 +2062,7 @@ async function executeTool(toolName, args, context, engine) {
1847
2062
  }
1848
2063
  const interimContent = typeof args.content === 'string' ? args.content : '';
1849
2064
  const expectsReply = args.expects_reply === true;
2065
+ const deferFollowUp = args.defer_follow_up === true;
1850
2066
  return engine.publishInterimUpdate({
1851
2067
  userId,
1852
2068
  runId,
@@ -1858,6 +2074,7 @@ async function executeTool(toolName, args, context, engine) {
1858
2074
  content: interimContent,
1859
2075
  kind: normalizeInterimKind(args.kind),
1860
2076
  expectsReply,
2077
+ deferFollowUp,
1861
2078
  });
1862
2079
  }
1863
2080
 
@@ -1873,6 +2090,25 @@ async function executeTool(toolName, args, context, engine) {
1873
2090
  stripNoResponseMarker: false
1874
2091
  });
1875
2092
  const suppressReply = normalizedMessage === '[NO RESPONSE]';
2093
+ if (isProactiveTrigger(triggerSource)) {
2094
+ const proactiveValidation = validateProactiveSendMessageArgs({
2095
+ purpose: args.purpose,
2096
+ normalizedMessage,
2097
+ });
2098
+ if (!proactiveValidation.ok) {
2099
+ if (proactiveValidation.error) {
2100
+ return {
2101
+ error: proactiveValidation.error,
2102
+ };
2103
+ }
2104
+ return {
2105
+ sent: false,
2106
+ suppressed: proactiveValidation.suppressed === true,
2107
+ skipped: proactiveValidation.skipped === true,
2108
+ reason: proactiveValidation.reason,
2109
+ };
2110
+ }
2111
+ }
1876
2112
  if (!suppressReply && hasAlreadySentProactiveMessage({
1877
2113
  triggerSource,
1878
2114
  runState,
@@ -1904,21 +2140,14 @@ async function executeTool(toolName, args, context, engine) {
1904
2140
 
1905
2141
  case 'read_file': {
1906
2142
  try {
1907
- const encoding = args.encoding || 'utf-8';
1908
- if (args.start_line || args.end_line) {
1909
- const content = fs.readFileSync(args.path, encoding);
1910
- const lines = content.split('\n');
1911
- const start = Math.max(0, (args.start_line || 1) - 1);
1912
- const end = args.end_line || lines.length;
1913
- const sliced = lines.slice(start, end).join('\n');
1914
- return {
1915
- content: sliced.length > 20000 ? sliced.slice(0, 20000) + '\n...[truncated]' : sliced,
1916
- totalLines: lines.length,
1917
- rangeShown: [start + 1, Math.min(end, lines.length)]
1918
- };
1919
- }
1920
- const content = fs.readFileSync(args.path, encoding);
1921
- return { content: content.length > 20000 ? content.slice(0, 20000) + '\n...[truncated]' : content };
2143
+ const workspace = wc();
2144
+ if (!workspace) return { error: 'Workspace service is unavailable.' };
2145
+ return workspace.readFile(userId, {
2146
+ path: args.path,
2147
+ encoding: args.encoding || 'utf-8',
2148
+ start_line: args.start_line,
2149
+ end_line: args.end_line,
2150
+ });
1922
2151
  } catch (err) {
1923
2152
  return { error: err.message };
1924
2153
  }
@@ -1926,14 +2155,13 @@ async function executeTool(toolName, args, context, engine) {
1926
2155
 
1927
2156
  case 'write_file': {
1928
2157
  try {
1929
- const dir = path.dirname(args.path);
1930
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
1931
- if (args.mode === 'append') {
1932
- fs.appendFileSync(args.path, args.content);
1933
- } else {
1934
- fs.writeFileSync(args.path, args.content);
1935
- }
1936
- return { success: true, path: args.path };
2158
+ const workspace = wc();
2159
+ if (!workspace) return { error: 'Workspace service is unavailable.' };
2160
+ return workspace.writeFile(userId, {
2161
+ path: args.path,
2162
+ content: args.content,
2163
+ mode: args.mode,
2164
+ });
1937
2165
  } catch (err) {
1938
2166
  return { error: err.message };
1939
2167
  }
@@ -1941,23 +2169,12 @@ async function executeTool(toolName, args, context, engine) {
1941
2169
 
1942
2170
  case 'edit_file': {
1943
2171
  try {
1944
- if (!fs.existsSync(args.path)) return { error: `File not found: ${args.path}` };
1945
- let content = fs.readFileSync(args.path, 'utf-8');
1946
- let modified = false;
1947
- const report = [];
1948
-
1949
- for (const edit of args.edits) {
1950
- if (content.includes(edit.oldText)) {
1951
- content = content.replace(edit.oldText, edit.newText);
1952
- modified = true;
1953
- report.push({ success: true, edit: edit.oldText.slice(0, 50) + '...' });
1954
- } else {
1955
- report.push({ success: false, error: 'Target text not found', edit: edit.oldText.slice(0, 50) + '...' });
1956
- }
1957
- }
1958
-
1959
- if (modified) fs.writeFileSync(args.path, content);
1960
- return { success: modified, report, path: args.path };
2172
+ const workspace = wc();
2173
+ if (!workspace) return { error: 'Workspace service is unavailable.' };
2174
+ return workspace.editFile(userId, {
2175
+ path: args.path,
2176
+ edits: args.edits,
2177
+ });
1961
2178
  } catch (err) {
1962
2179
  return { error: err.message };
1963
2180
  }
@@ -1965,28 +2182,13 @@ async function executeTool(toolName, args, context, engine) {
1965
2182
 
1966
2183
  case 'list_directory': {
1967
2184
  try {
1968
- const maxDepth = Math.min(args.depth || (args.recursive ? 3 : 1), 5);
1969
- const recurse = (dir, currentDepth = 1) => {
1970
- const entries = fs.readdirSync(dir, { withFileTypes: true });
1971
- const result = [];
1972
- for (const e of entries) {
1973
- const fullPath = path.join(dir, e.name);
1974
- const stats = fs.statSync(fullPath);
1975
- const item = {
1976
- name: e.name,
1977
- type: e.isDirectory() ? 'directory' : 'file',
1978
- path: fullPath,
1979
- size: stats.size,
1980
- mtime: stats.mtime.toISOString()
1981
- };
1982
- result.push(item);
1983
- if (e.isDirectory() && currentDepth < maxDepth && !e.name.startsWith('.') && e.name !== 'node_modules') {
1984
- result.push(...recurse(fullPath, currentDepth + 1));
1985
- }
1986
- }
1987
- return result;
1988
- };
1989
- return { entries: recurse(args.path) };
2185
+ const workspace = wc();
2186
+ if (!workspace) return { error: 'Workspace service is unavailable.' };
2187
+ return workspace.listDirectory(userId, {
2188
+ path: args.path,
2189
+ depth: args.depth,
2190
+ recursive: args.recursive,
2191
+ });
1990
2192
  } catch (err) {
1991
2193
  return { error: err.message };
1992
2194
  }
@@ -1994,23 +2196,13 @@ async function executeTool(toolName, args, context, engine) {
1994
2196
 
1995
2197
  case 'search_files': {
1996
2198
  try {
1997
- const { CLIExecutor } = require('../cli/executor');
1998
- const executor = new CLIExecutor();
1999
- const includePattern = args.include ? `--include="${args.include}"` : '';
2000
- const command = `grep -rnE "${args.query.replace(/"/g, '\\"')}" "${args.path}" ${includePattern} | head -n 100`;
2001
- const result = await executor.execute(command);
2002
- if (result.exitCode === 1 && !result.stdout) return { results: [], message: 'No matches found' };
2003
-
2004
- const lines = (result.stdout || '').split('\n').filter(Boolean);
2005
- const matches = lines.map(line => {
2006
- const parts = line.split(':');
2007
- return {
2008
- file: parts[0],
2009
- line: parseInt(parts[1]),
2010
- content: parts.slice(2).join(':').trim()
2011
- };
2199
+ const workspace = wc();
2200
+ if (!workspace) return { error: 'Workspace service is unavailable.' };
2201
+ return workspace.searchFiles(userId, {
2202
+ path: args.path,
2203
+ query: args.query,
2204
+ include: args.include,
2012
2205
  });
2013
- return { matches, count: matches.length };
2014
2206
  } catch (err) {
2015
2207
  return { error: err.message };
2016
2208
  }
@@ -2205,10 +2397,21 @@ async function executeTool(toolName, args, context, engine) {
2205
2397
  const s = taskRuntime();
2206
2398
  if (!s) return { error: 'Task runtime not available' };
2207
2399
  try {
2400
+ const resolvedTrigger = resolveTaskTriggerArgs(args, 'schedule');
2401
+ if (!resolvedTrigger.hasType || !resolvedTrigger.triggerType) {
2402
+ return { error: 'Task trigger type is required (use trigger.type or trigger_type).' };
2403
+ }
2404
+ const normalizedTriggerType = String(resolvedTrigger.triggerType || '').trim();
2405
+ const triggerConfig = (!resolvedTrigger.hasConfig || resolvedTrigger.triggerConfig === undefined)
2406
+ ? (normalizedTriggerType === 'manual' ? {} : undefined)
2407
+ : resolvedTrigger.triggerConfig;
2408
+ if (triggerConfig === undefined) {
2409
+ return { error: 'Task trigger config is required (use trigger.config or trigger_config).' };
2410
+ }
2208
2411
  const task = await s.createTask(userId, {
2209
2412
  name: args.name,
2210
- triggerType: args.trigger_type,
2211
- triggerConfig: args.trigger_config,
2413
+ triggerType: normalizedTriggerType,
2414
+ triggerConfig,
2212
2415
  prompt: args.prompt,
2213
2416
  enabled: args.enabled !== false,
2214
2417
  model: args.model || null,
@@ -2246,12 +2449,13 @@ async function executeTool(toolName, args, context, engine) {
2246
2449
  const s = taskRuntime();
2247
2450
  if (!s) return { error: 'Task runtime not available' };
2248
2451
  try {
2249
- const existing = db.prepare('SELECT agent_id FROM scheduled_tasks WHERE id = ? AND user_id = ?').get(args.task_id, userId);
2452
+ const existing = db.prepare('SELECT agent_id, trigger_type FROM scheduled_tasks WHERE id = ? AND user_id = ?').get(args.task_id, userId);
2250
2453
  if (!existing || existing.agent_id !== agentId) return { error: 'Task not found for this agent.' };
2251
2454
  const updates = {};
2455
+ const resolvedTrigger = resolveTaskTriggerArgs(args, existing.trigger_type || 'schedule');
2252
2456
  if (args.name !== undefined) updates.name = args.name;
2253
- if (args.trigger_type !== undefined) updates.triggerType = args.trigger_type;
2254
- if (args.trigger_config !== undefined) updates.triggerConfig = args.trigger_config;
2457
+ if (resolvedTrigger.hasType && resolvedTrigger.triggerType) updates.triggerType = resolvedTrigger.triggerType;
2458
+ if (resolvedTrigger.hasConfig && resolvedTrigger.triggerConfig !== undefined) updates.triggerConfig = resolvedTrigger.triggerConfig;
2255
2459
  if (args.prompt !== undefined) updates.prompt = args.prompt;
2256
2460
  if (args.enabled !== undefined) updates.enabled = args.enabled;
2257
2461
  if (args.model !== undefined) updates.model = args.model || null;
@@ -2363,7 +2567,17 @@ async function executeTool(toolName, args, context, engine) {
2363
2567
  if (!widgetService) return { error: 'Widget service not available' };
2364
2568
  if (!widgetId) return { error: 'save_widget_snapshot is only available during widget refresh runs.' };
2365
2569
  try {
2366
- const snapshot = widgetService.saveSnapshot(userId, widgetId, args.snapshot, {
2570
+ const snapshotPayload = (
2571
+ args
2572
+ && typeof args === 'object'
2573
+ && !Array.isArray(args)
2574
+ && args.snapshot
2575
+ && typeof args.snapshot === 'object'
2576
+ && !Array.isArray(args.snapshot)
2577
+ )
2578
+ ? args.snapshot
2579
+ : args;
2580
+ const snapshot = widgetService.saveSnapshot(userId, widgetId, snapshotPayload, {
2367
2581
  sourceRunId: runId,
2368
2582
  status: 'ready',
2369
2583
  });
@@ -2470,6 +2684,20 @@ async function executeTool(toolName, args, context, engine) {
2470
2684
  }
2471
2685
  }
2472
2686
 
2687
+ case 'ocr_extract': {
2688
+ try {
2689
+ const fs = require('fs');
2690
+ if (!fs.existsSync(args.image_path)) {
2691
+ return { error: 'File not found: ' + args.image_path };
2692
+ }
2693
+ const Tesseract = require('tesseract.js');
2694
+ const result = await Tesseract.recognize(args.image_path, 'eng');
2695
+ return { text: result.data.text, confidence: result.data.confidence };
2696
+ } catch (err) {
2697
+ return { error: err.message };
2698
+ }
2699
+ }
2700
+
2473
2701
  case 'spawn_subagent': {
2474
2702
  try {
2475
2703
  const task = args.context ? `${args.task}\n\nContext: ${args.context}` : args.task;
@@ -2537,7 +2765,12 @@ async function executeTool(toolName, args, context, engine) {
2537
2765
  const { detectPromptInjection } = require('../../utils/security');
2538
2766
  const mcpManager = mcp();
2539
2767
  if (mcpManager) {
2540
- const mcpResult = await mcpManager.callToolByName(toolName, args, userId, { agentId });
2768
+ let mcpResult = null;
2769
+ try {
2770
+ mcpResult = await mcpManager.callToolByName(toolName, args, userId, { agentId });
2771
+ } catch (mcpErr) {
2772
+ return { error: mcpErr.message, tool: toolName, source: 'mcp' };
2773
+ }
2541
2774
  if (mcpResult !== null) {
2542
2775
  const resultText = typeof mcpResult === 'string' ? mcpResult : JSON.stringify(mcpResult);
2543
2776
  if (detectPromptInjection(resultText)) {
@@ -2562,4 +2795,4 @@ async function executeTool(toolName, args, context, engine) {
2562
2795
  }
2563
2796
  }
2564
2797
 
2565
- module.exports = { getAvailableTools, executeTool };
2798
+ module.exports = { getAvailableTools, executeTool, validateProactiveSendMessageArgs };