neoagent 2.3.1-beta.3 → 2.3.1-beta.31
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.
- package/.env.example +44 -0
- package/docs/capabilities.md +2 -2
- package/docs/configuration.md +12 -5
- package/docs/hardware.md +1 -1
- package/docs/integrations.md +2 -3
- package/flutter_app/.metadata +42 -0
- package/flutter_app/README.md +21 -0
- package/flutter_app/analysis_options.yaml +32 -0
- package/flutter_app/android/app/build.gradle.kts +109 -0
- package/flutter_app/android/app/src/debug/AndroidManifest.xml +7 -0
- package/flutter_app/android/app/src/main/AndroidManifest.xml +147 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/MainActivity.kt +747 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthConnectGateway.kt +280 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncNotifications.kt +113 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncPayload.kt +57 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncScheduler.kt +78 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncWorker.kt +253 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/PermissionsRationaleActivity.kt +46 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingBootReceiver.kt +21 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingForegroundService.kt +586 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingStateStore.kt +78 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingUploadClient.kt +104 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiHomeWidgetProvider.kt +457 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiWidgetStore.kt +194 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/VoiceLaunchWidgetProvider.kt +67 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetConfigActivity.kt +228 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncScheduler.kt +72 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncWorker.kt +186 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetTaskRunWorker.kt +210 -0
- package/flutter_app/android/app/src/main/res/drawable/launch_background.xml +12 -0
- package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_bg.xml +11 -0
- package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_task_bg.xml +8 -0
- package/flutter_app/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
- package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget.xml +138 -0
- package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget_task_row.xml +52 -0
- package/flutter_app/android/app/src/main/res/layout/neoagent_voice_widget.xml +49 -0
- package/flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/values/strings.xml +12 -0
- package/flutter_app/android/app/src/main/res/values/styles.xml +18 -0
- package/flutter_app/android/app/src/main/res/values-night/styles.xml +18 -0
- package/flutter_app/android/app/src/main/res/xml/file_paths.xml +6 -0
- package/flutter_app/android/app/src/main/res/xml/neoagent_ai_widget_info.xml +12 -0
- package/flutter_app/android/app/src/main/res/xml/neoagent_voice_widget_info.xml +12 -0
- package/flutter_app/android/app/src/profile/AndroidManifest.xml +7 -0
- package/flutter_app/android/build.gradle.kts +24 -0
- package/flutter_app/android/ci-release.keystore +0 -0
- package/flutter_app/android/gradle/wrapper/gradle-wrapper.properties +5 -0
- package/flutter_app/android/gradle.properties +3 -0
- package/flutter_app/android/key.properties +4 -0
- package/flutter_app/android/settings.gradle.kts +26 -0
- package/flutter_app/assets/branding/app_icon_1024.png +0 -0
- package/flutter_app/assets/branding/app_icon_128.png +0 -0
- package/flutter_app/assets/branding/app_icon_192.png +0 -0
- package/flutter_app/assets/branding/app_icon_256.png +0 -0
- package/flutter_app/assets/branding/app_icon_32.png +0 -0
- package/flutter_app/assets/branding/app_icon_512.png +0 -0
- package/flutter_app/assets/branding/app_icon_64.png +0 -0
- package/flutter_app/assets/branding/tray_icon_template.png +0 -0
- package/flutter_app/lib/features/location/location_service.dart +119 -0
- package/flutter_app/lib/features/notifications/notification_interceptor.dart +97 -0
- package/flutter_app/lib/main.dart +99 -0
- package/flutter_app/lib/main_account_settings.dart +1250 -0
- package/flutter_app/lib/main_admin.dart +886 -0
- package/flutter_app/lib/main_app_shell.dart +1682 -0
- package/flutter_app/lib/main_chat.dart +3345 -0
- package/flutter_app/lib/main_controller.dart +6781 -0
- package/flutter_app/lib/main_devices.dart +2301 -0
- package/flutter_app/lib/main_integrations.dart +1129 -0
- package/flutter_app/lib/main_launcher.dart +959 -0
- package/flutter_app/lib/main_launcher_entry.dart +5 -0
- package/flutter_app/lib/main_models.dart +3546 -0
- package/flutter_app/lib/main_navigation.dart +193 -0
- package/flutter_app/lib/main_operations.dart +4851 -0
- package/flutter_app/lib/main_recordings.dart +870 -0
- package/flutter_app/lib/main_runtime.dart +806 -0
- package/flutter_app/lib/main_settings.dart +2024 -0
- package/flutter_app/lib/main_shared.dart +2861 -0
- package/flutter_app/lib/main_theme.dart +204 -0
- package/flutter_app/lib/main_voice_assistant.dart +957 -0
- package/flutter_app/lib/src/android_apk_drop_zone.dart +32 -0
- package/flutter_app/lib/src/android_apk_drop_zone_stub.dart +16 -0
- package/flutter_app/lib/src/android_apk_drop_zone_web.dart +348 -0
- package/flutter_app/lib/src/android_app_installer.dart +22 -0
- package/flutter_app/lib/src/android_app_installer_io.dart +122 -0
- package/flutter_app/lib/src/android_app_installer_stub.dart +21 -0
- package/flutter_app/lib/src/android_launcher_bridge.dart +239 -0
- package/flutter_app/lib/src/app_launch_bridge.dart +29 -0
- package/flutter_app/lib/src/app_release_updater.dart +511 -0
- package/flutter_app/lib/src/backend_client.dart +1833 -0
- package/flutter_app/lib/src/desktop_companion.dart +2 -0
- package/flutter_app/lib/src/desktop_companion_actions.dart +586 -0
- package/flutter_app/lib/src/desktop_companion_io.dart +538 -0
- package/flutter_app/lib/src/desktop_companion_stub.dart +59 -0
- package/flutter_app/lib/src/desktop_native_bridge.dart +91 -0
- package/flutter_app/lib/src/desktop_screen_capture.dart +21 -0
- package/flutter_app/lib/src/desktop_screen_capture_io.dart +142 -0
- package/flutter_app/lib/src/desktop_screen_capture_stub.dart +12 -0
- package/flutter_app/lib/src/diagnostics_logger.dart +119 -0
- package/flutter_app/lib/src/health_bridge.dart +136 -0
- package/flutter_app/lib/src/live_voice_capture.dart +85 -0
- package/flutter_app/lib/src/messaging_access_summary.dart +46 -0
- package/flutter_app/lib/src/network/app_http_client.dart +53 -0
- package/flutter_app/lib/src/network/app_http_client_factory.dart +6 -0
- package/flutter_app/lib/src/network/app_http_client_io.dart +138 -0
- package/flutter_app/lib/src/network/app_http_client_stub.dart +3 -0
- package/flutter_app/lib/src/network/app_http_client_web.dart +94 -0
- package/flutter_app/lib/src/oauth_launcher.dart +33 -0
- package/flutter_app/lib/src/oauth_launcher_io.dart +77 -0
- package/flutter_app/lib/src/oauth_launcher_stub.dart +33 -0
- package/flutter_app/lib/src/oauth_launcher_web.dart +107 -0
- package/flutter_app/lib/src/recording_bridge.dart +232 -0
- package/flutter_app/lib/src/recording_bridge_io.dart +1019 -0
- package/flutter_app/lib/src/recording_bridge_stub.dart +120 -0
- package/flutter_app/lib/src/recording_bridge_web.dart +689 -0
- package/flutter_app/lib/src/recording_payloads.dart +86 -0
- package/flutter_app/lib/src/theme/palette.dart +81 -0
- package/flutter_app/lib/src/widget_bridge.dart +49 -0
- package/flutter_app/linux/CMakeLists.txt +128 -0
- package/flutter_app/linux/flutter/CMakeLists.txt +88 -0
- package/flutter_app/linux/flutter/generated_plugin_registrant.cc +43 -0
- package/flutter_app/linux/flutter/generated_plugin_registrant.h +15 -0
- package/flutter_app/linux/flutter/generated_plugins.cmake +31 -0
- package/flutter_app/linux/runner/CMakeLists.txt +26 -0
- package/flutter_app/linux/runner/main.cc +6 -0
- package/flutter_app/linux/runner/my_application.cc +144 -0
- package/flutter_app/linux/runner/my_application.h +18 -0
- package/flutter_app/linux/runner/resources/app_icon.png +0 -0
- package/flutter_app/macos/Flutter/Flutter-Debug.xcconfig +2 -0
- package/flutter_app/macos/Flutter/Flutter-Release.xcconfig +2 -0
- package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +40 -0
- package/flutter_app/macos/Podfile +42 -0
- package/flutter_app/macos/Podfile.lock +87 -0
- package/flutter_app/macos/Runner/AppDelegate.swift +576 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +68 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
- package/flutter_app/macos/Runner/Base.lproj/MainMenu.xib +342 -0
- package/flutter_app/macos/Runner/Configs/AppInfo.xcconfig +14 -0
- package/flutter_app/macos/Runner/Configs/Debug.xcconfig +2 -0
- package/flutter_app/macos/Runner/Configs/Release.xcconfig +2 -0
- package/flutter_app/macos/Runner/Configs/Warnings.xcconfig +13 -0
- package/flutter_app/macos/Runner/DebugProfile.entitlements +16 -0
- package/flutter_app/macos/Runner/Info.plist +36 -0
- package/flutter_app/macos/Runner/MainFlutterWindow.swift +19 -0
- package/flutter_app/macos/Runner/Release.entitlements +12 -0
- package/flutter_app/macos/Runner.xcodeproj/project.pbxproj +801 -0
- package/flutter_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/flutter_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +99 -0
- package/flutter_app/macos/Runner.xcworkspace/contents.xcworkspacedata +10 -0
- package/flutter_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/flutter_app/macos/RunnerTests/RunnerTests.swift +12 -0
- package/flutter_app/patch_strings.py +12 -0
- package/flutter_app/pubspec.lock +1088 -0
- package/flutter_app/pubspec.yaml +53 -0
- package/flutter_app/test/messaging_access_summary_test.dart +22 -0
- package/flutter_app/test/recording_payloads_test.dart +53 -0
- package/flutter_app/third_party/desktop_audio_capture/LICENSE +21 -0
- package/flutter_app/third_party/desktop_audio_capture/README.md +262 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/audio_capture.dart +65 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/config/mic_audio_config.dart +153 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/config/system_adudio_config.dart +110 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/mic/mic_audio_capture.dart +461 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/model/audio_status.dart +91 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/model/decibel_data.dart +106 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/model/input_device_type.dart +219 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/system/system_audio_capture.dart +336 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/CMakeLists.txt +101 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/audio_capture_plugin.cc +692 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/audio_capture_plugin.h +35 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/mic_capture_plugin.h +36 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/audio_capture_plugin.h +32 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/mic_capture_plugin.h +32 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/mic_capture_plugin.cc +878 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/Classes/AudioCapturePlugin.swift +27 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/Classes/MicCapturePlugin.swift +1172 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/Classes/SystemCapturePlugin.swift +655 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/Resources/PrivacyInfo.xcprivacy +12 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/desktop_audio_capture.podspec +30 -0
- package/flutter_app/third_party/desktop_audio_capture/pubspec.yaml +87 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/CMakeLists.txt +105 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.cpp +80 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.h +31 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin_c_api.cpp +12 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/include/audio_capture/audio_capture_plugin_c_api.h +23 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/include/desktop_audio_capture/audio_capture_plugin.h +25 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.cpp +1117 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.h +115 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.cpp +777 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.h +87 -0
- package/flutter_app/third_party/flutter_secure_storage_linux/linux/CMakeLists.txt +30 -0
- package/flutter_app/third_party/flutter_secure_storage_linux/linux/flutter_secure_storage_linux_plugin.cc +215 -0
- package/flutter_app/third_party/flutter_secure_storage_linux/linux/include/flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h +27 -0
- package/flutter_app/third_party/flutter_secure_storage_linux/pubspec.yaml +20 -0
- package/flutter_app/tool/generate_desktop_branding.py +219 -0
- package/flutter_app/web/favicon.png +0 -0
- package/flutter_app/web/favicon.svg +12 -0
- package/flutter_app/web/icons/Icon-192.png +0 -0
- package/flutter_app/web/icons/Icon-512.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-192.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-512.png +0 -0
- package/flutter_app/web/index.html +39 -0
- package/flutter_app/web/manifest.json +35 -0
- package/flutter_app/windows/CMakeLists.txt +108 -0
- package/flutter_app/windows/flutter/CMakeLists.txt +109 -0
- package/flutter_app/windows/flutter/generated_plugin_registrant.cc +47 -0
- package/flutter_app/windows/flutter/generated_plugin_registrant.h +15 -0
- package/flutter_app/windows/flutter/generated_plugins.cmake +35 -0
- package/flutter_app/windows/runner/CMakeLists.txt +41 -0
- package/flutter_app/windows/runner/Runner.rc +121 -0
- package/flutter_app/windows/runner/flutter_window.cpp +533 -0
- package/flutter_app/windows/runner/flutter_window.h +37 -0
- package/flutter_app/windows/runner/main.cpp +53 -0
- package/flutter_app/windows/runner/resource.h +16 -0
- package/flutter_app/windows/runner/resources/app_icon.ico +0 -0
- package/flutter_app/windows/runner/runner.exe.manifest +14 -0
- package/flutter_app/windows/runner/utils.cpp +65 -0
- package/flutter_app/windows/runner/utils.h +19 -0
- package/flutter_app/windows/runner/win32_window.cpp +299 -0
- package/flutter_app/windows/runner/win32_window.h +102 -0
- package/lib/install_helpers.js +31 -0
- package/lib/manager.js +227 -6
- package/package.json +5 -1
- package/server/db/database.js +110 -0
- package/server/http/middleware.js +55 -2
- package/server/http/routes.js +1 -0
- package/server/index.js +3 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/NOTICES +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/canvaskit/wimp.wasm +0 -0
- package/server/public/flutter_bootstrap.js +2 -2
- package/server/public/main.dart.js +75176 -73986
- package/server/routes/integrations.js +108 -1
- package/server/routes/memory.js +11 -2
- package/server/{http/routes → routes}/screenHistory.js +6 -6
- package/server/routes/settings.js +75 -2
- package/server/{http/routes → routes}/triggers.js +4 -4
- package/server/routes/wearable.js +67 -0
- package/server/services/ai/models.js +30 -0
- package/server/services/ai/providers/githubCopilot.js +97 -0
- package/server/services/ai/providers/openai.js +2 -1
- package/server/services/ai/providers/openaiCodex.js +31 -0
- package/server/services/ai/settings.js +20 -0
- package/server/services/ai/tools.js +71 -3
- package/server/services/desktop/screenRecorder.js +71 -15
- package/server/services/integrations/env.js +5 -0
- package/server/services/integrations/figma/provider.js +1 -0
- package/server/services/integrations/github/common.js +106 -0
- package/server/services/integrations/github/provider.js +499 -0
- package/server/services/integrations/github/repos.js +1124 -0
- package/server/services/integrations/google/provider.js +1 -0
- package/server/services/integrations/home_assistant/provider.js +325 -26
- package/server/services/integrations/manager.js +88 -12
- package/server/services/integrations/microsoft/provider.js +1 -0
- package/server/services/integrations/oauth_provider.js +25 -8
- package/server/services/integrations/provider_config_store.js +85 -0
- package/server/services/integrations/registry.js +4 -0
- package/server/services/integrations/spotify/provider.js +1 -0
- package/server/services/integrations/trello/provider.js +842 -0
- package/server/services/manager.js +46 -1
- package/server/services/memory/manager.js +39 -2
- package/server/services/messaging/access_policy.js +10 -0
- package/server/services/messaging/manager.js +49 -0
- package/server/services/messaging/meshtastic.js +322 -0
- package/server/services/messaging/meshtastic_env.js +100 -0
- package/server/services/tasks/runtime.js +1 -1
- package/server/services/voice/openaiClient.js +4 -1
- package/server/services/voice/openaiSpeech.js +6 -1
- package/server/services/voice/providers.js +52 -12
- package/server/services/voice/runtimeManager.js +136 -19
- package/server/services/voice/turnRunner.js +29 -9
- package/server/services/wearable/firmware_manifest.js +370 -0
- package/server/services/wearable/gateway.js +350 -0
- package/server/services/wearable/protocol.js +45 -0
- package/server/services/wearable/service.js +244 -0
- package/server/utils/local_secrets.js +56 -0
- package/server/utils/logger.js +37 -9
|
@@ -25,6 +25,7 @@ const { BrowserExtensionRegistry } = require('./browser/extension/registry');
|
|
|
25
25
|
const { DesktopCompanionRegistry } = require('./desktop/registry');
|
|
26
26
|
const { DesktopProvider } = require('./desktop/provider');
|
|
27
27
|
const { ScreenRecorder } = require('./desktop/screenRecorder');
|
|
28
|
+
const { WearableService } = require('./wearable/service');
|
|
28
29
|
const { assertRuntimeValidation, getRuntimeValidation } = require('./runtime/validation');
|
|
29
30
|
const {
|
|
30
31
|
getErrorMessage,
|
|
@@ -429,11 +430,46 @@ function createWidgetService(app) {
|
|
|
429
430
|
return widgetService;
|
|
430
431
|
}
|
|
431
432
|
|
|
433
|
+
function createWearableService(app) {
|
|
434
|
+
const wearableService = registerLocal(
|
|
435
|
+
app,
|
|
436
|
+
'wearableService',
|
|
437
|
+
new WearableService({ app }),
|
|
438
|
+
);
|
|
439
|
+
logServiceReady('Wearable service ready');
|
|
440
|
+
return wearableService;
|
|
441
|
+
}
|
|
442
|
+
|
|
432
443
|
function createScreenRecorder(app) {
|
|
444
|
+
const hasActiveRemoteCaptureSession = () => {
|
|
445
|
+
const desktopRegistry = app.locals.desktopCompanionRegistry;
|
|
446
|
+
if (desktopRegistry?.connectionsByUser instanceof Map) {
|
|
447
|
+
for (const userMap of desktopRegistry.connectionsByUser.values()) {
|
|
448
|
+
if (!(userMap instanceof Map)) continue;
|
|
449
|
+
for (const connection of userMap.values()) {
|
|
450
|
+
if (typeof connection?.isOpen === 'function' && connection.isOpen()) {
|
|
451
|
+
return true;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const extensionRegistry = app.locals.browserExtensionRegistry;
|
|
458
|
+
if (extensionRegistry?.connectionsByUser instanceof Map) {
|
|
459
|
+
for (const connection of extensionRegistry.connectionsByUser.values()) {
|
|
460
|
+
if (typeof connection?.isOpen === 'function' && connection.isOpen()) {
|
|
461
|
+
return true;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return false;
|
|
467
|
+
};
|
|
468
|
+
|
|
433
469
|
const screenRecorder = registerLocal(
|
|
434
470
|
app,
|
|
435
471
|
'screenRecorder',
|
|
436
|
-
new ScreenRecorder(),
|
|
472
|
+
new ScreenRecorder({ hasActiveRemoteCaptureSession }),
|
|
437
473
|
);
|
|
438
474
|
screenRecorder.start();
|
|
439
475
|
logServiceReady('Screen recorder started');
|
|
@@ -525,6 +561,7 @@ async function startServices(app, io) {
|
|
|
525
561
|
const messagingManager = createMessagingManager(app, io, agentEngine);
|
|
526
562
|
const recordingManager = createRecordingManager(app, io);
|
|
527
563
|
createWidgetService(app);
|
|
564
|
+
createWearableService(app);
|
|
528
565
|
createScreenRecorder(app);
|
|
529
566
|
|
|
530
567
|
restoreMessagingConnections(messagingManager);
|
|
@@ -589,6 +626,14 @@ async function stopServices(app) {
|
|
|
589
626
|
);
|
|
590
627
|
}
|
|
591
628
|
|
|
629
|
+
if (app.locals.wearableGateway?.close) {
|
|
630
|
+
tasks.push(
|
|
631
|
+
app.locals.wearableGateway.close().catch((err) => {
|
|
632
|
+
console.error('[WearableGateway] Shutdown error:', getErrorMessage(err));
|
|
633
|
+
}),
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
|
|
592
637
|
if (app.locals.browserControllers instanceof Map) {
|
|
593
638
|
for (const controller of app.locals.browserControllers.values()) {
|
|
594
639
|
tasks.push(
|
|
@@ -12,6 +12,11 @@ const {
|
|
|
12
12
|
const { getMemoryStorageDecision } = require('./policy');
|
|
13
13
|
const { AGENT_DATA_DIR } = require('../../../runtime/paths');
|
|
14
14
|
const { isMainAgent, resolveAgentId } = require('../agents/manager');
|
|
15
|
+
const {
|
|
16
|
+
decryptLocalValue,
|
|
17
|
+
encryptLocalValue,
|
|
18
|
+
isLocalEncryptedValue,
|
|
19
|
+
} = require('../../utils/local_secrets');
|
|
15
20
|
|
|
16
21
|
async function getActiveProvider(userId, agentId = null) {
|
|
17
22
|
try {
|
|
@@ -516,11 +521,43 @@ class MemoryManager {
|
|
|
516
521
|
return {};
|
|
517
522
|
}
|
|
518
523
|
}
|
|
519
|
-
try {
|
|
524
|
+
try {
|
|
525
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
526
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
527
|
+
return {};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
let shouldMigrate = false;
|
|
531
|
+
const normalized = {};
|
|
532
|
+
|
|
533
|
+
for (const [service, rawValue] of Object.entries(parsed)) {
|
|
534
|
+
const value = String(rawValue || '');
|
|
535
|
+
if (!value) {
|
|
536
|
+
normalized[service] = '';
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (!isLocalEncryptedValue(value)) shouldMigrate = true;
|
|
541
|
+
normalized[service] = decryptLocalValue(value);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (shouldMigrate) {
|
|
545
|
+
this.writeApiKeys(normalized, userId);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return normalized;
|
|
549
|
+
} catch {
|
|
550
|
+
return {};
|
|
551
|
+
}
|
|
520
552
|
}
|
|
521
553
|
|
|
522
554
|
writeApiKeys(keys, userId = null) {
|
|
523
|
-
|
|
555
|
+
const encrypted = {};
|
|
556
|
+
for (const [service, rawValue] of Object.entries(keys || {})) {
|
|
557
|
+
const value = String(rawValue || '');
|
|
558
|
+
encrypted[service] = value ? encryptLocalValue(value) : '';
|
|
559
|
+
}
|
|
560
|
+
fs.writeFileSync(this._userApiKeysPath(userId), JSON.stringify(encrypted, null, 2), 'utf-8');
|
|
524
561
|
}
|
|
525
562
|
|
|
526
563
|
setApiKey(service, key, userId = null) {
|
|
@@ -165,6 +165,16 @@ const PLATFORM_CAPABILITIES = Object.freeze({
|
|
|
165
165
|
sharedSpaceRuleScopes: Object.freeze(['channel', 'chat']),
|
|
166
166
|
sharedActorRuleScopes: Object.freeze(['user']),
|
|
167
167
|
}),
|
|
168
|
+
meshtastic: capabilityTemplate({
|
|
169
|
+
supportsDirectPolicy: false,
|
|
170
|
+
supportsSharedPolicy: true,
|
|
171
|
+
supportsMentionGate: false,
|
|
172
|
+
supportsDiscovery: false,
|
|
173
|
+
directRuleScopes: Object.freeze([]),
|
|
174
|
+
sharedSpaceRuleScopes: Object.freeze(['channel', 'chat']),
|
|
175
|
+
sharedActorRuleScopes: Object.freeze(['user']),
|
|
176
|
+
manualEntryHint: 'Add a Meshtastic node number or channel id.',
|
|
177
|
+
}),
|
|
168
178
|
feishu: capabilityTemplate({ supportsDiscovery: false }),
|
|
169
179
|
nextcloud_talk: capabilityTemplate({ supportsDiscovery: false }),
|
|
170
180
|
nostr: capabilityTemplate({ supportsDiscovery: false }),
|
|
@@ -9,6 +9,7 @@ const { WhatsAppPlatform } = require('./whatsapp');
|
|
|
9
9
|
const { TelnyxVoicePlatform } = require('./telnyx');
|
|
10
10
|
const { DiscordPlatform } = require('./discord');
|
|
11
11
|
const { TelegramPlatform } = require('./telegram');
|
|
12
|
+
const { MeshtasticPlatform } = require('./meshtastic');
|
|
12
13
|
const {
|
|
13
14
|
SlackPlatform,
|
|
14
15
|
GoogleChatPlatform,
|
|
@@ -34,6 +35,7 @@ const {
|
|
|
34
35
|
classifyRecentTarget,
|
|
35
36
|
} = require('./access_policy');
|
|
36
37
|
const { decryptValue, encryptValue } = require('../integrations/secrets');
|
|
38
|
+
const { readMeshtasticEnabled } = require('./meshtastic_env');
|
|
37
39
|
|
|
38
40
|
const LEGACY_WHATSAPP_AUTH_DIR = path.join(DATA_DIR, 'whatsapp-auth');
|
|
39
41
|
|
|
@@ -78,6 +80,7 @@ class MessagingManager extends EventEmitter {
|
|
|
78
80
|
feishu: createGenericPlatformClass('feishu'),
|
|
79
81
|
line: LinePlatform,
|
|
80
82
|
mattermost: MattermostPlatform,
|
|
83
|
+
meshtastic: MeshtasticPlatform,
|
|
81
84
|
nextcloud_talk: createGenericPlatformClass('nextcloud_talk'),
|
|
82
85
|
nostr: createGenericPlatformClass('nostr'),
|
|
83
86
|
synology_chat: createGenericPlatformClass('synology_chat'),
|
|
@@ -302,6 +305,9 @@ class MessagingManager extends EventEmitter {
|
|
|
302
305
|
config.accessPolicy = this._loadAccessPolicy(userId, agentId, platformName);
|
|
303
306
|
const PlatformClass = this.platformTypes[platformName];
|
|
304
307
|
if (!PlatformClass) throw new Error(`Unknown platform: ${platformName}`);
|
|
308
|
+
if (platformName === 'meshtastic' && !readMeshtasticEnabled()) {
|
|
309
|
+
throw new Error('Meshtastic is disabled by environment configuration');
|
|
310
|
+
}
|
|
305
311
|
|
|
306
312
|
if (platformName === 'whatsapp' && !config.authDir) {
|
|
307
313
|
config.authDir = this._scopedPlatformAuthDir(userId, agentId, platformName);
|
|
@@ -545,6 +551,14 @@ class MessagingManager extends EventEmitter {
|
|
|
545
551
|
|
|
546
552
|
getPlatformStatus(userId, platformName, options = {}) {
|
|
547
553
|
const agentId = this._agentId(userId, options);
|
|
554
|
+
if (platformName === 'meshtastic' && !readMeshtasticEnabled()) {
|
|
555
|
+
return {
|
|
556
|
+
status: 'disabled',
|
|
557
|
+
authInfo: {
|
|
558
|
+
label: 'Disabled in env',
|
|
559
|
+
},
|
|
560
|
+
};
|
|
561
|
+
}
|
|
548
562
|
const key = this._key(userId, agentId, platformName);
|
|
549
563
|
const platform = this.platforms.get(key);
|
|
550
564
|
if (!platform) {
|
|
@@ -562,7 +576,21 @@ class MessagingManager extends EventEmitter {
|
|
|
562
576
|
const connections = db.prepare('SELECT platform, status, last_connected, agent_id FROM platform_connections WHERE user_id = ? AND agent_id = ?').all(userId, agentId);
|
|
563
577
|
const statuses = {};
|
|
564
578
|
|
|
579
|
+
if (!readMeshtasticEnabled()) {
|
|
580
|
+
statuses.meshtastic = {
|
|
581
|
+
status: 'disabled',
|
|
582
|
+
agentId,
|
|
583
|
+
lastConnected: null,
|
|
584
|
+
authInfo: {
|
|
585
|
+
label: 'Disabled in env',
|
|
586
|
+
},
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
|
|
565
590
|
for (const conn of connections) {
|
|
591
|
+
if (conn.platform === 'meshtastic' && !readMeshtasticEnabled()) {
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
566
594
|
const key = this._key(userId, agentId, conn.platform);
|
|
567
595
|
const platform = this.platforms.get(key);
|
|
568
596
|
statuses[conn.platform] = {
|
|
@@ -616,6 +644,11 @@ class MessagingManager extends EventEmitter {
|
|
|
616
644
|
).all();
|
|
617
645
|
for (const row of rows) {
|
|
618
646
|
try {
|
|
647
|
+
if (row.platform === 'meshtastic' && !readMeshtasticEnabled()) {
|
|
648
|
+
db.prepare("UPDATE platform_connections SET status = 'disabled' WHERE user_id = ? AND agent_id = ? AND platform = ?")
|
|
649
|
+
.run(row.user_id, row.agent_id, row.platform);
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
619
652
|
const config = this._decodeStoredConfig(row.config);
|
|
620
653
|
console.log(`[Messaging] Restoring ${row.platform} for user ${row.user_id} agent ${row.agent_id || 'main'}`);
|
|
621
654
|
await this.connectPlatform(row.user_id, row.platform, config, { agentId: row.agent_id });
|
|
@@ -627,6 +660,22 @@ class MessagingManager extends EventEmitter {
|
|
|
627
660
|
}
|
|
628
661
|
}
|
|
629
662
|
|
|
663
|
+
async updateMeshtasticEnabled(enabled) {
|
|
664
|
+
if (enabled) return;
|
|
665
|
+
const disconnects = [];
|
|
666
|
+
for (const [key, platform] of this.platforms.entries()) {
|
|
667
|
+
if (!key.endsWith(':meshtastic')) continue;
|
|
668
|
+
disconnects.push(
|
|
669
|
+
Promise.resolve(platform.disconnect()).catch(() => {}).then(() => {
|
|
670
|
+
this.platforms.delete(key);
|
|
671
|
+
})
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
await Promise.all(disconnects);
|
|
675
|
+
db.prepare("UPDATE platform_connections SET status = 'disabled' WHERE platform = 'meshtastic'")
|
|
676
|
+
.run();
|
|
677
|
+
}
|
|
678
|
+
|
|
630
679
|
async shutdown() {
|
|
631
680
|
this.isShuttingDown = true;
|
|
632
681
|
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { BasePlatform } = require('./base');
|
|
4
|
+
const { readMeshtasticEnabled } = require('./meshtastic_env');
|
|
5
|
+
|
|
6
|
+
const DEFAULT_TCP_PORT = 4403;
|
|
7
|
+
const DEFAULT_CHANNEL = 0;
|
|
8
|
+
|
|
9
|
+
let meshtasticModulesPromise = null;
|
|
10
|
+
|
|
11
|
+
function requireText(value, label) {
|
|
12
|
+
const text = String(value || '').trim();
|
|
13
|
+
if (!text) throw new Error(`${label} is required`);
|
|
14
|
+
return text;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function parseTcpHost(input) {
|
|
18
|
+
const raw = requireText(input, 'Meshtastic device IP address');
|
|
19
|
+
if (raw.startsWith('[')) {
|
|
20
|
+
const match = raw.match(/^\[([^\]]+)\](?::(\d+))?$/);
|
|
21
|
+
if (!match) {
|
|
22
|
+
throw new Error('Meshtastic device IP address is invalid');
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
host: match[1],
|
|
26
|
+
port: match[2] ? Number(match[2]) : DEFAULT_TCP_PORT,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const parts = raw.split(':');
|
|
31
|
+
if (parts.length === 2 && /^\d+$/.test(parts[1])) {
|
|
32
|
+
return {
|
|
33
|
+
host: parts[0],
|
|
34
|
+
port: Number(parts[1]),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return { host: raw, port: DEFAULT_TCP_PORT };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function parseChannel(value) {
|
|
42
|
+
const channel = Number(String(value ?? DEFAULT_CHANNEL).trim() || DEFAULT_CHANNEL);
|
|
43
|
+
if (!Number.isInteger(channel) || channel < 0 || channel > 6) {
|
|
44
|
+
throw new Error('Meshtastic channel must be an integer from 0 to 6');
|
|
45
|
+
}
|
|
46
|
+
return channel;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function loadMeshtasticModules() {
|
|
50
|
+
if (!meshtasticModulesPromise) {
|
|
51
|
+
meshtasticModulesPromise = Promise.all([
|
|
52
|
+
import('@meshtastic/core'),
|
|
53
|
+
import('@meshtastic/transport-node'),
|
|
54
|
+
]).then(([core, transport]) => ({
|
|
55
|
+
MeshDevice: core.MeshDevice,
|
|
56
|
+
Types: core.Types,
|
|
57
|
+
TransportNode: transport.TransportNode,
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
return meshtasticModulesPromise;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function normalizeNodeId(value) {
|
|
64
|
+
const text = String(value || '').trim();
|
|
65
|
+
if (!text) return '';
|
|
66
|
+
return text.startsWith('!') ? text : `!${text}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function toIsoTimestamp(value) {
|
|
70
|
+
if (value instanceof Date) return value.toISOString();
|
|
71
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
72
|
+
const normalized = value < 1e12 ? value * 1000 : value;
|
|
73
|
+
return new Date(normalized).toISOString();
|
|
74
|
+
}
|
|
75
|
+
return new Date().toISOString();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
class MeshtasticPlatform extends BasePlatform {
|
|
79
|
+
constructor(config = {}) {
|
|
80
|
+
super('meshtastic', config);
|
|
81
|
+
this.supportsGroups = true;
|
|
82
|
+
this.supportsMedia = false;
|
|
83
|
+
this.supportsVoice = false;
|
|
84
|
+
this.status = 'disconnected';
|
|
85
|
+
this.host = null;
|
|
86
|
+
this.port = DEFAULT_TCP_PORT;
|
|
87
|
+
this.channel = DEFAULT_CHANNEL;
|
|
88
|
+
this.authInfo = null;
|
|
89
|
+
this._transport = null;
|
|
90
|
+
this._device = null;
|
|
91
|
+
this._modules = null;
|
|
92
|
+
this._connectPromise = null;
|
|
93
|
+
this._disconnecting = false;
|
|
94
|
+
this._configured = false;
|
|
95
|
+
this._lastMyNodeInfo = null;
|
|
96
|
+
this._lastNodeUsers = new Map();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async connect() {
|
|
100
|
+
if (!readMeshtasticEnabled()) {
|
|
101
|
+
throw new Error('Meshtastic is disabled by environment configuration');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (this._connectPromise) {
|
|
105
|
+
return this._connectPromise;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
this._connectPromise = this._connect();
|
|
109
|
+
try {
|
|
110
|
+
return await this._connectPromise;
|
|
111
|
+
} finally {
|
|
112
|
+
this._connectPromise = null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async _connect() {
|
|
117
|
+
const endpoint = parseTcpHost(this.config.host || this.config.ipAddress || this.config.baseUrl);
|
|
118
|
+
this.host = endpoint.host;
|
|
119
|
+
this.port = endpoint.port;
|
|
120
|
+
this.channel = parseChannel(this.config.channel);
|
|
121
|
+
this._disconnecting = false;
|
|
122
|
+
this._configured = false;
|
|
123
|
+
this._lastMyNodeInfo = null;
|
|
124
|
+
this._lastNodeUsers.clear();
|
|
125
|
+
this.status = 'connecting';
|
|
126
|
+
|
|
127
|
+
const modules = await loadMeshtasticModules();
|
|
128
|
+
this._modules = modules;
|
|
129
|
+
|
|
130
|
+
const transport = await modules.TransportNode.create(this.host, this.port, 60000);
|
|
131
|
+
this._transport = transport;
|
|
132
|
+
|
|
133
|
+
const device = new modules.MeshDevice(transport);
|
|
134
|
+
this._device = device;
|
|
135
|
+
this._wireDeviceEvents(device, modules.Types);
|
|
136
|
+
|
|
137
|
+
const ready = new Promise((resolve, reject) => {
|
|
138
|
+
let settled = false;
|
|
139
|
+
const resolveOnce = () => {
|
|
140
|
+
if (settled) return;
|
|
141
|
+
settled = true;
|
|
142
|
+
resolve({ status: this.status });
|
|
143
|
+
};
|
|
144
|
+
const rejectOnce = (error) => {
|
|
145
|
+
if (settled) return;
|
|
146
|
+
settled = true;
|
|
147
|
+
reject(error);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
device.events.onDeviceStatus.subscribe((status) => {
|
|
151
|
+
if (status === modules.Types.DeviceStatusEnum.DeviceConnected) {
|
|
152
|
+
device.configure().catch((error) => {
|
|
153
|
+
if (!this._disconnecting) {
|
|
154
|
+
rejectOnce(error);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (status === modules.Types.DeviceStatusEnum.DeviceConfigured) {
|
|
161
|
+
this._configured = true;
|
|
162
|
+
this.status = 'connected';
|
|
163
|
+
this.authInfo = this._buildAuthInfo();
|
|
164
|
+
this.emit('connected');
|
|
165
|
+
resolveOnce();
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (status === modules.Types.DeviceStatusEnum.DeviceDisconnected) {
|
|
170
|
+
this.status = 'disconnected';
|
|
171
|
+
if (!this._disconnecting) {
|
|
172
|
+
const error = new Error('Meshtastic device disconnected');
|
|
173
|
+
rejectOnce(error);
|
|
174
|
+
this.emit('disconnected', { reason: 'device_disconnected' });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
await ready;
|
|
181
|
+
return { status: this.status };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
_wireDeviceEvents(device, Types) {
|
|
185
|
+
device.events.onMyNodeInfo.subscribe((info) => {
|
|
186
|
+
this._lastMyNodeInfo = info;
|
|
187
|
+
this.authInfo = this._buildAuthInfo();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
device.events.onUserPacket.subscribe((packet) => {
|
|
191
|
+
const user = packet?.data;
|
|
192
|
+
const from = Number(packet?.from || 0);
|
|
193
|
+
if (!user || !Number.isFinite(from) || from <= 0) return;
|
|
194
|
+
this._lastNodeUsers.set(from, user);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
device.events.onMessagePacket.subscribe((packet) => {
|
|
198
|
+
if (!packet) return;
|
|
199
|
+
const channel = Number(packet.channel);
|
|
200
|
+
if (channel !== this.channel) return;
|
|
201
|
+
|
|
202
|
+
const senderNum = Number(packet.from || 0);
|
|
203
|
+
const localNodeNum = Number(this._lastMyNodeInfo?.myNodeNum || 0);
|
|
204
|
+
if (senderNum > 0 && localNodeNum > 0 && senderNum === localNodeNum) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const senderUser = this._lastNodeUsers.get(senderNum) || null;
|
|
208
|
+
const senderName = senderUser?.longName || senderUser?.shortName || null;
|
|
209
|
+
const senderUsername = normalizeNodeId(senderUser?.id || '');
|
|
210
|
+
const chatId = `channel:${channel}`;
|
|
211
|
+
|
|
212
|
+
const access = this._checkInboundAccess({
|
|
213
|
+
platform: this.name,
|
|
214
|
+
senderId: String(senderNum || ''),
|
|
215
|
+
chatId,
|
|
216
|
+
isDirect: false,
|
|
217
|
+
isShared: true,
|
|
218
|
+
groupId: chatId,
|
|
219
|
+
channelId: chatId,
|
|
220
|
+
serverId: '',
|
|
221
|
+
roomId: chatId,
|
|
222
|
+
roleIds: [],
|
|
223
|
+
phoneNumber: '',
|
|
224
|
+
wasMentioned: false,
|
|
225
|
+
}, {
|
|
226
|
+
senderName,
|
|
227
|
+
groupLabel: chatId,
|
|
228
|
+
channelLabel: chatId,
|
|
229
|
+
roomLabel: chatId,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
if (!access.allowed) return;
|
|
233
|
+
|
|
234
|
+
this.emit('message', {
|
|
235
|
+
chatId,
|
|
236
|
+
sender: String(senderNum || ''),
|
|
237
|
+
senderName,
|
|
238
|
+
senderUsername: senderUsername || null,
|
|
239
|
+
senderTag: senderUsername || null,
|
|
240
|
+
content: String(packet.data || ''),
|
|
241
|
+
mediaType: null,
|
|
242
|
+
isGroup: true,
|
|
243
|
+
messageId: String(packet.id || `${Date.now()}`),
|
|
244
|
+
timestamp: toIsoTimestamp(packet.rxTime),
|
|
245
|
+
metadata: {
|
|
246
|
+
channel,
|
|
247
|
+
host: this.host,
|
|
248
|
+
meshNodeId: senderUsername || null,
|
|
249
|
+
meshDestination: packet.type || 'broadcast',
|
|
250
|
+
},
|
|
251
|
+
rawMessage: {
|
|
252
|
+
id: packet.id,
|
|
253
|
+
from: packet.from,
|
|
254
|
+
to: packet.to,
|
|
255
|
+
type: packet.type,
|
|
256
|
+
channel: packet.channel,
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
_buildAuthInfo() {
|
|
263
|
+
const info = this._lastMyNodeInfo || {};
|
|
264
|
+
const user = info.user || {};
|
|
265
|
+
return {
|
|
266
|
+
label: user.longName || user.shortName || normalizeNodeId(user.id) || this.host || 'Meshtastic',
|
|
267
|
+
nodeId: normalizeNodeId(user.id),
|
|
268
|
+
host: this.host,
|
|
269
|
+
channel: this.channel,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async sendMessage(to, content) {
|
|
274
|
+
if (this.status !== 'connected' || !this._device || !this._modules) {
|
|
275
|
+
throw new Error('Meshtastic is not connected');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const chatId = String(to || '').trim();
|
|
279
|
+
if (chatId && chatId !== `channel:${this.channel}` && chatId !== String(this.channel)) {
|
|
280
|
+
throw new Error(`Meshtastic is configured for channel ${this.channel}`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
await this._device.sendText(
|
|
284
|
+
String(content || ''),
|
|
285
|
+
'broadcast',
|
|
286
|
+
true,
|
|
287
|
+
this.channel,
|
|
288
|
+
);
|
|
289
|
+
return { success: true };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async disconnect() {
|
|
293
|
+
this._disconnecting = true;
|
|
294
|
+
this.status = 'disconnected';
|
|
295
|
+
|
|
296
|
+
const device = this._device;
|
|
297
|
+
const transport = this._transport;
|
|
298
|
+
this._device = null;
|
|
299
|
+
this._transport = null;
|
|
300
|
+
this._configured = false;
|
|
301
|
+
|
|
302
|
+
if (device && typeof device.disconnect === 'function') {
|
|
303
|
+
await device.disconnect().catch(() => {});
|
|
304
|
+
} else if (transport && typeof transport.disconnect === 'function') {
|
|
305
|
+
await transport.disconnect().catch(() => {});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async logout() {
|
|
310
|
+
await this.disconnect();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
getAuthInfo() {
|
|
314
|
+
return this.authInfo || this._buildAuthInfo();
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
module.exports = {
|
|
319
|
+
MeshtasticPlatform,
|
|
320
|
+
parseChannel,
|
|
321
|
+
parseTcpHost,
|
|
322
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { ENV_FILE } = require('../../../runtime/paths');
|
|
6
|
+
const { parseEnv } = require('../../../runtime/env');
|
|
7
|
+
|
|
8
|
+
const MESHTASTIC_ENABLED_ENV_KEY = 'MESHTASTIC_ENABLED';
|
|
9
|
+
const FALSE_VALUES = new Set(['0', 'false', 'no', 'off', 'disabled']);
|
|
10
|
+
|
|
11
|
+
function normalizeMeshtasticEnabled(value, fallback = true) {
|
|
12
|
+
if (value == null) return fallback;
|
|
13
|
+
const normalized = String(value).trim().toLowerCase();
|
|
14
|
+
if (!normalized) return fallback;
|
|
15
|
+
return !FALSE_VALUES.has(normalized);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function readEnvFileRaw(envFile = ENV_FILE) {
|
|
19
|
+
if (!fs.existsSync(envFile)) return '';
|
|
20
|
+
return fs.readFileSync(envFile, 'utf8');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function writeEnvFileValue(key, value, envFile = ENV_FILE) {
|
|
24
|
+
const raw = readEnvFileRaw(envFile);
|
|
25
|
+
const lines = raw ? raw.split('\n') : [];
|
|
26
|
+
let replaced = false;
|
|
27
|
+
|
|
28
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
29
|
+
if (lines[i].startsWith(`${key}=`)) {
|
|
30
|
+
lines[i] = `${key}=${value}`;
|
|
31
|
+
replaced = true;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!replaced) {
|
|
37
|
+
lines.push(`${key}=${value}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const output = `${lines
|
|
41
|
+
.filter((_, idx, items) => idx !== items.length - 1 || items[idx] !== '')
|
|
42
|
+
.join('\n')}\n`;
|
|
43
|
+
fs.mkdirSync(path.dirname(envFile), { recursive: true });
|
|
44
|
+
fs.writeFileSync(envFile, output, { mode: 0o600 });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function removeEnvFileValue(key, envFile = ENV_FILE) {
|
|
48
|
+
const raw = readEnvFileRaw(envFile);
|
|
49
|
+
if (!raw) return false;
|
|
50
|
+
const lines = raw.split('\n').filter((line) => !line.startsWith(`${key}=`));
|
|
51
|
+
const output = `${lines
|
|
52
|
+
.filter((_, idx, items) => idx !== items.length - 1 || items[idx] !== '')
|
|
53
|
+
.join('\n')}\n`;
|
|
54
|
+
fs.mkdirSync(path.dirname(envFile), { recursive: true });
|
|
55
|
+
fs.writeFileSync(envFile, output, { mode: 0o600 });
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function readMeshtasticEnabled({
|
|
60
|
+
env = process.env,
|
|
61
|
+
envFile = ENV_FILE,
|
|
62
|
+
fallback = true,
|
|
63
|
+
} = {}) {
|
|
64
|
+
const envValue = env?.[MESHTASTIC_ENABLED_ENV_KEY];
|
|
65
|
+
if (envValue != null && String(envValue).trim()) {
|
|
66
|
+
return normalizeMeshtasticEnabled(envValue, fallback);
|
|
67
|
+
}
|
|
68
|
+
const parsed = parseEnv(readEnvFileRaw(envFile));
|
|
69
|
+
return normalizeMeshtasticEnabled(parsed.get(MESHTASTIC_ENABLED_ENV_KEY), fallback);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function setMeshtasticEnabled(enabled, {
|
|
73
|
+
env = process.env,
|
|
74
|
+
envFile = ENV_FILE,
|
|
75
|
+
} = {}) {
|
|
76
|
+
const normalized = normalizeMeshtasticEnabled(enabled, true);
|
|
77
|
+
writeEnvFileValue(MESHTASTIC_ENABLED_ENV_KEY, normalized ? 'true' : 'false', envFile);
|
|
78
|
+
if (env && typeof env === 'object') {
|
|
79
|
+
env[MESHTASTIC_ENABLED_ENV_KEY] = normalized ? 'true' : 'false';
|
|
80
|
+
}
|
|
81
|
+
return normalized;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function resetMeshtasticEnabled({
|
|
85
|
+
env = process.env,
|
|
86
|
+
envFile = ENV_FILE,
|
|
87
|
+
} = {}) {
|
|
88
|
+
removeEnvFileValue(MESHTASTIC_ENABLED_ENV_KEY, envFile);
|
|
89
|
+
if (env && typeof env === 'object') {
|
|
90
|
+
delete env[MESHTASTIC_ENABLED_ENV_KEY];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = {
|
|
95
|
+
MESHTASTIC_ENABLED_ENV_KEY,
|
|
96
|
+
normalizeMeshtasticEnabled,
|
|
97
|
+
readMeshtasticEnabled,
|
|
98
|
+
setMeshtasticEnabled,
|
|
99
|
+
resetMeshtasticEnabled,
|
|
100
|
+
};
|
|
@@ -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
|