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.
- package/.env.example +45 -0
- package/docs/capabilities.md +2 -2
- package/docs/configuration.md +6 -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 +111 -0
- package/flutter_app/android/app/src/debug/AndroidManifest.xml +7 -0
- package/flutter_app/android/app/src/main/AndroidManifest.xml +171 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/MainActivity.kt +804 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/auto/NeoAgentCarAppService.kt +114 -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/telecom/NeoAgentConnection.kt +118 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/telecom/NeoAgentConnectionService.kt +86 -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/arrays.xml +8 -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/automotive_app_desc.xml +4 -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/onboarding_intro.mp4 +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/features/onboarding/onboarding_chrome.dart +571 -0
- package/flutter_app/lib/features/onboarding/onboarding_messaging_step.dart +275 -0
- package/flutter_app/lib/features/onboarding/onboarding_model_step.dart +366 -0
- package/flutter_app/lib/features/onboarding/onboarding_shell.dart +57 -0
- package/flutter_app/lib/features/onboarding/onboarding_video_step.dart +218 -0
- package/flutter_app/lib/features/onboarding/onboarding_welcome_step.dart +46 -0
- package/flutter_app/lib/main.dart +103 -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 +2134 -0
- package/flutter_app/lib/main_chat.dart +3316 -0
- package/flutter_app/lib/main_controller.dart +6836 -0
- package/flutter_app/lib/main_devices.dart +2396 -0
- package/flutter_app/lib/main_integrations.dart +909 -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 +3539 -0
- package/flutter_app/lib/main_navigation.dart +188 -0
- package/flutter_app/lib/main_operations.dart +4851 -0
- package/flutter_app/lib/main_recordings.dart +920 -0
- package/flutter_app/lib/main_runtime.dart +838 -0
- package/flutter_app/lib/main_settings.dart +2040 -0
- package/flutter_app/lib/main_shared.dart +3147 -0
- package/flutter_app/lib/main_theme.dart +257 -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_auto_bridge.dart +59 -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 +1837 -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 +95 -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/web_app_update_monitor.dart +17 -0
- package/flutter_app/lib/src/web_app_update_monitor_stub.dart +24 -0
- package/flutter_app/lib/src/web_app_update_monitor_web.dart +123 -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 +47 -0
- package/flutter_app/linux/flutter/generated_plugin_registrant.h +15 -0
- package/flutter_app/linux/flutter/generated_plugins.cmake +32 -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 +44 -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 +1224 -0
- package/flutter_app/pubspec.yaml +57 -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 +50 -0
- package/flutter_app/windows/flutter/generated_plugin_registrant.h +15 -0
- package/flutter_app/windows/flutter/generated_plugins.cmake +36 -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 +201 -6
- package/package.json +5 -3
- package/server/db/database.js +125 -1
- package/server/http/middleware.js +55 -2
- package/server/http/routes.js +1 -0
- package/server/http/static.js +41 -1
- package/server/index.js +3 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/AssetManifest.bin +1 -1
- package/server/public/assets/AssetManifest.bin.json +1 -1
- package/server/public/assets/NOTICES +281 -1
- package/server/public/assets/assets/branding/onboarding_intro.mp4 +0 -0
- 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 +86707 -82621
- package/server/routes/auth.js +26 -5
- package/server/routes/integrations.js +108 -1
- package/server/routes/memory.js +11 -2
- package/server/routes/settings.js +84 -2
- package/server/routes/wearable.js +67 -0
- package/server/services/ai/engine.js +3 -2
- 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/toolSelector.js +54 -1
- package/server/services/ai/tools.js +88 -5
- package/server/services/desktop/screenRecorder.js +65 -9
- 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/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 -2
- 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/mcp/client.js +120 -23
- 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 +277 -0
- package/server/services/messaging/meshtastic_env.js +40 -0
- package/server/services/messaging/meshtastic_protocol.js +530 -0
- package/server/services/messaging/meshtastic_protocol.test.js +33 -0
- package/server/services/messaging/meshtastic_tcp_transport.js +25 -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 +144 -15
- package/server/services/voice/runtimeManager.js +156 -19
- package/server/services/voice/turnRunner.js +29 -9
- package/server/services/wearable/firmware_manifest.js +353 -0
- package/server/services/wearable/gateway.js +350 -0
- package/server/services/wearable/protocol.js +45 -0
- package/server/services/wearable/service.js +240 -0
- package/server/services/widgets/service.js +3 -0
- package/server/utils/local_secrets.js +56 -0
- package/server/utils/logger.js +37 -9
- package/server/services/integrations/home_assistant/provider.js +0 -350
package/server/routes/auth.js
CHANGED
|
@@ -75,6 +75,7 @@ function toUserPayload(user) {
|
|
|
75
75
|
createdAt: user.created_at || null,
|
|
76
76
|
lastLogin: user.last_login || null,
|
|
77
77
|
hasPassword: Number(user.password_login_enabled || 0) === 1,
|
|
78
|
+
hasCompletedOnboarding: Number(user.has_completed_onboarding || 0) === 1,
|
|
78
79
|
};
|
|
79
80
|
}
|
|
80
81
|
|
|
@@ -119,7 +120,7 @@ function readAuthenticatedUser(req) {
|
|
|
119
120
|
return null;
|
|
120
121
|
}
|
|
121
122
|
const user = db.prepare(
|
|
122
|
-
`SELECT id, username, email, email_verified_at, password_login_enabled, created_at, last_login
|
|
123
|
+
`SELECT id, username, email, email_verified_at, password_login_enabled, created_at, last_login, has_completed_onboarding
|
|
123
124
|
FROM users
|
|
124
125
|
WHERE id = ?`,
|
|
125
126
|
).get(req.session.userId);
|
|
@@ -322,14 +323,19 @@ router.get('/api/auth/providers/complete', async (req, res) => {
|
|
|
322
323
|
}
|
|
323
324
|
|
|
324
325
|
const user = db.prepare(
|
|
325
|
-
`SELECT id, username, email, email_verified_at, password_login_enabled, created_at, last_login
|
|
326
|
+
`SELECT id, username, email, email_verified_at, password_login_enabled, created_at, last_login, has_completed_onboarding
|
|
326
327
|
FROM users
|
|
327
328
|
WHERE id = ?`,
|
|
328
329
|
).get(completion.result.userId);
|
|
329
330
|
if (!user) {
|
|
330
331
|
return res.status(404).json({ error: 'User not found' });
|
|
331
332
|
}
|
|
332
|
-
|
|
333
|
+
// Skip secondary two-factor when signing in via certain social providers.
|
|
334
|
+
// Currently skip for Google (`provider === 'google`).
|
|
335
|
+
const providerKey = String(completion.provider || '').trim();
|
|
336
|
+
const twoFactor = getTwoFactorStatus(user.id);
|
|
337
|
+
const skipTwoFactorForProviders = new Set(['google']);
|
|
338
|
+
if (twoFactor.enabled && !skipTwoFactorForProviders.has(providerKey)) {
|
|
333
339
|
return establishPendingTwoFactorSession(req, res, user);
|
|
334
340
|
}
|
|
335
341
|
updateLastLogin(user.id);
|
|
@@ -412,6 +418,7 @@ router.post('/api/auth/register', authLimiter, async (req, res) => {
|
|
|
412
418
|
password_login_enabled: 1,
|
|
413
419
|
created_at: new Date().toISOString(),
|
|
414
420
|
last_login: null,
|
|
421
|
+
has_completed_onboarding: 0,
|
|
415
422
|
});
|
|
416
423
|
} catch (err) {
|
|
417
424
|
if (err?.statusCode) {
|
|
@@ -443,7 +450,7 @@ router.post('/api/auth/login', authLimiter, async (req, res) => {
|
|
|
443
450
|
}
|
|
444
451
|
|
|
445
452
|
const user = db.prepare(
|
|
446
|
-
`SELECT id, username, email, email_verified_at, password, password_login_enabled, created_at, last_login
|
|
453
|
+
`SELECT id, username, email, email_verified_at, password, password_login_enabled, created_at, last_login, has_completed_onboarding
|
|
447
454
|
FROM users
|
|
448
455
|
WHERE username = ?`,
|
|
449
456
|
).get(username);
|
|
@@ -494,7 +501,7 @@ router.post('/api/auth/login/2fa', authLimiter, async (req, res) => {
|
|
|
494
501
|
}
|
|
495
502
|
|
|
496
503
|
const user = db.prepare(
|
|
497
|
-
`SELECT id, username, email, email_verified_at, password_login_enabled, created_at, last_login
|
|
504
|
+
`SELECT id, username, email, email_verified_at, password_login_enabled, created_at, last_login, has_completed_onboarding
|
|
498
505
|
FROM users
|
|
499
506
|
WHERE id = ?`,
|
|
500
507
|
).get(pendingUserId);
|
|
@@ -728,4 +735,18 @@ router.get('/api/auth/me', (req, res) => {
|
|
|
728
735
|
res.json({ user: toUserPayload(user) });
|
|
729
736
|
});
|
|
730
737
|
|
|
738
|
+
router.post('/api/auth/onboarding/complete', (req, res) => {
|
|
739
|
+
const user = readAuthenticatedUser(req);
|
|
740
|
+
if (!user) {
|
|
741
|
+
return res.status(401).json({ error: 'Not authenticated' });
|
|
742
|
+
}
|
|
743
|
+
try {
|
|
744
|
+
db.prepare('UPDATE users SET has_completed_onboarding = 1 WHERE id = ?').run(user.id);
|
|
745
|
+
res.json({ success: true });
|
|
746
|
+
} catch (err) {
|
|
747
|
+
console.error('Onboarding complete error:', err);
|
|
748
|
+
res.status(500).json({ error: 'Failed to update onboarding status' });
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
|
|
731
752
|
module.exports = router;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
|
+
const QRCode = require('qrcode');
|
|
2
3
|
const router = express.Router();
|
|
3
4
|
const { requireAuth } = require('../middleware/auth');
|
|
4
5
|
const { sanitizeError } = require('../utils/security');
|
|
@@ -143,6 +144,26 @@ router.get('/oauth/callback', async (req, res) => {
|
|
|
143
144
|
|
|
144
145
|
router.use(requireAuth);
|
|
145
146
|
|
|
147
|
+
router.get('/qr-image', async (req, res) => {
|
|
148
|
+
try {
|
|
149
|
+
const data = String(req.query.data || '').trim();
|
|
150
|
+
if (!data) {
|
|
151
|
+
return res.status(400).send('Missing QR data.');
|
|
152
|
+
}
|
|
153
|
+
const svg = await QRCode.toString(data, {
|
|
154
|
+
errorCorrectionLevel: 'M',
|
|
155
|
+
margin: 1,
|
|
156
|
+
type: 'svg',
|
|
157
|
+
width: 320,
|
|
158
|
+
});
|
|
159
|
+
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
|
160
|
+
res.setHeader('Cache-Control', 'no-store, max-age=0');
|
|
161
|
+
return res.send(svg);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
return res.status(400).send(escapeHtml(sanitizeError(err)));
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
146
167
|
router.get('/:provider/connect/:sessionId', (req, res) => {
|
|
147
168
|
try {
|
|
148
169
|
const manager = getIntegrationManager(req);
|
|
@@ -211,7 +232,7 @@ router.get('/:provider/connect/:sessionId', (req, res) => {
|
|
|
211
232
|
const status = String(data.status || 'connecting');
|
|
212
233
|
if (status === 'awaiting_qr' && data.qr) {
|
|
213
234
|
statusEl.textContent = 'Scan this QR code to continue linking.';
|
|
214
|
-
qrEl.src = '
|
|
235
|
+
qrEl.src = '/api/integrations/qr-image?data=' + encodeURIComponent(data.qr);
|
|
215
236
|
qrEl.style.display = 'block';
|
|
216
237
|
} else if (status === 'connected') {
|
|
217
238
|
qrEl.style.display = 'none';
|
|
@@ -361,4 +382,90 @@ router.get('/:provider/tools/status', (req, res) => {
|
|
|
361
382
|
}
|
|
362
383
|
});
|
|
363
384
|
|
|
385
|
+
router.get('/:provider/config', (req, res) => {
|
|
386
|
+
try {
|
|
387
|
+
const manager = getIntegrationManager(req);
|
|
388
|
+
if (!manager) {
|
|
389
|
+
throw new Error('Official integration manager is not available on app.locals.integrationManager.');
|
|
390
|
+
}
|
|
391
|
+
const provider = manager.getProvider(req.params.provider);
|
|
392
|
+
if (!provider) {
|
|
393
|
+
throw new Error(`Unknown integration provider: ${req.params.provider}`);
|
|
394
|
+
}
|
|
395
|
+
if (typeof provider.getUserConfig !== 'function') {
|
|
396
|
+
return res.status(404).json({
|
|
397
|
+
error: `${provider.label} does not support per-user configuration.`,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
const config = provider.getUserConfig({
|
|
401
|
+
userId: req.session.userId,
|
|
402
|
+
agentId: resolveAgentId(req.session.userId, getAgentIdFromRequest(req)),
|
|
403
|
+
});
|
|
404
|
+
res.json({
|
|
405
|
+
provider: provider.key,
|
|
406
|
+
config,
|
|
407
|
+
});
|
|
408
|
+
} catch (err) {
|
|
409
|
+
res.status(400).json({ error: sanitizeError(err) });
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
router.put('/:provider/config', async (req, res) => {
|
|
414
|
+
try {
|
|
415
|
+
const manager = getIntegrationManager(req);
|
|
416
|
+
if (!manager) {
|
|
417
|
+
throw new Error('Official integration manager is not available on app.locals.integrationManager.');
|
|
418
|
+
}
|
|
419
|
+
const provider = manager.getProvider(req.params.provider);
|
|
420
|
+
if (!provider) {
|
|
421
|
+
throw new Error(`Unknown integration provider: ${req.params.provider}`);
|
|
422
|
+
}
|
|
423
|
+
if (typeof provider.saveUserConfig !== 'function') {
|
|
424
|
+
return res.status(404).json({
|
|
425
|
+
error: `${provider.label} does not support per-user configuration.`,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
const config = await provider.saveUserConfig({
|
|
429
|
+
userId: req.session.userId,
|
|
430
|
+
agentId: resolveAgentId(req.session.userId, getAgentIdFromRequest(req)),
|
|
431
|
+
config: req.body?.config || req.body || {},
|
|
432
|
+
});
|
|
433
|
+
res.json({
|
|
434
|
+
provider: provider.key,
|
|
435
|
+
config,
|
|
436
|
+
saved: true,
|
|
437
|
+
});
|
|
438
|
+
} catch (err) {
|
|
439
|
+
res.status(400).json({ error: sanitizeError(err) });
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
router.delete('/:provider/config', async (req, res) => {
|
|
444
|
+
try {
|
|
445
|
+
const manager = getIntegrationManager(req);
|
|
446
|
+
if (!manager) {
|
|
447
|
+
throw new Error('Official integration manager is not available on app.locals.integrationManager.');
|
|
448
|
+
}
|
|
449
|
+
const provider = manager.getProvider(req.params.provider);
|
|
450
|
+
if (!provider) {
|
|
451
|
+
throw new Error(`Unknown integration provider: ${req.params.provider}`);
|
|
452
|
+
}
|
|
453
|
+
if (typeof provider.clearUserConfig !== 'function') {
|
|
454
|
+
return res.status(404).json({
|
|
455
|
+
error: `${provider.label} does not support per-user configuration.`,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
await provider.clearUserConfig({
|
|
459
|
+
userId: req.session.userId,
|
|
460
|
+
agentId: resolveAgentId(req.session.userId, getAgentIdFromRequest(req)),
|
|
461
|
+
});
|
|
462
|
+
res.json({
|
|
463
|
+
provider: provider.key,
|
|
464
|
+
cleared: true,
|
|
465
|
+
});
|
|
466
|
+
} catch (err) {
|
|
467
|
+
res.status(400).json({ error: sanitizeError(err) });
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
|
|
364
471
|
module.exports = router;
|
package/server/routes/memory.js
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
const router = express.Router();
|
|
3
|
+
const rateLimit = require('express-rate-limit');
|
|
3
4
|
const { requireAuth } = require('../middleware/auth');
|
|
4
5
|
const { sanitizeError } = require('../utils/security');
|
|
5
6
|
const { getAgentIdFromRequest, resolveAgentId } = require('../services/agents/manager');
|
|
6
7
|
|
|
7
8
|
router.use(requireAuth);
|
|
8
9
|
|
|
10
|
+
const apiKeyMutationLimiter = rateLimit({
|
|
11
|
+
windowMs: 15 * 60 * 1000,
|
|
12
|
+
max: 60,
|
|
13
|
+
message: { error: 'Too many API key update attempts, try again later' },
|
|
14
|
+
standardHeaders: true,
|
|
15
|
+
legacyHeaders: false,
|
|
16
|
+
});
|
|
17
|
+
|
|
9
18
|
function normalizeMemoryIds(value) {
|
|
10
19
|
return [...new Set(
|
|
11
20
|
(Array.isArray(value) ? value : [])
|
|
@@ -232,12 +241,12 @@ router.get('/api-keys', (req, res) => {
|
|
|
232
241
|
res.json(masked);
|
|
233
242
|
});
|
|
234
243
|
|
|
235
|
-
router.put('/api-keys/:service', (req, res) => {
|
|
244
|
+
router.put('/api-keys/:service', apiKeyMutationLimiter, (req, res) => {
|
|
236
245
|
req.app.locals.memoryManager.setApiKey(req.params.service, req.body.key, req.session.userId);
|
|
237
246
|
res.json({ success: true });
|
|
238
247
|
});
|
|
239
248
|
|
|
240
|
-
router.delete('/api-keys/:service', (req, res) => {
|
|
249
|
+
router.delete('/api-keys/:service', apiKeyMutationLimiter, (req, res) => {
|
|
241
250
|
req.app.locals.memoryManager.deleteApiKey(req.params.service, req.session.userId);
|
|
242
251
|
res.json({ success: true });
|
|
243
252
|
});
|
|
@@ -21,6 +21,9 @@ const {
|
|
|
21
21
|
ensureDefaultAiSettings,
|
|
22
22
|
normalizeProviderConfigs,
|
|
23
23
|
} = require('../services/ai/settings');
|
|
24
|
+
const {
|
|
25
|
+
readMeshtasticEnabled,
|
|
26
|
+
} = require('../services/messaging/meshtastic_env');
|
|
24
27
|
const {
|
|
25
28
|
ensureDefaultRuntimeSettings,
|
|
26
29
|
getRuntimeSettings,
|
|
@@ -75,6 +78,14 @@ const VOICE_SETTING_KEYS = new Set([
|
|
|
75
78
|
'voice_live_voice',
|
|
76
79
|
]);
|
|
77
80
|
|
|
81
|
+
const ENV_BACKED_SETTING_KEYS = new Set([
|
|
82
|
+
'meshtastic_enabled',
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
const READ_ONLY_ENV_SETTING_KEYS = new Set([
|
|
86
|
+
'meshtastic_enabled',
|
|
87
|
+
]);
|
|
88
|
+
|
|
78
89
|
function toOptionalTrimmedString(value) {
|
|
79
90
|
if (typeof value !== 'string') return undefined;
|
|
80
91
|
const trimmed = value.trim();
|
|
@@ -151,6 +162,38 @@ function applyHeadlessSetting(req, value) {
|
|
|
151
162
|
.catch(() => { });
|
|
152
163
|
}
|
|
153
164
|
|
|
165
|
+
function isEnvBackedSettingKey(key) {
|
|
166
|
+
return ENV_BACKED_SETTING_KEYS.has(key);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function readEnvBackedSettingValue(key) {
|
|
170
|
+
switch (key) {
|
|
171
|
+
case 'meshtastic_enabled':
|
|
172
|
+
return readMeshtasticEnabled();
|
|
173
|
+
default:
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function writeEnvBackedSettingValue(req, key, value) {
|
|
179
|
+
switch (key) {
|
|
180
|
+
case 'meshtastic_enabled':
|
|
181
|
+
return readMeshtasticEnabled();
|
|
182
|
+
default:
|
|
183
|
+
return value;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function resetEnvBackedSettingValue(req, key) {
|
|
188
|
+
switch (key) {
|
|
189
|
+
case 'meshtastic_enabled': {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
default:
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
154
197
|
// Get supported models metadata
|
|
155
198
|
router.get('/meta/models', async (req, res) => {
|
|
156
199
|
const { getSupportedModels } = require('../services/ai/models');
|
|
@@ -209,11 +252,12 @@ router.get('/', (req, res) => {
|
|
|
209
252
|
}
|
|
210
253
|
settings.agentId = agentId;
|
|
211
254
|
settings.ai_provider_configs = normalizeProviderConfigs(settings.ai_provider_configs);
|
|
255
|
+
settings.meshtastic_enabled = readMeshtasticEnabled();
|
|
212
256
|
res.json(settings);
|
|
213
257
|
});
|
|
214
258
|
|
|
215
259
|
// Update settings (batch)
|
|
216
|
-
router.put('/', (req, res) => {
|
|
260
|
+
router.put('/', async (req, res) => {
|
|
217
261
|
const userId = req.session.userId;
|
|
218
262
|
const agentId = resolveAgentId(userId, getAgentIdFromRequest(req));
|
|
219
263
|
ensureDefaultAiSettings(userId, agentId);
|
|
@@ -238,6 +282,16 @@ router.put('/', (req, res) => {
|
|
|
238
282
|
normalizedBody.ai_provider_configs = normalizeProviderConfigs(normalizedBody.ai_provider_configs);
|
|
239
283
|
}
|
|
240
284
|
|
|
285
|
+
for (const key of Object.keys(normalizedBody)) {
|
|
286
|
+
if (READ_ONLY_ENV_SETTING_KEYS.has(key)) {
|
|
287
|
+
delete normalizedBody[key];
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (isEnvBackedSettingKey(key)) {
|
|
291
|
+
normalizedBody[key] = await writeEnvBackedSettingValue(req, key, normalizedBody[key]);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
241
295
|
if (
|
|
242
296
|
'runtime_profile' in normalizedBody
|
|
243
297
|
|| 'runtime_backend' in normalizedBody
|
|
@@ -264,6 +318,8 @@ router.put('/', (req, res) => {
|
|
|
264
318
|
const v = serializeRuntimeSettingValue(key, value);
|
|
265
319
|
if (isAgentScopedSettingKey(key)) {
|
|
266
320
|
upsertAgent.run(userId, agentId, key, v);
|
|
321
|
+
} else if (isEnvBackedSettingKey(key)) {
|
|
322
|
+
continue;
|
|
267
323
|
} else if (key !== 'agentId' && key !== 'agent_id') {
|
|
268
324
|
upsert.run(userId, key, v);
|
|
269
325
|
}
|
|
@@ -377,6 +433,9 @@ router.get('/token-usage/summary', (req, res) => {
|
|
|
377
433
|
|
|
378
434
|
// Get single setting
|
|
379
435
|
router.get('/:key', (req, res) => {
|
|
436
|
+
if (isEnvBackedSettingKey(req.params.key)) {
|
|
437
|
+
return res.json({ value: readEnvBackedSettingValue(req.params.key) });
|
|
438
|
+
}
|
|
380
439
|
const userId = req.session.userId;
|
|
381
440
|
const agentId = resolveAgentId(userId, getAgentIdFromRequest(req));
|
|
382
441
|
ensureDefaultRuntimeSettings(userId);
|
|
@@ -401,11 +460,22 @@ router.get('/:key', (req, res) => {
|
|
|
401
460
|
});
|
|
402
461
|
|
|
403
462
|
// Set single setting
|
|
404
|
-
router.put('/:key', (req, res) => {
|
|
463
|
+
router.put('/:key', async (req, res) => {
|
|
405
464
|
const userId = req.session.userId;
|
|
406
465
|
const agentId = resolveAgentId(userId, getAgentIdFromRequest(req));
|
|
407
466
|
ensureDefaultRuntimeSettings(userId);
|
|
408
467
|
let value = req.body.value;
|
|
468
|
+
if (READ_ONLY_ENV_SETTING_KEYS.has(req.params.key)) {
|
|
469
|
+
return res.status(403).json({
|
|
470
|
+
success: false,
|
|
471
|
+
error: `${req.params.key} is managed via environment only`,
|
|
472
|
+
value: readEnvBackedSettingValue(req.params.key),
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
if (isEnvBackedSettingKey(req.params.key)) {
|
|
476
|
+
const saved = await writeEnvBackedSettingValue(req, req.params.key, value);
|
|
477
|
+
return res.json({ success: true, value: saved });
|
|
478
|
+
}
|
|
409
479
|
if (req.params.key === 'platform_whitelist_whatsapp') {
|
|
410
480
|
if (typeof value === 'string') {
|
|
411
481
|
try {
|
|
@@ -467,6 +537,18 @@ router.put('/:key', (req, res) => {
|
|
|
467
537
|
|
|
468
538
|
// Delete setting
|
|
469
539
|
router.delete('/:key', (req, res) => {
|
|
540
|
+
if (READ_ONLY_ENV_SETTING_KEYS.has(req.params.key)) {
|
|
541
|
+
return res.status(403).json({
|
|
542
|
+
success: false,
|
|
543
|
+
error: `${req.params.key} is managed via environment only`,
|
|
544
|
+
value: readEnvBackedSettingValue(req.params.key),
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
if (isEnvBackedSettingKey(req.params.key)) {
|
|
548
|
+
return resetEnvBackedSettingValue(req, req.params.key)
|
|
549
|
+
.then(() => res.json({ success: true }))
|
|
550
|
+
.catch((err) => res.status(500).json({ success: false, error: err.message }));
|
|
551
|
+
}
|
|
470
552
|
const userId = req.session.userId;
|
|
471
553
|
const agentId = resolveAgentId(userId, getAgentIdFromRequest(req));
|
|
472
554
|
if (
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const express = require('express');
|
|
4
|
+
const { requireAuth } = require('../middleware/auth');
|
|
5
|
+
const { sanitizeError } = require('../utils/security');
|
|
6
|
+
|
|
7
|
+
const router = express.Router();
|
|
8
|
+
|
|
9
|
+
function wearableService(req) {
|
|
10
|
+
return req.app?.locals?.wearableService || null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Intentionally public so an already-provisioned wearable can sync local time
|
|
14
|
+
// before the user finishes session-based pairing on the device.
|
|
15
|
+
router.get('/timezone', (req, res) => {
|
|
16
|
+
try {
|
|
17
|
+
const service = wearableService(req);
|
|
18
|
+
if (!service) {
|
|
19
|
+
return res.status(500).json({ error: 'Wearable service unavailable.' });
|
|
20
|
+
}
|
|
21
|
+
res.json(service.buildTimeConfig(req));
|
|
22
|
+
} catch (err) {
|
|
23
|
+
res.status(400).json({ error: sanitizeError(err) });
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
router.use(requireAuth);
|
|
28
|
+
|
|
29
|
+
router.get('/bootstrap', async (req, res) => {
|
|
30
|
+
try {
|
|
31
|
+
const service = wearableService(req);
|
|
32
|
+
if (!service) {
|
|
33
|
+
return res.status(500).json({ error: 'Wearable service unavailable.' });
|
|
34
|
+
}
|
|
35
|
+
res.json(await service.buildBootstrap(req));
|
|
36
|
+
} catch (err) {
|
|
37
|
+
res.status(400).json({ error: sanitizeError(err) });
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
router.get('/firmware/manifest', async (req, res) => {
|
|
42
|
+
try {
|
|
43
|
+
const service = wearableService(req);
|
|
44
|
+
if (!service) {
|
|
45
|
+
return res.status(500).json({ error: 'Wearable service unavailable.' });
|
|
46
|
+
}
|
|
47
|
+
res.json(await service.buildFirmwareManifest(req));
|
|
48
|
+
} catch (err) {
|
|
49
|
+
res.status(400).json({ error: sanitizeError(err) });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
router.get('/connections', (req, res) => {
|
|
54
|
+
try {
|
|
55
|
+
const service = wearableService(req);
|
|
56
|
+
if (!service) {
|
|
57
|
+
return res.status(500).json({ error: 'Wearable service unavailable.' });
|
|
58
|
+
}
|
|
59
|
+
res.json({
|
|
60
|
+
connections: service.listConnections(req.session.userId),
|
|
61
|
+
});
|
|
62
|
+
} catch (err) {
|
|
63
|
+
res.status(400).json({ error: sanitizeError(err) });
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
module.exports = router;
|
|
@@ -38,8 +38,8 @@ const {
|
|
|
38
38
|
normalizeInterimKind,
|
|
39
39
|
} = require('./interim');
|
|
40
40
|
|
|
41
|
-
const MAX_CONSECUTIVE_TOOL_FAILURES =
|
|
42
|
-
const WIDGET_REFRESH_MAX_ITERATIONS =
|
|
41
|
+
const MAX_CONSECUTIVE_TOOL_FAILURES = 5;
|
|
42
|
+
const WIDGET_REFRESH_MAX_ITERATIONS = 30;
|
|
43
43
|
|
|
44
44
|
function generateTitle(task) {
|
|
45
45
|
if (!task || typeof task !== 'string') return 'Untitled';
|
|
@@ -1551,6 +1551,7 @@ class AgentEngine {
|
|
|
1551
1551
|
agentId,
|
|
1552
1552
|
triggerType,
|
|
1553
1553
|
triggerSource,
|
|
1554
|
+
widgetId: options.widgetId || null,
|
|
1554
1555
|
});
|
|
1555
1556
|
const mcpManager = app?.locals?.mcpManager || app?.locals?.mcpClient || this.mcpManager;
|
|
1556
1557
|
const integrationManager = app?.locals?.integrationManager || null;
|
|
@@ -3,6 +3,8 @@ const { GoogleProvider } = require('./providers/google');
|
|
|
3
3
|
const { GrokProvider } = require('./providers/grok');
|
|
4
4
|
const { OllamaProvider } = require('./providers/ollama');
|
|
5
5
|
const { OpenAIProvider } = require('./providers/openai');
|
|
6
|
+
const { GithubCopilotProvider } = require('./providers/githubCopilot');
|
|
7
|
+
const { OpenAICodexProvider } = require('./providers/openaiCodex');
|
|
6
8
|
const {
|
|
7
9
|
AI_PROVIDER_DEFINITIONS,
|
|
8
10
|
getProviderConfigs,
|
|
@@ -16,6 +18,30 @@ const STATIC_MODELS = [
|
|
|
16
18
|
provider: 'grok',
|
|
17
19
|
purpose: 'general'
|
|
18
20
|
},
|
|
21
|
+
{
|
|
22
|
+
id: 'gpt-5.3',
|
|
23
|
+
label: 'GPT-5.3 (Copilot Default)',
|
|
24
|
+
provider: 'github-copilot',
|
|
25
|
+
purpose: 'general'
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 'gpt-4.1',
|
|
29
|
+
label: 'GPT-4.1 (Copilot Fast)',
|
|
30
|
+
provider: 'github-copilot',
|
|
31
|
+
purpose: 'coding'
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'gpt-5.3-codex',
|
|
35
|
+
label: 'GPT-5.3 (Codex Default)',
|
|
36
|
+
provider: 'openai-codex',
|
|
37
|
+
purpose: 'general'
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'gpt-4.1-codex',
|
|
41
|
+
label: 'GPT-4.1 (Codex Fast)',
|
|
42
|
+
provider: 'openai-codex',
|
|
43
|
+
purpose: 'coding'
|
|
44
|
+
},
|
|
19
45
|
{
|
|
20
46
|
id: 'gpt-5-nano',
|
|
21
47
|
label: 'GPT-5 Nano (Fast / Subagents)',
|
|
@@ -302,6 +328,10 @@ function createProviderInstance(providerStr, userId = null, configOverrides = {}
|
|
|
302
328
|
return new AnthropicProvider({ apiKey: runtime.apiKey, baseUrl: runtime.baseUrl, ...providerOverrides });
|
|
303
329
|
} else if (providerStr === 'ollama') {
|
|
304
330
|
return new OllamaProvider({ baseUrl: runtime.baseUrl, ...providerOverrides });
|
|
331
|
+
} else if (providerStr === 'github-copilot') {
|
|
332
|
+
return new GithubCopilotProvider({ apiKey: runtime.apiKey, ...providerOverrides });
|
|
333
|
+
} else if (providerStr === 'openai-codex') {
|
|
334
|
+
return new OpenAICodexProvider({ apiKey: runtime.apiKey, ...providerOverrides });
|
|
305
335
|
}
|
|
306
336
|
throw new Error(`Unknown provider: ${providerStr}`);
|
|
307
337
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const { OpenAIProvider } = require('./openai');
|
|
2
|
+
|
|
3
|
+
class GithubCopilotProvider extends OpenAIProvider {
|
|
4
|
+
constructor(config = {}) {
|
|
5
|
+
// GitHub Copilot base URL defaults to the individual endpoint
|
|
6
|
+
const defaultBaseUrl = 'https://api.individual.githubcopilot.com';
|
|
7
|
+
const baseUrl = config.baseUrl || process.env.GITHUB_COPILOT_BASE_URL || defaultBaseUrl;
|
|
8
|
+
|
|
9
|
+
super({
|
|
10
|
+
...config,
|
|
11
|
+
apiKey: config.apiKey || process.env.GITHUB_COPILOT_ACCESS_TOKEN,
|
|
12
|
+
baseUrl,
|
|
13
|
+
// Pass special headers required by GitHub Copilot
|
|
14
|
+
defaultHeaders: {
|
|
15
|
+
'Editor-Version': 'vscode/1.90.0',
|
|
16
|
+
'Editor-Plugin-Version': 'copilot-chat/0.15.0',
|
|
17
|
+
'User-Agent': 'GithubCopilot/1.155.0',
|
|
18
|
+
'X-Github-Api-Version': '2023-07-07',
|
|
19
|
+
'Copilot-Integration-Id': 'vscode-chat'
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
this.name = 'github-copilot';
|
|
23
|
+
this.githubToken = config.apiKey || process.env.GITHUB_COPILOT_ACCESS_TOKEN;
|
|
24
|
+
this.copilotToken = null;
|
|
25
|
+
this.tokenExpiresAt = 0;
|
|
26
|
+
this._refreshPromise = null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async _refreshCopilotToken() {
|
|
30
|
+
if (this._refreshPromise) return this._refreshPromise;
|
|
31
|
+
|
|
32
|
+
const now = Math.floor(Date.now() / 1000);
|
|
33
|
+
// Refresh token if missing or expiring in less than 5 minutes
|
|
34
|
+
if (this.copilotToken && this.tokenExpiresAt >= now + 300) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this._refreshPromise = (async () => {
|
|
39
|
+
try {
|
|
40
|
+
if (!this.githubToken) {
|
|
41
|
+
throw new Error('GitHub Copilot access token is missing. Please run `neoagent login github-copilot`.');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const res = await fetch('https://api.github.com/copilot_internal/v2/token', {
|
|
45
|
+
headers: {
|
|
46
|
+
'Authorization': `token ${this.githubToken}`,
|
|
47
|
+
'Accept': 'application/json',
|
|
48
|
+
'User-Agent': 'NeoAgent/1.0.0'
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (!res.ok) {
|
|
53
|
+
const errorText = await res.text().catch(() => 'Unknown error');
|
|
54
|
+
throw new Error(`Failed to refresh GitHub Copilot token: HTTP ${res.status} - ${errorText}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const data = await res.json();
|
|
58
|
+
if (!data || typeof data.token !== 'string' || !data.token) {
|
|
59
|
+
throw new Error('Invalid token response from GitHub Copilot.');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.copilotToken = data.token;
|
|
63
|
+
this.tokenExpiresAt = typeof data.expires_at === 'number'
|
|
64
|
+
? data.expires_at
|
|
65
|
+
: Math.floor(new Date(data.expires_at).getTime() / 1000);
|
|
66
|
+
|
|
67
|
+
if (isNaN(this.tokenExpiresAt)) {
|
|
68
|
+
this.tokenExpiresAt = Math.floor(Date.now() / 1000) + 1800;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Update the client's API key
|
|
72
|
+
this.client.apiKey = this.copilotToken;
|
|
73
|
+
} finally {
|
|
74
|
+
this._refreshPromise = null;
|
|
75
|
+
}
|
|
76
|
+
})();
|
|
77
|
+
|
|
78
|
+
return this._refreshPromise;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async chat(messages, tools = [], options = {}) {
|
|
82
|
+
await this._refreshCopilotToken();
|
|
83
|
+
return super.chat(messages, tools, options);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async *stream(messages, tools = [], options = {}) {
|
|
87
|
+
await this._refreshCopilotToken();
|
|
88
|
+
yield* super.stream(messages, tools, options);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async analyzeImage(options = {}) {
|
|
92
|
+
await this._refreshCopilotToken();
|
|
93
|
+
return super.analyzeImage(options);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = { GithubCopilotProvider };
|
|
@@ -29,7 +29,8 @@ class OpenAIProvider extends BaseProvider {
|
|
|
29
29
|
};
|
|
30
30
|
this.client = new OpenAI({
|
|
31
31
|
apiKey: config.apiKey || process.env.OPENAI_API_KEY,
|
|
32
|
-
baseURL: config.baseUrl || process.env.OPENAI_BASE_URL || undefined
|
|
32
|
+
baseURL: config.baseUrl || process.env.OPENAI_BASE_URL || undefined,
|
|
33
|
+
defaultHeaders: config.defaultHeaders || undefined
|
|
33
34
|
});
|
|
34
35
|
}
|
|
35
36
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const { OpenAIProvider } = require('./openai');
|
|
2
|
+
|
|
3
|
+
class OpenAICodexProvider extends OpenAIProvider {
|
|
4
|
+
constructor(config = {}) {
|
|
5
|
+
const officialBaseUrl = 'https://api.openai.com/v1';
|
|
6
|
+
const baseUrl = config.baseUrl || process.env.OPENAI_CODEX_BASE_URL || 'https://chatgpt.com/backend-api/codex';
|
|
7
|
+
|
|
8
|
+
if (!baseUrl.includes('api.openai.com') && !baseUrl.includes('chatgpt.com')) {
|
|
9
|
+
console.warn(`[OpenAICodex] Using non-official base URL: ${baseUrl}`);
|
|
10
|
+
} else if (baseUrl.includes('chatgpt.com')) {
|
|
11
|
+
console.info(`[OpenAICodex] Using ChatGPT subscription endpoint: ${baseUrl}`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
super({
|
|
15
|
+
...config,
|
|
16
|
+
apiKey: config.apiKey || process.env.OPENAI_CODEX_ACCESS_TOKEN,
|
|
17
|
+
baseUrl,
|
|
18
|
+
defaultHeaders: {
|
|
19
|
+
'Editor-Version': process.env.OPENAI_CODEX_EDITOR_VERSION || 'vscode/1.99.0',
|
|
20
|
+
'Editor-Plugin-Version': process.env.OPENAI_CODEX_EDITOR_PLUGIN_VERSION || 'neoagent/1.0.0',
|
|
21
|
+
'User-Agent': process.env.OPENAI_CODEX_USER_AGENT || 'NeoAgent/1.0.0'
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
this.name = 'openai-codex';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// OpenAI Codex (subscription-based) uses the OAuth token directly as the API key.
|
|
28
|
+
// The base URL routes it through the ChatGPT backend-api.
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = { OpenAICodexProvider };
|