neoagent 2.3.1-beta.4 → 2.3.1-beta.41

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 (290) hide show
  1. package/.env.example +45 -0
  2. package/docs/capabilities.md +2 -2
  3. package/docs/configuration.md +12 -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 +109 -0
  10. package/flutter_app/android/app/src/debug/AndroidManifest.xml +7 -0
  11. package/flutter_app/android/app/src/main/AndroidManifest.xml +147 -0
  12. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/MainActivity.kt +747 -0
  13. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthConnectGateway.kt +280 -0
  14. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncNotifications.kt +113 -0
  15. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncPayload.kt +57 -0
  16. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncScheduler.kt +78 -0
  17. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncWorker.kt +253 -0
  18. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/PermissionsRationaleActivity.kt +46 -0
  19. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingBootReceiver.kt +21 -0
  20. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingForegroundService.kt +586 -0
  21. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingStateStore.kt +78 -0
  22. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingUploadClient.kt +104 -0
  23. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiHomeWidgetProvider.kt +457 -0
  24. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiWidgetStore.kt +194 -0
  25. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/VoiceLaunchWidgetProvider.kt +67 -0
  26. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetConfigActivity.kt +228 -0
  27. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncScheduler.kt +72 -0
  28. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncWorker.kt +186 -0
  29. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetTaskRunWorker.kt +210 -0
  30. package/flutter_app/android/app/src/main/res/drawable/launch_background.xml +12 -0
  31. package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_bg.xml +11 -0
  32. package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_task_bg.xml +8 -0
  33. package/flutter_app/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
  34. package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget.xml +138 -0
  35. package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget_task_row.xml +52 -0
  36. package/flutter_app/android/app/src/main/res/layout/neoagent_voice_widget.xml +49 -0
  37. package/flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  38. package/flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  39. package/flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  40. package/flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  41. package/flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  42. package/flutter_app/android/app/src/main/res/values/strings.xml +12 -0
  43. package/flutter_app/android/app/src/main/res/values/styles.xml +18 -0
  44. package/flutter_app/android/app/src/main/res/values-night/styles.xml +18 -0
  45. package/flutter_app/android/app/src/main/res/xml/file_paths.xml +6 -0
  46. package/flutter_app/android/app/src/main/res/xml/neoagent_ai_widget_info.xml +12 -0
  47. package/flutter_app/android/app/src/main/res/xml/neoagent_voice_widget_info.xml +12 -0
  48. package/flutter_app/android/app/src/profile/AndroidManifest.xml +7 -0
  49. package/flutter_app/android/build.gradle.kts +24 -0
  50. package/flutter_app/android/ci-release.keystore +0 -0
  51. package/flutter_app/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  52. package/flutter_app/android/gradle.properties +3 -0
  53. package/flutter_app/android/key.properties +4 -0
  54. package/flutter_app/android/settings.gradle.kts +26 -0
  55. package/flutter_app/assets/branding/app_icon_1024.png +0 -0
  56. package/flutter_app/assets/branding/app_icon_128.png +0 -0
  57. package/flutter_app/assets/branding/app_icon_192.png +0 -0
  58. package/flutter_app/assets/branding/app_icon_256.png +0 -0
  59. package/flutter_app/assets/branding/app_icon_32.png +0 -0
  60. package/flutter_app/assets/branding/app_icon_512.png +0 -0
  61. package/flutter_app/assets/branding/app_icon_64.png +0 -0
  62. package/flutter_app/assets/branding/tray_icon_template.png +0 -0
  63. package/flutter_app/lib/features/location/location_service.dart +119 -0
  64. package/flutter_app/lib/features/notifications/notification_interceptor.dart +97 -0
  65. package/flutter_app/lib/main.dart +99 -0
  66. package/flutter_app/lib/main_account_settings.dart +1250 -0
  67. package/flutter_app/lib/main_admin.dart +886 -0
  68. package/flutter_app/lib/main_app_shell.dart +1682 -0
  69. package/flutter_app/lib/main_chat.dart +3352 -0
  70. package/flutter_app/lib/main_controller.dart +6781 -0
  71. package/flutter_app/lib/main_devices.dart +2301 -0
  72. package/flutter_app/lib/main_integrations.dart +1129 -0
  73. package/flutter_app/lib/main_launcher.dart +959 -0
  74. package/flutter_app/lib/main_launcher_entry.dart +5 -0
  75. package/flutter_app/lib/main_models.dart +3546 -0
  76. package/flutter_app/lib/main_navigation.dart +193 -0
  77. package/flutter_app/lib/main_operations.dart +4851 -0
  78. package/flutter_app/lib/main_recordings.dart +870 -0
  79. package/flutter_app/lib/main_runtime.dart +806 -0
  80. package/flutter_app/lib/main_settings.dart +2024 -0
  81. package/flutter_app/lib/main_shared.dart +2861 -0
  82. package/flutter_app/lib/main_theme.dart +204 -0
  83. package/flutter_app/lib/main_voice_assistant.dart +957 -0
  84. package/flutter_app/lib/src/android_apk_drop_zone.dart +32 -0
  85. package/flutter_app/lib/src/android_apk_drop_zone_stub.dart +16 -0
  86. package/flutter_app/lib/src/android_apk_drop_zone_web.dart +348 -0
  87. package/flutter_app/lib/src/android_app_installer.dart +22 -0
  88. package/flutter_app/lib/src/android_app_installer_io.dart +122 -0
  89. package/flutter_app/lib/src/android_app_installer_stub.dart +21 -0
  90. package/flutter_app/lib/src/android_launcher_bridge.dart +239 -0
  91. package/flutter_app/lib/src/app_launch_bridge.dart +29 -0
  92. package/flutter_app/lib/src/app_release_updater.dart +511 -0
  93. package/flutter_app/lib/src/backend_client.dart +1833 -0
  94. package/flutter_app/lib/src/desktop_companion.dart +2 -0
  95. package/flutter_app/lib/src/desktop_companion_actions.dart +586 -0
  96. package/flutter_app/lib/src/desktop_companion_io.dart +538 -0
  97. package/flutter_app/lib/src/desktop_companion_stub.dart +59 -0
  98. package/flutter_app/lib/src/desktop_native_bridge.dart +91 -0
  99. package/flutter_app/lib/src/desktop_screen_capture.dart +21 -0
  100. package/flutter_app/lib/src/desktop_screen_capture_io.dart +142 -0
  101. package/flutter_app/lib/src/desktop_screen_capture_stub.dart +12 -0
  102. package/flutter_app/lib/src/diagnostics_logger.dart +119 -0
  103. package/flutter_app/lib/src/health_bridge.dart +136 -0
  104. package/flutter_app/lib/src/live_voice_capture.dart +85 -0
  105. package/flutter_app/lib/src/messaging_access_summary.dart +46 -0
  106. package/flutter_app/lib/src/network/app_http_client.dart +53 -0
  107. package/flutter_app/lib/src/network/app_http_client_factory.dart +6 -0
  108. package/flutter_app/lib/src/network/app_http_client_io.dart +138 -0
  109. package/flutter_app/lib/src/network/app_http_client_stub.dart +3 -0
  110. package/flutter_app/lib/src/network/app_http_client_web.dart +94 -0
  111. package/flutter_app/lib/src/oauth_launcher.dart +33 -0
  112. package/flutter_app/lib/src/oauth_launcher_io.dart +77 -0
  113. package/flutter_app/lib/src/oauth_launcher_stub.dart +33 -0
  114. package/flutter_app/lib/src/oauth_launcher_web.dart +107 -0
  115. package/flutter_app/lib/src/recording_bridge.dart +232 -0
  116. package/flutter_app/lib/src/recording_bridge_io.dart +1019 -0
  117. package/flutter_app/lib/src/recording_bridge_stub.dart +120 -0
  118. package/flutter_app/lib/src/recording_bridge_web.dart +689 -0
  119. package/flutter_app/lib/src/recording_payloads.dart +86 -0
  120. package/flutter_app/lib/src/theme/palette.dart +81 -0
  121. package/flutter_app/lib/src/widget_bridge.dart +49 -0
  122. package/flutter_app/linux/CMakeLists.txt +128 -0
  123. package/flutter_app/linux/flutter/CMakeLists.txt +88 -0
  124. package/flutter_app/linux/flutter/generated_plugin_registrant.cc +43 -0
  125. package/flutter_app/linux/flutter/generated_plugin_registrant.h +15 -0
  126. package/flutter_app/linux/flutter/generated_plugins.cmake +31 -0
  127. package/flutter_app/linux/runner/CMakeLists.txt +26 -0
  128. package/flutter_app/linux/runner/main.cc +6 -0
  129. package/flutter_app/linux/runner/my_application.cc +144 -0
  130. package/flutter_app/linux/runner/my_application.h +18 -0
  131. package/flutter_app/linux/runner/resources/app_icon.png +0 -0
  132. package/flutter_app/macos/Flutter/Flutter-Debug.xcconfig +2 -0
  133. package/flutter_app/macos/Flutter/Flutter-Release.xcconfig +2 -0
  134. package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +40 -0
  135. package/flutter_app/macos/Podfile +42 -0
  136. package/flutter_app/macos/Podfile.lock +87 -0
  137. package/flutter_app/macos/Runner/AppDelegate.swift +576 -0
  138. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +68 -0
  139. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
  140. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
  141. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
  142. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
  143. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
  144. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
  145. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
  146. package/flutter_app/macos/Runner/Base.lproj/MainMenu.xib +342 -0
  147. package/flutter_app/macos/Runner/Configs/AppInfo.xcconfig +14 -0
  148. package/flutter_app/macos/Runner/Configs/Debug.xcconfig +2 -0
  149. package/flutter_app/macos/Runner/Configs/Release.xcconfig +2 -0
  150. package/flutter_app/macos/Runner/Configs/Warnings.xcconfig +13 -0
  151. package/flutter_app/macos/Runner/DebugProfile.entitlements +16 -0
  152. package/flutter_app/macos/Runner/Info.plist +36 -0
  153. package/flutter_app/macos/Runner/MainFlutterWindow.swift +19 -0
  154. package/flutter_app/macos/Runner/Release.entitlements +12 -0
  155. package/flutter_app/macos/Runner.xcodeproj/project.pbxproj +801 -0
  156. package/flutter_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  157. package/flutter_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +99 -0
  158. package/flutter_app/macos/Runner.xcworkspace/contents.xcworkspacedata +10 -0
  159. package/flutter_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  160. package/flutter_app/macos/RunnerTests/RunnerTests.swift +12 -0
  161. package/flutter_app/patch_strings.py +12 -0
  162. package/flutter_app/pubspec.lock +1088 -0
  163. package/flutter_app/pubspec.yaml +53 -0
  164. package/flutter_app/test/messaging_access_summary_test.dart +22 -0
  165. package/flutter_app/test/recording_payloads_test.dart +53 -0
  166. package/flutter_app/third_party/desktop_audio_capture/LICENSE +21 -0
  167. package/flutter_app/third_party/desktop_audio_capture/README.md +262 -0
  168. package/flutter_app/third_party/desktop_audio_capture/lib/audio_capture.dart +65 -0
  169. package/flutter_app/third_party/desktop_audio_capture/lib/config/mic_audio_config.dart +153 -0
  170. package/flutter_app/third_party/desktop_audio_capture/lib/config/system_adudio_config.dart +110 -0
  171. package/flutter_app/third_party/desktop_audio_capture/lib/mic/mic_audio_capture.dart +461 -0
  172. package/flutter_app/third_party/desktop_audio_capture/lib/model/audio_status.dart +91 -0
  173. package/flutter_app/third_party/desktop_audio_capture/lib/model/decibel_data.dart +106 -0
  174. package/flutter_app/third_party/desktop_audio_capture/lib/model/input_device_type.dart +219 -0
  175. package/flutter_app/third_party/desktop_audio_capture/lib/system/system_audio_capture.dart +336 -0
  176. package/flutter_app/third_party/desktop_audio_capture/linux/CMakeLists.txt +101 -0
  177. package/flutter_app/third_party/desktop_audio_capture/linux/audio_capture_plugin.cc +692 -0
  178. package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/audio_capture_plugin.h +35 -0
  179. package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/mic_capture_plugin.h +36 -0
  180. package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/audio_capture_plugin.h +32 -0
  181. package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/mic_capture_plugin.h +32 -0
  182. package/flutter_app/third_party/desktop_audio_capture/linux/mic_capture_plugin.cc +878 -0
  183. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/AudioCapturePlugin.swift +27 -0
  184. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/MicCapturePlugin.swift +1172 -0
  185. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/SystemCapturePlugin.swift +655 -0
  186. package/flutter_app/third_party/desktop_audio_capture/macos/Resources/PrivacyInfo.xcprivacy +12 -0
  187. package/flutter_app/third_party/desktop_audio_capture/macos/desktop_audio_capture.podspec +30 -0
  188. package/flutter_app/third_party/desktop_audio_capture/pubspec.yaml +87 -0
  189. package/flutter_app/third_party/desktop_audio_capture/windows/CMakeLists.txt +105 -0
  190. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.cpp +80 -0
  191. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.h +31 -0
  192. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin_c_api.cpp +12 -0
  193. package/flutter_app/third_party/desktop_audio_capture/windows/include/audio_capture/audio_capture_plugin_c_api.h +23 -0
  194. package/flutter_app/third_party/desktop_audio_capture/windows/include/desktop_audio_capture/audio_capture_plugin.h +25 -0
  195. package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.cpp +1117 -0
  196. package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.h +115 -0
  197. package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.cpp +777 -0
  198. package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.h +87 -0
  199. package/flutter_app/third_party/flutter_secure_storage_linux/linux/CMakeLists.txt +30 -0
  200. package/flutter_app/third_party/flutter_secure_storage_linux/linux/flutter_secure_storage_linux_plugin.cc +215 -0
  201. package/flutter_app/third_party/flutter_secure_storage_linux/linux/include/flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h +27 -0
  202. package/flutter_app/third_party/flutter_secure_storage_linux/pubspec.yaml +20 -0
  203. package/flutter_app/tool/generate_desktop_branding.py +219 -0
  204. package/flutter_app/web/favicon.png +0 -0
  205. package/flutter_app/web/favicon.svg +12 -0
  206. package/flutter_app/web/icons/Icon-192.png +0 -0
  207. package/flutter_app/web/icons/Icon-512.png +0 -0
  208. package/flutter_app/web/icons/Icon-maskable-192.png +0 -0
  209. package/flutter_app/web/icons/Icon-maskable-512.png +0 -0
  210. package/flutter_app/web/index.html +39 -0
  211. package/flutter_app/web/manifest.json +35 -0
  212. package/flutter_app/windows/CMakeLists.txt +108 -0
  213. package/flutter_app/windows/flutter/CMakeLists.txt +109 -0
  214. package/flutter_app/windows/flutter/generated_plugin_registrant.cc +47 -0
  215. package/flutter_app/windows/flutter/generated_plugin_registrant.h +15 -0
  216. package/flutter_app/windows/flutter/generated_plugins.cmake +35 -0
  217. package/flutter_app/windows/runner/CMakeLists.txt +41 -0
  218. package/flutter_app/windows/runner/Runner.rc +121 -0
  219. package/flutter_app/windows/runner/flutter_window.cpp +533 -0
  220. package/flutter_app/windows/runner/flutter_window.h +37 -0
  221. package/flutter_app/windows/runner/main.cpp +53 -0
  222. package/flutter_app/windows/runner/resource.h +16 -0
  223. package/flutter_app/windows/runner/resources/app_icon.ico +0 -0
  224. package/flutter_app/windows/runner/runner.exe.manifest +14 -0
  225. package/flutter_app/windows/runner/utils.cpp +65 -0
  226. package/flutter_app/windows/runner/utils.h +19 -0
  227. package/flutter_app/windows/runner/win32_window.cpp +299 -0
  228. package/flutter_app/windows/runner/win32_window.h +102 -0
  229. package/lib/install_helpers.js +31 -0
  230. package/lib/manager.js +227 -6
  231. package/package.json +3 -1
  232. package/server/db/database.js +110 -0
  233. package/server/http/middleware.js +55 -2
  234. package/server/http/routes.js +1 -0
  235. package/server/index.js +3 -0
  236. package/server/public/.last_build_id +1 -1
  237. package/server/public/assets/NOTICES +1 -1
  238. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  239. package/server/public/canvaskit/wimp.wasm +0 -0
  240. package/server/public/flutter_bootstrap.js +2 -2
  241. package/server/public/main.dart.js +74324 -73132
  242. package/server/routes/integrations.js +108 -1
  243. package/server/routes/memory.js +11 -2
  244. package/server/{http/routes → routes}/screenHistory.js +2 -2
  245. package/server/routes/settings.js +75 -2
  246. package/server/{http/routes → routes}/triggers.js +2 -2
  247. package/server/routes/wearable.js +67 -0
  248. package/server/services/ai/models.js +30 -0
  249. package/server/services/ai/providers/githubCopilot.js +97 -0
  250. package/server/services/ai/providers/openai.js +2 -1
  251. package/server/services/ai/providers/openaiCodex.js +31 -0
  252. package/server/services/ai/settings.js +20 -0
  253. package/server/services/ai/toolSelector.js +14 -1
  254. package/server/services/ai/tools.js +77 -4
  255. package/server/services/desktop/screenRecorder.js +65 -9
  256. package/server/services/integrations/env.js +5 -0
  257. package/server/services/integrations/figma/provider.js +1 -0
  258. package/server/services/integrations/github/common.js +106 -0
  259. package/server/services/integrations/github/provider.js +499 -0
  260. package/server/services/integrations/github/repos.js +1124 -0
  261. package/server/services/integrations/google/provider.js +1 -0
  262. package/server/services/integrations/home_assistant/provider.js +325 -26
  263. package/server/services/integrations/manager.js +88 -12
  264. package/server/services/integrations/microsoft/provider.js +1 -0
  265. package/server/services/integrations/oauth_provider.js +25 -8
  266. package/server/services/integrations/provider_config_store.js +85 -0
  267. package/server/services/integrations/registry.js +4 -0
  268. package/server/services/integrations/spotify/provider.js +1 -0
  269. package/server/services/integrations/trello/provider.js +842 -0
  270. package/server/services/manager.js +46 -1
  271. package/server/services/mcp/client.js +120 -23
  272. package/server/services/memory/manager.js +39 -2
  273. package/server/services/messaging/access_policy.js +10 -0
  274. package/server/services/messaging/manager.js +49 -0
  275. package/server/services/messaging/meshtastic.js +260 -0
  276. package/server/services/messaging/meshtastic_env.js +100 -0
  277. package/server/services/messaging/meshtastic_protocol.js +476 -0
  278. package/server/services/messaging/meshtastic_tcp_transport.js +25 -0
  279. package/server/services/tasks/runtime.js +1 -1
  280. package/server/services/voice/openaiClient.js +4 -1
  281. package/server/services/voice/openaiSpeech.js +6 -1
  282. package/server/services/voice/providers.js +52 -12
  283. package/server/services/voice/runtimeManager.js +136 -19
  284. package/server/services/voice/turnRunner.js +29 -9
  285. package/server/services/wearable/firmware_manifest.js +370 -0
  286. package/server/services/wearable/gateway.js +350 -0
  287. package/server/services/wearable/protocol.js +45 -0
  288. package/server/services/wearable/service.js +244 -0
  289. package/server/utils/local_secrets.js +56 -0
  290. package/server/utils/logger.js +37 -9
