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
@@ -37,9 +37,17 @@ const {
37
37
  buildInterimSignature,
38
38
  normalizeInterimKind,
39
39
  } = require('./interim');
40
-
41
- const MAX_CONSECUTIVE_TOOL_FAILURES = 3;
42
- const WIDGET_REFRESH_MAX_ITERATIONS = 6;
40
+ const { recordRunEvent } = require('./runEvents');
41
+ const {
42
+ buildDeliverableWorkflowGuidance,
43
+ DeliverableValidationError,
44
+ extractArtifactsFromResult,
45
+ getDeliverableWorkflow,
46
+ selectDeliverableWorkflow,
47
+ validateDeliverableExecution,
48
+ } = require('./deliverables');
49
+ const { buildLoopPolicy, resolveToolResultLimits } = require('./loopPolicy');
50
+ const { globalHooks } = require('./hooks');
43
51
 
44
52
  function generateTitle(task) {
45
53
  if (!task || typeof task !== 'string') return 'Untitled';
@@ -72,11 +80,12 @@ function buildSkipTaskAnalysisResult(forceMode) {
72
80
  };
73
81
  }
74
82
 
75
- function buildAnalyzeTaskFallback(forceMode) {
83
+ function buildAnalyzeTaskFallback(forceMode, userMessage = '') {
76
84
  return {
77
85
  mode: forceMode || 'execute',
78
86
  verification_need: 'light',
79
87
  planning_depth: planningDepthForForceMode(forceMode),
88
+ goal: userMessage ? String(userMessage).trim().slice(0, 300) : '',
80
89
  };
81
90
  }
82
91
 
@@ -640,10 +649,10 @@ class AgentEngine {
640
649
  this.activeRuns = new Map();
641
650
  this.subagents = new Map();
642
651
  this.app = services.app || null;
643
- this.cliExecutor = services.cliExecutor || null;
644
652
  this.browserController = services.browserController || null;
645
653
  this.androidController = services.androidController || null;
646
654
  this.runtimeManager = services.runtimeManager || null;
655
+ this.workspaceManager = services.workspaceManager || null;
647
656
  this.messagingManager = services.messagingManager || null;
648
657
  this.mcpManager = services.mcpManager || services.mcpClient || null;
649
658
  this.skillRunner = services.skillRunner || null;
@@ -675,6 +684,54 @@ class AgentEngine {
675
684
  .run(JSON.stringify(next), runId);
676
685
  }
677
686
 
687
+ recordRunEvent(userId, runId, eventType, payload = {}, options = {}) {
688
+ try {
689
+ return recordRunEvent({
690
+ runId,
691
+ userId,
692
+ agentId: options.agentId || null,
693
+ eventType,
694
+ requestId: options.requestId || null,
695
+ stepId: options.stepId || null,
696
+ payload,
697
+ });
698
+ } catch {
699
+ return null;
700
+ }
701
+ }
702
+
703
+ async persistDeliverableMemory(userId, runId, agentId, deliverableResult) {
704
+ if (!this.memoryManager?.saveMemory || !deliverableResult?.summary) return;
705
+ try {
706
+ await this.memoryManager.saveMemory(
707
+ userId,
708
+ deliverableResult.summary,
709
+ 'tasks',
710
+ deliverableResult.validation?.status === 'passed' ? 7 : 5,
711
+ {
712
+ agentId,
713
+ sourceRef: {
714
+ sourceType: 'deliverable_run',
715
+ sourceId: runId,
716
+ sourceLabel: deliverableResult.type || 'deliverable',
717
+ },
718
+ metadata: {
719
+ deliverableType: deliverableResult.type,
720
+ status: deliverableResult.status,
721
+ artifactCount: Array.isArray(deliverableResult.artifacts)
722
+ ? deliverableResult.artifacts.length
723
+ : 0,
724
+ artifacts: Array.isArray(deliverableResult.artifacts)
725
+ ? deliverableResult.artifacts.slice(0, 6)
726
+ : [],
727
+ },
728
+ },
729
+ );
730
+ } catch (error) {
731
+ console.error('[Engine] Failed to persist deliverable memory:', error?.message || error);
732
+ }
733
+ }
734
+
678
735
  async publishInterimUpdate({
679
736
  userId,
680
737
  runId,
@@ -686,6 +743,7 @@ class AgentEngine {
686
743
  content,
687
744
  kind,
688
745
  expectsReply = false,
746
+ deferFollowUp = false,
689
747
  } = {}) {
690
748
  const runMeta = this.getRunMeta(runId);
691
749
  if (!runMeta || runMeta.aborted) {
@@ -715,6 +773,9 @@ class AgentEngine {
715
773
  kind: normalizedKind,
716
774
  expectsReply,
717
775
  });
776
+ if (deferFollowUp === true) {
777
+ metadata.defer_follow_up = true;
778
+ }
718
779
  const createdAt = new Date().toISOString();
719
780
 
720
781
  if (triggerSource === 'messaging') {
@@ -739,6 +800,7 @@ class AgentEngine {
739
800
  content: normalizedContent,
740
801
  kind: normalizedKind,
741
802
  expectsReply,
803
+ deferFollowUp,
742
804
  });
743
805
  } else {
744
806
  db.prepare(
@@ -758,6 +820,7 @@ class AgentEngine {
758
820
  content: normalizedContent,
759
821
  kind: normalizedKind,
760
822
  expectsReply: expectsReply === true,
823
+ deferFollowUp: deferFollowUp === true,
761
824
  createdAt,
762
825
  });
763
826
  runMeta.lastInterimMessage = normalizedContent;
@@ -767,6 +830,7 @@ class AgentEngine {
767
830
  content: normalizedContent,
768
831
  kind: normalizedKind,
769
832
  expectsReply: expectsReply === true,
833
+ deferFollowUp: deferFollowUp === true,
770
834
  triggerSource,
771
835
  platform: triggerSource === 'messaging' ? platform : 'web',
772
836
  });
@@ -783,6 +847,7 @@ class AgentEngine {
783
847
  latestInterim: {
784
848
  kind: normalizedKind,
785
849
  expectsReply: expectsReply === true,
850
+ deferFollowUp: deferFollowUp === true,
786
851
  content: normalizedContent,
787
852
  createdAt,
788
853
  },
@@ -795,6 +860,7 @@ class AgentEngine {
795
860
  sent: true,
796
861
  kind: normalizedKind,
797
862
  expectsReply: expectsReply === true,
863
+ deferFollowUp: deferFollowUp === true,
798
864
  content: normalizedContent,
799
865
  terminal: terminalInterim,
800
866
  };
@@ -898,6 +964,7 @@ class AgentEngine {
898
964
  tools,
899
965
  capabilityHealth,
900
966
  forceMode,
967
+ userMessage,
901
968
  options,
902
969
  }) {
903
970
  const summary = summarizeCapabilityHealth(capabilityHealth);
@@ -913,7 +980,7 @@ class AgentEngine {
913
980
  }),
914
981
  maxTokens: 1100,
915
982
  normalize: normalizeTaskAnalysis,
916
- fallback: buildAnalyzeTaskFallback(forceMode),
983
+ fallback: buildAnalyzeTaskFallback(forceMode, userMessage),
917
984
  reasoningEffort: this.getReasoningEffort(providerName, options),
918
985
  });
919
986
 
@@ -966,6 +1033,7 @@ class AgentEngine {
966
1033
  toolExecutions,
967
1034
  lastReply,
968
1035
  triggerSource,
1036
+ messagingSent,
969
1037
  iteration,
970
1038
  maxIterations,
971
1039
  options,
@@ -995,9 +1063,11 @@ class AgentEngine {
995
1063
  '- A progress update is not complete.',
996
1064
  '- A single failed tool attempt is not blocked if another safe retry, verification step, or alternative path remains.',
997
1065
  '- A tool-specific API error, timeout, rate limit, or missing result inside this run is usually "continue", not "blocked", if any other available tool could still make progress.',
998
- triggerSource === 'messaging'
999
- ? '- For messaging, do not stop on a partial status message. Continue unless the task is actually complete or externally blocked. If you already asked for missing user input, choose "blocked" and wait.'
1000
- : '- Do not stop just because you wrote a status update. Continue unless the task is actually complete or externally blocked.',
1066
+ triggerSource === 'messaging' && messagingSent
1067
+ ? '- A reply was already delivered to the user via send_message. Use "complete" unless there is concrete remaining work (e.g., a tool call you still need to make) before the task is truly done. Do not send follow-up elaborations or re-introductions.'
1068
+ : triggerSource === 'messaging'
1069
+ ? '- For messaging, do not stop on a partial status message. Continue unless the task is actually complete or externally blocked. If you already asked for missing user input, choose "blocked" and wait.'
1070
+ : '- Do not stop just because you wrote a status update. Continue unless the task is actually complete or externally blocked.',
1001
1071
  analysis?.goal ? `Goal: ${analysis.goal}` : '',
1002
1072
  successCriteria.length > 0 ? `Success criteria:\n${successCriteria.map((item, index) => `${index + 1}. ${item}`).join('\n')}` : '',
1003
1073
  `Current iteration: ${iteration} of ${maxIterations}.`,
@@ -1314,8 +1384,10 @@ class AgentEngine {
1314
1384
  const runMeta = this.getRunMeta(runId);
1315
1385
  if (!runMeta || !pid) return;
1316
1386
  runMeta.toolPids.add(pid);
1317
- if (runMeta.aborted && this.cliExecutor) {
1318
- this.cliExecutor.kill(pid, 'aborted');
1387
+ if (runMeta.aborted) {
1388
+ if (this.runtimeManager && typeof this.runtimeManager.killCommand === 'function') {
1389
+ void this.runtimeManager.killCommand(runMeta.userId, pid, 'aborted');
1390
+ }
1319
1391
  }
1320
1392
  }
1321
1393
 
@@ -1325,11 +1397,8 @@ class AgentEngine {
1325
1397
  runMeta.toolPids.delete(pid);
1326
1398
  }
1327
1399
 
1328
- getIterationLimit(triggerType, aiSettings, options = {}) {
1329
- if (triggerType === 'subagent') return aiSettings.subagent_max_iterations;
1330
- if (options.widgetId) return Math.min(this.maxIterations, WIDGET_REFRESH_MAX_ITERATIONS);
1331
- return this.maxIterations;
1332
- }
1400
+ // getIterationLimit() removed use buildLoopPolicy() directly.
1401
+ // maxIterations is derived in runWithModel from loopPolicy.maxIterations.
1333
1402
 
1334
1403
  getReasoningEffort(providerName, options = {}) {
1335
1404
  if (providerName === 'google') return undefined;
@@ -1468,8 +1537,11 @@ class AgentEngine {
1468
1537
  1,
1469
1538
  Number(options.historyWindow || aiSettings.chat_history_window) || aiSettings.chat_history_window,
1470
1539
  );
1471
- const toolReplayBudget = aiSettings.tool_replay_budget_chars;
1472
- const maxIterations = this.getIterationLimit(triggerType, aiSettings, options);
1540
+ // loopPolicy is built after task analysis so analysisMode can be passed in;
1541
+ // we build a provisional policy now (with default mode) and rebuild after
1542
+ // analysis when the mode is known. See the post-analysis policy rebuild below.
1543
+ let loopPolicy = buildLoopPolicy(aiSettings, triggerType, 'execute', options);
1544
+ let maxIterations = loopPolicy.maxIterations;
1473
1545
  const providerStatusConfig = {
1474
1546
  agentId,
1475
1547
  onStatus: (status) => {
@@ -1524,6 +1596,12 @@ class AgentEngine {
1524
1596
  toolPids: new Set(),
1525
1597
  });
1526
1598
  this.emit(userId, 'run:start', { runId, agentId, title: runTitle, model, triggerType, triggerSource });
1599
+ this.recordRunEvent(userId, runId, 'run_started', {
1600
+ title: runTitle,
1601
+ model,
1602
+ triggerType,
1603
+ triggerSource,
1604
+ }, { agentId });
1527
1605
  console.info(
1528
1606
  `[Run ${shortenRunId(runId)}] started trigger=${triggerSource} type=${triggerType} model=${model} title=${summarizeForLog(runTitle, 120)}`
1529
1607
  );
@@ -1542,6 +1620,7 @@ class AgentEngine {
1542
1620
  agentId,
1543
1621
  triggerType,
1544
1622
  triggerSource,
1623
+ widgetId: options.widgetId || null,
1545
1624
  });
1546
1625
  const mcpManager = app?.locals?.mcpManager || app?.locals?.mcpClient || this.mcpManager;
1547
1626
  const integrationManager = app?.locals?.integrationManager || null;
@@ -1583,6 +1662,11 @@ class AgentEngine {
1583
1662
  if (threadStateMessage) {
1584
1663
  messages.push({ role: 'system', content: threadStateMessage });
1585
1664
  }
1665
+ this.recordRunEvent(userId, runId, 'memory_injected', {
1666
+ hasRecallContext: Boolean(recallMsg),
1667
+ hasThreadState: Boolean(threadStateMessage),
1668
+ recallPreview: recallMsg ? String(recallMsg).slice(0, 240) : '',
1669
+ }, { agentId });
1586
1670
  messages.push(this.buildUserMessage(userMessage, options));
1587
1671
  messages = sanitizeConversationMessages(messages);
1588
1672
 
@@ -1602,6 +1686,10 @@ class AgentEngine {
1602
1686
  let analysis = null;
1603
1687
  let plan = null;
1604
1688
  let verification = null;
1689
+ let deliverableWorkflow = null;
1690
+ let deliverablePlan = null;
1691
+ let deliverableArtifacts = [];
1692
+ let deliverableValidation = null;
1605
1693
  let directAnswerEligible = false;
1606
1694
  let analysisUsage = 0;
1607
1695
 
@@ -1617,11 +1705,15 @@ class AgentEngine {
1617
1705
  tools,
1618
1706
  capabilityHealth,
1619
1707
  forceMode: options.forceMode || null,
1708
+ userMessage,
1620
1709
  options: { ...options, triggerSource },
1621
1710
  });
1622
1711
  analysisUsage = analysisResult.usage || 0;
1623
1712
  totalTokens += analysisUsage;
1624
1713
  analysis = applyForcedAnalysisMode({ ...analysisResult.analysis }, options.forceMode);
1714
+ if (!analysis.goal && userMessage) {
1715
+ analysis.goal = String(userMessage).trim().slice(0, 300);
1716
+ }
1625
1717
  analysis.mode = promoteAnalysisMode(analysis.mode, {
1626
1718
  verificationNeed: analysis.verification_need,
1627
1719
  freshnessRisk: analysis.freshness_risk,
@@ -1652,6 +1744,62 @@ class AgentEngine {
1652
1744
  ...analysis,
1653
1745
  capabilitySummary,
1654
1746
  });
1747
+
1748
+ }
1749
+
1750
+ // Rebuild loop policy with the resolved analysis mode. Runs in both the
1751
+ // normal path and the skipTaskAnalysis path so that forceMode='plan_execute'
1752
+ // (or any mode set by buildSkipTaskAnalysisResult) raises the iteration
1753
+ // ceiling correctly.
1754
+ loopPolicy = buildLoopPolicy(aiSettings, triggerType, analysis.mode || 'execute', options);
1755
+ maxIterations = loopPolicy.maxIterations;
1756
+
1757
+ if (options.skipDeliverableWorkflow !== true) {
1758
+ const deliverableSelectionResult = await selectDeliverableWorkflow({
1759
+ engine: this,
1760
+ provider,
1761
+ providerName,
1762
+ model,
1763
+ messages,
1764
+ tools,
1765
+ options,
1766
+ });
1767
+ totalTokens += deliverableSelectionResult.usage || 0;
1768
+ const selectedWorkflow = getDeliverableWorkflow(deliverableSelectionResult.selection.type);
1769
+ if (selectedWorkflow?.canHandle(deliverableSelectionResult.selection)) {
1770
+ deliverableWorkflow = {
1771
+ workflow: selectedWorkflow,
1772
+ selection: deliverableSelectionResult.selection,
1773
+ request: selectedWorkflow.normalizeRequest({
1774
+ ...deliverableSelectionResult.selection,
1775
+ userMessage,
1776
+ }),
1777
+ };
1778
+ deliverablePlan = selectedWorkflow.buildExecutionPlan(deliverableWorkflow.request, {
1779
+ analysis,
1780
+ tools,
1781
+ options,
1782
+ });
1783
+ await selectedWorkflow.run(deliverablePlan, {
1784
+ engine: this,
1785
+ userId,
1786
+ runId,
1787
+ agentId,
1788
+ app,
1789
+ });
1790
+ this.persistRunMetadata(runId, {
1791
+ deliverableWorkflow: {
1792
+ ...deliverableWorkflow.selection,
1793
+ plan: deliverablePlan,
1794
+ },
1795
+ });
1796
+ this.recordRunEvent(userId, runId, 'deliverable_workflow_selected', {
1797
+ type: deliverableWorkflow.selection.type,
1798
+ confidence: deliverableWorkflow.selection.confidence,
1799
+ goal: deliverableWorkflow.selection.goal,
1800
+ requestedOutputs: deliverableWorkflow.selection.requestedOutputs,
1801
+ }, { agentId });
1802
+ }
1655
1803
  }
1656
1804
 
1657
1805
  if (analysis.mode === 'plan_execute') {
@@ -1697,6 +1845,17 @@ class AgentEngine {
1697
1845
  capabilityHealth: capabilitySummary,
1698
1846
  }),
1699
1847
  });
1848
+ if (deliverablePlan) {
1849
+ messages.push({
1850
+ role: 'system',
1851
+ content: buildDeliverableWorkflowGuidance(deliverablePlan),
1852
+ });
1853
+ this.recordRunEvent(userId, runId, 'deliverable_execution_started', {
1854
+ type: deliverableWorkflow?.selection?.type,
1855
+ preferredTools: deliverablePlan.preferredTools || [],
1856
+ expectedOutputs: deliverablePlan.expectedOutputs || [],
1857
+ }, { agentId });
1858
+ }
1700
1859
  messages = sanitizeConversationMessages(messages);
1701
1860
 
1702
1861
  directAnswerEligible = isDirectAnswerEligibleAnalysis(analysis)
@@ -1712,10 +1871,15 @@ class AgentEngine {
1712
1871
  }
1713
1872
  }
1714
1873
 
1874
+ // BUG FIX: consecutiveToolFailures was previously declared INSIDE the
1875
+ // while loop (resetting each iteration). It is now tracked across the
1876
+ // full run so the failure guard fires correctly after 5 consecutive failures
1877
+ // regardless of which iteration they fall in.
1878
+ let consecutiveToolFailures = 0;
1879
+
1715
1880
  while (!directAnswerEligible && iteration < maxIterations) {
1716
1881
  if (this.isRunStopped(runId)) break;
1717
1882
  iteration++;
1718
- let consecutiveToolFailures = 0;
1719
1883
 
1720
1884
  const steeringAtLoopStart = this.applyQueuedSteering(runId, messages, {
1721
1885
  userId,
@@ -1726,7 +1890,7 @@ class AgentEngine {
1726
1890
 
1727
1891
  let metrics = this.estimatePromptMetrics(messages, tools);
1728
1892
  const contextWindow = provider.getContextWindow(model);
1729
- if (metrics.totalEstimatedTokens > contextWindow * 0.85) {
1893
+ if (metrics.totalEstimatedTokens > contextWindow * loopPolicy.compactionThreshold) {
1730
1894
  messages = await compact(messages, provider, model, contextWindow);
1731
1895
  messages = sanitizeConversationMessages(messages);
1732
1896
  this.emit(userId, 'run:compaction', { runId, iteration });
@@ -1736,6 +1900,10 @@ class AgentEngine {
1736
1900
  promptMetrics = this.mergePromptMetrics(promptMetrics, metrics, iteration, tools.length);
1737
1901
  this.persistPromptMetrics(runId, promptMetrics).catch(() => { });
1738
1902
  this.emit(userId, 'run:thinking', { runId, iteration });
1903
+ this.recordRunEvent(userId, runId, 'model_turn_started', {
1904
+ iteration,
1905
+ toolCount: tools.length,
1906
+ }, { agentId });
1739
1907
 
1740
1908
  let response;
1741
1909
  let responseModel = model;
@@ -1813,7 +1981,7 @@ class AgentEngine {
1813
1981
  const isFatalModelError = /no ai providers? are currently available|missing an api key|disabled in settings|unauthorized|forbidden|authentication failed/i
1814
1982
  .test(modelError);
1815
1983
 
1816
- if (!isFatalModelError && modelFailureRecoveries < 2) {
1984
+ if (!isFatalModelError && modelFailureRecoveries < loopPolicy.maxModelFailureRecoveries) {
1817
1985
  modelFailureRecoveries += 1;
1818
1986
  failedStepCount += 1;
1819
1987
  const failedModel = model;
@@ -1869,6 +2037,12 @@ class AgentEngine {
1869
2037
  }
1870
2038
  }
1871
2039
 
2040
+ this.recordRunEvent(userId, runId, 'model_turn_completed', {
2041
+ iteration,
2042
+ toolCallCount: response.toolCalls?.length || 0,
2043
+ contentPreview: String(lastContent || streamContent || '').slice(0, 240),
2044
+ }, { agentId });
2045
+
1872
2046
  const assistantMessage = { role: 'assistant', content: lastContent };
1873
2047
  if (response.toolCalls?.length) assistantMessage.tool_calls = response.toolCalls;
1874
2048
  if (response.providerContentBlocks?.length) assistantMessage.providerContentBlocks = response.providerContentBlocks;
@@ -1919,6 +2093,7 @@ class AgentEngine {
1919
2093
  toolExecutions,
1920
2094
  lastReply: lastContent,
1921
2095
  triggerSource,
2096
+ messagingSent,
1922
2097
  iteration,
1923
2098
  maxIterations,
1924
2099
  options,
@@ -1955,6 +2130,44 @@ class AgentEngine {
1955
2130
  toolArgs = {};
1956
2131
  }
1957
2132
 
2133
+ // ── task_complete: AI explicitly signals the task is fully done ──
2134
+ // Handle before DB insert / before_tool_call hook — this is not a
2135
+ // regular tool execution, it is a loop-exit signal.
2136
+ if (toolName === 'task_complete') {
2137
+ const finalMessage = String(toolArgs.message || '').trim();
2138
+ this.recordRunEvent(userId, runId, 'task_complete_signaled', {
2139
+ confidence: toolArgs.confidence || 'high',
2140
+ iteration,
2141
+ messageLength: finalMessage.length,
2142
+ }, { agentId });
2143
+ console.info(
2144
+ `[Run ${shortenRunId(runId)}] task_complete signaled at iteration=${iteration} confidence=${toolArgs.confidence || 'high'}`
2145
+ );
2146
+ // Always honor task_complete as a stop signal, even with no message.
2147
+ lastContent = finalMessage; // empty string is valid; downstream handles it
2148
+ directAnswerEligible = true;
2149
+ break; // exit the for-loop; the while condition will also exit
2150
+ }
2151
+
2152
+ // ── before_tool_call hook ──
2153
+ // Plugins can block a tool call (e.g. security policy) or mutate args.
2154
+ if (globalHooks.has('before_tool_call')) {
2155
+ const hookCtx = { toolName, toolArgs, runId, userId, agentId, iteration };
2156
+ const hookResult = await globalHooks.run('before_tool_call', hookCtx);
2157
+ if (hookResult.block) {
2158
+ console.warn(`[Run ${shortenRunId(runId)}] before_tool_call hook blocked tool=${toolName}`);
2159
+ // Treat as a soft skip — add a skipped tool message so the model knows
2160
+ messages.push({
2161
+ role: 'tool',
2162
+ name: toolName,
2163
+ tool_call_id: toolCall.id,
2164
+ content: JSON.stringify({ tool: toolName, status: 'skipped', reason: 'Blocked by policy.' }),
2165
+ });
2166
+ continue;
2167
+ }
2168
+ if (hookResult.toolArgs) toolArgs = hookResult.toolArgs;
2169
+ }
2170
+
1958
2171
  db.prepare('INSERT INTO agent_steps (id, run_id, step_index, type, description, status, tool_name, tool_input, started_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime(\'now\'))')
1959
2172
  .run(stepId, runId, stepIndex, this.getStepType(toolName), `${toolName}: ${JSON.stringify(toolArgs).slice(0, 200)} `, 'running', toolName, JSON.stringify(toolArgs));
1960
2173
 
@@ -1962,6 +2175,12 @@ class AgentEngine {
1962
2175
  runId, stepId, stepIndex, toolName, toolArgs,
1963
2176
  type: this.getStepType(toolName)
1964
2177
  });
2178
+ this.recordRunEvent(userId, runId, 'tool_started', {
2179
+ stepIndex,
2180
+ toolName,
2181
+ toolArgs,
2182
+ type: this.getStepType(toolName),
2183
+ }, { agentId, stepId });
1965
2184
  console.info(
1966
2185
  `[Run ${shortenRunId(runId)}] step=${stepIndex} start tool=${toolName} args=${summarizeForLog(toolArgs)}`
1967
2186
  );
@@ -1996,11 +2215,24 @@ class AgentEngine {
1996
2215
  .run(stepStatus, JSON.stringify(toolResult).slice(0, 20000), toolErrorMessage || null, screenshotPath, stepId);
1997
2216
  if (toolErrorMessage) {
1998
2217
  this.emit(userId, 'run:tool_end', { runId, stepId, toolName, error: toolErrorMessage, result: toolResult, screenshotPath, status: stepStatus });
2218
+ this.recordRunEvent(userId, runId, 'tool_failed', {
2219
+ toolName,
2220
+ status: stepStatus,
2221
+ error: toolErrorMessage,
2222
+ durationMs: Date.now() - stepStartedAt,
2223
+ resultPreview: summarizeForLog(toolResult),
2224
+ }, { agentId, stepId });
1999
2225
  console.warn(
2000
2226
  `[Run ${shortenRunId(runId)}] step=${stepIndex} failed tool=${toolName} durationMs=${Date.now() - stepStartedAt} error=${summarizeForLog(toolErrorMessage, 160)}`
2001
2227
  );
2002
2228
  } else {
2003
2229
  this.emit(userId, 'run:tool_end', { runId, stepId, toolName, result: toolResult, screenshotPath, status: stepStatus });
2230
+ this.recordRunEvent(userId, runId, 'tool_completed', {
2231
+ toolName,
2232
+ status: stepStatus,
2233
+ durationMs: Date.now() - stepStartedAt,
2234
+ resultPreview: summarizeForLog(toolResult),
2235
+ }, { agentId, stepId });
2004
2236
  console.info(
2005
2237
  `[Run ${shortenRunId(runId)}] step=${stepIndex} done tool=${toolName} status=${stepStatus} durationMs=${Date.now() - stepStartedAt} result=${summarizeForLog(toolResult)}`
2006
2238
  );
@@ -2013,25 +2245,49 @@ class AgentEngine {
2013
2245
  db.prepare('UPDATE agent_steps SET status = ?, error = ?, completed_at = datetime(\'now\') WHERE id = ?')
2014
2246
  .run('failed', err.message, stepId);
2015
2247
  this.emit(userId, 'run:tool_end', { runId, stepId, toolName, error: err.message, status: 'failed' });
2248
+ this.recordRunEvent(userId, runId, 'tool_failed', {
2249
+ toolName,
2250
+ status: 'failed',
2251
+ error: err.message,
2252
+ durationMs: Date.now() - stepStartedAt,
2253
+ }, { agentId, stepId });
2016
2254
  console.warn(
2017
2255
  `[Run ${shortenRunId(runId)}] step=${stepIndex} failed tool=${toolName} durationMs=${Date.now() - stepStartedAt} error=${summarizeForLog(err.message, 160)}`
2018
2256
  );
2019
2257
  }
2020
2258
 
2021
2259
  const execution = classifyToolExecution(toolName, toolArgs, toolResult, toolErrorMessage);
2260
+ execution.artifacts = await extractArtifactsFromResult(toolName, toolResult);
2022
2261
  toolExecutions.push(execution);
2262
+ if (deliverableWorkflow && Array.isArray(execution.artifacts) && execution.artifacts.length > 0) {
2263
+ for (const artifact of execution.artifacts) {
2264
+ if (!deliverableArtifacts.some((existing) => (
2265
+ (existing.path && artifact.path && existing.path === artifact.path)
2266
+ || (existing.uri && artifact.uri && existing.uri === artifact.uri)
2267
+ ))) {
2268
+ deliverableArtifacts.push(artifact);
2269
+ this.recordRunEvent(userId, runId, 'deliverable_artifact_produced', {
2270
+ type: deliverableWorkflow.selection.type,
2271
+ toolName,
2272
+ artifact,
2273
+ }, { agentId, stepId });
2274
+ }
2275
+ }
2276
+ }
2023
2277
  this.persistRunMetadata(runId, {
2024
2278
  evidenceSources: [...new Set(toolExecutions.map((item) => item.evidenceSource).filter(Boolean))],
2025
2279
  subagentState: this.listSubagents(runId),
2280
+ deliverableArtifacts,
2026
2281
  });
2027
2282
 
2283
+ const toolResultLimits = resolveToolResultLimits(toolName, loopPolicy);
2028
2284
  const toolMessage = {
2029
2285
  role: 'tool',
2030
2286
  name: toolName,
2031
2287
  tool_call_id: toolCall.id,
2032
2288
  content: compactToolResult(toolName, toolArgs, toolResult, {
2033
- softLimit: toolReplayBudget,
2034
- hardLimit: 3200
2289
+ softLimit: toolResultLimits.softLimit,
2290
+ hardLimit: toolResultLimits.hardLimit,
2035
2291
  })
2036
2292
  };
2037
2293
  messages.push(toolMessage);
@@ -2050,7 +2306,7 @@ class AgentEngine {
2050
2306
  ].filter(Boolean).join(' ')
2051
2307
  });
2052
2308
 
2053
- if (consecutiveToolFailures >= MAX_CONSECUTIVE_TOOL_FAILURES) {
2309
+ if (consecutiveToolFailures >= loopPolicy.maxConsecutiveToolFailures) {
2054
2310
  messages.push({
2055
2311
  role: 'system',
2056
2312
  content: `There were ${consecutiveToolFailures} consecutive tool failures. Stop calling tools now and return a clear blocker response that summarizes attempted actions and concrete errors.`
@@ -2124,6 +2380,11 @@ class AgentEngine {
2124
2380
  );
2125
2381
  this.activeRuns.delete(runId);
2126
2382
  this.emit(userId, 'run:stopped', { runId, triggerSource });
2383
+ this.recordRunEvent(userId, runId, 'run_stopped', {
2384
+ triggerSource,
2385
+ totalTokens,
2386
+ iterations: iteration,
2387
+ }, { agentId });
2127
2388
  return { runId, content: '', totalTokens, iterations: iteration, status: 'stopped' };
2128
2389
  }
2129
2390
 
@@ -2237,6 +2498,46 @@ class AgentEngine {
2237
2498
  });
2238
2499
  }
2239
2500
 
2501
+ if (deliverableWorkflow && deliverablePlan) {
2502
+ this.recordRunEvent(userId, runId, 'deliverable_validation_started', {
2503
+ type: deliverableWorkflow.selection.type,
2504
+ artifactCount: deliverableArtifacts.length,
2505
+ }, { agentId });
2506
+ const validationResult = await validateDeliverableExecution({
2507
+ workflow: deliverableWorkflow.workflow,
2508
+ request: deliverableWorkflow.request,
2509
+ plan: deliverablePlan,
2510
+ finalReply: finalResponseText,
2511
+ artifacts: deliverableArtifacts,
2512
+ toolExecutions,
2513
+ runId,
2514
+ });
2515
+ deliverableValidation = validationResult.validation;
2516
+ this.persistRunMetadata(runId, {
2517
+ deliverable: validationResult.result,
2518
+ });
2519
+ if (deliverableValidation.status !== 'passed') {
2520
+ this.recordRunEvent(userId, runId, 'deliverable_validation_failed', {
2521
+ type: deliverableWorkflow.selection.type,
2522
+ errors: deliverableValidation.errors,
2523
+ warnings: deliverableValidation.warnings,
2524
+ }, { agentId });
2525
+ throw new DeliverableValidationError(
2526
+ deliverableValidation.summary || `Deliverable validation failed for ${deliverableWorkflow.selection.type}.`,
2527
+ {
2528
+ validation: deliverableValidation,
2529
+ result: validationResult.result,
2530
+ },
2531
+ );
2532
+ }
2533
+ await this.persistDeliverableMemory(userId, runId, agentId, validationResult.result);
2534
+ this.recordRunEvent(userId, runId, 'deliverable_completed', {
2535
+ type: deliverableWorkflow.selection.type,
2536
+ artifactCount: validationResult.result.artifacts.length,
2537
+ summary: validationResult.result.summary,
2538
+ }, { agentId });
2539
+ }
2540
+
2240
2541
  db.prepare('UPDATE agent_runs SET status = ?, total_tokens = ?, final_response = ?, updated_at = datetime(\'now\'), completed_at = datetime(\'now\') WHERE id = ?')
2241
2542
  .run('completed', totalTokens, finalResponseText || null, runId);
2242
2543
 
@@ -2327,6 +2628,26 @@ class AgentEngine {
2327
2628
  executionMode: analysis?.mode || 'execute',
2328
2629
  verificationStatus: verification?.status || 'skipped',
2329
2630
  });
2631
+ this.recordRunEvent(userId, runId, 'run_completed', {
2632
+ contentPreview: String(finalResponseText || lastContent || '').slice(0, 240),
2633
+ totalTokens,
2634
+ iterations: iteration,
2635
+ triggerSource,
2636
+ executionMode: analysis?.mode || 'execute',
2637
+ verificationStatus: verification?.status || 'skipped',
2638
+ }, { agentId });
2639
+
2640
+ // ── on_loop_end hook ──
2641
+ // Fire-and-forget: plugins can use this for self-improvement, memory
2642
+ // consolidation, analytics, or other post-run housekeeping.
2643
+ if (globalHooks.has('on_loop_end')) {
2644
+ globalHooks.run('on_loop_end', {
2645
+ userId, runId, agentId, status: 'completed',
2646
+ iterations: iteration, totalTokens,
2647
+ taskAnalysis: analysis,
2648
+ finalContent: finalResponseText,
2649
+ }).catch(() => {});
2650
+ }
2330
2651
 
2331
2652
  return { runId, content: lastContent, totalTokens, iterations: iteration, status: 'completed' };
2332
2653
  } catch (err) {
@@ -2339,6 +2660,11 @@ class AgentEngine {
2339
2660
  this.cleanupSubagentsForRun(runId, { cancelRunning: true });
2340
2661
  this.activeRuns.delete(runId);
2341
2662
  this.emit(userId, 'run:stopped', { runId, triggerSource });
2663
+ this.recordRunEvent(userId, runId, 'run_stopped', {
2664
+ triggerSource,
2665
+ totalTokens,
2666
+ iterations: iteration,
2667
+ }, { agentId });
2342
2668
  return { runId, content: '', totalTokens, iterations: iteration, status: 'stopped' };
2343
2669
  }
2344
2670
 
@@ -2348,6 +2674,7 @@ class AgentEngine {
2348
2674
  triggerSource === 'messaging'
2349
2675
  && options.source
2350
2676
  && options.chatId
2677
+ && err?.disableAutonomousRetry !== true
2351
2678
  && retryCount < this.getMessagingRetryLimit(maxIterations)
2352
2679
  );
2353
2680
 
@@ -2402,6 +2729,9 @@ class AgentEngine {
2402
2729
  return this.runWithModel(userId, userMessage, retryOptions, _modelOverride);
2403
2730
  }
2404
2731
 
2732
+ const deliverableFailureResponse = err?.deliverableResult?.summary
2733
+ || err?.deliverableValidation?.summary
2734
+ || '';
2405
2735
  let messagingFailureContent = '';
2406
2736
  let sendSucceeded = false;
2407
2737
  if (triggerSource === 'messaging' && options.source && options.chatId) {
@@ -2460,7 +2790,14 @@ class AgentEngine {
2460
2790
  }
2461
2791
 
2462
2792
  db.prepare('UPDATE agent_runs SET status = ?, error = ?, final_response = ?, updated_at = datetime(\'now\') WHERE id = ?')
2463
- .run('failed', err.message, sendSucceeded ? (messagingFailureContent || null) : null, runId);
2793
+ .run(
2794
+ 'failed',
2795
+ err.message,
2796
+ sendSucceeded
2797
+ ? (messagingFailureContent || null)
2798
+ : (deliverableFailureResponse || null),
2799
+ runId,
2800
+ );
2464
2801
  console.error(
2465
2802
  `[Run ${shortenRunId(runId)}] failed trigger=${triggerSource} steps=${stepIndex} tokens=${totalTokens} error=${summarizeForLog(err.message, 180)}`
2466
2803
  );
@@ -2468,6 +2805,12 @@ class AgentEngine {
2468
2805
  this.cleanupSubagentsForRun(runId, { cancelRunning: true });
2469
2806
  this.activeRuns.delete(runId);
2470
2807
  this.emit(userId, 'run:error', { runId, error: err.message });
2808
+ this.recordRunEvent(userId, runId, 'run_failed', {
2809
+ error: err.message,
2810
+ totalTokens,
2811
+ iterations: iteration,
2812
+ deliverableType: deliverableWorkflow?.selection?.type || null,
2813
+ }, { agentId });
2471
2814
 
2472
2815
  if (messagingFailureContent) {
2473
2816
  return {
@@ -2488,10 +2831,10 @@ class AgentEngine {
2488
2831
  const childRunId = uuidv4();
2489
2832
  const subEngine = new AgentEngine(this.io, {
2490
2833
  app: options.app || this.app,
2491
- cliExecutor: this.cliExecutor,
2492
2834
  browserController: this.browserController,
2493
2835
  androidController: this.androidController,
2494
2836
  runtimeManager: this.runtimeManager,
2837
+ workspaceManager: this.workspaceManager,
2495
2838
  messagingManager: this.messagingManager,
2496
2839
  mcpManager: this.mcpManager,
2497
2840
  skillRunner: this.skillRunner,
@@ -2798,7 +3141,9 @@ class AgentEngine {
2798
3141
  runMeta.aborted = true;
2799
3142
  this.emit(runMeta.userId, 'run:stopping', { runId });
2800
3143
  for (const pid of runMeta.toolPids) {
2801
- this.cliExecutor?.kill(pid, 'aborted');
3144
+ if (this.runtimeManager && typeof this.runtimeManager.killCommand === 'function') {
3145
+ void this.runtimeManager.killCommand(runMeta.userId, pid, 'aborted');
3146
+ }
2802
3147
  }
2803
3148
  runMeta.toolPids.clear();
2804
3149
  }