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
@@ -1,11 +1,35 @@
1
1
  const express = require('express');
2
2
  const router = express.Router();
3
+ const rateLimit = require('express-rate-limit');
3
4
  const { requireAuth } = require('../middleware/auth');
4
5
  const { sanitizeError } = require('../utils/security');
5
6
  const { getAgentIdFromRequest, resolveAgentId } = require('../services/agents/manager');
7
+ const {
8
+ buildLlmTransferPrompt,
9
+ parseLlmTransferText,
10
+ importanceForCategory,
11
+ } = require('../services/memory/llm_transfer');
6
12
 
7
13
  router.use(requireAuth);
8
14
 
15
+ const apiKeyMutationLimiter = rateLimit({
16
+ windowMs: 15 * 60 * 1000,
17
+ max: 60,
18
+ message: { error: 'Too many API key update attempts, try again later' },
19
+ standardHeaders: true,
20
+ legacyHeaders: false,
21
+ });
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
+
9
33
  function normalizeMemoryIds(value) {
10
34
  return [...new Set(
11
35
  (Array.isArray(value) ? value : [])
@@ -24,6 +48,59 @@ function findOwnedMemoryIds(db, userId, agentId, ids) {
24
48
  ).all(userId, agentId, ...ids).map((row) => row.id);
25
49
  }
26
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
+
27
104
  // ─────────────────────────────────────────────────────────────────────────────
28
105
  // Overview (for initial page load)
29
106
  // ─────────────────────────────────────────────────────────────────────────────
@@ -37,6 +114,7 @@ router.get('/', (req, res) => {
37
114
  res.json({
38
115
  agentId,
39
116
  assistantBehaviorNotes: mm.getAssistantBehaviorNotes(userId, { agentId }),
117
+ assistantSelfState: mm.getAssistantSelfState(userId, { agentId }),
40
118
  dailyLogs: mm.listDailyLogs(7, userId),
41
119
  apiKeys: Object.keys(mm.readApiKeys(userId)),
42
120
  coreMemory
@@ -72,13 +150,31 @@ router.post('/memories', async (req, res) => {
72
150
  const mm = req.app.locals.memoryManager;
73
151
  const userId = req.session.userId;
74
152
  const agentId = resolveAgentId(userId, getAgentIdFromRequest(req));
75
- const { content, category = 'episodic', importance = 5 } = req.body;
153
+ const { content, category = 'episodic', importance = 5, sourceRef, scope, staleAfterDays, metadata } = req.body;
76
154
  if (!content || !content.trim()) return res.status(400).json({ error: 'content is required' });
77
155
  try {
78
- 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
+ });
79
171
  res.json({ success: true, id });
80
172
  } catch (err) {
81
- 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 });
82
178
  }
83
179
  });
84
180
 
@@ -108,7 +204,7 @@ router.delete('/memories/:id', (req, res) => {
108
204
  const agentId = resolveAgentId(req.session.userId, getAgentIdFromRequest(req));
109
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);
110
206
  if (!existing) return res.status(404).json({ error: 'Memory not found' });
111
- mm.deleteMemory(req.params.id);
207
+ mm.deleteMemories([req.params.id], req.session.userId);
112
208
  res.json({ success: true });
113
209
  });
114
210
 
@@ -125,7 +221,7 @@ router.post('/memories/bulk-delete', (req, res) => {
125
221
  if (!ownedIds.length) {
126
222
  return res.status(404).json({ error: 'Memory not found' });
127
223
  }
128
- const deletedCount = mm.deleteMemories(ownedIds);
224
+ const deletedCount = mm.deleteMemories(ownedIds, req.session.userId);
129
225
  res.json({ success: true, deletedCount });
130
226
  } catch (err) {
131
227
  res.status(500).json({ error: sanitizeError(err) });
@@ -146,7 +242,7 @@ router.post('/memories/bulk-archive', (req, res) => {
146
242
  if (!ownedIds.length) {
147
243
  return res.status(404).json({ error: 'Memory not found' });
148
244
  }
149
- const archivedCount = mm.archiveMemories(ownedIds, archived);
245
+ const archivedCount = mm.archiveMemories(ownedIds, archived, req.session.userId);
150
246
  res.json({ success: true, archivedCount });
151
247
  } catch (err) {
152
248
  res.status(500).json({ error: sanitizeError(err) });
@@ -181,6 +277,81 @@ router.get('/core', (req, res) => {
181
277
  res.json(coreMemory);
182
278
  });
183
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
+
184
355
  router.put('/core/:key', (req, res) => {
185
356
  const mm = req.app.locals.memoryManager;
186
357
  const userId = req.session.userId;
@@ -232,12 +403,12 @@ router.get('/api-keys', (req, res) => {
232
403
  res.json(masked);
233
404
  });
234
405
 
235
- router.put('/api-keys/:service', (req, res) => {
406
+ router.put('/api-keys/:service', apiKeyMutationLimiter, (req, res) => {
236
407
  req.app.locals.memoryManager.setApiKey(req.params.service, req.body.key, req.session.userId);
237
408
  res.json({ success: true });
238
409
  });
239
410
 
240
- router.delete('/api-keys/:service', (req, res) => {
411
+ router.delete('/api-keys/:service', apiKeyMutationLimiter, (req, res) => {
241
412
  req.app.locals.memoryManager.deleteApiKey(req.params.service, req.session.userId);
242
413
  res.json({ success: true });
243
414
  });
@@ -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;
@@ -0,0 +1,46 @@
1
+ 'use strict';
2
+
3
+ const express = require('express');
4
+ const db = require('../db/database');
5
+ const { getErrorMessage } = require('../services/bootstrap_helpers');
6
+
7
+ const router = express.Router();
8
+
9
+ router.get('/search', (req, res) => {
10
+ const { q, limit = 50, offset = 0 } = req.query;
11
+
12
+ if (!req.user || !req.user.id) {
13
+ return res.status(401).json({ error: 'Unauthorized' });
14
+ }
15
+
16
+ try {
17
+ let results = [];
18
+ if (q) {
19
+ // Full text search
20
+ results = db.prepare(`
21
+ SELECT s.id, s.timestamp, s.app_name, s.text_content
22
+ FROM screen_history_fts fts
23
+ JOIN screen_history s ON fts.rowid = s.id
24
+ WHERE screen_history_fts MATCH ? AND s.user_id = ?
25
+ ORDER BY s.timestamp DESC
26
+ LIMIT ? OFFSET ?
27
+ `).all(q, req.user.id, Number(limit), Number(offset));
28
+ } else {
29
+ // Recent history
30
+ results = db.prepare(`
31
+ SELECT id, timestamp, app_name, text_content
32
+ FROM screen_history
33
+ WHERE user_id = ?
34
+ ORDER BY timestamp DESC
35
+ LIMIT ? OFFSET ?
36
+ `).all(req.user.id, Number(limit), Number(offset));
37
+ }
38
+
39
+ res.json({ results });
40
+ } catch (err) {
41
+ console.error('[ScreenHistory] Search error:', getErrorMessage(err));
42
+ res.status(500).json({ error: 'Failed to search screen history' });
43
+ }
44
+ });
45
+
46
+ module.exports = router;
@@ -1,5 +1,6 @@
1
1
  const express = require('express');
2
2
  const router = express.Router();
3
+ const rateLimit = require('express-rate-limit');
3
4
  const db = require('../db/database');
4
5
  const { requireAuth } = require('../middleware/auth');
5
6
  const { normalizeWhatsAppWhitelist } = require('../utils/whatsapp');
@@ -21,6 +22,9 @@ const {
21
22
  ensureDefaultAiSettings,
22
23
  normalizeProviderConfigs,
23
24
  } = require('../services/ai/settings');
25
+ const {
26
+ readMeshtasticEnabled,
27
+ } = require('../services/messaging/meshtastic_env');
24
28
  const {
25
29
  ensureDefaultRuntimeSettings,
26
30
  getRuntimeSettings,
@@ -75,6 +79,14 @@ const VOICE_SETTING_KEYS = new Set([
75
79
  'voice_live_voice',
76
80
  ]);
77
81
 
82
+ const ENV_BACKED_SETTING_KEYS = new Set([
83
+ 'meshtastic_enabled',
84
+ ]);
85
+
86
+ const READ_ONLY_ENV_SETTING_KEYS = new Set([
87
+ 'meshtastic_enabled',
88
+ ]);
89
+
78
90
  function toOptionalTrimmedString(value) {
79
91
  if (typeof value !== 'string') return undefined;
80
92
  const trimmed = value.trim();
@@ -95,7 +107,15 @@ function extractVoiceSettings(payload = {}) {
95
107
  };
96
108
  }
97
109
 
98
- router.get('/update/status', (req, res) => {
110
+ const updateTriggerLimiter = rateLimit({
111
+ windowMs: 5 * 60 * 1000,
112
+ max: 3,
113
+ message: { success: false, error: 'Too many update requests, try again later' },
114
+ standardHeaders: true,
115
+ legacyHeaders: false,
116
+ });
117
+
118
+ router.get('/update/status', requireAuth, (req, res) => {
99
119
  const status = readUpdateStatus();
100
120
  const version = getVersionInfo();
101
121
  res.json({
@@ -133,22 +153,41 @@ function getBrowserController(req) {
133
153
  if (runtimeManager && typeof runtimeManager.getBrowserProviderForUser === 'function') {
134
154
  return runtimeManager.getBrowserProviderForUser(req.session?.userId);
135
155
  }
136
- const resolver = req.app?.locals?.getBrowserControllerForUser;
137
- if (typeof resolver === 'function') {
138
- return resolver(req.session?.userId);
139
- }
140
- return req.app?.locals?.browserController;
156
+ throw new Error('Browser controller is unavailable. VM runtime is required.');
141
157
  }
142
158
 
143
- function applyHeadlessSetting(req, value) {
144
- Promise.resolve(getBrowserController(req))
145
- .then((controller) => {
146
- if (controller && typeof controller.setHeadless === 'function') {
147
- return controller.setHeadless(value);
148
- }
159
+
160
+
161
+ function isEnvBackedSettingKey(key) {
162
+ return ENV_BACKED_SETTING_KEYS.has(key);
163
+ }
164
+
165
+ function readEnvBackedSettingValue(key) {
166
+ switch (key) {
167
+ case 'meshtastic_enabled':
168
+ return readMeshtasticEnabled();
169
+ default:
149
170
  return null;
150
- })
151
- .catch(() => { });
171
+ }
172
+ }
173
+
174
+ async function writeEnvBackedSettingValue(req, key, value) {
175
+ switch (key) {
176
+ case 'meshtastic_enabled':
177
+ return readMeshtasticEnabled();
178
+ default:
179
+ return value;
180
+ }
181
+ }
182
+
183
+ async function resetEnvBackedSettingValue(req, key) {
184
+ switch (key) {
185
+ case 'meshtastic_enabled': {
186
+ return;
187
+ }
188
+ default:
189
+ return;
190
+ }
152
191
  }
153
192
 
154
193
  // Get supported models metadata
@@ -209,11 +248,17 @@ router.get('/', (req, res) => {
209
248
  }
210
249
  settings.agentId = agentId;
211
250
  settings.ai_provider_configs = normalizeProviderConfigs(settings.ai_provider_configs);
251
+ settings.meshtastic_enabled = readMeshtasticEnabled();
252
+
253
+ // Normalize runtime settings for consistency across deployments
254
+ const normalizedRuntime = getRuntimeSettings(req.session.userId);
255
+ Object.assign(settings, normalizedRuntime);
256
+
212
257
  res.json(settings);
213
258
  });
214
259
 
215
260
  // Update settings (batch)
216
- router.put('/', (req, res) => {
261
+ router.put('/', async (req, res) => {
217
262
  const userId = req.session.userId;
218
263
  const agentId = resolveAgentId(userId, getAgentIdFromRequest(req));
219
264
  ensureDefaultAiSettings(userId, agentId);
@@ -238,6 +283,16 @@ router.put('/', (req, res) => {
238
283
  normalizedBody.ai_provider_configs = normalizeProviderConfigs(normalizedBody.ai_provider_configs);
239
284
  }
240
285
 
286
+ for (const key of Object.keys(normalizedBody)) {
287
+ if (READ_ONLY_ENV_SETTING_KEYS.has(key)) {
288
+ delete normalizedBody[key];
289
+ continue;
290
+ }
291
+ if (isEnvBackedSettingKey(key)) {
292
+ normalizedBody[key] = await writeEnvBackedSettingValue(req, key, normalizedBody[key]);
293
+ }
294
+ }
295
+
241
296
  if (
242
297
  'runtime_profile' in normalizedBody
243
298
  || 'runtime_backend' in normalizedBody
@@ -264,6 +319,8 @@ router.put('/', (req, res) => {
264
319
  const v = serializeRuntimeSettingValue(key, value);
265
320
  if (isAgentScopedSettingKey(key)) {
266
321
  upsertAgent.run(userId, agentId, key, v);
322
+ } else if (isEnvBackedSettingKey(key)) {
323
+ continue;
267
324
  } else if (key !== 'agentId' && key !== 'agent_id') {
268
325
  upsert.run(userId, key, v);
269
326
  }
@@ -281,10 +338,7 @@ router.put('/', (req, res) => {
281
338
  }
282
339
  }
283
340
 
284
- // Apply headless toggle immediately without restarting
285
- if ('headless_browser' in normalizedBody) {
286
- applyHeadlessSetting(req, normalizedBody.headless_browser);
287
- }
341
+
288
342
 
289
343
  res.json({ success: true });
290
344
  });
@@ -377,6 +431,9 @@ router.get('/token-usage/summary', (req, res) => {
377
431
 
378
432
  // Get single setting
379
433
  router.get('/:key', (req, res) => {
434
+ if (isEnvBackedSettingKey(req.params.key)) {
435
+ return res.json({ value: readEnvBackedSettingValue(req.params.key) });
436
+ }
380
437
  const userId = req.session.userId;
381
438
  const agentId = resolveAgentId(userId, getAgentIdFromRequest(req));
382
439
  ensureDefaultRuntimeSettings(userId);
@@ -401,11 +458,22 @@ router.get('/:key', (req, res) => {
401
458
  });
402
459
 
403
460
  // Set single setting
404
- router.put('/:key', (req, res) => {
461
+ router.put('/:key', async (req, res) => {
405
462
  const userId = req.session.userId;
406
463
  const agentId = resolveAgentId(userId, getAgentIdFromRequest(req));
407
464
  ensureDefaultRuntimeSettings(userId);
408
465
  let value = req.body.value;
466
+ if (READ_ONLY_ENV_SETTING_KEYS.has(req.params.key)) {
467
+ return res.status(403).json({
468
+ success: false,
469
+ error: `${req.params.key} is managed via environment only`,
470
+ value: readEnvBackedSettingValue(req.params.key),
471
+ });
472
+ }
473
+ if (isEnvBackedSettingKey(req.params.key)) {
474
+ const saved = await writeEnvBackedSettingValue(req, req.params.key, value);
475
+ return res.json({ success: true, value: saved });
476
+ }
409
477
  if (req.params.key === 'platform_whitelist_whatsapp') {
410
478
  if (typeof value === 'string') {
411
479
  try {
@@ -459,14 +527,24 @@ router.put('/:key', (req, res) => {
459
527
  }
460
528
  }
461
529
 
462
- if (req.params.key === 'headless_browser') {
463
- applyHeadlessSetting(req, value);
464
- }
530
+
465
531
  res.json({ success: true });
466
532
  });
467
533
 
468
534
  // Delete setting
469
535
  router.delete('/:key', (req, res) => {
536
+ if (READ_ONLY_ENV_SETTING_KEYS.has(req.params.key)) {
537
+ return res.status(403).json({
538
+ success: false,
539
+ error: `${req.params.key} is managed via environment only`,
540
+ value: readEnvBackedSettingValue(req.params.key),
541
+ });
542
+ }
543
+ if (isEnvBackedSettingKey(req.params.key)) {
544
+ return resetEnvBackedSettingValue(req, req.params.key)
545
+ .then(() => res.json({ success: true }))
546
+ .catch((err) => res.status(500).json({ success: false, error: err.message }));
547
+ }
470
548
  const userId = req.session.userId;
471
549
  const agentId = resolveAgentId(userId, getAgentIdFromRequest(req));
472
550
  if (
@@ -481,7 +559,7 @@ router.delete('/:key', (req, res) => {
481
559
  });
482
560
 
483
561
  // Trigger auto-update script
484
- router.post('/update', (req, res) => {
562
+ router.post('/update', requireAuth, updateTriggerLimiter, (req, res) => {
485
563
  if (isManagedDeployment()) {
486
564
  return res.status(403).json({
487
565
  success: false,
@@ -10,6 +10,12 @@ const {
10
10
 
11
11
  router.use(requireAuth);
12
12
 
13
+ const SHELL_METACHAR_RE = /[;&|`$\n\r(){}\\<>]/;
14
+ function isValidCommandTemplate(template) {
15
+ const bare = String(template).replace(/\{[^{}]*\}/g, '');
16
+ return !SHELL_METACHAR_RE.test(bare);
17
+ }
18
+
13
19
  function parseSkillDocument(content) {
14
20
  const match = String(content || '').match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
15
21
  if (!match) {
@@ -79,6 +85,9 @@ router.post('/', async (req, res) => {
79
85
  const runner = await getSkillRunner(req.app);
80
86
  const parsed = parseSkillDocument(req.body.content);
81
87
  if (parsed.error) return res.status(400).json({ error: parsed.error });
88
+ if (parsed.metadata?.command && !isValidCommandTemplate(parsed.metadata.command)) {
89
+ return res.status(400).json({ error: 'Skill command template contains invalid characters' });
90
+ }
82
91
 
83
92
  const result = runner.createSkill(
84
93
  req.body.filename || parsed.name,
@@ -105,6 +114,9 @@ router.put('/:name', async (req, res) => {
105
114
 
106
115
  const parsed = parseSkillDocument(req.body.content);
107
116
  if (parsed.error) return res.status(400).json({ error: parsed.error });
117
+ if (parsed.metadata?.command && !isValidCommandTemplate(parsed.metadata.command)) {
118
+ return res.status(400).json({ error: 'Skill command template contains invalid characters' });
119
+ }
108
120
  const result = runner.updateSkill(req.params.name, {
109
121
  description: parsed.description,
110
122
  instructions: parsed.instructions,
@@ -0,0 +1,66 @@
1
+ const express = require('express');
2
+ const { requireAuth } = require('../middleware/auth');
3
+ const { getAgentIdFromRequest, resolveAgentId } = require('../services/agents/manager');
4
+ const { sanitizeError } = require('../utils/security');
5
+
6
+ const router = express.Router();
7
+
8
+ router.use(requireAuth);
9
+
10
+ router.get('/health', async (req, res) => {
11
+ try {
12
+ const service = req.app?.locals?.socialVideoService;
13
+ if (!service || typeof service.getHealthStatus !== 'function') {
14
+ return res.status(503).json({
15
+ ready: false,
16
+ error: 'Social video service is unavailable.',
17
+ });
18
+ }
19
+ const health = await service.getHealthStatus({
20
+ forceRefresh: req.query?.refresh === '1' || req.query?.refresh === 'true',
21
+ });
22
+ return res.json(health);
23
+ } catch (error) {
24
+ return res.status(500).json({ error: sanitizeError(error) });
25
+ }
26
+ });
27
+
28
+ router.post('/extract', async (req, res) => {
29
+ try {
30
+ const service = req.app?.locals?.socialVideoService;
31
+ if (!service || typeof service.extractFromUrl !== 'function') {
32
+ return res.status(503).json({ error: 'Social video service is unavailable.' });
33
+ }
34
+ if (!req.session || !req.session.userId) {
35
+ return res.status(401).json({ error: 'Authentication required.' });
36
+ }
37
+
38
+ const sourceUrl = String(req.body?.url || '').trim();
39
+ if (!sourceUrl) {
40
+ return res.status(400).json({ error: 'url is required.' });
41
+ }
42
+
43
+ const agentId = resolveAgentId(req.session.userId, getAgentIdFromRequest(req));
44
+
45
+ const result = await service.extractFromUrl(req.session.userId, sourceUrl, {
46
+ includeFrame: req.body?.include_frame !== false,
47
+ forceStt: req.body?.force_stt === true,
48
+ agentId,
49
+ });
50
+
51
+ if (result?.setup?.ready === false) {
52
+ return res.status(503).json(result);
53
+ }
54
+
55
+ if (Array.isArray(result.errors) && result.errors.length > 0) {
56
+ const failed = !result.title && !result.description && !result.transcript && !result.frameImage;
57
+ return res.status(failed ? 422 : 200).json(result);
58
+ }
59
+
60
+ return res.json(result);
61
+ } catch (error) {
62
+ return res.status(500).json({ error: sanitizeError(error) });
63
+ }
64
+ });
65
+
66
+ module.exports = router;
@@ -32,8 +32,17 @@ router.post('/', async (req, res) => {
32
32
  try {
33
33
  const tasks = req.app.locals.taskRuntime;
34
34
  const agentId = resolveAgentId(req.session.userId, getAgentIdFromRequest(req));
35
+ const {
36
+ name, triggerType, trigger_type, triggerConfig, trigger_config,
37
+ taskType, task_type, taskConfig, task_config, enabled,
38
+ prompt, callTo, callGreeting, model,
39
+ oneTime, one_time, cronExpression, cron_expression, runAt, run_at,
40
+ } = req.body || {};
35
41
  const task = await tasks.createTask(req.session.userId, {
36
- ...req.body,
42
+ name, triggerType, trigger_type, triggerConfig, trigger_config,
43
+ taskType, task_type, taskConfig, task_config, enabled,
44
+ prompt, callTo, callGreeting, model,
45
+ oneTime, one_time, cronExpression, cron_expression, runAt, run_at,
37
46
  agentId,
38
47
  });
39
48
  res.status(201).json(task);
@@ -49,7 +58,18 @@ router.put('/:id', async (req, res) => {
49
58
  return res.status(400).json({ error: 'Invalid task id' });
50
59
  }
51
60
  const tasks = req.app.locals.taskRuntime;
52
- const task = await tasks.updateTask(taskId, req.session.userId, req.body);
61
+ const {
62
+ name, triggerType, trigger_type, triggerConfig, trigger_config,
63
+ taskType, task_type, taskConfig, task_config, enabled,
64
+ prompt, callTo, callGreeting, model,
65
+ oneTime, one_time, cronExpression, cron_expression, runAt, run_at,
66
+ } = req.body || {};
67
+ const task = await tasks.updateTask(taskId, req.session.userId, {
68
+ name, triggerType, trigger_type, triggerConfig, trigger_config,
69
+ taskType, task_type, taskConfig, task_config, enabled,
70
+ prompt, callTo, callGreeting, model,
71
+ oneTime, one_time, cronExpression, cron_expression, runAt, run_at,
72
+ });
53
73
  res.json(task);
54
74
  } catch (err) {
55
75
  res.status(400).json({ error: sanitizeError(err) });