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
@@ -0,0 +1,888 @@
1
+ package com.neoagent.flutter_app
2
+
3
+ import android.content.Intent
4
+ import android.content.ActivityNotFoundException
5
+ import android.content.pm.PackageManager
6
+ import android.media.AudioManager
7
+ import android.net.Uri
8
+ import android.os.BatteryManager
9
+ import android.os.Build
10
+ import android.provider.Settings
11
+ import android.provider.OpenableColumns
12
+ import android.view.KeyEvent
13
+ import androidx.activity.result.ActivityResultLauncher
14
+ import androidx.activity.result.contract.ActivityResultContracts
15
+ import androidx.core.content.ContextCompat
16
+ import androidx.core.content.FileProvider
17
+ import androidx.health.connect.client.PermissionController
18
+ import androidx.lifecycle.lifecycleScope
19
+ import com.neoagent.flutter_app.health.HealthConnectGateway
20
+ import com.neoagent.flutter_app.health.HealthSyncScheduler
21
+ import com.neoagent.flutter_app.recording.RecordingForegroundService
22
+ import com.neoagent.flutter_app.recording.RecordingStateStore
23
+ import com.neoagent.flutter_app.widgets.AiHomeWidgetProvider
24
+ import com.neoagent.flutter_app.widgets.AiWidgetStore
25
+ import com.neoagent.flutter_app.widgets.VoiceLaunchWidgetProvider
26
+ import com.neoagent.flutter_app.widgets.WidgetSyncScheduler
27
+ import io.flutter.embedding.android.FlutterFragmentActivity
28
+ import io.flutter.embedding.engine.FlutterEngine
29
+ import io.flutter.plugin.common.EventChannel
30
+ import android.telecom.PhoneAccount
31
+ import android.telecom.PhoneAccountHandle
32
+ import android.telecom.TelecomManager
33
+ import android.content.ComponentName
34
+ import android.os.Bundle
35
+ import android.telecom.DisconnectCause
36
+ import android.content.Context
37
+ import com.neoagent.flutter_app.telecom.NeoAgentConnectionService
38
+ import io.flutter.plugin.common.MethodChannel
39
+ import io.flutter.plugins.GeneratedPluginRegistrant
40
+ import kotlinx.coroutines.launch
41
+ import java.io.File
42
+ import java.time.Instant
43
+
44
+ class MainActivity : FlutterFragmentActivity() {
45
+
46
+ private lateinit var healthGateway: HealthConnectGateway
47
+ private lateinit var healthSyncScheduler: HealthSyncScheduler
48
+ private lateinit var widgetSyncScheduler: WidgetSyncScheduler
49
+ private lateinit var recordingStateStore: RecordingStateStore
50
+ private lateinit var permissionLauncher: ActivityResultLauncher<Set<String>>
51
+ private lateinit var microphonePermissionLauncher: ActivityResultLauncher<String>
52
+ private var pendingPermissionResult: MethodChannel.Result? = null
53
+ private var pendingRecordingResult: MethodChannel.Result? = null
54
+ private var pendingRecordingArgs: Map<*, *>? = null
55
+ private var launcherButtonSink: EventChannel.EventSink? = null
56
+ private var widgetEventSink: EventChannel.EventSink? = null
57
+ private var appLaunchEventSink: EventChannel.EventSink? = null
58
+ private var pendingAppLaunchAction: String? = null
59
+ private var pendingSharePayload: Map<String, Any?>? = null
60
+
61
+ override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
62
+ super.configureFlutterEngine(flutterEngine)
63
+ io.flutter.embedding.engine.FlutterEngineCache.getInstance().put("main_engine", flutterEngine)
64
+ GeneratedPluginRegistrant.registerWith(flutterEngine)
65
+
66
+ healthGateway = HealthConnectGateway(this)
67
+ healthSyncScheduler = HealthSyncScheduler(this)
68
+ widgetSyncScheduler = WidgetSyncScheduler(this)
69
+
70
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
71
+ val telecomManager = getSystemService(Context.TELECOM_SERVICE) as TelecomManager
72
+ val componentName = ComponentName(this, NeoAgentConnectionService::class.java)
73
+ val phoneAccountHandle = PhoneAccountHandle(componentName, "NeoAgentVoiceId")
74
+ val phoneAccount = PhoneAccount.builder(phoneAccountHandle, "NeoAgent Voice Assistant")
75
+ .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
76
+ .build()
77
+ telecomManager.registerPhoneAccount(phoneAccount)
78
+ }
79
+ recordingStateStore = RecordingStateStore(this)
80
+ permissionLauncher = registerForActivityResult(
81
+ PermissionController.createRequestPermissionResultContract(),
82
+ ) {
83
+ val pending = pendingPermissionResult
84
+ pendingPermissionResult = null
85
+ lifecycleScope.launch {
86
+ pending?.success(buildStatusMap())
87
+ }
88
+ }
89
+ microphonePermissionLauncher = registerForActivityResult(
90
+ ActivityResultContracts.RequestPermission(),
91
+ ) { granted ->
92
+ val pending = pendingRecordingResult
93
+ val args = pendingRecordingArgs
94
+ pendingRecordingResult = null
95
+ pendingRecordingArgs = null
96
+ if (!granted) {
97
+ pending?.error(
98
+ "recording_permission_denied",
99
+ "Microphone permission is required for background recording.",
100
+ null,
101
+ )
102
+ return@registerForActivityResult
103
+ }
104
+ try {
105
+ startRecordingService(args)
106
+ pending?.success(recordingStateStore.statusMap())
107
+ } catch (err: Exception) {
108
+ pending?.error(
109
+ "recording_start_failed",
110
+ err.message ?: err.javaClass.simpleName,
111
+ null,
112
+ )
113
+ }
114
+ }
115
+ MethodChannel(
116
+ flutterEngine.dartExecutor.binaryMessenger,
117
+ "neoagent/telecom",
118
+ ).setMethodCallHandler { call, result ->
119
+ when (call.method) {
120
+ "startCallRouting" -> {
121
+ try {
122
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
123
+ result.error("UNSUPPORTED", "Self-managed calls require Android O or newer", null)
124
+ return@setMethodCallHandler
125
+ }
126
+ val telecomManager = getSystemService(Context.TELECOM_SERVICE) as TelecomManager
127
+ val componentName = ComponentName(this, NeoAgentConnectionService::class.java)
128
+ val phoneAccountHandle = PhoneAccountHandle(componentName, "NeoAgentVoiceId")
129
+ val extras = Bundle().apply {
130
+ putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle)
131
+ putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, android.telecom.VideoProfile.STATE_AUDIO_ONLY)
132
+ putBoolean("is_flutter_initiated", true)
133
+ }
134
+ telecomManager.placeCall(Uri.parse("tel:NeoAgent"), extras)
135
+ result.success(true)
136
+ } catch (e: SecurityException) {
137
+ result.error("PERMISSION_DENIED", "Missing Manage Own Calls permission", null)
138
+ } catch (e: Exception) {
139
+ result.error("ERROR", e.message, null)
140
+ }
141
+ }
142
+ "stopCallRouting" -> {
143
+ NeoAgentConnectionService.getAndClearCurrentConnection()?.let {
144
+ it.setDisconnected(DisconnectCause(DisconnectCause.LOCAL))
145
+ it.destroy()
146
+ }
147
+ result.success(true)
148
+ }
149
+ else -> result.notImplemented()
150
+ }
151
+ }
152
+
153
+ MethodChannel(
154
+ flutterEngine.dartExecutor.binaryMessenger,
155
+ "neoagent/health",
156
+ ).setMethodCallHandler { call, result ->
157
+ when (call.method) {
158
+ "status" -> lifecycleScope.launch {
159
+ result.success(buildStatusMap())
160
+ }
161
+
162
+ "requestPermissions" -> lifecycleScope.launch {
163
+ val client = healthGateway.getClientOrNull()
164
+ if (client == null) {
165
+ result.error(
166
+ "health_unavailable",
167
+ "Health Connect is unavailable on this device.",
168
+ null,
169
+ )
170
+ return@launch
171
+ }
172
+
173
+ pendingPermissionResult = result
174
+ permissionLauncher.launch(healthGateway.getRequestedPermissions(client))
175
+ }
176
+
177
+ "collectBatch" -> lifecycleScope.launch {
178
+ try {
179
+ val client = healthGateway.getClientOrNull()
180
+ if (client == null) {
181
+ result.error(
182
+ "health_unavailable",
183
+ "Health Connect is unavailable on this device.",
184
+ null,
185
+ )
186
+ return@launch
187
+ }
188
+
189
+ val required = healthGateway.getRequestedPermissions(client)
190
+ val granted = client.permissionController.getGrantedPermissions()
191
+ if (!granted.containsAll(required)) {
192
+ result.error(
193
+ "health_permissions",
194
+ "Grant Health Connect permissions before syncing.",
195
+ null,
196
+ )
197
+ return@launch
198
+ }
199
+
200
+ val args = call.arguments as? Map<*, *>
201
+ val windowStartRaw = args?.get("windowStart")?.toString()
202
+ val windowEndRaw = args?.get("windowEnd")?.toString()
203
+ if (windowStartRaw.isNullOrBlank() || windowEndRaw.isNullOrBlank()) {
204
+ result.error(
205
+ "health_sync_window",
206
+ "windowStart and windowEnd are required.",
207
+ null,
208
+ )
209
+ return@launch
210
+ }
211
+ val windowStart =
212
+ try {
213
+ Instant.parse(windowStartRaw)
214
+ } catch (_: Exception) {
215
+ result.error(
216
+ "health_sync_window",
217
+ "windowStart must be an ISO-8601 timestamp.",
218
+ null,
219
+ )
220
+ return@launch
221
+ }
222
+ val windowEnd =
223
+ try {
224
+ Instant.parse(windowEndRaw)
225
+ } catch (_: Exception) {
226
+ result.error(
227
+ "health_sync_window",
228
+ "windowEnd must be an ISO-8601 timestamp.",
229
+ null,
230
+ )
231
+ return@launch
232
+ }
233
+ val payload = healthGateway.collectBatch(client, windowStart, windowEnd)
234
+ result.success(payload.toJson().toString())
235
+ } catch (err: Exception) {
236
+ result.error(
237
+ "health_sync_failed",
238
+ err.message ?: err.javaClass.simpleName,
239
+ null,
240
+ )
241
+ }
242
+ }
243
+
244
+ "configureBackgroundSync" -> {
245
+ val args = call.arguments as? Map<*, *>
246
+ val enabled = args?.get("enabled") == true
247
+ val backendUrl = args?.get("backendUrl")?.toString().orEmpty()
248
+ val sessionCookie = args?.get("sessionCookie")?.toString().orEmpty()
249
+ healthSyncScheduler.configure(
250
+ enabled = enabled,
251
+ backendUrl = backendUrl,
252
+ sessionCookie = sessionCookie,
253
+ )
254
+ result.success(null)
255
+ }
256
+
257
+ else -> result.notImplemented()
258
+ }
259
+ }
260
+
261
+ MethodChannel(
262
+ flutterEngine.dartExecutor.binaryMessenger,
263
+ "neoagent/recordings",
264
+ ).setMethodCallHandler { call, result ->
265
+ when (call.method) {
266
+ "status" -> result.success(recordingStateStore.statusMap())
267
+
268
+ "startBackgroundRecording" -> {
269
+ try {
270
+ val args = call.arguments as? Map<*, *>
271
+ if (ContextCompat.checkSelfPermission(
272
+ this,
273
+ android.Manifest.permission.RECORD_AUDIO,
274
+ ) == android.content.pm.PackageManager.PERMISSION_GRANTED
275
+ ) {
276
+ startRecordingService(args)
277
+ result.success(recordingStateStore.statusMap())
278
+ } else {
279
+ pendingRecordingResult = result
280
+ pendingRecordingArgs = args
281
+ microphonePermissionLauncher.launch(android.Manifest.permission.RECORD_AUDIO)
282
+ }
283
+ } catch (err: Exception) {
284
+ result.error(
285
+ "recording_start_failed",
286
+ err.message ?: err.javaClass.simpleName,
287
+ null,
288
+ )
289
+ }
290
+ }
291
+
292
+ "pauseBackgroundRecording" -> {
293
+ startService(RecordingForegroundService.buildPauseIntent(this))
294
+ result.success(recordingStateStore.statusMap())
295
+ }
296
+
297
+ "resumeBackgroundRecording" -> {
298
+ startService(RecordingForegroundService.buildResumeIntent(this))
299
+ result.success(recordingStateStore.statusMap())
300
+ }
301
+
302
+ "stopBackgroundRecording" -> {
303
+ startService(RecordingForegroundService.buildStopIntent(this))
304
+ result.success(recordingStateStore.statusMap())
305
+ }
306
+
307
+ else -> result.notImplemented()
308
+ }
309
+ }
310
+
311
+ MethodChannel(
312
+ flutterEngine.dartExecutor.binaryMessenger,
313
+ "neoagent/app_update",
314
+ ).setMethodCallHandler { call, result ->
315
+ when (call.method) {
316
+ "canRequestPackageInstalls" -> {
317
+ result.success(canRequestPackageInstalls())
318
+ }
319
+
320
+ "openInstallUnknownAppsSettings" -> {
321
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
322
+ result.error(
323
+ "app_update_unknown_sources_unsupported",
324
+ "Install unknown apps settings require Android 8.0 or newer.",
325
+ null,
326
+ )
327
+ return@setMethodCallHandler
328
+ }
329
+ try {
330
+ val intent = Intent(
331
+ Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,
332
+ Uri.parse("package:$packageName"),
333
+ ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
334
+ startActivity(intent)
335
+ result.success(null)
336
+ } catch (err: ActivityNotFoundException) {
337
+ result.error(
338
+ "app_update_unknown_sources_failed",
339
+ err.message ?: err.javaClass.simpleName,
340
+ null,
341
+ )
342
+ } catch (err: Exception) {
343
+ result.error(
344
+ "app_update_unknown_sources_failed",
345
+ err.message ?: err.javaClass.simpleName,
346
+ null,
347
+ )
348
+ }
349
+ }
350
+
351
+ "installApk" -> {
352
+ try {
353
+ val args = call.arguments as? Map<*, *>
354
+ val apkPath = args?.get("apkPath")?.toString().orEmpty()
355
+ if (apkPath.isBlank()) {
356
+ result.error(
357
+ "app_update_invalid_args",
358
+ "apkPath is required.",
359
+ null,
360
+ )
361
+ return@setMethodCallHandler
362
+ }
363
+ result.success(launchApkInstaller(apkPath))
364
+ } catch (err: Exception) {
365
+ result.error(
366
+ "app_update_install_failed",
367
+ err.message ?: err.javaClass.simpleName,
368
+ null,
369
+ )
370
+ }
371
+ }
372
+
373
+ else -> result.notImplemented()
374
+ }
375
+ }
376
+
377
+ MethodChannel(
378
+ flutterEngine.dartExecutor.binaryMessenger,
379
+ "neoagent/launcher_device",
380
+ ).setMethodCallHandler { call, result ->
381
+ when (call.method) {
382
+ "getVolumeState" -> {
383
+ val audioManager = getSystemService(AudioManager::class.java)
384
+ if (audioManager == null) {
385
+ result.error(
386
+ "launcher_audio_unavailable",
387
+ "Audio manager is unavailable on this device.",
388
+ null,
389
+ )
390
+ return@setMethodCallHandler
391
+ }
392
+ result.success(buildVolumeState(audioManager))
393
+ }
394
+
395
+ "setVolume" -> {
396
+ val audioManager = getSystemService(AudioManager::class.java)
397
+ if (audioManager == null) {
398
+ result.error(
399
+ "launcher_audio_unavailable",
400
+ "Audio manager is unavailable on this device.",
401
+ null,
402
+ )
403
+ return@setMethodCallHandler
404
+ }
405
+ val args = call.arguments as? Map<*, *>
406
+ val target = (args?.get("value") as? Number)?.toInt()
407
+ if (target == null) {
408
+ result.error(
409
+ "launcher_volume_invalid_args",
410
+ "value is required.",
411
+ null,
412
+ )
413
+ return@setMethodCallHandler
414
+ }
415
+ val minVolume =
416
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
417
+ audioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC)
418
+ } else {
419
+ 0
420
+ }
421
+ val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
422
+ audioManager.setStreamVolume(
423
+ AudioManager.STREAM_MUSIC,
424
+ target.coerceIn(minVolume, maxVolume),
425
+ 0,
426
+ )
427
+ result.success(buildVolumeState(audioManager))
428
+ }
429
+
430
+ "adjustVolume" -> {
431
+ val audioManager = getSystemService(AudioManager::class.java)
432
+ if (audioManager == null) {
433
+ result.error(
434
+ "launcher_audio_unavailable",
435
+ "Audio manager is unavailable on this device.",
436
+ null,
437
+ )
438
+ return@setMethodCallHandler
439
+ }
440
+ val args = call.arguments as? Map<*, *>
441
+ val delta = (args?.get("delta") as? Number)?.toInt() ?: 0
442
+ val minVolume =
443
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
444
+ audioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC)
445
+ } else {
446
+ 0
447
+ }
448
+ val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
449
+ val currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
450
+ audioManager.setStreamVolume(
451
+ AudioManager.STREAM_MUSIC,
452
+ (currentVolume + delta).coerceIn(minVolume, maxVolume),
453
+ 0,
454
+ )
455
+ result.success(buildVolumeState(audioManager))
456
+ }
457
+
458
+ "openWifiSettings" -> {
459
+ try {
460
+ val intent = Intent(Settings.ACTION_WIFI_SETTINGS).apply {
461
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
462
+ }
463
+ startActivity(intent)
464
+ result.success(null)
465
+ } catch (err: Exception) {
466
+ result.error(
467
+ "launcher_wifi_settings_failed",
468
+ err.message ?: err.javaClass.simpleName,
469
+ null,
470
+ )
471
+ }
472
+ }
473
+
474
+ "openDateSettings", "openTimeSettings" -> {
475
+ try {
476
+ val intent = Intent(Settings.ACTION_DATE_SETTINGS)
477
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
478
+ if (intent.resolveActivity(packageManager) != null) {
479
+ startActivity(intent)
480
+ } else {
481
+ startActivity(
482
+ Intent(Settings.ACTION_SETTINGS)
483
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
484
+ )
485
+ }
486
+ result.success(null)
487
+ } catch (err: Exception) {
488
+ result.error(
489
+ "launcher_date_settings_failed",
490
+ err.message ?: err.javaClass.simpleName,
491
+ null,
492
+ )
493
+ }
494
+ }
495
+
496
+ "openSystemSettings" -> {
497
+ try {
498
+ startActivity(
499
+ Intent(Settings.ACTION_SETTINGS)
500
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
501
+ )
502
+ result.success(null)
503
+ } catch (err: Exception) {
504
+ result.error(
505
+ "launcher_settings_failed",
506
+ err.message ?: err.javaClass.simpleName,
507
+ null,
508
+ )
509
+ }
510
+ }
511
+
512
+ "getDeviceStatus", "getBatteryState" -> {
513
+ result.success(buildDeviceStatusMap())
514
+ }
515
+
516
+ "getAppMode" -> result.success(currentAppMode())
517
+
518
+ else -> result.notImplemented()
519
+ }
520
+ }
521
+
522
+ MethodChannel(
523
+ flutterEngine.dartExecutor.binaryMessenger,
524
+ "neoagent/widgets",
525
+ ).setMethodCallHandler { call, result ->
526
+ when (call.method) {
527
+ "configureHomeWidgets" -> {
528
+ val args = call.arguments as? Map<*, *>
529
+ widgetSyncScheduler.configure(
530
+ enabled = args?.get("enabled") == true,
531
+ backendUrl = args?.get("backendUrl")?.toString().orEmpty(),
532
+ sessionCookie = args?.get("sessionCookie")?.toString().orEmpty(),
533
+ )
534
+ result.success(null)
535
+ }
536
+
537
+ "syncHomeWidgetsNow" -> {
538
+ widgetSyncScheduler.syncNow()
539
+ result.success(null)
540
+ }
541
+
542
+ else -> result.notImplemented()
543
+ }
544
+ }
545
+
546
+ EventChannel(
547
+ flutterEngine.dartExecutor.binaryMessenger,
548
+ "neoagent/launcher_buttons",
549
+ ).setStreamHandler(
550
+ object : EventChannel.StreamHandler {
551
+ override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
552
+ launcherButtonSink = events
553
+ }
554
+
555
+ override fun onCancel(arguments: Any?) {
556
+ launcherButtonSink = null
557
+ }
558
+ },
559
+ )
560
+
561
+ EventChannel(
562
+ flutterEngine.dartExecutor.binaryMessenger,
563
+ "neoagent/widgets/events",
564
+ ).setStreamHandler(
565
+ object : EventChannel.StreamHandler {
566
+ override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
567
+ widgetEventSink = events
568
+ emitPendingWidgetIntent()
569
+ }
570
+
571
+ override fun onCancel(arguments: Any?) {
572
+ widgetEventSink = null
573
+ }
574
+ },
575
+ )
576
+
577
+ EventChannel(
578
+ flutterEngine.dartExecutor.binaryMessenger,
579
+ "neoagent/app_launch/events",
580
+ ).setStreamHandler(
581
+ object : EventChannel.StreamHandler {
582
+ override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
583
+ appLaunchEventSink = events
584
+ emitPendingAppLaunchIntent()
585
+ }
586
+
587
+ override fun onCancel(arguments: Any?) {
588
+ appLaunchEventSink = null
589
+ }
590
+ },
591
+ )
592
+
593
+ captureWidgetIntent(intent)
594
+ captureAppLaunchIntent(intent)
595
+ captureShareIntent(intent)
596
+ }
597
+
598
+ override fun onNewIntent(intent: Intent) {
599
+ super.onNewIntent(intent)
600
+ setIntent(intent)
601
+ captureWidgetIntent(intent)
602
+ captureAppLaunchIntent(intent)
603
+ captureShareIntent(intent)
604
+ }
605
+
606
+ override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
607
+ emitLauncherButtonEvent(event, "down")
608
+ return super.onKeyDown(keyCode, event)
609
+ }
610
+
611
+ override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
612
+ emitLauncherButtonEvent(event, "up")
613
+ return super.onKeyUp(keyCode, event)
614
+ }
615
+
616
+ private fun startRecordingService(args: Map<*, *>?) {
617
+ val backendUrl = args?.get("backendUrl")?.toString().orEmpty()
618
+ val sessionCookie = args?.get("sessionCookie")?.toString().orEmpty()
619
+ val sessionId = args?.get("sessionId")?.toString().orEmpty()
620
+ val intent = RecordingForegroundService.buildStartIntent(
621
+ this,
622
+ backendUrl = backendUrl,
623
+ sessionCookie = sessionCookie,
624
+ sessionId = sessionId,
625
+ )
626
+ ContextCompat.startForegroundService(this, intent)
627
+ }
628
+
629
+ private fun currentAppMode(): String {
630
+ return try {
631
+ val appInfo = packageManager.getApplicationInfo(
632
+ packageName,
633
+ PackageManager.GET_META_DATA,
634
+ )
635
+ appInfo.metaData?.getString("com.neoagent.APP_MODE")?.trim().orEmpty()
636
+ .ifBlank { "standard" }
637
+ } catch (_: Exception) {
638
+ "standard"
639
+ }
640
+ }
641
+
642
+ private fun canRequestPackageInstalls(): Boolean {
643
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
644
+ packageManager.canRequestPackageInstalls()
645
+ } else {
646
+ true
647
+ }
648
+ }
649
+
650
+ private fun launchApkInstaller(apkPath: String): Boolean {
651
+ val apkFile = File(apkPath)
652
+ if (!apkFile.exists() || !apkFile.isFile) {
653
+ throw IllegalArgumentException("APK not found: $apkPath")
654
+ }
655
+ val contentUri = FileProvider.getUriForFile(
656
+ this,
657
+ "$packageName.fileprovider",
658
+ apkFile,
659
+ )
660
+ val installIntent = Intent(Intent.ACTION_VIEW).apply {
661
+ setDataAndType(contentUri, "application/vnd.android.package-archive")
662
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
663
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
664
+ putExtra(Intent.EXTRA_RETURN_RESULT, false)
665
+ }
666
+ if (installIntent.resolveActivity(packageManager) == null) {
667
+ throw IllegalStateException("No Android package installer is available.")
668
+ }
669
+ startActivity(installIntent)
670
+ return true
671
+ }
672
+
673
+ private fun shouldEmitLauncherButtonEvent(keyCode: Int): Boolean {
674
+ if (currentAppMode() != "launcher") {
675
+ return false
676
+ }
677
+ return keyCode != KeyEvent.KEYCODE_BACK &&
678
+ keyCode != KeyEvent.KEYCODE_HOME &&
679
+ keyCode != KeyEvent.KEYCODE_VOLUME_DOWN &&
680
+ keyCode != KeyEvent.KEYCODE_VOLUME_UP &&
681
+ keyCode != KeyEvent.KEYCODE_VOLUME_MUTE &&
682
+ keyCode != KeyEvent.KEYCODE_APP_SWITCH &&
683
+ keyCode != KeyEvent.KEYCODE_MENU
684
+ }
685
+
686
+ private fun emitLauncherButtonEvent(event: KeyEvent, action: String) {
687
+ if (!shouldEmitLauncherButtonEvent(event.keyCode)) {
688
+ return
689
+ }
690
+ launcherButtonSink?.success(
691
+ mapOf(
692
+ "keyCode" to event.keyCode,
693
+ "scanCode" to event.scanCode,
694
+ "repeatCount" to event.repeatCount,
695
+ "action" to action,
696
+ "eventTimeMs" to event.eventTime,
697
+ ),
698
+ )
699
+ }
700
+
701
+ private fun captureWidgetIntent(intent: Intent?) {
702
+ if (intent?.action != AiHomeWidgetProvider.ACTION_OPEN_WIDGET) {
703
+ return
704
+ }
705
+ val widgetId =
706
+ intent.getStringExtra(AiHomeWidgetProvider.EXTRA_WIDGET_ID)?.trim().orEmpty()
707
+ if (widgetId.isBlank()) {
708
+ return
709
+ }
710
+ AiWidgetStore(this).setPendingOpenWidgetId(widgetId)
711
+ emitPendingWidgetIntent()
712
+ }
713
+
714
+ private fun emitPendingWidgetIntent() {
715
+ val sink = widgetEventSink ?: return
716
+ val widgetId = AiWidgetStore(this).consumePendingOpenWidgetId() ?: return
717
+ sink.success(mapOf("widgetId" to widgetId))
718
+ }
719
+
720
+ private fun captureAppLaunchIntent(intent: Intent?) {
721
+ if (intent?.action != VoiceLaunchWidgetProvider.ACTION_OPEN_VOICE_ASSISTANT) {
722
+ return
723
+ }
724
+ pendingAppLaunchAction = VoiceLaunchWidgetProvider.OPEN_TARGET_VOICE_ASSISTANT
725
+ emitPendingAppLaunchIntent()
726
+ }
727
+
728
+ private fun emitPendingAppLaunchIntent() {
729
+ val sink = appLaunchEventSink ?: return
730
+ val sharePayload = pendingSharePayload
731
+ if (sharePayload != null) {
732
+ pendingSharePayload = null
733
+ sink.success(sharePayload)
734
+ return
735
+ }
736
+ val action = pendingAppLaunchAction ?: return
737
+ pendingAppLaunchAction = null
738
+ sink.success(mapOf("action" to action))
739
+ }
740
+
741
+ private fun captureShareIntent(intent: Intent?) {
742
+ if (intent == null) {
743
+ return
744
+ }
745
+ if (intent.action != Intent.ACTION_SEND && intent.action != Intent.ACTION_SEND_MULTIPLE) {
746
+ return
747
+ }
748
+
749
+ val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT)?.trim().orEmpty()
750
+ val subject = intent.getStringExtra(Intent.EXTRA_SUBJECT)?.trim().orEmpty()
751
+ val fileUris = mutableListOf<Uri>()
752
+
753
+ @Suppress("DEPRECATION")
754
+ val single = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
755
+ if (single != null) {
756
+ fileUris.add(single)
757
+ }
758
+ @Suppress("DEPRECATION")
759
+ val multiple = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
760
+ if (multiple != null) {
761
+ fileUris.addAll(multiple)
762
+ }
763
+ val clip = intent.clipData
764
+ if (clip != null) {
765
+ for (index in 0 until clip.itemCount) {
766
+ val uri = clip.getItemAt(index)?.uri ?: continue
767
+ fileUris.add(uri)
768
+ }
769
+ }
770
+
771
+ val uniqueUris = linkedSetOf<String>()
772
+ val files = mutableListOf<Map<String, Any?>>()
773
+ for (uri in fileUris) {
774
+ val key = uri.toString()
775
+ if (!uniqueUris.add(key)) continue
776
+ files.add(buildSharedFileDescriptor(uri))
777
+ }
778
+
779
+ pendingSharePayload = mapOf(
780
+ "action" to "share_to_chat",
781
+ "text" to sharedText,
782
+ "subject" to subject,
783
+ "files" to files,
784
+ )
785
+ emitPendingAppLaunchIntent()
786
+ }
787
+
788
+ private fun buildSharedFileDescriptor(uri: Uri): Map<String, Any?> {
789
+ var name: String? = null
790
+ var sizeBytes: Long? = null
791
+ val type = contentResolver.getType(uri)?.trim().orEmpty()
792
+ contentResolver.query(uri, null, null, null, null)?.use { cursor ->
793
+ val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
794
+ val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
795
+ if (cursor.moveToFirst()) {
796
+ if (nameIndex >= 0) {
797
+ name = cursor.getString(nameIndex)?.trim()
798
+ }
799
+ if (sizeIndex >= 0 && !cursor.isNull(sizeIndex)) {
800
+ sizeBytes = cursor.getLong(sizeIndex)
801
+ }
802
+ }
803
+ }
804
+ val fallbackName = uri.lastPathSegment?.substringAfterLast('/')?.trim().orEmpty()
805
+ val resolvedName = (name ?: fallbackName).ifBlank { "Attachment" }
806
+ return mapOf(
807
+ "uri" to uri.toString(),
808
+ "name" to resolvedName,
809
+ "mimeType" to if (type.isBlank()) "application/octet-stream" else type,
810
+ "sizeBytes" to sizeBytes,
811
+ "source" to "android_share_intent",
812
+ )
813
+ }
814
+
815
+ private fun buildVolumeState(audioManager: AudioManager): Map<String, Any> {
816
+ val minVolume =
817
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
818
+ audioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC)
819
+ } else {
820
+ 0
821
+ }
822
+ return mapOf(
823
+ "current" to audioManager.getStreamVolume(AudioManager.STREAM_MUSIC),
824
+ "max" to audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC),
825
+ "min" to minVolume,
826
+ "muted" to audioManager.isStreamMute(AudioManager.STREAM_MUSIC),
827
+ )
828
+ }
829
+
830
+ private fun buildDeviceStatusMap(): Map<String, Any?> {
831
+ val batteryStateIntent = registerReceiver(
832
+ null,
833
+ android.content.IntentFilter(Intent.ACTION_BATTERY_CHANGED),
834
+ )
835
+ val batteryLevel = batteryStateIntent?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1
836
+ val batteryScale = batteryStateIntent?.getIntExtra(BatteryManager.EXTRA_SCALE, -1) ?: -1
837
+ val batteryPercentFromIntent =
838
+ if (batteryLevel >= 0 && batteryScale > 0) {
839
+ ((batteryLevel * 100f) / batteryScale).toInt().coerceIn(0, 100)
840
+ } else {
841
+ null
842
+ }
843
+ val batteryManager = getSystemService(BatteryManager::class.java)
844
+ val batteryPercentFromManager = batteryManager
845
+ ?.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
846
+ ?.takeIf { it in 0..100 }
847
+ val batteryPercent = batteryPercentFromIntent ?: batteryPercentFromManager
848
+ val batteryStatus = batteryStateIntent?.getIntExtra(BatteryManager.EXTRA_STATUS, -1) ?: -1
849
+ val plugged = batteryStateIntent?.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) ?: 0
850
+ val charging =
851
+ batteryStatus == BatteryManager.BATTERY_STATUS_CHARGING ||
852
+ batteryStatus == BatteryManager.BATTERY_STATUS_FULL ||
853
+ plugged != 0
854
+ return mapOf(
855
+ "batteryPercent" to batteryPercent,
856
+ "charging" to charging,
857
+ )
858
+ }
859
+
860
+ private suspend fun buildStatusMap(): Map<String, Any?> {
861
+ val available = healthGateway.isAvailable()
862
+ val client = healthGateway.getClientOrNull()
863
+ val required = if (client != null) {
864
+ healthGateway.getRequestedPermissions(client).toList()
865
+ } else {
866
+ emptyList()
867
+ }
868
+ val granted = if (client != null) {
869
+ client.permissionController.getGrantedPermissions().toList()
870
+ } else {
871
+ emptyList()
872
+ }
873
+
874
+ val message = when {
875
+ !available -> "Health Connect is unavailable on this device."
876
+ !granted.containsAll(required) -> "Permissions are required for sync."
877
+ else -> "Health sync is ready."
878
+ }
879
+
880
+ return mapOf(
881
+ "available" to available,
882
+ "permissionsGranted" to granted.containsAll(required),
883
+ "requiredPermissions" to required,
884
+ "grantedPermissions" to granted,
885
+ "message" to message,
886
+ )
887
+ }
888
+ }