neoagent 2.3.1-beta.6 → 2.3.1-beta.61

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 (312) hide show
  1. package/.env.example +45 -0
  2. package/docs/capabilities.md +2 -2
  3. package/docs/configuration.md +6 -5
  4. package/docs/hardware.md +1 -1
  5. package/docs/integrations.md +2 -3
  6. package/flutter_app/.metadata +42 -0
  7. package/flutter_app/README.md +21 -0
  8. package/flutter_app/analysis_options.yaml +32 -0
  9. package/flutter_app/android/app/build.gradle.kts +111 -0
  10. package/flutter_app/android/app/src/debug/AndroidManifest.xml +7 -0
  11. package/flutter_app/android/app/src/main/AndroidManifest.xml +171 -0
  12. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/MainActivity.kt +804 -0
  13. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/auto/NeoAgentCarAppService.kt +114 -0
  14. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthConnectGateway.kt +280 -0
  15. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncNotifications.kt +113 -0
  16. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncPayload.kt +57 -0
  17. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncScheduler.kt +78 -0
  18. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncWorker.kt +253 -0
  19. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/PermissionsRationaleActivity.kt +46 -0
  20. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingBootReceiver.kt +21 -0
  21. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingForegroundService.kt +586 -0
  22. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingStateStore.kt +78 -0
  23. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingUploadClient.kt +104 -0
  24. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/telecom/NeoAgentConnection.kt +118 -0
  25. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/telecom/NeoAgentConnectionService.kt +86 -0
  26. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiHomeWidgetProvider.kt +457 -0
  27. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiWidgetStore.kt +194 -0
  28. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/VoiceLaunchWidgetProvider.kt +67 -0
  29. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetConfigActivity.kt +228 -0
  30. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncScheduler.kt +72 -0
  31. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncWorker.kt +186 -0
  32. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetTaskRunWorker.kt +210 -0
  33. package/flutter_app/android/app/src/main/res/drawable/launch_background.xml +12 -0
  34. package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_bg.xml +11 -0
  35. package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_task_bg.xml +8 -0
  36. package/flutter_app/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
  37. package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget.xml +138 -0
  38. package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget_task_row.xml +52 -0
  39. package/flutter_app/android/app/src/main/res/layout/neoagent_voice_widget.xml +49 -0
  40. package/flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  41. package/flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  42. package/flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  43. package/flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  44. package/flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  45. package/flutter_app/android/app/src/main/res/values/arrays.xml +8 -0
  46. package/flutter_app/android/app/src/main/res/values/strings.xml +12 -0
  47. package/flutter_app/android/app/src/main/res/values/styles.xml +18 -0
  48. package/flutter_app/android/app/src/main/res/values-night/styles.xml +18 -0
  49. package/flutter_app/android/app/src/main/res/xml/automotive_app_desc.xml +4 -0
  50. package/flutter_app/android/app/src/main/res/xml/file_paths.xml +6 -0
  51. package/flutter_app/android/app/src/main/res/xml/neoagent_ai_widget_info.xml +12 -0
  52. package/flutter_app/android/app/src/main/res/xml/neoagent_voice_widget_info.xml +12 -0
  53. package/flutter_app/android/app/src/profile/AndroidManifest.xml +7 -0
  54. package/flutter_app/android/build.gradle.kts +24 -0
  55. package/flutter_app/android/ci-release.keystore +0 -0
  56. package/flutter_app/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  57. package/flutter_app/android/gradle.properties +3 -0
  58. package/flutter_app/android/key.properties +4 -0
  59. package/flutter_app/android/settings.gradle.kts +26 -0
  60. package/flutter_app/assets/branding/app_icon_1024.png +0 -0
  61. package/flutter_app/assets/branding/app_icon_128.png +0 -0
  62. package/flutter_app/assets/branding/app_icon_192.png +0 -0
  63. package/flutter_app/assets/branding/app_icon_256.png +0 -0
  64. package/flutter_app/assets/branding/app_icon_32.png +0 -0
  65. package/flutter_app/assets/branding/app_icon_512.png +0 -0
  66. package/flutter_app/assets/branding/app_icon_64.png +0 -0
  67. package/flutter_app/assets/branding/onboarding_intro.mp4 +0 -0
  68. package/flutter_app/assets/branding/tray_icon_template.png +0 -0
  69. package/flutter_app/lib/features/location/location_service.dart +119 -0
  70. package/flutter_app/lib/features/notifications/notification_interceptor.dart +97 -0
  71. package/flutter_app/lib/features/onboarding/onboarding_chrome.dart +571 -0
  72. package/flutter_app/lib/features/onboarding/onboarding_messaging_step.dart +275 -0
  73. package/flutter_app/lib/features/onboarding/onboarding_model_step.dart +366 -0
  74. package/flutter_app/lib/features/onboarding/onboarding_shell.dart +57 -0
  75. package/flutter_app/lib/features/onboarding/onboarding_video_step.dart +218 -0
  76. package/flutter_app/lib/features/onboarding/onboarding_welcome_step.dart +46 -0
  77. package/flutter_app/lib/main.dart +103 -0
  78. package/flutter_app/lib/main_account_settings.dart +1250 -0
  79. package/flutter_app/lib/main_admin.dart +886 -0
  80. package/flutter_app/lib/main_app_shell.dart +2134 -0
  81. package/flutter_app/lib/main_chat.dart +3316 -0
  82. package/flutter_app/lib/main_controller.dart +6836 -0
  83. package/flutter_app/lib/main_devices.dart +2396 -0
  84. package/flutter_app/lib/main_integrations.dart +909 -0
  85. package/flutter_app/lib/main_launcher.dart +959 -0
  86. package/flutter_app/lib/main_launcher_entry.dart +5 -0
  87. package/flutter_app/lib/main_models.dart +3539 -0
  88. package/flutter_app/lib/main_navigation.dart +188 -0
  89. package/flutter_app/lib/main_operations.dart +4851 -0
  90. package/flutter_app/lib/main_recordings.dart +920 -0
  91. package/flutter_app/lib/main_runtime.dart +838 -0
  92. package/flutter_app/lib/main_settings.dart +2040 -0
  93. package/flutter_app/lib/main_shared.dart +3147 -0
  94. package/flutter_app/lib/main_theme.dart +257 -0
  95. package/flutter_app/lib/main_voice_assistant.dart +957 -0
  96. package/flutter_app/lib/src/android_apk_drop_zone.dart +32 -0
  97. package/flutter_app/lib/src/android_apk_drop_zone_stub.dart +16 -0
  98. package/flutter_app/lib/src/android_apk_drop_zone_web.dart +348 -0
  99. package/flutter_app/lib/src/android_app_installer.dart +22 -0
  100. package/flutter_app/lib/src/android_app_installer_io.dart +122 -0
  101. package/flutter_app/lib/src/android_app_installer_stub.dart +21 -0
  102. package/flutter_app/lib/src/android_auto_bridge.dart +59 -0
  103. package/flutter_app/lib/src/android_launcher_bridge.dart +239 -0
  104. package/flutter_app/lib/src/app_launch_bridge.dart +29 -0
  105. package/flutter_app/lib/src/app_release_updater.dart +511 -0
  106. package/flutter_app/lib/src/backend_client.dart +1837 -0
  107. package/flutter_app/lib/src/desktop_companion.dart +2 -0
  108. package/flutter_app/lib/src/desktop_companion_actions.dart +586 -0
  109. package/flutter_app/lib/src/desktop_companion_io.dart +538 -0
  110. package/flutter_app/lib/src/desktop_companion_stub.dart +59 -0
  111. package/flutter_app/lib/src/desktop_native_bridge.dart +91 -0
  112. package/flutter_app/lib/src/desktop_screen_capture.dart +21 -0
  113. package/flutter_app/lib/src/desktop_screen_capture_io.dart +142 -0
  114. package/flutter_app/lib/src/desktop_screen_capture_stub.dart +12 -0
  115. package/flutter_app/lib/src/diagnostics_logger.dart +119 -0
  116. package/flutter_app/lib/src/health_bridge.dart +136 -0
  117. package/flutter_app/lib/src/live_voice_capture.dart +85 -0
  118. package/flutter_app/lib/src/messaging_access_summary.dart +46 -0
  119. package/flutter_app/lib/src/network/app_http_client.dart +53 -0
  120. package/flutter_app/lib/src/network/app_http_client_factory.dart +6 -0
  121. package/flutter_app/lib/src/network/app_http_client_io.dart +138 -0
  122. package/flutter_app/lib/src/network/app_http_client_stub.dart +3 -0
  123. package/flutter_app/lib/src/network/app_http_client_web.dart +94 -0
  124. package/flutter_app/lib/src/oauth_launcher.dart +33 -0
  125. package/flutter_app/lib/src/oauth_launcher_io.dart +95 -0
  126. package/flutter_app/lib/src/oauth_launcher_stub.dart +33 -0
  127. package/flutter_app/lib/src/oauth_launcher_web.dart +107 -0
  128. package/flutter_app/lib/src/recording_bridge.dart +232 -0
  129. package/flutter_app/lib/src/recording_bridge_io.dart +1019 -0
  130. package/flutter_app/lib/src/recording_bridge_stub.dart +120 -0
  131. package/flutter_app/lib/src/recording_bridge_web.dart +689 -0
  132. package/flutter_app/lib/src/recording_payloads.dart +86 -0
  133. package/flutter_app/lib/src/theme/palette.dart +81 -0
  134. package/flutter_app/lib/src/web_app_update_monitor.dart +17 -0
  135. package/flutter_app/lib/src/web_app_update_monitor_stub.dart +24 -0
  136. package/flutter_app/lib/src/web_app_update_monitor_web.dart +123 -0
  137. package/flutter_app/lib/src/widget_bridge.dart +49 -0
  138. package/flutter_app/linux/CMakeLists.txt +128 -0
  139. package/flutter_app/linux/flutter/CMakeLists.txt +88 -0
  140. package/flutter_app/linux/flutter/generated_plugin_registrant.cc +47 -0
  141. package/flutter_app/linux/flutter/generated_plugin_registrant.h +15 -0
  142. package/flutter_app/linux/flutter/generated_plugins.cmake +32 -0
  143. package/flutter_app/linux/runner/CMakeLists.txt +26 -0
  144. package/flutter_app/linux/runner/main.cc +6 -0
  145. package/flutter_app/linux/runner/my_application.cc +144 -0
  146. package/flutter_app/linux/runner/my_application.h +18 -0
  147. package/flutter_app/linux/runner/resources/app_icon.png +0 -0
  148. package/flutter_app/macos/Flutter/Flutter-Debug.xcconfig +2 -0
  149. package/flutter_app/macos/Flutter/Flutter-Release.xcconfig +2 -0
  150. package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +44 -0
  151. package/flutter_app/macos/Podfile +42 -0
  152. package/flutter_app/macos/Podfile.lock +87 -0
  153. package/flutter_app/macos/Runner/AppDelegate.swift +576 -0
  154. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +68 -0
  155. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
  156. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
  157. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
  158. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
  159. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
  160. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
  161. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
  162. package/flutter_app/macos/Runner/Base.lproj/MainMenu.xib +342 -0
  163. package/flutter_app/macos/Runner/Configs/AppInfo.xcconfig +14 -0
  164. package/flutter_app/macos/Runner/Configs/Debug.xcconfig +2 -0
  165. package/flutter_app/macos/Runner/Configs/Release.xcconfig +2 -0
  166. package/flutter_app/macos/Runner/Configs/Warnings.xcconfig +13 -0
  167. package/flutter_app/macos/Runner/DebugProfile.entitlements +16 -0
  168. package/flutter_app/macos/Runner/Info.plist +36 -0
  169. package/flutter_app/macos/Runner/MainFlutterWindow.swift +19 -0
  170. package/flutter_app/macos/Runner/Release.entitlements +12 -0
  171. package/flutter_app/macos/Runner.xcodeproj/project.pbxproj +801 -0
  172. package/flutter_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  173. package/flutter_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +99 -0
  174. package/flutter_app/macos/Runner.xcworkspace/contents.xcworkspacedata +10 -0
  175. package/flutter_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  176. package/flutter_app/macos/RunnerTests/RunnerTests.swift +12 -0
  177. package/flutter_app/patch_strings.py +12 -0
  178. package/flutter_app/pubspec.lock +1224 -0
  179. package/flutter_app/pubspec.yaml +57 -0
  180. package/flutter_app/test/messaging_access_summary_test.dart +22 -0
  181. package/flutter_app/test/recording_payloads_test.dart +53 -0
  182. package/flutter_app/third_party/desktop_audio_capture/LICENSE +21 -0
  183. package/flutter_app/third_party/desktop_audio_capture/README.md +262 -0
  184. package/flutter_app/third_party/desktop_audio_capture/lib/audio_capture.dart +65 -0
  185. package/flutter_app/third_party/desktop_audio_capture/lib/config/mic_audio_config.dart +153 -0
  186. package/flutter_app/third_party/desktop_audio_capture/lib/config/system_adudio_config.dart +110 -0
  187. package/flutter_app/third_party/desktop_audio_capture/lib/mic/mic_audio_capture.dart +461 -0
  188. package/flutter_app/third_party/desktop_audio_capture/lib/model/audio_status.dart +91 -0
  189. package/flutter_app/third_party/desktop_audio_capture/lib/model/decibel_data.dart +106 -0
  190. package/flutter_app/third_party/desktop_audio_capture/lib/model/input_device_type.dart +219 -0
  191. package/flutter_app/third_party/desktop_audio_capture/lib/system/system_audio_capture.dart +336 -0
  192. package/flutter_app/third_party/desktop_audio_capture/linux/CMakeLists.txt +101 -0
  193. package/flutter_app/third_party/desktop_audio_capture/linux/audio_capture_plugin.cc +692 -0
  194. package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/audio_capture_plugin.h +35 -0
  195. package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/mic_capture_plugin.h +36 -0
  196. package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/audio_capture_plugin.h +32 -0
  197. package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/mic_capture_plugin.h +32 -0
  198. package/flutter_app/third_party/desktop_audio_capture/linux/mic_capture_plugin.cc +878 -0
  199. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/AudioCapturePlugin.swift +27 -0
  200. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/MicCapturePlugin.swift +1172 -0
  201. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/SystemCapturePlugin.swift +655 -0
  202. package/flutter_app/third_party/desktop_audio_capture/macos/Resources/PrivacyInfo.xcprivacy +12 -0
  203. package/flutter_app/third_party/desktop_audio_capture/macos/desktop_audio_capture.podspec +30 -0
  204. package/flutter_app/third_party/desktop_audio_capture/pubspec.yaml +87 -0
  205. package/flutter_app/third_party/desktop_audio_capture/windows/CMakeLists.txt +105 -0
  206. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.cpp +80 -0
  207. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.h +31 -0
  208. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin_c_api.cpp +12 -0
  209. package/flutter_app/third_party/desktop_audio_capture/windows/include/audio_capture/audio_capture_plugin_c_api.h +23 -0
  210. package/flutter_app/third_party/desktop_audio_capture/windows/include/desktop_audio_capture/audio_capture_plugin.h +25 -0
  211. package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.cpp +1117 -0
  212. package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.h +115 -0
  213. package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.cpp +777 -0
  214. package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.h +87 -0
  215. package/flutter_app/third_party/flutter_secure_storage_linux/linux/CMakeLists.txt +30 -0
  216. package/flutter_app/third_party/flutter_secure_storage_linux/linux/flutter_secure_storage_linux_plugin.cc +215 -0
  217. package/flutter_app/third_party/flutter_secure_storage_linux/linux/include/flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h +27 -0
  218. package/flutter_app/third_party/flutter_secure_storage_linux/pubspec.yaml +20 -0
  219. package/flutter_app/tool/generate_desktop_branding.py +219 -0
  220. package/flutter_app/web/favicon.png +0 -0
  221. package/flutter_app/web/favicon.svg +12 -0
  222. package/flutter_app/web/icons/Icon-192.png +0 -0
  223. package/flutter_app/web/icons/Icon-512.png +0 -0
  224. package/flutter_app/web/icons/Icon-maskable-192.png +0 -0
  225. package/flutter_app/web/icons/Icon-maskable-512.png +0 -0
  226. package/flutter_app/web/index.html +39 -0
  227. package/flutter_app/web/manifest.json +35 -0
  228. package/flutter_app/windows/CMakeLists.txt +108 -0
  229. package/flutter_app/windows/flutter/CMakeLists.txt +109 -0
  230. package/flutter_app/windows/flutter/generated_plugin_registrant.cc +50 -0
  231. package/flutter_app/windows/flutter/generated_plugin_registrant.h +15 -0
  232. package/flutter_app/windows/flutter/generated_plugins.cmake +36 -0
  233. package/flutter_app/windows/runner/CMakeLists.txt +41 -0
  234. package/flutter_app/windows/runner/Runner.rc +121 -0
  235. package/flutter_app/windows/runner/flutter_window.cpp +533 -0
  236. package/flutter_app/windows/runner/flutter_window.h +37 -0
  237. package/flutter_app/windows/runner/main.cpp +53 -0
  238. package/flutter_app/windows/runner/resource.h +16 -0
  239. package/flutter_app/windows/runner/resources/app_icon.ico +0 -0
  240. package/flutter_app/windows/runner/runner.exe.manifest +14 -0
  241. package/flutter_app/windows/runner/utils.cpp +65 -0
  242. package/flutter_app/windows/runner/utils.h +19 -0
  243. package/flutter_app/windows/runner/win32_window.cpp +299 -0
  244. package/flutter_app/windows/runner/win32_window.h +102 -0
  245. package/lib/install_helpers.js +31 -0
  246. package/lib/manager.js +201 -6
  247. package/package.json +5 -3
  248. package/server/db/database.js +125 -1
  249. package/server/http/middleware.js +55 -2
  250. package/server/http/routes.js +1 -0
  251. package/server/http/static.js +41 -1
  252. package/server/index.js +3 -0
  253. package/server/public/.last_build_id +1 -1
  254. package/server/public/assets/AssetManifest.bin +1 -1
  255. package/server/public/assets/AssetManifest.bin.json +1 -1
  256. package/server/public/assets/NOTICES +281 -1
  257. package/server/public/assets/assets/branding/onboarding_intro.mp4 +0 -0
  258. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  259. package/server/public/canvaskit/wimp.wasm +0 -0
  260. package/server/public/flutter_bootstrap.js +2 -2
  261. package/server/public/main.dart.js +86707 -82621
  262. package/server/routes/auth.js +26 -5
  263. package/server/routes/integrations.js +108 -1
  264. package/server/routes/memory.js +11 -2
  265. package/server/routes/settings.js +84 -2
  266. package/server/routes/wearable.js +67 -0
  267. package/server/services/ai/engine.js +3 -2
  268. package/server/services/ai/models.js +30 -0
  269. package/server/services/ai/providers/githubCopilot.js +97 -0
  270. package/server/services/ai/providers/openai.js +2 -1
  271. package/server/services/ai/providers/openaiCodex.js +31 -0
  272. package/server/services/ai/settings.js +20 -0
  273. package/server/services/ai/toolSelector.js +54 -1
  274. package/server/services/ai/tools.js +88 -5
  275. package/server/services/desktop/screenRecorder.js +65 -9
  276. package/server/services/integrations/env.js +5 -0
  277. package/server/services/integrations/figma/provider.js +1 -0
  278. package/server/services/integrations/github/common.js +106 -0
  279. package/server/services/integrations/github/provider.js +499 -0
  280. package/server/services/integrations/github/repos.js +1124 -0
  281. package/server/services/integrations/google/provider.js +1 -0
  282. package/server/services/integrations/manager.js +88 -12
  283. package/server/services/integrations/microsoft/provider.js +1 -0
  284. package/server/services/integrations/oauth_provider.js +25 -8
  285. package/server/services/integrations/provider_config_store.js +85 -0
  286. package/server/services/integrations/registry.js +4 -2
  287. package/server/services/integrations/spotify/provider.js +1 -0
  288. package/server/services/integrations/trello/provider.js +842 -0
  289. package/server/services/manager.js +46 -1
  290. package/server/services/mcp/client.js +120 -23
  291. package/server/services/memory/manager.js +39 -2
  292. package/server/services/messaging/access_policy.js +10 -0
  293. package/server/services/messaging/manager.js +49 -0
  294. package/server/services/messaging/meshtastic.js +277 -0
  295. package/server/services/messaging/meshtastic_env.js +40 -0
  296. package/server/services/messaging/meshtastic_protocol.js +530 -0
  297. package/server/services/messaging/meshtastic_protocol.test.js +33 -0
  298. package/server/services/messaging/meshtastic_tcp_transport.js +25 -0
  299. package/server/services/tasks/runtime.js +1 -1
  300. package/server/services/voice/openaiClient.js +4 -1
  301. package/server/services/voice/openaiSpeech.js +6 -1
  302. package/server/services/voice/providers.js +144 -15
  303. package/server/services/voice/runtimeManager.js +156 -19
  304. package/server/services/voice/turnRunner.js +29 -9
  305. package/server/services/wearable/firmware_manifest.js +353 -0
  306. package/server/services/wearable/gateway.js +350 -0
  307. package/server/services/wearable/protocol.js +45 -0
  308. package/server/services/wearable/service.js +240 -0
  309. package/server/services/widgets/service.js +3 -0
  310. package/server/utils/local_secrets.js +56 -0
  311. package/server/utils/logger.js +37 -9
  312. package/server/services/integrations/home_assistant/provider.js +0 -350
