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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (390) hide show
  1. package/.env.example +53 -5
  2. package/README.md +16 -7
  3. package/com.neoagent.plist +1 -1
  4. package/docs/automation.md +82 -41
  5. package/docs/capabilities.md +85 -89
  6. package/docs/configuration.md +87 -94
  7. package/docs/getting-started.md +46 -27
  8. package/docs/hardware.md +1 -1
  9. package/docs/index.md +24 -30
  10. package/docs/integrations.md +46 -64
  11. package/docs/migration.md +41 -146
  12. package/docs/operations.md +23 -20
  13. package/docs/skills.md +20 -20
  14. package/docs/why-neoagent.md +15 -13
  15. package/extensions/chrome-browser/background.mjs +10 -2
  16. package/flutter_app/.metadata +42 -0
  17. package/flutter_app/DESIGN_SYSTEM.md +166 -0
  18. package/flutter_app/README.md +21 -0
  19. package/flutter_app/analysis_options.yaml +32 -0
  20. package/flutter_app/android/app/build.gradle.kts +111 -0
  21. package/flutter_app/android/app/src/debug/AndroidManifest.xml +7 -0
  22. package/flutter_app/android/app/src/main/AndroidManifest.xml +185 -0
  23. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/MainActivity.kt +888 -0
  24. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/auto/NeoAgentCarAppService.kt +114 -0
  25. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthConnectGateway.kt +280 -0
  26. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncNotifications.kt +113 -0
  27. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncPayload.kt +57 -0
  28. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncScheduler.kt +78 -0
  29. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncWorker.kt +253 -0
  30. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/PermissionsRationaleActivity.kt +46 -0
  31. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingBootReceiver.kt +21 -0
  32. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingForegroundService.kt +586 -0
  33. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingStateStore.kt +78 -0
  34. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingUploadClient.kt +104 -0
  35. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/telecom/NeoAgentConnection.kt +118 -0
  36. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/telecom/NeoAgentConnectionService.kt +86 -0
  37. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiHomeWidgetProvider.kt +457 -0
  38. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiWidgetStore.kt +194 -0
  39. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/VoiceLaunchWidgetProvider.kt +67 -0
  40. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetConfigActivity.kt +228 -0
  41. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncScheduler.kt +72 -0
  42. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncWorker.kt +186 -0
  43. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetTaskRunWorker.kt +210 -0
  44. package/flutter_app/android/app/src/main/res/drawable/launch_background.xml +12 -0
  45. package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_bg.xml +11 -0
  46. package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_task_bg.xml +8 -0
  47. package/flutter_app/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
  48. package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget.xml +138 -0
  49. package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget_task_row.xml +52 -0
  50. package/flutter_app/android/app/src/main/res/layout/neoagent_voice_widget.xml +49 -0
  51. package/flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  52. package/flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  53. package/flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  54. package/flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  55. package/flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  56. package/flutter_app/android/app/src/main/res/values/arrays.xml +8 -0
  57. package/flutter_app/android/app/src/main/res/values/strings.xml +12 -0
  58. package/flutter_app/android/app/src/main/res/values/styles.xml +18 -0
  59. package/flutter_app/android/app/src/main/res/values-night/styles.xml +18 -0
  60. package/flutter_app/android/app/src/main/res/xml/automotive_app_desc.xml +4 -0
  61. package/flutter_app/android/app/src/main/res/xml/file_paths.xml +6 -0
  62. package/flutter_app/android/app/src/main/res/xml/neoagent_ai_widget_info.xml +12 -0
  63. package/flutter_app/android/app/src/main/res/xml/neoagent_voice_widget_info.xml +12 -0
  64. package/flutter_app/android/app/src/profile/AndroidManifest.xml +7 -0
  65. package/flutter_app/android/build.gradle.kts +24 -0
  66. package/flutter_app/android/ci-release.keystore +0 -0
  67. package/flutter_app/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  68. package/flutter_app/android/gradle.properties +3 -0
  69. package/flutter_app/android/key.properties +4 -0
  70. package/flutter_app/android/settings.gradle.kts +26 -0
  71. package/flutter_app/assets/branding/app_icon_1024.png +0 -0
  72. package/flutter_app/assets/branding/app_icon_128.png +0 -0
  73. package/flutter_app/assets/branding/app_icon_192.png +0 -0
  74. package/flutter_app/assets/branding/app_icon_256.png +0 -0
  75. package/flutter_app/assets/branding/app_icon_32.png +0 -0
  76. package/flutter_app/assets/branding/app_icon_512.png +0 -0
  77. package/flutter_app/assets/branding/app_icon_64.png +0 -0
  78. package/flutter_app/assets/branding/onboarding_intro.mp4 +0 -0
  79. package/flutter_app/assets/branding/tray_icon_template.png +0 -0
  80. package/flutter_app/lib/features/location/location_service.dart +117 -0
  81. package/flutter_app/lib/features/notifications/notification_interceptor.dart +73 -0
  82. package/flutter_app/lib/features/onboarding/onboarding_chrome.dart +571 -0
  83. package/flutter_app/lib/features/onboarding/onboarding_messaging_step.dart +275 -0
  84. package/flutter_app/lib/features/onboarding/onboarding_model_step.dart +366 -0
  85. package/flutter_app/lib/features/onboarding/onboarding_shell.dart +57 -0
  86. package/flutter_app/lib/features/onboarding/onboarding_video_step.dart +218 -0
  87. package/flutter_app/lib/features/onboarding/onboarding_welcome_step.dart +46 -0
  88. package/flutter_app/lib/main.dart +107 -0
  89. package/flutter_app/lib/main_account_settings.dart +1340 -0
  90. package/flutter_app/lib/main_admin.dart +900 -0
  91. package/flutter_app/lib/main_app_shell.dart +2162 -0
  92. package/flutter_app/lib/main_chat.dart +3902 -0
  93. package/flutter_app/lib/main_controller.dart +7229 -0
  94. package/flutter_app/lib/main_devices.dart +1738 -0
  95. package/flutter_app/lib/main_integrations.dart +917 -0
  96. package/flutter_app/lib/main_launcher.dart +959 -0
  97. package/flutter_app/lib/main_launcher_entry.dart +5 -0
  98. package/flutter_app/lib/main_models.dart +3807 -0
  99. package/flutter_app/lib/main_navigation.dart +206 -0
  100. package/flutter_app/lib/main_operations.dart +5249 -0
  101. package/flutter_app/lib/main_recordings.dart +920 -0
  102. package/flutter_app/lib/main_runtime.dart +869 -0
  103. package/flutter_app/lib/main_settings.dart +2288 -0
  104. package/flutter_app/lib/main_shared.dart +3393 -0
  105. package/flutter_app/lib/main_spacing.dart +33 -0
  106. package/flutter_app/lib/main_theme.dart +276 -0
  107. package/flutter_app/lib/main_unified.dart +388 -0
  108. package/flutter_app/lib/main_voice_assistant.dart +957 -0
  109. package/flutter_app/lib/src/analytics_service.dart +294 -0
  110. package/flutter_app/lib/src/android_apk_drop_zone.dart +32 -0
  111. package/flutter_app/lib/src/android_apk_drop_zone_stub.dart +16 -0
  112. package/flutter_app/lib/src/android_apk_drop_zone_web.dart +348 -0
  113. package/flutter_app/lib/src/android_app_installer.dart +22 -0
  114. package/flutter_app/lib/src/android_app_installer_io.dart +122 -0
  115. package/flutter_app/lib/src/android_app_installer_stub.dart +21 -0
  116. package/flutter_app/lib/src/android_auto_bridge.dart +59 -0
  117. package/flutter_app/lib/src/android_launcher_bridge.dart +239 -0
  118. package/flutter_app/lib/src/app_launch_bridge.dart +58 -0
  119. package/flutter_app/lib/src/app_release_updater.dart +511 -0
  120. package/flutter_app/lib/src/backend_client.dart +1870 -0
  121. package/flutter_app/lib/src/desktop_companion.dart +2 -0
  122. package/flutter_app/lib/src/desktop_companion_actions.dart +586 -0
  123. package/flutter_app/lib/src/desktop_companion_io.dart +538 -0
  124. package/flutter_app/lib/src/desktop_companion_stub.dart +59 -0
  125. package/flutter_app/lib/src/desktop_native_bridge.dart +91 -0
  126. package/flutter_app/lib/src/desktop_screen_capture.dart +21 -0
  127. package/flutter_app/lib/src/desktop_screen_capture_io.dart +142 -0
  128. package/flutter_app/lib/src/desktop_screen_capture_stub.dart +12 -0
  129. package/flutter_app/lib/src/diagnostics_logger.dart +119 -0
  130. package/flutter_app/lib/src/health_bridge.dart +136 -0
  131. package/flutter_app/lib/src/live_voice_capture.dart +85 -0
  132. package/flutter_app/lib/src/messaging_access_summary.dart +46 -0
  133. package/flutter_app/lib/src/network/app_http_client.dart +53 -0
  134. package/flutter_app/lib/src/network/app_http_client_factory.dart +6 -0
  135. package/flutter_app/lib/src/network/app_http_client_io.dart +138 -0
  136. package/flutter_app/lib/src/network/app_http_client_stub.dart +3 -0
  137. package/flutter_app/lib/src/network/app_http_client_web.dart +94 -0
  138. package/flutter_app/lib/src/oauth_launcher.dart +33 -0
  139. package/flutter_app/lib/src/oauth_launcher_io.dart +95 -0
  140. package/flutter_app/lib/src/oauth_launcher_stub.dart +33 -0
  141. package/flutter_app/lib/src/oauth_launcher_web.dart +107 -0
  142. package/flutter_app/lib/src/recording_bridge.dart +232 -0
  143. package/flutter_app/lib/src/recording_bridge_io.dart +1019 -0
  144. package/flutter_app/lib/src/recording_bridge_stub.dart +120 -0
  145. package/flutter_app/lib/src/recording_bridge_web.dart +689 -0
  146. package/flutter_app/lib/src/recording_payloads.dart +86 -0
  147. package/flutter_app/lib/src/theme/palette.dart +81 -0
  148. package/flutter_app/lib/src/web_app_update_monitor.dart +17 -0
  149. package/flutter_app/lib/src/web_app_update_monitor_stub.dart +24 -0
  150. package/flutter_app/lib/src/web_app_update_monitor_web.dart +123 -0
  151. package/flutter_app/lib/src/widget_bridge.dart +49 -0
  152. package/flutter_app/linux/CMakeLists.txt +128 -0
  153. package/flutter_app/linux/flutter/CMakeLists.txt +88 -0
  154. package/flutter_app/linux/flutter/generated_plugin_registrant.cc +47 -0
  155. package/flutter_app/linux/flutter/generated_plugin_registrant.h +15 -0
  156. package/flutter_app/linux/flutter/generated_plugins.cmake +32 -0
  157. package/flutter_app/linux/runner/CMakeLists.txt +26 -0
  158. package/flutter_app/linux/runner/main.cc +6 -0
  159. package/flutter_app/linux/runner/my_application.cc +144 -0
  160. package/flutter_app/linux/runner/my_application.h +18 -0
  161. package/flutter_app/linux/runner/resources/app_icon.png +0 -0
  162. package/flutter_app/macos/Flutter/Flutter-Debug.xcconfig +2 -0
  163. package/flutter_app/macos/Flutter/Flutter-Release.xcconfig +2 -0
  164. package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +48 -0
  165. package/flutter_app/macos/Podfile +42 -0
  166. package/flutter_app/macos/Podfile.lock +87 -0
  167. package/flutter_app/macos/Runner/AppDelegate.swift +576 -0
  168. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +68 -0
  169. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
  170. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
  171. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
  172. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
  173. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
  174. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
  175. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
  176. package/flutter_app/macos/Runner/Base.lproj/MainMenu.xib +342 -0
  177. package/flutter_app/macos/Runner/Configs/AppInfo.xcconfig +14 -0
  178. package/flutter_app/macos/Runner/Configs/Debug.xcconfig +2 -0
  179. package/flutter_app/macos/Runner/Configs/Release.xcconfig +2 -0
  180. package/flutter_app/macos/Runner/Configs/Warnings.xcconfig +13 -0
  181. package/flutter_app/macos/Runner/DebugProfile.entitlements +16 -0
  182. package/flutter_app/macos/Runner/Info.plist +36 -0
  183. package/flutter_app/macos/Runner/MainFlutterWindow.swift +19 -0
  184. package/flutter_app/macos/Runner/Release.entitlements +12 -0
  185. package/flutter_app/macos/Runner.xcodeproj/project.pbxproj +801 -0
  186. package/flutter_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  187. package/flutter_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +99 -0
  188. package/flutter_app/macos/Runner.xcworkspace/contents.xcworkspacedata +10 -0
  189. package/flutter_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  190. package/flutter_app/macos/RunnerTests/RunnerTests.swift +12 -0
  191. package/flutter_app/patch_strings.py +12 -0
  192. package/flutter_app/pubspec.lock +1256 -0
  193. package/flutter_app/pubspec.yaml +59 -0
  194. package/flutter_app/third_party/desktop_audio_capture/LICENSE +21 -0
  195. package/flutter_app/third_party/desktop_audio_capture/README.md +262 -0
  196. package/flutter_app/third_party/desktop_audio_capture/lib/audio_capture.dart +65 -0
  197. package/flutter_app/third_party/desktop_audio_capture/lib/config/mic_audio_config.dart +153 -0
  198. package/flutter_app/third_party/desktop_audio_capture/lib/config/system_adudio_config.dart +110 -0
  199. package/flutter_app/third_party/desktop_audio_capture/lib/mic/mic_audio_capture.dart +461 -0
  200. package/flutter_app/third_party/desktop_audio_capture/lib/model/audio_status.dart +91 -0
  201. package/flutter_app/third_party/desktop_audio_capture/lib/model/decibel_data.dart +106 -0
  202. package/flutter_app/third_party/desktop_audio_capture/lib/model/input_device_type.dart +219 -0
  203. package/flutter_app/third_party/desktop_audio_capture/lib/system/system_audio_capture.dart +336 -0
  204. package/flutter_app/third_party/desktop_audio_capture/linux/CMakeLists.txt +101 -0
  205. package/flutter_app/third_party/desktop_audio_capture/linux/audio_capture_plugin.cc +692 -0
  206. package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/audio_capture_plugin.h +35 -0
  207. package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/mic_capture_plugin.h +36 -0
  208. package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/audio_capture_plugin.h +32 -0
  209. package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/mic_capture_plugin.h +32 -0
  210. package/flutter_app/third_party/desktop_audio_capture/linux/mic_capture_plugin.cc +878 -0
  211. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/AudioCapturePlugin.swift +27 -0
  212. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/MicCapturePlugin.swift +1172 -0
  213. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/SystemCapturePlugin.swift +655 -0
  214. package/flutter_app/third_party/desktop_audio_capture/macos/Resources/PrivacyInfo.xcprivacy +12 -0
  215. package/flutter_app/third_party/desktop_audio_capture/macos/desktop_audio_capture.podspec +30 -0
  216. package/flutter_app/third_party/desktop_audio_capture/pubspec.yaml +87 -0
  217. package/flutter_app/third_party/desktop_audio_capture/windows/CMakeLists.txt +105 -0
  218. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.cpp +80 -0
  219. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.h +31 -0
  220. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin_c_api.cpp +12 -0
  221. package/flutter_app/third_party/desktop_audio_capture/windows/include/audio_capture/audio_capture_plugin_c_api.h +23 -0
  222. package/flutter_app/third_party/desktop_audio_capture/windows/include/desktop_audio_capture/audio_capture_plugin.h +25 -0
  223. package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.cpp +1117 -0
  224. package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.h +115 -0
  225. package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.cpp +777 -0
  226. package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.h +87 -0
  227. package/flutter_app/third_party/flutter_secure_storage_linux/linux/CMakeLists.txt +30 -0
  228. package/flutter_app/third_party/flutter_secure_storage_linux/linux/flutter_secure_storage_linux_plugin.cc +215 -0
  229. package/flutter_app/third_party/flutter_secure_storage_linux/linux/include/flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h +27 -0
  230. package/flutter_app/third_party/flutter_secure_storage_linux/pubspec.yaml +20 -0
  231. package/flutter_app/tool/generate_desktop_branding.py +219 -0
  232. package/flutter_app/web/favicon.png +0 -0
  233. package/flutter_app/web/favicon.svg +12 -0
  234. package/flutter_app/web/icons/Icon-192.png +0 -0
  235. package/flutter_app/web/icons/Icon-512.png +0 -0
  236. package/flutter_app/web/icons/Icon-maskable-192.png +0 -0
  237. package/flutter_app/web/icons/Icon-maskable-512.png +0 -0
  238. package/flutter_app/web/index.html +39 -0
  239. package/flutter_app/web/manifest.json +35 -0
  240. package/flutter_app/windows/CMakeLists.txt +108 -0
  241. package/flutter_app/windows/flutter/CMakeLists.txt +109 -0
  242. package/flutter_app/windows/flutter/generated_plugin_registrant.cc +50 -0
  243. package/flutter_app/windows/flutter/generated_plugin_registrant.h +15 -0
  244. package/flutter_app/windows/flutter/generated_plugins.cmake +36 -0
  245. package/flutter_app/windows/runner/CMakeLists.txt +41 -0
  246. package/flutter_app/windows/runner/Runner.rc +121 -0
  247. package/flutter_app/windows/runner/flutter_window.cpp +533 -0
  248. package/flutter_app/windows/runner/flutter_window.h +37 -0
  249. package/flutter_app/windows/runner/main.cpp +53 -0
  250. package/flutter_app/windows/runner/resource.h +16 -0
  251. package/flutter_app/windows/runner/resources/app_icon.ico +0 -0
  252. package/flutter_app/windows/runner/runner.exe.manifest +14 -0
  253. package/flutter_app/windows/runner/utils.cpp +65 -0
  254. package/flutter_app/windows/runner/utils.h +19 -0
  255. package/flutter_app/windows/runner/win32_window.cpp +299 -0
  256. package/flutter_app/windows/runner/win32_window.h +102 -0
  257. package/lib/install_helpers.js +31 -0
  258. package/lib/manager.js +579 -122
  259. package/package.json +10 -3
  260. package/runtime/paths.js +137 -0
  261. package/server/config/analytics.js +30 -0
  262. package/server/db/database.js +296 -22
  263. package/server/guest-agent.android.package.json +13 -0
  264. package/server/guest-agent.browser.package.json +14 -0
  265. package/server/guest_agent.js +98 -52
  266. package/server/http/middleware.js +5 -2
  267. package/server/http/routes.js +3 -0
  268. package/server/http/static.js +41 -1
  269. package/server/index.js +15 -16
  270. package/server/public/.last_build_id +1 -1
  271. package/server/public/assets/AssetManifest.bin +1 -1
  272. package/server/public/assets/AssetManifest.bin.json +1 -1
  273. package/server/public/assets/AssetManifest.json +1 -1
  274. package/server/public/assets/NOTICES +644 -155
  275. package/server/public/assets/assets/branding/onboarding_intro.mp4 +0 -0
  276. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  277. package/server/public/assets/packages/mixpanel_flutter/assets/mixpanel.js +3 -0
  278. package/server/public/canvaskit/wimp.wasm +0 -0
  279. package/server/public/flutter_bootstrap.js +2 -2
  280. package/server/public/main.dart.js +96067 -88139
  281. package/server/routes/account.js +26 -1
  282. package/server/routes/agents.js +6 -2
  283. package/server/routes/android.js +22 -6
  284. package/server/routes/auth.js +25 -5
  285. package/server/routes/browser.js +47 -13
  286. package/server/routes/integrations.js +22 -1
  287. package/server/routes/memory.js +168 -6
  288. package/server/routes/runtime.js +14 -0
  289. package/server/routes/settings.js +102 -24
  290. package/server/routes/skills.js +12 -0
  291. package/server/routes/social_video.js +66 -0
  292. package/server/routes/tasks.js +22 -2
  293. package/server/routes/triggers.js +44 -35
  294. package/server/routes/wearable.js +67 -0
  295. package/server/routes/widgets.js +4 -4
  296. package/server/services/ai/capabilityHealth.js +23 -51
  297. package/server/services/ai/compaction.js +1 -1
  298. package/server/services/ai/deliverables/artifact_helpers.js +190 -0
  299. package/server/services/ai/deliverables/contracts.js +113 -0
  300. package/server/services/ai/deliverables/index.js +20 -0
  301. package/server/services/ai/deliverables/selector.js +94 -0
  302. package/server/services/ai/deliverables/validator.js +63 -0
  303. package/server/services/ai/deliverables/workflows.js +207 -0
  304. package/server/services/ai/engine.js +363 -27
  305. package/server/services/ai/hooks.js +127 -0
  306. package/server/services/ai/integrated_tools/index.js +98 -0
  307. package/server/services/ai/integrated_tools/remotion.js +373 -0
  308. package/server/services/ai/integrated_tools/shared.js +204 -0
  309. package/server/services/ai/integrated_tools/slidev.js +207 -0
  310. package/server/services/ai/loopPolicy.js +146 -0
  311. package/server/services/ai/models.js +31 -4
  312. package/server/services/ai/providers/claudeCode.js +49 -0
  313. package/server/services/ai/providers/openai.js +2 -1
  314. package/server/services/ai/providers/openaiCodex.js +422 -15
  315. package/server/services/ai/recordingInsights.js +11 -13
  316. package/server/services/ai/runEvents.js +100 -0
  317. package/server/services/ai/settings.js +12 -2
  318. package/server/services/ai/systemPrompt.js +15 -1
  319. package/server/services/ai/taskAnalysis.js +2 -0
  320. package/server/services/ai/toolResult.js +20 -0
  321. package/server/services/ai/toolRunner.js +46 -6
  322. package/server/services/ai/toolSelector.js +54 -1
  323. package/server/services/ai/tools.js +210 -116
  324. package/server/services/android/android_bootstrap_worker.js +63 -0
  325. package/server/services/android/controller.js +442 -1760
  326. package/server/services/bootstrap_helpers.js +0 -34
  327. package/server/services/browser/controller.js +279 -49
  328. package/server/services/cli/executor.js +40 -1
  329. package/server/services/desktop/screenRecorder.js +65 -9
  330. package/server/services/integrations/figma/provider.js +1 -0
  331. package/server/services/integrations/github/provider.js +3 -3
  332. package/server/services/integrations/google/provider.js +1 -0
  333. package/server/services/integrations/manager.js +25 -5
  334. package/server/services/integrations/microsoft/provider.js +1 -0
  335. package/server/services/integrations/oauth_provider.js +15 -5
  336. package/server/services/integrations/provider_config_store.js +22 -13
  337. package/server/services/integrations/registry.js +2 -4
  338. package/server/services/integrations/spotify/provider.js +1 -0
  339. package/server/services/integrations/trello/provider.js +842 -0
  340. package/server/services/manager.js +88 -155
  341. package/server/services/mcp/client.js +120 -23
  342. package/server/services/memory/llm_transfer.js +218 -0
  343. package/server/services/memory/manager.js +260 -33
  344. package/server/services/messaging/access_policy.js +10 -0
  345. package/server/services/messaging/automation.js +1 -1
  346. package/server/services/messaging/manager.js +64 -1
  347. package/server/services/messaging/meshtastic.js +277 -0
  348. package/server/services/messaging/meshtastic_env.js +40 -0
  349. package/server/services/messaging/meshtastic_protocol.js +530 -0
  350. package/server/services/messaging/meshtastic_tcp_transport.js +25 -0
  351. package/server/services/runtime/backends/local-vm.js +221 -158
  352. package/server/services/runtime/docker-vm-manager.js +392 -0
  353. package/server/services/runtime/guest_bootstrap.js +654 -0
  354. package/server/services/runtime/manager.js +85 -29
  355. package/server/services/runtime/settings.js +22 -31
  356. package/server/services/runtime/validation.js +16 -31
  357. package/server/services/social_video/adapters/base.js +26 -0
  358. package/server/services/social_video/adapters/index.js +27 -0
  359. package/server/services/social_video/adapters/instagram.js +17 -0
  360. package/server/services/social_video/adapters/tiktok.js +17 -0
  361. package/server/services/social_video/adapters/x.js +17 -0
  362. package/server/services/social_video/adapters/youtube.js +17 -0
  363. package/server/services/social_video/captions.js +187 -0
  364. package/server/services/social_video/frame.js +42 -0
  365. package/server/services/social_video/index.js +7 -0
  366. package/server/services/social_video/metadata.js +63 -0
  367. package/server/services/social_video/result.js +63 -0
  368. package/server/services/social_video/service.js +720 -0
  369. package/server/services/social_video/url.js +83 -0
  370. package/server/services/tasks/integration_runtime.js +1 -1
  371. package/server/services/tasks/runtime.js +1 -1
  372. package/server/services/tasks/task_repository.js +3 -6
  373. package/server/services/voice/openaiSpeech.js +6 -1
  374. package/server/services/voice/providers.js +142 -14
  375. package/server/services/voice/runtime.js +8 -8
  376. package/server/services/voice/runtimeManager.js +156 -19
  377. package/server/services/voice/turnRunner.js +29 -9
  378. package/server/services/wearable/firmware_manifest.js +353 -0
  379. package/server/services/wearable/gateway.js +350 -0
  380. package/server/services/wearable/protocol.js +45 -0
  381. package/server/services/wearable/service.js +240 -0
  382. package/server/services/websocket.js +3 -1
  383. package/server/services/widgets/focus_widget.js +137 -0
  384. package/server/services/widgets/service.js +133 -2
  385. package/server/services/workspace/manager.js +301 -0
  386. package/server/utils/deployment.js +16 -9
  387. package/server/services/integrations/home_assistant/provider.js +0 -630
  388. package/server/services/messaging/access_policy.test.js +0 -228
  389. package/server/services/runtime/backends/host.js +0 -37
  390. package/server/services/runtime/qemu.js +0 -391
