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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (312) hide show
  1. package/.env.example +45 -0
  2. package/docs/capabilities.md +2 -2
  3. package/docs/configuration.md +6 -5
  4. package/docs/hardware.md +1 -1
  5. package/docs/integrations.md +2 -3
  6. package/flutter_app/.metadata +42 -0
  7. package/flutter_app/README.md +21 -0
  8. package/flutter_app/analysis_options.yaml +32 -0
  9. package/flutter_app/android/app/build.gradle.kts +111 -0
  10. package/flutter_app/android/app/src/debug/AndroidManifest.xml +7 -0
  11. package/flutter_app/android/app/src/main/AndroidManifest.xml +171 -0
  12. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/MainActivity.kt +804 -0
  13. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/auto/NeoAgentCarAppService.kt +114 -0
  14. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthConnectGateway.kt +280 -0
  15. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncNotifications.kt +113 -0
  16. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncPayload.kt +57 -0
  17. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncScheduler.kt +78 -0
  18. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncWorker.kt +253 -0
  19. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/PermissionsRationaleActivity.kt +46 -0
  20. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingBootReceiver.kt +21 -0
  21. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingForegroundService.kt +586 -0
  22. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingStateStore.kt +78 -0
  23. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingUploadClient.kt +104 -0
  24. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/telecom/NeoAgentConnection.kt +118 -0
  25. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/telecom/NeoAgentConnectionService.kt +86 -0
  26. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiHomeWidgetProvider.kt +457 -0
  27. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiWidgetStore.kt +194 -0
  28. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/VoiceLaunchWidgetProvider.kt +67 -0
  29. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetConfigActivity.kt +228 -0
  30. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncScheduler.kt +72 -0
  31. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncWorker.kt +186 -0
  32. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetTaskRunWorker.kt +210 -0
  33. package/flutter_app/android/app/src/main/res/drawable/launch_background.xml +12 -0
  34. package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_bg.xml +11 -0
  35. package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_task_bg.xml +8 -0
  36. package/flutter_app/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
  37. package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget.xml +138 -0
  38. package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget_task_row.xml +52 -0
  39. package/flutter_app/android/app/src/main/res/layout/neoagent_voice_widget.xml +49 -0
  40. package/flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  41. package/flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  42. package/flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  43. package/flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  44. package/flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  45. package/flutter_app/android/app/src/main/res/values/arrays.xml +8 -0
  46. package/flutter_app/android/app/src/main/res/values/strings.xml +12 -0
  47. package/flutter_app/android/app/src/main/res/values/styles.xml +18 -0
  48. package/flutter_app/android/app/src/main/res/values-night/styles.xml +18 -0
  49. package/flutter_app/android/app/src/main/res/xml/automotive_app_desc.xml +4 -0
  50. package/flutter_app/android/app/src/main/res/xml/file_paths.xml +6 -0
  51. package/flutter_app/android/app/src/main/res/xml/neoagent_ai_widget_info.xml +12 -0
  52. package/flutter_app/android/app/src/main/res/xml/neoagent_voice_widget_info.xml +12 -0
  53. package/flutter_app/android/app/src/profile/AndroidManifest.xml +7 -0
  54. package/flutter_app/android/build.gradle.kts +24 -0
  55. package/flutter_app/android/ci-release.keystore +0 -0
  56. package/flutter_app/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  57. package/flutter_app/android/gradle.properties +3 -0
  58. package/flutter_app/android/key.properties +4 -0
  59. package/flutter_app/android/settings.gradle.kts +26 -0
  60. package/flutter_app/assets/branding/app_icon_1024.png +0 -0
  61. package/flutter_app/assets/branding/app_icon_128.png +0 -0
  62. package/flutter_app/assets/branding/app_icon_192.png +0 -0
  63. package/flutter_app/assets/branding/app_icon_256.png +0 -0
  64. package/flutter_app/assets/branding/app_icon_32.png +0 -0
  65. package/flutter_app/assets/branding/app_icon_512.png +0 -0
  66. package/flutter_app/assets/branding/app_icon_64.png +0 -0
  67. package/flutter_app/assets/branding/onboarding_intro.mp4 +0 -0
  68. package/flutter_app/assets/branding/tray_icon_template.png +0 -0
  69. package/flutter_app/lib/features/location/location_service.dart +119 -0
  70. package/flutter_app/lib/features/notifications/notification_interceptor.dart +97 -0
  71. package/flutter_app/lib/features/onboarding/onboarding_chrome.dart +571 -0
  72. package/flutter_app/lib/features/onboarding/onboarding_messaging_step.dart +275 -0
  73. package/flutter_app/lib/features/onboarding/onboarding_model_step.dart +366 -0
  74. package/flutter_app/lib/features/onboarding/onboarding_shell.dart +57 -0
  75. package/flutter_app/lib/features/onboarding/onboarding_video_step.dart +218 -0
  76. package/flutter_app/lib/features/onboarding/onboarding_welcome_step.dart +46 -0
  77. package/flutter_app/lib/main.dart +103 -0
  78. package/flutter_app/lib/main_account_settings.dart +1250 -0
  79. package/flutter_app/lib/main_admin.dart +886 -0
  80. package/flutter_app/lib/main_app_shell.dart +2134 -0
  81. package/flutter_app/lib/main_chat.dart +3316 -0
  82. package/flutter_app/lib/main_controller.dart +6836 -0
  83. package/flutter_app/lib/main_devices.dart +2396 -0
  84. package/flutter_app/lib/main_integrations.dart +909 -0
  85. package/flutter_app/lib/main_launcher.dart +959 -0
  86. package/flutter_app/lib/main_launcher_entry.dart +5 -0
  87. package/flutter_app/lib/main_models.dart +3539 -0
  88. package/flutter_app/lib/main_navigation.dart +188 -0
  89. package/flutter_app/lib/main_operations.dart +4851 -0
  90. package/flutter_app/lib/main_recordings.dart +920 -0
  91. package/flutter_app/lib/main_runtime.dart +838 -0
  92. package/flutter_app/lib/main_settings.dart +2040 -0
  93. package/flutter_app/lib/main_shared.dart +3147 -0
  94. package/flutter_app/lib/main_theme.dart +257 -0
  95. package/flutter_app/lib/main_voice_assistant.dart +957 -0
  96. package/flutter_app/lib/src/android_apk_drop_zone.dart +32 -0
  97. package/flutter_app/lib/src/android_apk_drop_zone_stub.dart +16 -0
  98. package/flutter_app/lib/src/android_apk_drop_zone_web.dart +348 -0
  99. package/flutter_app/lib/src/android_app_installer.dart +22 -0
  100. package/flutter_app/lib/src/android_app_installer_io.dart +122 -0
  101. package/flutter_app/lib/src/android_app_installer_stub.dart +21 -0
  102. package/flutter_app/lib/src/android_auto_bridge.dart +59 -0
  103. package/flutter_app/lib/src/android_launcher_bridge.dart +239 -0
  104. package/flutter_app/lib/src/app_launch_bridge.dart +29 -0
  105. package/flutter_app/lib/src/app_release_updater.dart +511 -0
  106. package/flutter_app/lib/src/backend_client.dart +1837 -0
  107. package/flutter_app/lib/src/desktop_companion.dart +2 -0
  108. package/flutter_app/lib/src/desktop_companion_actions.dart +586 -0
  109. package/flutter_app/lib/src/desktop_companion_io.dart +538 -0
  110. package/flutter_app/lib/src/desktop_companion_stub.dart +59 -0
  111. package/flutter_app/lib/src/desktop_native_bridge.dart +91 -0
  112. package/flutter_app/lib/src/desktop_screen_capture.dart +21 -0
  113. package/flutter_app/lib/src/desktop_screen_capture_io.dart +142 -0
  114. package/flutter_app/lib/src/desktop_screen_capture_stub.dart +12 -0
  115. package/flutter_app/lib/src/diagnostics_logger.dart +119 -0
  116. package/flutter_app/lib/src/health_bridge.dart +136 -0
  117. package/flutter_app/lib/src/live_voice_capture.dart +85 -0
  118. package/flutter_app/lib/src/messaging_access_summary.dart +46 -0
  119. package/flutter_app/lib/src/network/app_http_client.dart +53 -0
  120. package/flutter_app/lib/src/network/app_http_client_factory.dart +6 -0
  121. package/flutter_app/lib/src/network/app_http_client_io.dart +138 -0
  122. package/flutter_app/lib/src/network/app_http_client_stub.dart +3 -0
  123. package/flutter_app/lib/src/network/app_http_client_web.dart +94 -0
  124. package/flutter_app/lib/src/oauth_launcher.dart +33 -0
  125. package/flutter_app/lib/src/oauth_launcher_io.dart +95 -0
  126. package/flutter_app/lib/src/oauth_launcher_stub.dart +33 -0
  127. package/flutter_app/lib/src/oauth_launcher_web.dart +107 -0
  128. package/flutter_app/lib/src/recording_bridge.dart +232 -0
  129. package/flutter_app/lib/src/recording_bridge_io.dart +1019 -0
  130. package/flutter_app/lib/src/recording_bridge_stub.dart +120 -0
  131. package/flutter_app/lib/src/recording_bridge_web.dart +689 -0
  132. package/flutter_app/lib/src/recording_payloads.dart +86 -0
  133. package/flutter_app/lib/src/theme/palette.dart +81 -0
  134. package/flutter_app/lib/src/web_app_update_monitor.dart +17 -0
  135. package/flutter_app/lib/src/web_app_update_monitor_stub.dart +24 -0
  136. package/flutter_app/lib/src/web_app_update_monitor_web.dart +123 -0
  137. package/flutter_app/lib/src/widget_bridge.dart +49 -0
  138. package/flutter_app/linux/CMakeLists.txt +128 -0
  139. package/flutter_app/linux/flutter/CMakeLists.txt +88 -0
  140. package/flutter_app/linux/flutter/generated_plugin_registrant.cc +47 -0
  141. package/flutter_app/linux/flutter/generated_plugin_registrant.h +15 -0
  142. package/flutter_app/linux/flutter/generated_plugins.cmake +32 -0
  143. package/flutter_app/linux/runner/CMakeLists.txt +26 -0
  144. package/flutter_app/linux/runner/main.cc +6 -0
  145. package/flutter_app/linux/runner/my_application.cc +144 -0
  146. package/flutter_app/linux/runner/my_application.h +18 -0
  147. package/flutter_app/linux/runner/resources/app_icon.png +0 -0
  148. package/flutter_app/macos/Flutter/Flutter-Debug.xcconfig +2 -0
  149. package/flutter_app/macos/Flutter/Flutter-Release.xcconfig +2 -0
  150. package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +44 -0
  151. package/flutter_app/macos/Podfile +42 -0
  152. package/flutter_app/macos/Podfile.lock +87 -0
  153. package/flutter_app/macos/Runner/AppDelegate.swift +576 -0
  154. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +68 -0
  155. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
  156. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
  157. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
  158. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
  159. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
  160. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
  161. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
  162. package/flutter_app/macos/Runner/Base.lproj/MainMenu.xib +342 -0
  163. package/flutter_app/macos/Runner/Configs/AppInfo.xcconfig +14 -0
  164. package/flutter_app/macos/Runner/Configs/Debug.xcconfig +2 -0
  165. package/flutter_app/macos/Runner/Configs/Release.xcconfig +2 -0
  166. package/flutter_app/macos/Runner/Configs/Warnings.xcconfig +13 -0
  167. package/flutter_app/macos/Runner/DebugProfile.entitlements +16 -0
  168. package/flutter_app/macos/Runner/Info.plist +36 -0
  169. package/flutter_app/macos/Runner/MainFlutterWindow.swift +19 -0
  170. package/flutter_app/macos/Runner/Release.entitlements +12 -0
  171. package/flutter_app/macos/Runner.xcodeproj/project.pbxproj +801 -0
  172. package/flutter_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  173. package/flutter_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +99 -0
  174. package/flutter_app/macos/Runner.xcworkspace/contents.xcworkspacedata +10 -0
  175. package/flutter_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  176. package/flutter_app/macos/RunnerTests/RunnerTests.swift +12 -0
  177. package/flutter_app/patch_strings.py +12 -0
  178. package/flutter_app/pubspec.lock +1224 -0
  179. package/flutter_app/pubspec.yaml +57 -0
  180. package/flutter_app/test/messaging_access_summary_test.dart +22 -0
  181. package/flutter_app/test/recording_payloads_test.dart +53 -0
  182. package/flutter_app/third_party/desktop_audio_capture/LICENSE +21 -0
  183. package/flutter_app/third_party/desktop_audio_capture/README.md +262 -0
  184. package/flutter_app/third_party/desktop_audio_capture/lib/audio_capture.dart +65 -0
  185. package/flutter_app/third_party/desktop_audio_capture/lib/config/mic_audio_config.dart +153 -0
  186. package/flutter_app/third_party/desktop_audio_capture/lib/config/system_adudio_config.dart +110 -0
  187. package/flutter_app/third_party/desktop_audio_capture/lib/mic/mic_audio_capture.dart +461 -0
  188. package/flutter_app/third_party/desktop_audio_capture/lib/model/audio_status.dart +91 -0
  189. package/flutter_app/third_party/desktop_audio_capture/lib/model/decibel_data.dart +106 -0
  190. package/flutter_app/third_party/desktop_audio_capture/lib/model/input_device_type.dart +219 -0
  191. package/flutter_app/third_party/desktop_audio_capture/lib/system/system_audio_capture.dart +336 -0
  192. package/flutter_app/third_party/desktop_audio_capture/linux/CMakeLists.txt +101 -0
  193. package/flutter_app/third_party/desktop_audio_capture/linux/audio_capture_plugin.cc +692 -0
  194. package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/audio_capture_plugin.h +35 -0
  195. package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/mic_capture_plugin.h +36 -0
  196. package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/audio_capture_plugin.h +32 -0
  197. package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/mic_capture_plugin.h +32 -0
  198. package/flutter_app/third_party/desktop_audio_capture/linux/mic_capture_plugin.cc +878 -0
  199. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/AudioCapturePlugin.swift +27 -0
  200. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/MicCapturePlugin.swift +1172 -0
  201. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/SystemCapturePlugin.swift +655 -0
  202. package/flutter_app/third_party/desktop_audio_capture/macos/Resources/PrivacyInfo.xcprivacy +12 -0
  203. package/flutter_app/third_party/desktop_audio_capture/macos/desktop_audio_capture.podspec +30 -0
  204. package/flutter_app/third_party/desktop_audio_capture/pubspec.yaml +87 -0
  205. package/flutter_app/third_party/desktop_audio_capture/windows/CMakeLists.txt +105 -0
  206. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.cpp +80 -0
  207. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.h +31 -0
  208. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin_c_api.cpp +12 -0
  209. package/flutter_app/third_party/desktop_audio_capture/windows/include/audio_capture/audio_capture_plugin_c_api.h +23 -0
  210. package/flutter_app/third_party/desktop_audio_capture/windows/include/desktop_audio_capture/audio_capture_plugin.h +25 -0
  211. package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.cpp +1117 -0
  212. package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.h +115 -0
  213. package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.cpp +777 -0
  214. package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.h +87 -0
  215. package/flutter_app/third_party/flutter_secure_storage_linux/linux/CMakeLists.txt +30 -0
  216. package/flutter_app/third_party/flutter_secure_storage_linux/linux/flutter_secure_storage_linux_plugin.cc +215 -0
  217. package/flutter_app/third_party/flutter_secure_storage_linux/linux/include/flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h +27 -0
  218. package/flutter_app/third_party/flutter_secure_storage_linux/pubspec.yaml +20 -0
  219. package/flutter_app/tool/generate_desktop_branding.py +219 -0
  220. package/flutter_app/web/favicon.png +0 -0
  221. package/flutter_app/web/favicon.svg +12 -0
  222. package/flutter_app/web/icons/Icon-192.png +0 -0
  223. package/flutter_app/web/icons/Icon-512.png +0 -0
  224. package/flutter_app/web/icons/Icon-maskable-192.png +0 -0
  225. package/flutter_app/web/icons/Icon-maskable-512.png +0 -0
  226. package/flutter_app/web/index.html +39 -0
  227. package/flutter_app/web/manifest.json +35 -0
  228. package/flutter_app/windows/CMakeLists.txt +108 -0
  229. package/flutter_app/windows/flutter/CMakeLists.txt +109 -0
  230. package/flutter_app/windows/flutter/generated_plugin_registrant.cc +50 -0
  231. package/flutter_app/windows/flutter/generated_plugin_registrant.h +15 -0
  232. package/flutter_app/windows/flutter/generated_plugins.cmake +36 -0
  233. package/flutter_app/windows/runner/CMakeLists.txt +41 -0
  234. package/flutter_app/windows/runner/Runner.rc +121 -0
  235. package/flutter_app/windows/runner/flutter_window.cpp +533 -0
  236. package/flutter_app/windows/runner/flutter_window.h +37 -0
  237. package/flutter_app/windows/runner/main.cpp +53 -0
  238. package/flutter_app/windows/runner/resource.h +16 -0
  239. package/flutter_app/windows/runner/resources/app_icon.ico +0 -0
  240. package/flutter_app/windows/runner/runner.exe.manifest +14 -0
  241. package/flutter_app/windows/runner/utils.cpp +65 -0
  242. package/flutter_app/windows/runner/utils.h +19 -0
  243. package/flutter_app/windows/runner/win32_window.cpp +299 -0
  244. package/flutter_app/windows/runner/win32_window.h +102 -0
  245. package/lib/install_helpers.js +31 -0
  246. package/lib/manager.js +201 -6
  247. package/package.json +5 -3
  248. package/server/db/database.js +125 -1
  249. package/server/http/middleware.js +55 -2
  250. package/server/http/routes.js +1 -0
  251. package/server/http/static.js +41 -1
  252. package/server/index.js +3 -0
  253. package/server/public/.last_build_id +1 -1
  254. package/server/public/assets/AssetManifest.bin +1 -1
  255. package/server/public/assets/AssetManifest.bin.json +1 -1
  256. package/server/public/assets/NOTICES +281 -1
  257. package/server/public/assets/assets/branding/onboarding_intro.mp4 +0 -0
  258. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  259. package/server/public/canvaskit/wimp.wasm +0 -0
  260. package/server/public/flutter_bootstrap.js +2 -2
  261. package/server/public/main.dart.js +86707 -82621
  262. package/server/routes/auth.js +26 -5
  263. package/server/routes/integrations.js +108 -1
  264. package/server/routes/memory.js +11 -2
  265. package/server/routes/settings.js +84 -2
  266. package/server/routes/wearable.js +67 -0
  267. package/server/services/ai/engine.js +3 -2
  268. package/server/services/ai/models.js +30 -0
  269. package/server/services/ai/providers/githubCopilot.js +97 -0
  270. package/server/services/ai/providers/openai.js +2 -1
  271. package/server/services/ai/providers/openaiCodex.js +31 -0
  272. package/server/services/ai/settings.js +20 -0
  273. package/server/services/ai/toolSelector.js +54 -1
  274. package/server/services/ai/tools.js +88 -5
  275. package/server/services/desktop/screenRecorder.js +65 -9
  276. package/server/services/integrations/env.js +5 -0
  277. package/server/services/integrations/figma/provider.js +1 -0
  278. package/server/services/integrations/github/common.js +106 -0
  279. package/server/services/integrations/github/provider.js +499 -0
  280. package/server/services/integrations/github/repos.js +1124 -0
  281. package/server/services/integrations/google/provider.js +1 -0
  282. package/server/services/integrations/manager.js +88 -12
  283. package/server/services/integrations/microsoft/provider.js +1 -0
  284. package/server/services/integrations/oauth_provider.js +25 -8
  285. package/server/services/integrations/provider_config_store.js +85 -0
  286. package/server/services/integrations/registry.js +4 -2
  287. package/server/services/integrations/spotify/provider.js +1 -0
  288. package/server/services/integrations/trello/provider.js +842 -0
  289. package/server/services/manager.js +46 -1
  290. package/server/services/mcp/client.js +120 -23
  291. package/server/services/memory/manager.js +39 -2
  292. package/server/services/messaging/access_policy.js +10 -0
  293. package/server/services/messaging/manager.js +49 -0
  294. package/server/services/messaging/meshtastic.js +277 -0
  295. package/server/services/messaging/meshtastic_env.js +40 -0
  296. package/server/services/messaging/meshtastic_protocol.js +530 -0
  297. package/server/services/messaging/meshtastic_protocol.test.js +33 -0
  298. package/server/services/messaging/meshtastic_tcp_transport.js +25 -0
  299. package/server/services/tasks/runtime.js +1 -1
  300. package/server/services/voice/openaiClient.js +4 -1
  301. package/server/services/voice/openaiSpeech.js +6 -1
  302. package/server/services/voice/providers.js +144 -15
  303. package/server/services/voice/runtimeManager.js +156 -19
  304. package/server/services/voice/turnRunner.js +29 -9
  305. package/server/services/wearable/firmware_manifest.js +353 -0
  306. package/server/services/wearable/gateway.js +350 -0
  307. package/server/services/wearable/protocol.js +45 -0
  308. package/server/services/wearable/service.js +240 -0
  309. package/server/services/widgets/service.js +3 -0
  310. package/server/utils/local_secrets.js +56 -0
  311. package/server/utils/logger.js +37 -9
  312. package/server/services/integrations/home_assistant/provider.js +0 -350
@@ -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
- if (getTwoFactorStatus(user.id).enabled) {
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 = 'https://api.qrserver.com/v1/create-qr-code/?data=' + encodeURIComponent(data.qr) + '&size=320x320';
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;
@@ -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 = 3;
42
- const WIDGET_REFRESH_MAX_ITERATIONS = 6;
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 };