@@ -0,0 +1,804 @@
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.view.KeyEvent
12
+ import androidx.activity.result.ActivityResultLauncher
13
+ import androidx.activity.result.contract.ActivityResultContracts
14
+ import androidx.core.content.ContextCompat
15
+ import androidx.core.content.FileProvider
16
+ import androidx.health.connect.client.PermissionController
17
+ import androidx.lifecycle.lifecycleScope
18
+ import com.neoagent.flutter_app.health.HealthConnectGateway
19
+ import com.neoagent.flutter_app.health.HealthSyncScheduler
20
+ import com.neoagent.flutter_app.recording.RecordingForegroundService
21
+ import com.neoagent.flutter_app.recording.RecordingStateStore
22
+ import com.neoagent.flutter_app.widgets.AiHomeWidgetProvider
23
+ import com.neoagent.flutter_app.widgets.AiWidgetStore
24
+ import com.neoagent.flutter_app.widgets.VoiceLaunchWidgetProvider
25
+ import com.neoagent.flutter_app.widgets.WidgetSyncScheduler
26
+ import io.flutter.embedding.android.FlutterFragmentActivity
27
+ import io.flutter.embedding.engine.FlutterEngine
28
+ import io.flutter.plugin.common.EventChannel
29
+ import android.telecom.PhoneAccount
30
+ import android.telecom.PhoneAccountHandle
31
+ import android.telecom.TelecomManager
32
+ import android.content.ComponentName
33
+ import android.os.Bundle
34
+ import android.telecom.DisconnectCause
35
+ import android.content.Context
36
+ import com.neoagent.flutter_app.telecom.NeoAgentConnectionService
37
+ import io.flutter.plugin.common.MethodChannel
38
+ import io.flutter.plugins.GeneratedPluginRegistrant
39
+ import kotlinx.coroutines.launch
40
+ import java.io.File
41
+ import java.time.Instant
42
+
43
+ class MainActivity : FlutterFragmentActivity() {
44
+
45
+ private lateinit var healthGateway: HealthConnectGateway
46
+ private lateinit var healthSyncScheduler: HealthSyncScheduler
47
+ private lateinit var widgetSyncScheduler: WidgetSyncScheduler
48
+ private lateinit var recordingStateStore: RecordingStateStore
49
+ private lateinit var permissionLauncher: ActivityResultLauncher<Set<String>>
50
+ private lateinit var microphonePermissionLauncher: ActivityResultLauncher<String>
51
+ private var pendingPermissionResult: MethodChannel.Result? = null
52
+ private var pendingRecordingResult: MethodChannel.Result? = null
53
+ private var pendingRecordingArgs: Map<*, *>? = null
54
+ private var launcherButtonSink: EventChannel.EventSink? = null
55
+ private var widgetEventSink: EventChannel.EventSink? = null
56
+ private var appLaunchEventSink: EventChannel.EventSink? = null
57
+ private var pendingAppLaunchAction: String? = null
58
+
59
+ override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
60
+ super.configureFlutterEngine(flutterEngine)
61
+ io.flutter.embedding.engine.FlutterEngineCache.getInstance().put("main_engine", flutterEngine)
62
+ GeneratedPluginRegistrant.registerWith(flutterEngine)
63
+
64
+ healthGateway = HealthConnectGateway(this)
65
+ healthSyncScheduler = HealthSyncScheduler(this)
66
+ widgetSyncScheduler = WidgetSyncScheduler(this)
67
+
68
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
69
+ val telecomManager = getSystemService(Context.TELECOM_SERVICE) as TelecomManager
70
+ val componentName = ComponentName(this, NeoAgentConnectionService::class.java)
71
+ val phoneAccountHandle = PhoneAccountHandle(componentName, "NeoAgentVoiceId")
72
+ val phoneAccount = PhoneAccount.builder(phoneAccountHandle, "NeoAgent Voice Assistant")
73
+ .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
74
+ .build()
75
+ telecomManager.registerPhoneAccount(phoneAccount)
76
+ }
77
+ recordingStateStore = RecordingStateStore(this)
78
+ permissionLauncher = registerForActivityResult(
79
+ PermissionController.createRequestPermissionResultContract(),
80
+ ) {
81
+ val pending = pendingPermissionResult
82
+ pendingPermissionResult = null
83
+ lifecycleScope.launch {
84
+ pending?.success(buildStatusMap())
85
+ }
86
+ }
87
+ microphonePermissionLauncher = registerForActivityResult(
88
+ ActivityResultContracts.RequestPermission(),
89
+ ) { granted ->
90
+ val pending = pendingRecordingResult
91
+ val args = pendingRecordingArgs
92
+ pendingRecordingResult = null
93
+ pendingRecordingArgs = null
94
+ if (!granted) {
95
+ pending?.error(
96
+ "recording_permission_denied",
97
+ "Microphone permission is required for background recording.",
98
+ null,
99
+ )
100
+ return@registerForActivityResult
101
+ }
102
+ try {
103
+ startRecordingService(args)
104
+ pending?.success(recordingStateStore.statusMap())
105
+ } catch (err: Exception) {
106
+ pending?.error(
107
+ "recording_start_failed",
108
+ err.message ?: err.javaClass.simpleName,
109
+ null,
110
+ )
111
+ }
112
+ }
113
+ MethodChannel(
114
+ flutterEngine.dartExecutor.binaryMessenger,
115
+ "neoagent/telecom",
116
+ ).setMethodCallHandler { call, result ->
117
+ when (call.method) {
118
+ "startCallRouting" -> {
119
+ try {
120
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
121
+ result.error("UNSUPPORTED", "Self-managed calls require Android O or newer", null)
122
+ return@setMethodCallHandler
123
+ }
124
+ val telecomManager = getSystemService(Context.TELECOM_SERVICE) as TelecomManager
125
+ val componentName = ComponentName(this, NeoAgentConnectionService::class.java)
126
+ val phoneAccountHandle = PhoneAccountHandle(componentName, "NeoAgentVoiceId")
127
+ val extras = Bundle().apply {
128
+ putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle)
129
+ putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, android.telecom.VideoProfile.STATE_AUDIO_ONLY)
130
+ putBoolean("is_flutter_initiated", true)
131
+ }
132
+ telecomManager.placeCall(Uri.parse("tel:NeoAgent"), extras)
133
+ result.success(true)
134
+ } catch (e: SecurityException) {
135
+ result.error("PERMISSION_DENIED", "Missing Manage Own Calls permission", null)
136
+ } catch (e: Exception) {
137
+ result.error("ERROR", e.message, null)
138
+ }
139
+ }
140
+ "stopCallRouting" -> {
141
+ NeoAgentConnectionService.getAndClearCurrentConnection()?.let {
142
+ it.setDisconnected(DisconnectCause(DisconnectCause.LOCAL))
143
+ it.destroy()
144
+ }
145
+ result.success(true)
146
+ }
147
+ else -> result.notImplemented()
148
+ }
149
+ }
150
+
151
+ MethodChannel(
152
+ flutterEngine.dartExecutor.binaryMessenger,
153
+ "neoagent/health",
154
+ ).setMethodCallHandler { call, result ->
155
+ when (call.method) {
156
+ "status" -> lifecycleScope.launch {
157
+ result.success(buildStatusMap())
158
+ }
159
+
160
+ "requestPermissions" -> lifecycleScope.launch {
161
+ val client = healthGateway.getClientOrNull()
162
+ if (client == null) {
163
+ result.error(
164
+ "health_unavailable",
165
+ "Health Connect is unavailable on this device.",
166
+ null,
167
+ )
168
+ return@launch
169
+ }
170
+
171
+ pendingPermissionResult = result
172
+ permissionLauncher.launch(healthGateway.getRequestedPermissions(client))
173
+ }
174
+
175
+ "collectBatch" -> lifecycleScope.launch {
176
+ try {
177
+ val client = healthGateway.getClientOrNull()
178
+ if (client == null) {
179
+ result.error(
180
+ "health_unavailable",
181
+ "Health Connect is unavailable on this device.",
182
+ null,
183
+ )
184
+ return@launch
185
+ }
186
+
187
+ val required = healthGateway.getRequestedPermissions(client)
188
+ val granted = client.permissionController.getGrantedPermissions()
189
+ if (!granted.containsAll(required)) {
190
+ result.error(
191
+ "health_permissions",
192
+ "Grant Health Connect permissions before syncing.",
193
+ null,
194
+ )
195
+ return@launch
196
+ }
197
+
198
+ val args = call.arguments as? Map<*, *>
199
+ val windowStartRaw = args?.get("windowStart")?.toString()
200
+ val windowEndRaw = args?.get("windowEnd")?.toString()
201
+ if (windowStartRaw.isNullOrBlank() || windowEndRaw.isNullOrBlank()) {
202
+ result.error(
203
+ "health_sync_window",
204
+ "windowStart and windowEnd are required.",
205
+ null,
206
+ )
207
+ return@launch
208
+ }
209
+ val windowStart =
210
+ try {
211
+ Instant.parse(windowStartRaw)
212
+ } catch (_: Exception) {
213
+ result.error(
214
+ "health_sync_window",
215
+ "windowStart must be an ISO-8601 timestamp.",
216
+ null,
217
+ )
218
+ return@launch
219
+ }
220
+ val windowEnd =
221
+ try {
222
+ Instant.parse(windowEndRaw)
223
+ } catch (_: Exception) {
224
+ result.error(
225
+ "health_sync_window",
226
+ "windowEnd must be an ISO-8601 timestamp.",
227
+ null,
228
+ )
229
+ return@launch
230
+ }
231
+ val payload = healthGateway.collectBatch(client, windowStart, windowEnd)
232
+ result.success(payload.toJson().toString())
233
+ } catch (err: Exception) {
234
+ result.error(
235
+ "health_sync_failed",
236
+ err.message ?: err.javaClass.simpleName,
237
+ null,
238
+ )
239
+ }
240
+ }
241
+
242
+ "configureBackgroundSync" -> {
243
+ val args = call.arguments as? Map<*, *>
244
+ val enabled = args?.get("enabled") == true
245
+ val backendUrl = args?.get("backendUrl")?.toString().orEmpty()
246
+ val sessionCookie = args?.get("sessionCookie")?.toString().orEmpty()
247
+ healthSyncScheduler.configure(
248
+ enabled = enabled,
249
+ backendUrl = backendUrl,
250
+ sessionCookie = sessionCookie,
251
+ )
252
+ result.success(null)
253
+ }
254
+
255
+ else -> result.notImplemented()
256
+ }
257
+ }
258
+
259
+ MethodChannel(
260
+ flutterEngine.dartExecutor.binaryMessenger,
261
+ "neoagent/recordings",
262
+ ).setMethodCallHandler { call, result ->
263
+ when (call.method) {
264
+ "status" -> result.success(recordingStateStore.statusMap())
265
+
266
+ "startBackgroundRecording" -> {
267
+ try {
268
+ val args = call.arguments as? Map<*, *>
269
+ if (ContextCompat.checkSelfPermission(
270
+ this,
271
+ android.Manifest.permission.RECORD_AUDIO,
272
+ ) == android.content.pm.PackageManager.PERMISSION_GRANTED
273
+ ) {
274
+ startRecordingService(args)
275
+ result.success(recordingStateStore.statusMap())
276
+ } else {
277
+ pendingRecordingResult = result
278
+ pendingRecordingArgs = args
279
+ microphonePermissionLauncher.launch(android.Manifest.permission.RECORD_AUDIO)
280
+ }
281
+ } catch (err: Exception) {
282
+ result.error(
283
+ "recording_start_failed",
284
+ err.message ?: err.javaClass.simpleName,
285
+ null,
286
+ )
287
+ }
288
+ }
289
+
290
+ "pauseBackgroundRecording" -> {
291
+ startService(RecordingForegroundService.buildPauseIntent(this))
292
+ result.success(recordingStateStore.statusMap())
293
+ }
294
+
295
+ "resumeBackgroundRecording" -> {
296
+ startService(RecordingForegroundService.buildResumeIntent(this))
297
+ result.success(recordingStateStore.statusMap())
298
+ }
299
+
300
+ "stopBackgroundRecording" -> {
301
+ startService(RecordingForegroundService.buildStopIntent(this))
302
+ result.success(recordingStateStore.statusMap())
303
+ }
304
+
305
+ else -> result.notImplemented()
306
+ }
307
+ }
308
+
309
+ MethodChannel(
310
+ flutterEngine.dartExecutor.binaryMessenger,
311
+ "neoagent/app_update",
312
+ ).setMethodCallHandler { call, result ->
313
+ when (call.method) {
314
+ "canRequestPackageInstalls" -> {
315
+ result.success(canRequestPackageInstalls())
316
+ }
317
+
318
+ "openInstallUnknownAppsSettings" -> {
319
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
320
+ result.error(
321
+ "app_update_unknown_sources_unsupported",
322
+ "Install unknown apps settings require Android 8.0 or newer.",
323
+ null,
324
+ )
325
+ return@setMethodCallHandler
326
+ }
327
+ try {
328
+ val intent = Intent(
329
+ Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,
330
+ Uri.parse("package:$packageName"),
331
+ ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
332
+ startActivity(intent)
333
+ result.success(null)
334
+ } catch (err: ActivityNotFoundException) {
335
+ result.error(
336
+ "app_update_unknown_sources_failed",
337
+ err.message ?: err.javaClass.simpleName,
338
+ null,
339
+ )
340
+ } catch (err: Exception) {
341
+ result.error(
342
+ "app_update_unknown_sources_failed",
343
+ err.message ?: err.javaClass.simpleName,
344
+ null,
345
+ )
346
+ }
347
+ }
348
+
349
+ "installApk" -> {
350
+ try {
351
+ val args = call.arguments as? Map<*, *>
352
+ val apkPath = args?.get("apkPath")?.toString().orEmpty()
353
+ if (apkPath.isBlank()) {
354
+ result.error(
355
+ "app_update_invalid_args",
356
+ "apkPath is required.",
357
+ null,
358
+ )
359
+ return@setMethodCallHandler
360
+ }
361
+ result.success(launchApkInstaller(apkPath))
362
+ } catch (err: Exception) {
363
+ result.error(
364
+ "app_update_install_failed",
365
+ err.message ?: err.javaClass.simpleName,
366
+ null,
367
+ )
368
+ }
369
+ }
370
+
371
+ else -> result.notImplemented()
372
+ }
373
+ }
374
+
375
+ MethodChannel(
376
+ flutterEngine.dartExecutor.binaryMessenger,
377
+ "neoagent/launcher_device",
378
+ ).setMethodCallHandler { call, result ->
379
+ when (call.method) {
380
+ "getVolumeState" -> {
381
+ val audioManager = getSystemService(AudioManager::class.java)
382
+ if (audioManager == null) {
383
+ result.error(
384
+ "launcher_audio_unavailable",
385
+ "Audio manager is unavailable on this device.",
386
+ null,
387
+ )
388
+ return@setMethodCallHandler
389
+ }
390
+ result.success(buildVolumeState(audioManager))
391
+ }
392
+
393
+ "setVolume" -> {
394
+ val audioManager = getSystemService(AudioManager::class.java)
395
+ if (audioManager == null) {
396
+ result.error(
397
+ "launcher_audio_unavailable",
398
+ "Audio manager is unavailable on this device.",
399
+ null,
400
+ )
401
+ return@setMethodCallHandler
402
+ }
403
+ val args = call.arguments as? Map<*, *>
404
+ val target = (args?.get("value") as? Number)?.toInt()
405
+ if (target == null) {
406
+ result.error(
407
+ "launcher_volume_invalid_args",
408
+ "value is required.",
409
+ null,
410
+ )
411
+ return@setMethodCallHandler
412
+ }
413
+ val minVolume =
414
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
415
+ audioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC)
416
+ } else {
417
+ 0
418
+ }
419
+ val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
420
+ audioManager.setStreamVolume(
421
+ AudioManager.STREAM_MUSIC,
422
+ target.coerceIn(minVolume, maxVolume),
423
+ 0,
424
+ )
425
+ result.success(buildVolumeState(audioManager))
426
+ }
427
+
428
+ "adjustVolume" -> {
429
+ val audioManager = getSystemService(AudioManager::class.java)
430
+ if (audioManager == null) {
431
+ result.error(
432
+ "launcher_audio_unavailable",
433
+ "Audio manager is unavailable on this device.",
434
+ null,
435
+ )
436
+ return@setMethodCallHandler
437
+ }
438
+ val args = call.arguments as? Map<*, *>
439
+ val delta = (args?.get("delta") as? Number)?.toInt() ?: 0
440
+ val minVolume =
441
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
442
+ audioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC)
443
+ } else {
444
+ 0
445
+ }
446
+ val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
447
+ val currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
448
+ audioManager.setStreamVolume(
449
+ AudioManager.STREAM_MUSIC,
450
+ (currentVolume + delta).coerceIn(minVolume, maxVolume),
451
+ 0,
452
+ )
453
+ result.success(buildVolumeState(audioManager))
454
+ }
455
+
456
+ "openWifiSettings" -> {
457
+ try {
458
+ val intent = Intent(Settings.ACTION_WIFI_SETTINGS).apply {
459
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
460
+ }
461
+ startActivity(intent)
462
+ result.success(null)
463
+ } catch (err: Exception) {
464
+ result.error(
465
+ "launcher_wifi_settings_failed",
466
+ err.message ?: err.javaClass.simpleName,
467
+ null,
468
+ )
469
+ }
470
+ }
471
+
472
+ "openDateSettings", "openTimeSettings" -> {
473
+ try {
474
+ val intent = Intent(Settings.ACTION_DATE_SETTINGS)
475
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
476
+ if (intent.resolveActivity(packageManager) != null) {
477
+ startActivity(intent)
478
+ } else {
479
+ startActivity(
480
+ Intent(Settings.ACTION_SETTINGS)
481
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
482
+ )
483
+ }
484
+ result.success(null)
485
+ } catch (err: Exception) {
486
+ result.error(
487
+ "launcher_date_settings_failed",
488
+ err.message ?: err.javaClass.simpleName,
489
+ null,
490
+ )
491
+ }
492
+ }
493
+
494
+ "openSystemSettings" -> {
495
+ try {
496
+ startActivity(
497
+ Intent(Settings.ACTION_SETTINGS)
498
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
499
+ )
500
+ result.success(null)
501
+ } catch (err: Exception) {
502
+ result.error(
503
+ "launcher_settings_failed",
504
+ err.message ?: err.javaClass.simpleName,
505
+ null,
506
+ )
507
+ }
508
+ }
509
+
510
+ "getDeviceStatus", "getBatteryState" -> {
511
+ result.success(buildDeviceStatusMap())
512
+ }
513
+
514
+ "getAppMode" -> result.success(currentAppMode())
515
+
516
+ else -> result.notImplemented()
517
+ }
518
+ }
519
+
520
+ MethodChannel(
521
+ flutterEngine.dartExecutor.binaryMessenger,
522
+ "neoagent/widgets",
523
+ ).setMethodCallHandler { call, result ->
524
+ when (call.method) {
525
+ "configureHomeWidgets" -> {
526
+ val args = call.arguments as? Map<*, *>
527
+ widgetSyncScheduler.configure(
528
+ enabled = args?.get("enabled") == true,
529
+ backendUrl = args?.get("backendUrl")?.toString().orEmpty(),
530
+ sessionCookie = args?.get("sessionCookie")?.toString().orEmpty(),
531
+ )
532
+ result.success(null)
533
+ }
534
+
535
+ "syncHomeWidgetsNow" -> {
536
+ widgetSyncScheduler.syncNow()
537
+ result.success(null)
538
+ }
539
+
540
+ else -> result.notImplemented()
541
+ }
542
+ }
543
+
544
+ EventChannel(
545
+ flutterEngine.dartExecutor.binaryMessenger,
546
+ "neoagent/launcher_buttons",
547
+ ).setStreamHandler(
548
+ object : EventChannel.StreamHandler {
549
+ override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
550
+ launcherButtonSink = events
551
+ }
552
+
553
+ override fun onCancel(arguments: Any?) {
554
+ launcherButtonSink = null
555
+ }
556
+ },
557
+ )
558
+
559
+ EventChannel(
560
+ flutterEngine.dartExecutor.binaryMessenger,
561
+ "neoagent/widgets/events",
562
+ ).setStreamHandler(
563
+ object : EventChannel.StreamHandler {
564
+ override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
565
+ widgetEventSink = events
566
+ emitPendingWidgetIntent()
567
+ }
568
+
569
+ override fun onCancel(arguments: Any?) {
570
+ widgetEventSink = null
571
+ }
572
+ },
573
+ )
574
+
575
+ EventChannel(
576
+ flutterEngine.dartExecutor.binaryMessenger,
577
+ "neoagent/app_launch/events",
578
+ ).setStreamHandler(
579
+ object : EventChannel.StreamHandler {
580
+ override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
581
+ appLaunchEventSink = events
582
+ emitPendingAppLaunchIntent()
583
+ }
584
+
585
+ override fun onCancel(arguments: Any?) {
586
+ appLaunchEventSink = null
587
+ }
588
+ },
589
+ )
590
+
591
+ captureWidgetIntent(intent)
592
+ captureAppLaunchIntent(intent)
593
+ }
594
+
595
+ override fun onNewIntent(intent: Intent) {
596
+ super.onNewIntent(intent)
597
+ setIntent(intent)
598
+ captureWidgetIntent(intent)
599
+ captureAppLaunchIntent(intent)
600
+ }
601
+
602
+ override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
603
+ emitLauncherButtonEvent(event, "down")
604
+ return super.onKeyDown(keyCode, event)
605
+ }
606
+
607
+ override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
608
+ emitLauncherButtonEvent(event, "up")
609
+ return super.onKeyUp(keyCode, event)
610
+ }
611
+
612
+ private fun startRecordingService(args: Map<*, *>?) {
613
+ val backendUrl = args?.get("backendUrl")?.toString().orEmpty()
614
+ val sessionCookie = args?.get("sessionCookie")?.toString().orEmpty()
615
+ val sessionId = args?.get("sessionId")?.toString().orEmpty()
616
+ val intent = RecordingForegroundService.buildStartIntent(
617
+ this,
618
+ backendUrl = backendUrl,
619
+ sessionCookie = sessionCookie,
620
+ sessionId = sessionId,
621
+ )
622
+ ContextCompat.startForegroundService(this, intent)
623
+ }
624
+
625
+ private fun currentAppMode(): String {
626
+ return try {
627
+ val appInfo = packageManager.getApplicationInfo(
628
+ packageName,
629
+ PackageManager.GET_META_DATA,
630
+ )
631
+ appInfo.metaData?.getString("com.neoagent.APP_MODE")?.trim().orEmpty()
632
+ .ifBlank { "standard" }
633
+ } catch (_: Exception) {
634
+ "standard"
635
+ }
636
+ }
637
+
638
+ private fun canRequestPackageInstalls(): Boolean {
639
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
640
+ packageManager.canRequestPackageInstalls()
641
+ } else {
642
+ true
643
+ }
644
+ }
645
+
646
+ private fun launchApkInstaller(apkPath: String): Boolean {
647
+ val apkFile = File(apkPath)
648
+ if (!apkFile.exists() || !apkFile.isFile) {
649
+ throw IllegalArgumentException("APK not found: $apkPath")
650
+ }
651
+ val contentUri = FileProvider.getUriForFile(
652
+ this,
653
+ "$packageName.fileprovider",
654
+ apkFile,
655
+ )
656
+ val installIntent = Intent(Intent.ACTION_VIEW).apply {
657
+ setDataAndType(contentUri, "application/vnd.android.package-archive")
658
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
659
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
660
+ putExtra(Intent.EXTRA_RETURN_RESULT, false)
661
+ }
662
+ if (installIntent.resolveActivity(packageManager) == null) {
663
+ throw IllegalStateException("No Android package installer is available.")
664
+ }
665
+ startActivity(installIntent)
666
+ return true
667
+ }
668
+
669
+ private fun shouldEmitLauncherButtonEvent(keyCode: Int): Boolean {
670
+ if (currentAppMode() != "launcher") {
671
+ return false
672
+ }
673
+ return keyCode != KeyEvent.KEYCODE_BACK &&
674
+ keyCode != KeyEvent.KEYCODE_HOME &&
675
+ keyCode != KeyEvent.KEYCODE_VOLUME_DOWN &&
676
+ keyCode != KeyEvent.KEYCODE_VOLUME_UP &&
677
+ keyCode != KeyEvent.KEYCODE_VOLUME_MUTE &&
678
+ keyCode != KeyEvent.KEYCODE_APP_SWITCH &&
679
+ keyCode != KeyEvent.KEYCODE_MENU
680
+ }
681
+
682
+ private fun emitLauncherButtonEvent(event: KeyEvent, action: String) {
683
+ if (!shouldEmitLauncherButtonEvent(event.keyCode)) {
684
+ return
685
+ }
686
+ launcherButtonSink?.success(
687
+ mapOf(
688
+ "keyCode" to event.keyCode,
689
+ "scanCode" to event.scanCode,
690
+ "repeatCount" to event.repeatCount,
691
+ "action" to action,
692
+ "eventTimeMs" to event.eventTime,
693
+ ),
694
+ )
695
+ }
696
+
697
+ private fun captureWidgetIntent(intent: Intent?) {
698
+ if (intent?.action != AiHomeWidgetProvider.ACTION_OPEN_WIDGET) {
699
+ return
700
+ }
701
+ val widgetId =
702
+ intent.getStringExtra(AiHomeWidgetProvider.EXTRA_WIDGET_ID)?.trim().orEmpty()
703
+ if (widgetId.isBlank()) {
704
+ return
705
+ }
706
+ AiWidgetStore(this).setPendingOpenWidgetId(widgetId)
707
+ emitPendingWidgetIntent()
708
+ }
709
+
710
+ private fun emitPendingWidgetIntent() {
711
+ val sink = widgetEventSink ?: return
712
+ val widgetId = AiWidgetStore(this).consumePendingOpenWidgetId() ?: return
713
+ sink.success(mapOf("widgetId" to widgetId))
714
+ }
715
+
716
+ private fun captureAppLaunchIntent(intent: Intent?) {
717
+ if (intent?.action != VoiceLaunchWidgetProvider.ACTION_OPEN_VOICE_ASSISTANT) {
718
+ return
719
+ }
720
+ pendingAppLaunchAction = VoiceLaunchWidgetProvider.OPEN_TARGET_VOICE_ASSISTANT
721
+ emitPendingAppLaunchIntent()
722
+ }
723
+
724
+ private fun emitPendingAppLaunchIntent() {
725
+ val sink = appLaunchEventSink ?: return
726
+ val action = pendingAppLaunchAction ?: return
727
+ pendingAppLaunchAction = null
728
+ sink.success(mapOf("action" to action))
729
+ }
730
+
731
+ private fun buildVolumeState(audioManager: AudioManager): Map<String, Any> {
732
+ val minVolume =
733
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
734
+ audioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC)
735
+ } else {
736
+ 0
737
+ }
738
+ return mapOf(
739
+ "current" to audioManager.getStreamVolume(AudioManager.STREAM_MUSIC),
740
+ "max" to audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC),
741
+ "min" to minVolume,
742
+ "muted" to audioManager.isStreamMute(AudioManager.STREAM_MUSIC),
743
+ )
744
+ }
745
+
746
+ private fun buildDeviceStatusMap(): Map<String, Any?> {
747
+ val batteryStateIntent = registerReceiver(
748
+ null,
749
+ android.content.IntentFilter(Intent.ACTION_BATTERY_CHANGED),
750
+ )
751
+ val batteryLevel = batteryStateIntent?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1
752
+ val batteryScale = batteryStateIntent?.getIntExtra(BatteryManager.EXTRA_SCALE, -1) ?: -1
753
+ val batteryPercentFromIntent =
754
+ if (batteryLevel >= 0 && batteryScale > 0) {
755
+ ((batteryLevel * 100f) / batteryScale).toInt().coerceIn(0, 100)
756
+ } else {
757
+ null
758
+ }
759
+ val batteryManager = getSystemService(BatteryManager::class.java)
760
+ val batteryPercentFromManager = batteryManager
761
+ ?.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
762
+ ?.takeIf { it in 0..100 }
763
+ val batteryPercent = batteryPercentFromIntent ?: batteryPercentFromManager
764
+ val batteryStatus = batteryStateIntent?.getIntExtra(BatteryManager.EXTRA_STATUS, -1) ?: -1
765
+ val plugged = batteryStateIntent?.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) ?: 0
766
+ val charging =
767
+ batteryStatus == BatteryManager.BATTERY_STATUS_CHARGING ||
768
+ batteryStatus == BatteryManager.BATTERY_STATUS_FULL ||
769
+ plugged != 0
770
+ return mapOf(
771
+ "batteryPercent" to batteryPercent,
772
+ "charging" to charging,
773
+ )
774
+ }
775
+
776
+ private suspend fun buildStatusMap(): Map<String, Any?> {
777
+ val available = healthGateway.isAvailable()
778
+ val client = healthGateway.getClientOrNull()
779
+ val required = if (client != null) {
780
+ healthGateway.getRequestedPermissions(client).toList()
781
+ } else {
782
+ emptyList()
783
+ }
784
+ val granted = if (client != null) {
785
+ client.permissionController.getGrantedPermissions().toList()
786
+ } else {
787
+ emptyList()
788
+ }
789
+
790
+ val message = when {
791
+ !available -> "Health Connect is unavailable on this device."
792
+ !granted.containsAll(required) -> "Permissions are required for sync."
793
+ else -> "Health sync is ready."
794
+ }
795
+
796
+ return mapOf(
797
+ "available" to available,
798
+ "permissionsGranted" to granted.containsAll(required),
799
+ "requiredPermissions" to required,
800
+ "grantedPermissions" to granted,
801
+ "message" to message,
802
+ )
803
+ }
804
+ }