@@ -57,7 +57,7 @@ function accountPayload(req) {
57
57
  recordCurrentSession(req, req.session.userId);
58
58
  const user = db
59
59
  .prepare(
60
- `SELECT id, username, email, email_verified_at, created_at, last_login, password_login_enabled
60
+ `SELECT id, username, display_name, email, email_verified_at, created_at, last_login, password_login_enabled
61
61
  FROM users
62
62
  WHERE id = ?`,
63
63
  )
@@ -68,6 +68,7 @@ function accountPayload(req) {
68
68
  ? {
69
69
  id: user.id,
70
70
  username: user.username,
71
+ display_name: user.display_name,
71
72
  email: user.email,
72
73
  email_verified_at: user.email_verified_at,
73
74
  created_at: user.created_at,
@@ -90,6 +91,17 @@ function sendRouteError(res, err) {
90
91
  res.status(statusCode).json({ error: sanitizeError(err) });
91
92
  }
92
93
 
94
+ function normalizeDisplayName(value) {
95
+ const name = String(value || '').trim();
96
+ if (!name) return null;
97
+ if (name.length > 64) {
98
+ const error = new Error('Display name must be 64 characters or fewer.');
99
+ error.statusCode = 400;
100
+ throw error;
101
+ }
102
+ return name;
103
+ }
104
+
93
105
  router.get('/', (req, res) => {
94
106
  try {
95
107
  res.json(accountPayload(req));
@@ -98,6 +110,19 @@ router.get('/', (req, res) => {
98
110
  }
99
111
  });
100
112
 
113
+ router.put('/display-name', accountLimiter, (req, res) => {
114
+ try {
115
+ const displayName = normalizeDisplayName(req.body?.displayName);
116
+ db.prepare('UPDATE users SET display_name = ? WHERE id = ?').run(
117
+ displayName,
118
+ req.session.userId,
119
+ );
120
+ res.json(accountPayload(req));
121
+ } catch (err) {
122
+ sendRouteError(res, err);
123
+ }
124
+ });
125
+
101
126
  router.put('/email', accountLimiter, async (req, res) => {
102
127
  try {
103
128
  const email = requireValidEmail(req.body?.email);
@@ -4,6 +4,7 @@ const db = require('../db/database');
4
4
  const { requireAuth } = require('../middleware/auth');
5
5
  const { sanitizeError } = require('../utils/security');
6
6
  const { getAgentIdFromRequest, resolveAgentId } = require('../services/agents/manager');
7
+ const { listRunEvents } = require('../services/ai/runEvents');
7
8
  const { isInterimAssistantMetadata } = require('../services/ai/interim');
8
9
  const { buildAgentRunContext } = require('./_helpers/agentRunContext');
9
10
 
@@ -176,12 +177,14 @@ router.get('/:id/steps', (req, res) => {
176
177
  || run.final_response
177
178
  || null;
178
179
 
179
- res.json({ run, steps, response });
180
+ res.json({ run, steps, events: listRunEvents(run.id), response });
180
181
  });
181
182
 
182
183
  // Abort a run
183
184
  router.post('/:id/abort', (req, res) => {
184
185
  try {
186
+ const run = db.prepare('SELECT id FROM agent_runs WHERE id = ? AND user_id = ?').get(req.params.id, req.session.userId);
187
+ if (!run) return res.status(404).json({ error: 'Run not found' });
185
188
  const engine = req.app.locals.agentEngine;
186
189
  engine.abort(req.params.id);
187
190
  res.json({ success: true });
@@ -206,7 +209,8 @@ router.post('/multi-step', async (req, res) => {
206
209
  try {
207
210
  const { task, steps, options } = req.body;
208
211
  const agentId = resolveAgentId(req.session.userId, getAgentIdFromRequest(req));
209
- if (!task) return res.status(400).json({ error: 'Task is required' });
212
+ if (!task || typeof task !== 'string') return res.status(400).json({ error: 'Task must be a non-empty string' });
213
+ if (task.length > 50000) return res.status(400).json({ error: 'Task exceeds maximum length of 50,000 characters' });
210
214
 
211
215
  const multiStep = req.app.locals.multiStep;
212
216
  if (!multiStep || typeof multiStep.planAndExecute !== 'function') {
@@ -6,6 +6,7 @@ const router = express.Router();
6
6
  const { DATA_DIR } = require('../../runtime/paths');
7
7
  const { requireAuth } = require('../middleware/auth');
8
8
  const { sanitizeError } = require('../utils/security');
9
+ const { getRuntimeValidation } = require('../services/runtime/validation');
9
10
 
10
11
  router.use(requireAuth);
11
12
 
@@ -47,11 +48,19 @@ async function getAndroidController(req) {
47
48
  if (runtimeManager && typeof runtimeManager.getAndroidProviderForUser === 'function') {
48
49
  return runtimeManager.getAndroidProviderForUser(req.session?.userId);
49
50
  }
50
- const resolver = req.app?.locals?.getAndroidControllerForUser;
51
- if (typeof resolver === 'function') {
52
- return resolver(req.session?.userId);
53
- }
54
- return req.app.locals.androidController;
51
+ throw new Error('Android controller is unavailable.');
52
+ }
53
+
54
+ function getAndroidStatusSnapshot(req) {
55
+ const runtimeValidation = getRuntimeValidation(req.app?.locals?.runtimeManager);
56
+ const ready = Boolean(runtimeValidation?.ready);
57
+ return {
58
+ bootstrapped: false,
59
+ canBootstrap: ready,
60
+ devices: [],
61
+ lastStartError: ready ? null : (runtimeValidation?.issues?.[0] || 'VM runtime is not ready.'),
62
+ runtimeReady: ready,
63
+ };
55
64
  }
56
65
 
57
66
  function handleAndroidAction(action) {
@@ -66,7 +75,14 @@ function handleAndroidAction(action) {
66
75
  };
67
76
  }
68
77
 
69
- router.get('/status', handleAndroidAction((controller) => controller.getStatus()));
78
+ router.get('/status', async (req, res) => {
79
+ try {
80
+ const controller = await getAndroidController(req);
81
+ res.json(await controller.getStatus().catch(() => getAndroidStatusSnapshot(req)));
82
+ } catch (err) {
83
+ res.status(500).json({ error: sanitizeError(err) });
84
+ }
85
+ });
70
86
 
71
87
  router.post('/start', handleAndroidAction((controller, req) =>
72
88
  controller.requestStartEmulator(req.body || {})));
@@ -70,11 +70,14 @@ function toUserPayload(user) {
70
70
  return {
71
71
  id: user.id,
72
72
  username: user.username,
73
+ displayName: user.display_name || null,
74
+ display_name: user.display_name || null,
73
75
  email: user.email || null,
74
76
  emailVerifiedAt: user.email_verified_at || null,
75
77
  createdAt: user.created_at || null,
76
78
  lastLogin: user.last_login || null,
77
79
  hasPassword: Number(user.password_login_enabled || 0) === 1,
80
+ hasCompletedOnboarding: Number(user.has_completed_onboarding || 0) === 1,
78
81
  };
79
82
  }
80
83
 
@@ -119,7 +122,7 @@ function readAuthenticatedUser(req) {
119
122
  return null;
120
123
  }
121
124
  const user = db.prepare(
122
- `SELECT id, username, email, email_verified_at, password_login_enabled, created_at, last_login
125
+ `SELECT id, username, display_name, email, email_verified_at, password_login_enabled, created_at, last_login, has_completed_onboarding
123
126
  FROM users
124
127
  WHERE id = ?`,
125
128
  ).get(req.session.userId);
@@ -322,14 +325,16 @@ router.get('/api/auth/providers/complete', async (req, res) => {
322
325
  }
323
326
 
324
327
  const user = db.prepare(
325
- `SELECT id, username, email, email_verified_at, password_login_enabled, created_at, last_login
328
+ `SELECT id, username, display_name, email, email_verified_at, password_login_enabled, created_at, last_login, has_completed_onboarding
326
329
  FROM users
327
330
  WHERE id = ?`,
328
331
  ).get(completion.result.userId);
329
332
  if (!user) {
330
333
  return res.status(404).json({ error: 'User not found' });
331
334
  }
332
- if (getTwoFactorStatus(user.id).enabled) {
335
+ const providerKey = String(completion.provider || '').trim();
336
+ const twoFactor = getTwoFactorStatus(user.id);
337
+ if (twoFactor.enabled) {
333
338
  return establishPendingTwoFactorSession(req, res, user);
334
339
  }
335
340
  updateLastLogin(user.id);
@@ -412,6 +417,7 @@ router.post('/api/auth/register', authLimiter, async (req, res) => {
412
417
  password_login_enabled: 1,
413
418
  created_at: new Date().toISOString(),
414
419
  last_login: null,
420
+ has_completed_onboarding: 0,
415
421
  });
416
422
  } catch (err) {
417
423
  if (err?.statusCode) {
@@ -443,7 +449,7 @@ router.post('/api/auth/login', authLimiter, async (req, res) => {
443
449
  }
444
450
 
445
451
  const user = db.prepare(
446
- `SELECT id, username, email, email_verified_at, password, password_login_enabled, created_at, last_login
452
+ `SELECT id, username, display_name, email, email_verified_at, password, password_login_enabled, created_at, last_login, has_completed_onboarding
447
453
  FROM users
448
454
  WHERE username = ?`,
449
455
  ).get(username);
@@ -494,7 +500,7 @@ router.post('/api/auth/login/2fa', authLimiter, async (req, res) => {
494
500
  }
495
501
 
496
502
  const user = db.prepare(
497
- `SELECT id, username, email, email_verified_at, password_login_enabled, created_at, last_login
503
+ `SELECT id, username, display_name, email, email_verified_at, password_login_enabled, created_at, last_login, has_completed_onboarding
498
504
  FROM users
499
505
  WHERE id = ?`,
500
506
  ).get(pendingUserId);
@@ -728,4 +734,18 @@ router.get('/api/auth/me', (req, res) => {
728
734
  res.json({ user: toUserPayload(user) });
729
735
  });
730
736
 
737
+ router.post('/api/auth/onboarding/complete', (req, res) => {
738
+ const user = readAuthenticatedUser(req);
739
+ if (!user) {
740
+ return res.status(401).json({ error: 'Not authenticated' });
741
+ }
742
+ try {
743
+ db.prepare('UPDATE users SET has_completed_onboarding = 1 WHERE id = ?').run(user.id);
744
+ res.json({ success: true });
745
+ } catch (err) {
746
+ console.error('Onboarding complete error:', err);
747
+ res.status(500).json({ error: 'Failed to update onboarding status' });
748
+ }
749
+ });
750
+
731
751
  module.exports = router;
@@ -2,36 +2,53 @@ const express = require('express');
2
2
  const router = express.Router();
3
3
  const { requireAuth } = require('../middleware/auth');
4
4
  const { sanitizeError } = require('../utils/security');
5
+ const { getRuntimeValidation } = require('../services/runtime/validation');
5
6
 
6
7
  router.use(requireAuth);
7
8
 
8
9
  async function getBrowserController(req) {
9
10
  const runtimeManager = req.app?.locals?.runtimeManager;
10
11
  if (runtimeManager && typeof runtimeManager.getBrowserProviderForUser === 'function') {
12
+ const start = Date.now();
11
13
  const runtimeController = await runtimeManager.getBrowserProviderForUser(req.session?.userId);
14
+ const duration = Date.now() - start;
15
+ if (duration > 1000) {
16
+ console.log(`[HTTP] Browser controller acquired for user ${req.session?.userId} in ${duration}ms`);
17
+ }
12
18
  if (runtimeController) {
13
19
  return runtimeController;
14
20
  }
15
21
  }
16
- const resolver = req.app?.locals?.getBrowserControllerForUser;
17
- const userId = req.session?.userId;
18
- let controller;
19
- if (typeof resolver === "function") {
20
- controller = await resolver(userId);
21
- } else {
22
- controller = req.app?.locals?.browserController;
23
- }
24
-
25
- if (!controller) {
26
- throw new Error(`getBrowserController: missing browser controller for userId=${userId ?? 'unknown'}`);
27
- }
22
+ throw new Error('Browser controller is unavailable. VM runtime is required.');
23
+ }
28
24
 
29
- return controller;
25
+ function getBrowserStatusSnapshot(req) {
26
+ const runtimeValidation = getRuntimeValidation(req.app?.locals?.runtimeManager);
27
+ const ready = Boolean(runtimeValidation?.ready);
28
+ return {
29
+ launched: false,
30
+ pages: 0,
31
+ headless: true,
32
+ pageInfo: null,
33
+ bootstrapped: false,
34
+ canBootstrap: ready,
35
+ runtimeReady: ready,
36
+ lastStartError: ready ? null : (runtimeValidation?.issues?.[0] || 'VM runtime is not ready.'),
37
+ };
30
38
  }
31
39
 
32
40
  // Get browser status
33
41
  router.get('/status', async (req, res) => {
34
42
  try {
43
+ const runtimeManager = req.app?.locals?.runtimeManager;
44
+ if (!runtimeManager?.hasVmForUser?.(req.session?.userId, 'browser')) {
45
+ res.json(getBrowserStatusSnapshot(req));
46
+ return;
47
+ }
48
+ if (!await runtimeManager?.isGuestAgentReadyForUser?.(req.session?.userId, 6000, 'browser')) {
49
+ res.json(getBrowserStatusSnapshot(req));
50
+ return;
51
+ }
35
52
  const bc = await getBrowserController(req);
36
53
  const pageInfo = await bc.getPageInfo();
37
54
  res.json({
@@ -39,12 +56,29 @@ router.get('/status', async (req, res) => {
39
56
  pages: await Promise.resolve(bc.getPageCount()),
40
57
  headless: bc.headless,
41
58
  pageInfo,
59
+ bootstrapped: true,
60
+ canBootstrap: true,
61
+ runtimeReady: true,
62
+ lastStartError: null,
42
63
  });
43
64
  } catch (err) {
44
65
  res.status(500).json({ error: sanitizeError(err) });
45
66
  }
46
67
  });
47
68
 
69
+ router.get('/cookies', async (req, res) => {
70
+ try {
71
+ const bc = await getBrowserController(req);
72
+ if (typeof bc.getCookies !== 'function') {
73
+ return res.status(501).json({ error: 'Cookie export is unavailable for this browser provider.' });
74
+ }
75
+ const result = await bc.getCookies();
76
+ res.json(result);
77
+ } catch (err) {
78
+ res.status(500).json({ error: sanitizeError(err) });
79
+ }
80
+ });
81
+
48
82
  // Launch browser
49
83
  router.post('/launch', async (req, res) => {
50
84
  try {
@@ -1,4 +1,5 @@
1
1
  const express = require('express');
2
+ const QRCode = require('qrcode');
2
3
  const router = express.Router();
3
4
  const { requireAuth } = require('../middleware/auth');
4
5
  const { sanitizeError } = require('../utils/security');
@@ -143,6 +144,26 @@ router.get('/oauth/callback', async (req, res) => {
143
144
 
144
145
  router.use(requireAuth);
145
146
 
147
+ router.get('/qr-image', async (req, res) => {
148
+ try {
149
+ const data = String(req.query.data || '').trim();
150
+ if (!data) {
151
+ return res.status(400).send('Missing QR data.');
152
+ }
153
+ const svg = await QRCode.toString(data, {
154
+ errorCorrectionLevel: 'M',
155
+ margin: 1,
156
+ type: 'svg',
157
+ width: 320,
158
+ });
159
+ res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
160
+ res.setHeader('Cache-Control', 'no-store, max-age=0');
161
+ return res.send(svg);
162
+ } catch (err) {
163
+ return res.status(400).send(escapeHtml(sanitizeError(err)));
164
+ }
165
+ });
166
+
146
167
  router.get('/:provider/connect/:sessionId', (req, res) => {
147
168
  try {
148
169
  const manager = getIntegrationManager(req);
@@ -211,7 +232,7 @@ router.get('/:provider/connect/:sessionId', (req, res) => {
211
232
  const status = String(data.status || 'connecting');
212
233
  if (status === 'awaiting_qr' && data.qr) {
213
234
  statusEl.textContent = 'Scan this QR code to continue linking.';
214
- qrEl.src = 'https://api.qrserver.com/v1/create-qr-code/?data=' + encodeURIComponent(data.qr) + '&size=320x320';
235
+ qrEl.src = '/api/integrations/qr-image?data=' + encodeURIComponent(data.qr);
215
236
  qrEl.style.display = 'block';
216
237
  } else if (status === 'connected') {
217
238
  qrEl.style.display = 'none';
@@ -4,6 +4,11 @@ const rateLimit = require('express-rate-limit');
4
4
  const { requireAuth } = require('../middleware/auth');
5
5
  const { sanitizeError } = require('../utils/security');
6
6
  const { getAgentIdFromRequest, resolveAgentId } = require('../services/agents/manager');
7
+ const {
8
+ buildLlmTransferPrompt,
9
+ parseLlmTransferText,
10
+ importanceForCategory,
11
+ } = require('../services/memory/llm_transfer');
7
12
 
8
13
  router.use(requireAuth);
9
14
 
@@ -15,6 +20,16 @@ const apiKeyMutationLimiter = rateLimit({
15
20
  legacyHeaders: false,
16
21
  });
17
22
 
23
+ const transferImportLimiter = rateLimit({
24
+ windowMs: 10 * 60 * 1000,
25
+ max: 20,
26
+ message: { error: 'Too many memory imports, try again later' },
27
+ standardHeaders: true,
28
+ legacyHeaders: false,
29
+ });
30
+
31
+ const MAX_TRANSFER_TEXT_BYTES = 60 * 1024;
32
+
18
33
  function normalizeMemoryIds(value) {
19
34
  return [...new Set(
20
35
  (Array.isArray(value) ? value : [])
@@ -33,6 +48,59 @@ function findOwnedMemoryIds(db, userId, agentId, ids) {
33
48
  ).all(userId, agentId, ...ids).map((row) => row.id);
34
49
  }
35
50
 
51
+ function parsePlainObject(input, fieldName) {
52
+ if (input == null) return null;
53
+ if (typeof input === 'string') {
54
+ try {
55
+ input = JSON.parse(input);
56
+ } catch {
57
+ throw new Error(`${fieldName} must be valid JSON.`);
58
+ }
59
+ }
60
+ if (!input || typeof input !== 'object' || Array.isArray(input)) {
61
+ throw new Error(`${fieldName} must be an object.`);
62
+ }
63
+ return input;
64
+ }
65
+
66
+ function normalizeOptionalStringField(value, fieldName, maxLength, pattern = null) {
67
+ if (value == null || value === '') return null;
68
+ if (typeof value !== 'string') {
69
+ throw new Error(`${fieldName} must be a string.`);
70
+ }
71
+ const normalized = value.trim().slice(0, maxLength);
72
+ if (!normalized) return null;
73
+ if (pattern && !pattern.test(normalized)) {
74
+ throw new Error(`${fieldName} has an invalid format.`);
75
+ }
76
+ return normalized;
77
+ }
78
+
79
+ function normalizeSourceRef(input) {
80
+ const raw = parsePlainObject(input, 'sourceRef');
81
+ if (!raw) return undefined;
82
+ return {
83
+ sourceType: normalizeOptionalStringField(raw.sourceType ?? raw.type, 'sourceRef.sourceType', 48, /^[a-z0-9_:-]+$/i),
84
+ sourceId: normalizeOptionalStringField(raw.sourceId ?? raw.id, 'sourceRef.sourceId', 128),
85
+ sourceLabel: normalizeOptionalStringField(raw.sourceLabel ?? raw.label, 'sourceRef.sourceLabel', 160),
86
+ };
87
+ }
88
+
89
+ function normalizeScope(input) {
90
+ const raw = parsePlainObject(input, 'scope');
91
+ if (!raw) return undefined;
92
+ const scopeType = normalizeOptionalStringField(raw.scopeType ?? raw.type, 'scope.scopeType', 32, /^(agent|conversation|task|channel|shared)$/i);
93
+ return {
94
+ scopeType: scopeType ? scopeType.toLowerCase() : null,
95
+ scopeId: normalizeOptionalStringField(raw.scopeId ?? raw.id, 'scope.scopeId', 128),
96
+ };
97
+ }
98
+
99
+ function normalizeMetadata(input) {
100
+ const raw = parsePlainObject(input, 'metadata');
101
+ return raw == null ? undefined : raw;
102
+ }
103
+
36
104
  // ─────────────────────────────────────────────────────────────────────────────
37
105
  // Overview (for initial page load)
38
106
  // ─────────────────────────────────────────────────────────────────────────────
@@ -46,6 +114,7 @@ router.get('/', (req, res) => {
46
114
  res.json({
47
115
  agentId,
48
116
  assistantBehaviorNotes: mm.getAssistantBehaviorNotes(userId, { agentId }),
117
+ assistantSelfState: mm.getAssistantSelfState(userId, { agentId }),
49
118
  dailyLogs: mm.listDailyLogs(7, userId),
50
119
  apiKeys: Object.keys(mm.readApiKeys(userId)),
51
120
  coreMemory
@@ -81,13 +150,31 @@ router.post('/memories', async (req, res) => {
81
150
  const mm = req.app.locals.memoryManager;
82
151
  const userId = req.session.userId;
83
152
  const agentId = resolveAgentId(userId, getAgentIdFromRequest(req));
84
- const { content, category = 'episodic', importance = 5 } = req.body;
153
+ const { content, category = 'episodic', importance = 5, sourceRef, scope, staleAfterDays, metadata } = req.body;
85
154
  if (!content || !content.trim()) return res.status(400).json({ error: 'content is required' });
86
155
  try {
87
- const id = await mm.saveMemory(userId, content, category, importance, { agentId });
156
+ let normalizedStaleAfterDays;
157
+ if (staleAfterDays != null && staleAfterDays !== '') {
158
+ normalizedStaleAfterDays = Number.parseInt(staleAfterDays, 10);
159
+ if (!Number.isInteger(normalizedStaleAfterDays) || normalizedStaleAfterDays <= 0) {
160
+ return res.status(400).json({ error: 'staleAfterDays must be a positive integer.' });
161
+ }
162
+ }
163
+
164
+ const id = await mm.saveMemory(userId, content, category, importance, {
165
+ agentId,
166
+ sourceRef: normalizeSourceRef(sourceRef),
167
+ scope: normalizeScope(scope),
168
+ staleAfterDays: normalizedStaleAfterDays,
169
+ metadata: normalizeMetadata(metadata),
170
+ });
88
171
  res.json({ success: true, id });
89
172
  } catch (err) {
90
- res.status(500).json({ error: sanitizeError(err) });
173
+ const message = sanitizeError(err);
174
+ if (/must be valid JSON|must be an object|must be a string|has an invalid format/i.test(message)) {
175
+ return res.status(400).json({ error: message });
176
+ }
177
+ res.status(500).json({ error: message });
91
178
  }
92
179
  });
93
180
 
@@ -117,7 +204,7 @@ router.delete('/memories/:id', (req, res) => {
117
204
  const agentId = resolveAgentId(req.session.userId, getAgentIdFromRequest(req));
118
205
  const existing = db.prepare('SELECT id FROM memories WHERE id = ? AND user_id = ? AND agent_id = ?').get(req.params.id, req.session.userId, agentId);
119
206
  if (!existing) return res.status(404).json({ error: 'Memory not found' });
120
- mm.deleteMemory(req.params.id);
207
+ mm.deleteMemories([req.params.id], req.session.userId);
121
208
  res.json({ success: true });
122
209
  });
123
210
 
@@ -134,7 +221,7 @@ router.post('/memories/bulk-delete', (req, res) => {
134
221
  if (!ownedIds.length) {
135
222
  return res.status(404).json({ error: 'Memory not found' });
136
223
  }
137
- const deletedCount = mm.deleteMemories(ownedIds);
224
+ const deletedCount = mm.deleteMemories(ownedIds, req.session.userId);
138
225
  res.json({ success: true, deletedCount });
139
226
  } catch (err) {
140
227
  res.status(500).json({ error: sanitizeError(err) });
@@ -155,7 +242,7 @@ router.post('/memories/bulk-archive', (req, res) => {
155
242
  if (!ownedIds.length) {
156
243
  return res.status(404).json({ error: 'Memory not found' });
157
244
  }
158
- const archivedCount = mm.archiveMemories(ownedIds, archived);
245
+ const archivedCount = mm.archiveMemories(ownedIds, archived, req.session.userId);
159
246
  res.json({ success: true, archivedCount });
160
247
  } catch (err) {
161
248
  res.status(500).json({ error: sanitizeError(err) });
@@ -190,6 +277,81 @@ router.get('/core', (req, res) => {
190
277
  res.json(coreMemory);
191
278
  });
192
279
 
280
+ // ─────────────────────────────────────────────────────────────────────────────
281
+ // LLM Transfer (prompt + import)
282
+ // ─────────────────────────────────────────────────────────────────────────────
283
+
284
+ router.get('/transfer-prompt', (req, res) => {
285
+ const prompt = buildLlmTransferPrompt({ agentLabel: 'NeoAgent' });
286
+ res.json({ prompt });
287
+ });
288
+
289
+ router.post('/transfer-import', transferImportLimiter, async (req, res) => {
290
+ const mm = req.app.locals.memoryManager;
291
+ const userId = req.session.userId;
292
+ const agentId = resolveAgentId(userId, getAgentIdFromRequest(req));
293
+ const text = String(req.body?.text || '').trim();
294
+ if (!text) {
295
+ return res.status(400).json({ error: 'text is required' });
296
+ }
297
+ if (Buffer.byteLength(text, 'utf8') > MAX_TRANSFER_TEXT_BYTES) {
298
+ return res.status(413).json({ error: 'text exceeds 60KB limit' });
299
+ }
300
+
301
+ const applyBehaviorNotes = req.body?.applyBehaviorNotes !== false;
302
+ const applyCoreMemory = req.body?.applyCoreMemory !== false;
303
+
304
+ try {
305
+ const parsed = parseLlmTransferText(text);
306
+ let importedCount = 0;
307
+ let skippedCount = 0;
308
+
309
+ for (const memory of parsed.memories) {
310
+ const content = String(memory.content || '').trim();
311
+ if (!content) {
312
+ skippedCount += 1;
313
+ continue;
314
+ }
315
+ await mm.saveMemory(userId, content, memory.category, importanceForCategory(memory.category), {
316
+ agentId,
317
+ sourceRef: {
318
+ sourceType: 'llm_import',
319
+ sourceLabel: 'LLM memory transfer',
320
+ },
321
+ metadata: {
322
+ importedFrom: 'llm_transfer',
323
+ },
324
+ });
325
+ importedCount += 1;
326
+ }
327
+
328
+ let coreUpdatedCount = 0;
329
+ if (applyCoreMemory && parsed.coreEntries) {
330
+ for (const [key, value] of Object.entries(parsed.coreEntries)) {
331
+ if (!key || key === 'active_context') continue;
332
+ mm.updateCore(userId, key, value, { agentId });
333
+ coreUpdatedCount += 1;
334
+ }
335
+ }
336
+
337
+ let behaviorNotesUpdated = false;
338
+ if (applyBehaviorNotes && parsed.behaviorNotes) {
339
+ mm.setAssistantBehaviorNotes(userId, parsed.behaviorNotes, { agentId });
340
+ behaviorNotesUpdated = true;
341
+ }
342
+
343
+ res.json({
344
+ importedCount,
345
+ skippedCount,
346
+ coreUpdatedCount,
347
+ behaviorNotesUpdated,
348
+ warnings: parsed.warnings || [],
349
+ });
350
+ } catch (err) {
351
+ res.status(500).json({ error: sanitizeError(err) });
352
+ }
353
+ });
354
+
193
355
  router.put('/core/:key', (req, res) => {
194
356
  const mm = req.app.locals.memoryManager;
195
357
  const userId = req.session.userId;
@@ -0,0 +1,14 @@
1
+ 'use strict';
2
+
3
+ const express = require('express');
4
+ const { getAnalyticsConfig } = require('../config/analytics');
5
+
6
+ const router = express.Router();
7
+
8
+ router.get('/config', (req, res) => {
9
+ res.json({
10
+ analytics: getAnalyticsConfig(),
11
+ });
12
+ });
13
+
14
+ module.exports = router;