@@ -0,0 +1,476 @@
1
+ 'use strict';
2
+
3
+ const { Socket } = require('node:net');
4
+ const { EventEmitter } = require('node:events');
5
+
6
+ // Meshtastic TCP wire framing constants (from public protocol docs)
7
+ const FRAME_START_1 = 0x94;
8
+ const FRAME_START_2 = 0xC3;
9
+
10
+ const BROADCAST_NUM = 0xFFFFFFFF;
11
+
12
+ // PortNum values from public protocol specification
13
+ const PortNum = Object.freeze({
14
+ UNKNOWN_APP: 0,
15
+ TEXT_MESSAGE_APP: 1,
16
+ POSITION_APP: 3,
17
+ NODEINFO_APP: 4,
18
+ ROUTING_APP: 5,
19
+ ADMIN_APP: 6,
20
+ TELEMETRY_APP: 67,
21
+ });
22
+
23
+ // --------------------------------------------------------------------------
24
+ // Minimal protobuf wire-format encoder/decoder (Google public standard)
25
+ // Implements only the subset needed: varint, length-delimited, fixed32
26
+ // --------------------------------------------------------------------------
27
+
28
+ const WIRE_VARINT = 0;
29
+ const WIRE_FIXED64 = 1;
30
+ const WIRE_LENGTH_DELIMITED = 2;
31
+ const WIRE_FIXED32 = 5;
32
+
33
+ function encodeVarint(value) {
34
+ const bytes = [];
35
+ let v = value >>> 0;
36
+ while (v > 0x7F) {
37
+ bytes.push((v & 0x7F) | 0x80);
38
+ v >>>= 7;
39
+ }
40
+ bytes.push(v & 0x7F);
41
+ return bytes;
42
+ }
43
+
44
+ function encodeTag(fieldNumber, wireType) {
45
+ return encodeVarint((fieldNumber << 3) | wireType);
46
+ }
47
+
48
+ function encodeVarintField(fieldNumber, value) {
49
+ if (value === 0 || value == null) return [];
50
+ return [...encodeTag(fieldNumber, WIRE_VARINT), ...encodeVarint(value)];
51
+ }
52
+
53
+ function encodeBoolField(fieldNumber, value) {
54
+ if (!value) return [];
55
+ return [...encodeTag(fieldNumber, WIRE_VARINT), 1];
56
+ }
57
+
58
+ function encodeFixed32Field(fieldNumber, value) {
59
+ if (value === 0 || value == null) return [];
60
+ const buf = Buffer.alloc(4);
61
+ buf.writeUInt32LE(value >>> 0);
62
+ return [...encodeTag(fieldNumber, WIRE_FIXED32), ...buf];
63
+ }
64
+
65
+ function encodeBytesField(fieldNumber, bytes) {
66
+ if (!bytes || bytes.length === 0) return [];
67
+ return [
68
+ ...encodeTag(fieldNumber, WIRE_LENGTH_DELIMITED),
69
+ ...encodeVarint(bytes.length),
70
+ ...bytes,
71
+ ];
72
+ }
73
+
74
+ function encodeStringField(fieldNumber, str) {
75
+ if (!str) return [];
76
+ return encodeBytesField(fieldNumber, Buffer.from(str, 'utf8'));
77
+ }
78
+
79
+ function encodeMessageField(fieldNumber, messageBytes) {
80
+ return encodeBytesField(fieldNumber, messageBytes);
81
+ }
82
+
83
+ function decodeVarint(buf, offset) {
84
+ let result = 0n;
85
+ let shift = 0n;
86
+ let pos = offset;
87
+ while (pos < buf.length) {
88
+ const b = BigInt(buf[pos++]);
89
+ result |= (b & 0x7Fn) << shift;
90
+ if ((b & 0x80n) === 0n) return { value: Number(result), offset: pos };
91
+ shift += 7n;
92
+ if (shift > 69n) throw new Error('Varint too long');
93
+ }
94
+ throw new Error('Unexpected end of varint');
95
+ }
96
+
97
+ function decodeFields(buf) {
98
+ const fields = [];
99
+ let offset = 0;
100
+ while (offset < buf.length) {
101
+ const tag = decodeVarint(buf, offset);
102
+ offset = tag.offset;
103
+ const fieldNumber = tag.value >>> 3;
104
+ const wireType = tag.value & 0x07;
105
+
106
+ switch (wireType) {
107
+ case WIRE_VARINT: {
108
+ const val = decodeVarint(buf, offset);
109
+ offset = val.offset;
110
+ fields.push({ field: fieldNumber, wire: wireType, value: val.value });
111
+ break;
112
+ }
113
+ case WIRE_FIXED64: {
114
+ offset += 8;
115
+ break;
116
+ }
117
+ case WIRE_LENGTH_DELIMITED: {
118
+ const len = decodeVarint(buf, offset);
119
+ offset = len.offset;
120
+ const data = buf.subarray(offset, offset + len.value);
121
+ offset += len.value;
122
+ fields.push({ field: fieldNumber, wire: wireType, value: data });
123
+ break;
124
+ }
125
+ case WIRE_FIXED32: {
126
+ const val32 = buf.readUInt32LE(offset);
127
+ offset += 4;
128
+ fields.push({ field: fieldNumber, wire: wireType, value: val32 });
129
+ break;
130
+ }
131
+ default:
132
+ throw new Error(`Unsupported wire type ${wireType}`);
133
+ }
134
+ }
135
+ return fields;
136
+ }
137
+
138
+ function getField(fields, fieldNumber) {
139
+ return fields.find((f) => f.field === fieldNumber) || null;
140
+ }
141
+
142
+ // --------------------------------------------------------------------------
143
+ // Protocol message builders and parsers
144
+ // Field numbers from the public Meshtastic protobuf specification
145
+ // --------------------------------------------------------------------------
146
+
147
+ function encodeData(payload, portnum, opts = {}) {
148
+ return new Uint8Array([
149
+ ...encodeVarintField(1, portnum),
150
+ ...encodeBytesField(2, payload),
151
+ ...encodeBoolField(3, opts.wantResponse),
152
+ ...encodeFixed32Field(6, opts.requestId),
153
+ ...encodeFixed32Field(7, opts.replyId),
154
+ ...encodeFixed32Field(8, opts.emoji),
155
+ ]);
156
+ }
157
+
158
+ function encodeMeshPacket(from, to, channel, id, decoded, opts = {}) {
159
+ return new Uint8Array([
160
+ ...encodeFixed32Field(1, from),
161
+ ...encodeFixed32Field(2, to),
162
+ ...encodeVarintField(3, channel),
163
+ ...encodeMessageField(4, decoded),
164
+ ...encodeFixed32Field(6, id),
165
+ ...encodeBoolField(10, opts.wantAck),
166
+ ]);
167
+ }
168
+
169
+ function encodeToRadioPacket(meshPacketBytes) {
170
+ return new Uint8Array(encodeMessageField(1, meshPacketBytes));
171
+ }
172
+
173
+ function encodeToRadioWantConfig(configId) {
174
+ return new Uint8Array(encodeVarintField(3, configId));
175
+ }
176
+
177
+ function decodeUser(buf) {
178
+ const fields = decodeFields(buf);
179
+ return {
180
+ id: getField(fields, 1)?.value?.toString('utf8') || '',
181
+ longName: getField(fields, 2)?.value?.toString('utf8') || '',
182
+ shortName: getField(fields, 3)?.value?.toString('utf8') || '',
183
+ };
184
+ }
185
+
186
+ function decodeData(buf) {
187
+ const fields = decodeFields(buf);
188
+ return {
189
+ portnum: getField(fields, 1)?.value || 0,
190
+ payload: getField(fields, 2)?.value || Buffer.alloc(0),
191
+ wantResponse: !!(getField(fields, 3)?.value),
192
+ source: getField(fields, 5)?.value || 0,
193
+ dest: getField(fields, 4)?.value || 0,
194
+ requestId: getField(fields, 6)?.value || 0,
195
+ };
196
+ }
197
+
198
+ function decodeMeshPacket(buf) {
199
+ const fields = decodeFields(buf);
200
+ const decodedField = getField(fields, 4);
201
+ const encryptedField = getField(fields, 5);
202
+ return {
203
+ from: getField(fields, 1)?.value || 0,
204
+ to: getField(fields, 2)?.value || 0,
205
+ channel: getField(fields, 3)?.value || 0,
206
+ decoded: decodedField ? decodeData(decodedField.value) : null,
207
+ encrypted: encryptedField ? encryptedField.value : null,
208
+ id: getField(fields, 6)?.value || 0,
209
+ rxTime: getField(fields, 7)?.value || 0,
210
+ };
211
+ }
212
+
213
+ function decodeNodeInfo(buf) {
214
+ const fields = decodeFields(buf);
215
+ const userField = getField(fields, 2);
216
+ return {
217
+ num: getField(fields, 1)?.value || 0,
218
+ user: userField ? decodeUser(userField.value) : null,
219
+ };
220
+ }
221
+
222
+ function decodeMyNodeInfo(buf) {
223
+ const fields = decodeFields(buf);
224
+ return {
225
+ myNodeNum: getField(fields, 1)?.value || 0,
226
+ };
227
+ }
228
+
229
+ function decodeFromRadio(buf) {
230
+ const fields = decodeFields(buf);
231
+ const id = getField(fields, 1)?.value || 0;
232
+
233
+ const packetField = getField(fields, 2);
234
+ if (packetField) return { id, type: 'packet', packet: decodeMeshPacket(packetField.value) };
235
+
236
+ const myInfoField = getField(fields, 3);
237
+ if (myInfoField) return { id, type: 'myInfo', myInfo: decodeMyNodeInfo(myInfoField.value) };
238
+
239
+ const nodeInfoField = getField(fields, 4);
240
+ if (nodeInfoField) return { id, type: 'nodeInfo', nodeInfo: decodeNodeInfo(nodeInfoField.value) };
241
+
242
+ const configCompleteField = getField(fields, 7);
243
+ if (configCompleteField) return { id, type: 'configComplete', configId: configCompleteField.value };
244
+
245
+ const rebootedField = getField(fields, 8);
246
+ if (rebootedField) return { id, type: 'rebooted' };
247
+
248
+ return { id, type: 'unknown' };
249
+ }
250
+
251
+ // --------------------------------------------------------------------------
252
+ // TCP wire framing: [0x94, 0xC3, len_msb, len_lsb, ...protobuf_payload]
253
+ // --------------------------------------------------------------------------
254
+
255
+ function framePacket(protobufBytes) {
256
+ const len = protobufBytes.length;
257
+ const frame = Buffer.alloc(4 + len);
258
+ frame[0] = FRAME_START_1;
259
+ frame[1] = FRAME_START_2;
260
+ frame[2] = (len >> 8) & 0xFF;
261
+ frame[3] = len & 0xFF;
262
+ frame.set(protobufBytes, 4);
263
+ return frame;
264
+ }
265
+
266
+ function createFrameParser(onPacket) {
267
+ let buffer = Buffer.alloc(0);
268
+ return (chunk) => {
269
+ buffer = Buffer.concat([buffer, chunk]);
270
+ while (buffer.length >= 4) {
271
+ const idx = buffer.indexOf(FRAME_START_1);
272
+ if (idx === -1) { buffer = Buffer.alloc(0); return; }
273
+ if (idx > 0) { buffer = buffer.subarray(idx); }
274
+ if (buffer.length < 2) return;
275
+ if (buffer[1] !== FRAME_START_2) { buffer = buffer.subarray(1); continue; }
276
+ if (buffer.length < 4) return;
277
+ const payloadLen = (buffer[2] << 8) | buffer[3];
278
+ if (buffer.length < 4 + payloadLen) return;
279
+ const payload = buffer.subarray(4, 4 + payloadLen);
280
+ buffer = buffer.subarray(4 + payloadLen);
281
+ onPacket(payload);
282
+ }
283
+ };
284
+ }
285
+
286
+ // --------------------------------------------------------------------------
287
+ // MeshtasticConnection — TCP connection + protocol state machine
288
+ // --------------------------------------------------------------------------
289
+
290
+ class MeshtasticConnection extends EventEmitter {
291
+ constructor() {
292
+ super();
293
+ this._socket = null;
294
+ this._configId = (Math.random() * 0x7FFFFFFF) >>> 0;
295
+ this._myNodeNum = 0;
296
+ this._configured = false;
297
+ this._closing = false;
298
+ this._nodeUsers = new Map();
299
+ // Prevent 'error' events with no listener from crashing the process
300
+ this.on('error', () => {});
301
+ }
302
+
303
+ get myNodeNum() { return this._myNodeNum; }
304
+ get nodeUsers() { return this._nodeUsers; }
305
+ get configured() { return this._configured; }
306
+
307
+ async connect(host, port, timeout = 60000) {
308
+ if (this._socket) throw new Error('Already connected');
309
+ this._closing = false;
310
+ this._configured = false;
311
+ this._myNodeNum = 0;
312
+ this._nodeUsers.clear();
313
+
314
+ return new Promise((resolve, reject) => {
315
+ const socket = new Socket();
316
+ let settled = false;
317
+
318
+ const fail = (err) => {
319
+ if (settled) return;
320
+ settled = true;
321
+ socket.destroy();
322
+ reject(err);
323
+ };
324
+
325
+ const timer = setTimeout(() => fail(new Error('Connection timeout')), timeout);
326
+
327
+ socket.once('error', fail);
328
+ socket.once('ready', () => {
329
+ socket.removeListener('error', fail);
330
+ this._socket = socket;
331
+ this._wireSocket(socket);
332
+ this.emit('status', 'connected');
333
+
334
+ const onConfigured = () => {
335
+ if (settled) return;
336
+ settled = true;
337
+ clearTimeout(timer);
338
+ resolve();
339
+ };
340
+ this.once('configured', onConfigured);
341
+
342
+ const toRadio = encodeToRadioWantConfig(this._configId);
343
+ socket.write(framePacket(toRadio));
344
+ });
345
+
346
+ socket.setTimeout(timeout);
347
+ socket.connect(port, host);
348
+ });
349
+ }
350
+
351
+ _wireSocket(socket) {
352
+ const parser = createFrameParser((payload) => {
353
+ if (this._closing) return;
354
+ try {
355
+ const msg = decodeFromRadio(payload);
356
+ this._handleFromRadio(msg);
357
+ } catch (err) {
358
+ // Bad packet from the mesh — log and skip, don't crash
359
+ console.warn('[Meshtastic] Decode error (skipping packet):', err.message);
360
+ }
361
+ });
362
+
363
+ socket.on('data', parser);
364
+ socket.on('error', (err) => this._onDisconnected(`socket-error: ${err.message}`))
365
+ socket.on('end', () => this._onDisconnected('socket-end'));
366
+ socket.on('close', () => this._onDisconnected('socket-closed'));
367
+ socket.on('timeout', () => {
368
+ this._onDisconnected('socket-timeout');
369
+ socket.destroy();
370
+ });
371
+ }
372
+
373
+ _onDisconnected(reason) {
374
+ if (this._closing) return;
375
+ this._configured = false;
376
+ this.emit('status', 'disconnected');
377
+ this.emit('disconnected', { reason });
378
+ }
379
+
380
+ _handleFromRadio(msg) {
381
+ switch (msg.type) {
382
+ case 'myInfo':
383
+ this._myNodeNum = msg.myInfo.myNodeNum;
384
+ this.emit('myNodeInfo', msg.myInfo);
385
+ break;
386
+
387
+ case 'nodeInfo':
388
+ if (msg.nodeInfo.user && msg.nodeInfo.num) {
389
+ this._nodeUsers.set(msg.nodeInfo.num, msg.nodeInfo.user);
390
+ this.emit('nodeInfo', msg.nodeInfo);
391
+ }
392
+ break;
393
+
394
+ case 'configComplete':
395
+ if (msg.configId === this._configId) {
396
+ this._configured = true;
397
+ this.emit('configured');
398
+ }
399
+ break;
400
+
401
+ case 'rebooted':
402
+ this._configured = false;
403
+ this._socket?.write(framePacket(encodeToRadioWantConfig(this._configId)));
404
+ break;
405
+
406
+ case 'packet': {
407
+ const pkt = msg.packet;
408
+ if (!pkt.decoded) break;
409
+ this._handleDecodedPacket(pkt);
410
+ break;
411
+ }
412
+ }
413
+ }
414
+
415
+ _handleDecodedPacket(pkt) {
416
+ const { decoded } = pkt;
417
+
418
+ switch (decoded.portnum) {
419
+ case PortNum.TEXT_MESSAGE_APP:
420
+ this.emit('textMessage', {
421
+ id: pkt.id,
422
+ from: pkt.from,
423
+ to: pkt.to,
424
+ channel: pkt.channel,
425
+ rxTime: pkt.rxTime,
426
+ type: pkt.to === BROADCAST_NUM ? 'broadcast' : 'direct',
427
+ data: decoded.payload.toString('utf8'),
428
+ });
429
+ break;
430
+
431
+ case PortNum.NODEINFO_APP:
432
+ try {
433
+ const user = decodeUser(decoded.payload);
434
+ if (pkt.from) this._nodeUsers.set(pkt.from, user);
435
+ this.emit('nodeInfo', { num: pkt.from, user });
436
+ } catch {}
437
+ break;
438
+ }
439
+ }
440
+
441
+ async sendText(text, channel = 0, destination = BROADCAST_NUM, wantAck = true) {
442
+ if (!this._socket || !this._configured) throw new Error('Not connected');
443
+ const payload = Buffer.from(text, 'utf8');
444
+ const data = encodeData(payload, PortNum.TEXT_MESSAGE_APP, { wantResponse: false });
445
+ const id = (Math.random() * 0x7FFFFFFF) >>> 0;
446
+ const packet = encodeMeshPacket(this._myNodeNum, destination, channel, id, data, { wantAck });
447
+ const toRadio = encodeToRadioPacket(packet);
448
+ this._socket.write(framePacket(toRadio));
449
+ return id;
450
+ }
451
+
452
+ async disconnect() {
453
+ this._closing = true;
454
+ this._configured = false;
455
+ const socket = this._socket;
456
+ this._socket = null;
457
+ if (socket) {
458
+ socket.removeAllListeners();
459
+ socket.destroy();
460
+ }
461
+ }
462
+ }
463
+
464
+ module.exports = {
465
+ MeshtasticConnection,
466
+ PortNum,
467
+ BROADCAST_NUM,
468
+ encodeVarint,
469
+ decodeVarint,
470
+ decodeFields,
471
+ decodeFromRadio,
472
+ decodeMeshPacket,
473
+ decodeUser,
474
+ framePacket,
475
+ createFrameParser,
476
+ };
@@ -0,0 +1,25 @@
1
+ 'use strict';
2
+
3
+ const { MeshtasticConnection } = require('./meshtastic_protocol');
4
+
5
+ class MeshtasticTcpTransport {
6
+ constructor(connection) {
7
+ this._connection = connection;
8
+ }
9
+
10
+ static async create(hostname, port = 4403, timeout = 60000) {
11
+ const connection = new MeshtasticConnection();
12
+ await connection.connect(hostname, port, timeout);
13
+ return new MeshtasticTcpTransport(connection);
14
+ }
15
+
16
+ get connection() { return this._connection; }
17
+
18
+ async disconnect() {
19
+ await this._connection.disconnect();
20
+ }
21
+ }
22
+
23
+ module.exports = {
24
+ MeshtasticTcpTransport,
25
+ };
@@ -296,7 +296,7 @@ class TaskRuntime {
296
296
  if (normalizedConfig.callTo) {
297
297
  notifyHint = `\n\nThis task is configured to notify the user by phone. Use the make_call tool to call "${normalizedConfig.callTo}" with an appropriate greeting based on your findings. The configured greeting hint is: "${normalizedConfig.callGreeting || 'Hello, this is your task reminder.'}"`;
298
298
  } else if (normalizedConfig.notifyPlatform && normalizedConfig.notifyTo) {
299
- notifyHint = `\n\nIf your task result is worth notifying the user about, send it proactively via send_message to platform="${normalizedConfig.notifyPlatform}" to="${normalizedConfig.notifyTo}".`;
299
+ notifyHint = `\n\nIf your task result is worth notifying the user about, send it proactively via send_message to platform="${normalizedConfig.notifyPlatform}" to="${normalizedConfig.notifyTo}" and set purpose="final_result" for a concrete useful outcome or purpose="blocker" for a real issue the user should know about. If nothing important or actionable changed, call send_message with purpose="no_response" and content="[NO RESPONSE]".`;
300
300
  }
301
301
 
302
302
  const triggerPayloadText = executionMeta.triggerPayload
@@ -4,6 +4,7 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
  const { OpenAI } = require('openai');
6
6
  const { AGENT_DATA_DIR } = require('../../../runtime/paths');
7
+ const { decryptLocalValue } = require('../../utils/local_secrets');
7
8
 
8
9
  let cachedClient = null;
9
10
 
@@ -15,7 +16,9 @@ function resolveOpenAiApiKey() {
15
16
  try {
16
17
  const keysPath = path.join(AGENT_DATA_DIR, 'API_KEYS.json');
17
18
  const keys = JSON.parse(fs.readFileSync(keysPath, 'utf8'));
18
- const candidate = keys.OPENAI_API_KEY || keys.openai_api_key || keys.openai;
19
+ const candidate = decryptLocalValue(keys.OPENAI_API_KEY)
20
+ || decryptLocalValue(keys.openai_api_key)
21
+ || decryptLocalValue(keys.openai);
19
22
  return typeof candidate === 'string' && candidate.trim() ? candidate.trim() : '';
20
23
  } catch {
21
24
  return '';
@@ -1,6 +1,10 @@
1
1
  'use strict';
2
2
 
3
- async function synthesizeSpeechBuffer(client, text, { model = 'gpt-4o-mini-tts', voice = 'alloy' } = {}) {
3
+ async function synthesizeSpeechBuffer(
4
+ client,
5
+ text,
6
+ { model = 'gpt-4o-mini-tts', voice = 'alloy', responseFormat = 'mp3' } = {},
7
+ ) {
4
8
  if (!client) {
5
9
  throw new Error('OpenAI client is not configured for speech synthesis.');
6
10
  }
@@ -14,6 +18,7 @@ async function synthesizeSpeechBuffer(client, text, { model = 'gpt-4o-mini-tts',
14
18
  model: String(model || 'gpt-4o-mini-tts').trim() || 'gpt-4o-mini-tts',
15
19
  voice: String(voice || 'alloy').trim() || 'alloy',
16
20
  input: content,
21
+ response_format: String(responseFormat || 'mp3').trim() || 'mp3',
17
22
  });
18
23
 
19
24
  return Buffer.from(await response.arrayBuffer());