neoagent 2.4.1-beta.8 → 2.4.1

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 (248) hide show
  1. package/.env.example +33 -3
  2. package/LICENSE +111 -56
  3. package/README.md +8 -3
  4. package/docs/configuration.md +8 -0
  5. package/docs/getting-started.md +9 -3
  6. package/docs/index.md +4 -0
  7. package/extensions/chrome-browser/background.mjs +45 -7
  8. package/extensions/chrome-browser/icons/icon128.png +0 -0
  9. package/extensions/chrome-browser/icons/icon16.png +0 -0
  10. package/extensions/chrome-browser/icons/icon48.png +0 -0
  11. package/extensions/chrome-browser/icons/logo.svg +39 -8
  12. package/extensions/chrome-browser/manifest.json +3 -2
  13. package/extensions/chrome-browser/popup.html +5 -1
  14. package/extensions/chrome-browser/popup.js +15 -2
  15. package/flutter_app/android/app/src/main/AndroidManifest.xml +2 -1
  16. package/flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  17. package/flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  18. package/flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  19. package/flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  20. package/flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  21. package/flutter_app/assets/branding/app_icon_1024.png +0 -0
  22. package/flutter_app/assets/branding/app_icon_128.png +0 -0
  23. package/flutter_app/assets/branding/app_icon_192.png +0 -0
  24. package/flutter_app/assets/branding/app_icon_256.png +0 -0
  25. package/flutter_app/assets/branding/app_icon_32.png +0 -0
  26. package/flutter_app/assets/branding/app_icon_512.png +0 -0
  27. package/flutter_app/assets/branding/app_icon_64.png +0 -0
  28. package/flutter_app/assets/branding/app_icon_light_1024.png +0 -0
  29. package/flutter_app/assets/branding/app_icon_light_128.png +0 -0
  30. package/flutter_app/assets/branding/app_icon_light_192.png +0 -0
  31. package/flutter_app/assets/branding/app_icon_light_256.png +0 -0
  32. package/flutter_app/assets/branding/app_icon_light_32.png +0 -0
  33. package/flutter_app/assets/branding/app_icon_light_512.png +0 -0
  34. package/flutter_app/assets/branding/app_icon_light_64.png +0 -0
  35. package/flutter_app/assets/branding/onboarding_intro.mp4 +0 -0
  36. package/flutter_app/assets/branding/tray_icon_light_template.png +0 -0
  37. package/flutter_app/assets/branding/tray_icon_template.png +0 -0
  38. package/flutter_app/lib/features/location/location_service.dart +3 -0
  39. package/flutter_app/lib/features/onboarding/onboarding_chrome.dart +391 -382
  40. package/flutter_app/lib/features/onboarding/onboarding_companion_step.dart +743 -0
  41. package/flutter_app/lib/features/onboarding/onboarding_messaging_step.dart +18 -16
  42. package/flutter_app/lib/features/onboarding/onboarding_model_step.dart +19 -18
  43. package/flutter_app/lib/features/onboarding/onboarding_shell.dart +8 -1
  44. package/flutter_app/lib/features/onboarding/onboarding_video_step.dart +16 -13
  45. package/flutter_app/lib/features/onboarding/onboarding_welcome_step.dart +17 -13
  46. package/flutter_app/lib/main.dart +3 -0
  47. package/flutter_app/lib/main_account_settings.dart +10 -34
  48. package/flutter_app/lib/main_admin.dart +14 -1
  49. package/flutter_app/lib/main_app_shell.dart +377 -340
  50. package/flutter_app/lib/main_chat.dart +707 -227
  51. package/flutter_app/lib/main_controller.dart +338 -46
  52. package/flutter_app/lib/main_devices.dart +851 -122
  53. package/flutter_app/lib/main_integrations.dart +255 -6
  54. package/flutter_app/lib/main_launcher.dart +1 -1
  55. package/flutter_app/lib/main_model_picker.dart +685 -0
  56. package/flutter_app/lib/main_models.dart +212 -0
  57. package/flutter_app/lib/main_navigation.dart +1 -9
  58. package/flutter_app/lib/main_operations.dart +1417 -614
  59. package/flutter_app/lib/main_settings.dart +764 -887
  60. package/flutter_app/lib/main_shared.dart +971 -443
  61. package/flutter_app/lib/main_spacing.dart +4 -4
  62. package/flutter_app/lib/main_theme.dart +22 -14
  63. package/flutter_app/lib/main_unified.dart +5 -72
  64. package/flutter_app/lib/src/android_apk_drop_zone.dart +24 -0
  65. package/flutter_app/lib/src/android_apk_drop_zone_stub.dart +13 -0
  66. package/flutter_app/lib/src/android_apk_drop_zone_web.dart +219 -0
  67. package/flutter_app/lib/src/backend_client.dart +79 -0
  68. package/flutter_app/lib/src/desktop_companion_actions.dart +56 -7
  69. package/flutter_app/lib/src/desktop_companion_io.dart +77 -1
  70. package/flutter_app/lib/src/desktop_native_bridge.dart +13 -0
  71. package/flutter_app/lib/src/security/password_strength.dart +84 -0
  72. package/flutter_app/lib/src/stream_renderer.dart +205 -35
  73. package/flutter_app/lib/src/theme/palette.dart +76 -34
  74. package/flutter_app/linux/runner/resources/app_icon.png +0 -0
  75. package/flutter_app/macos/Runner/AppDelegate.swift +44 -0
  76. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
  77. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
  78. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
  79. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
  80. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
  81. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
  82. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
  83. package/flutter_app/pubspec.lock +2 -2
  84. package/flutter_app/pubspec.yaml +7 -1
  85. package/flutter_app/tool/branding_source/neoagent-icon-1024.png +0 -0
  86. package/flutter_app/tool/branding_source/neoagent-icon-128.png +0 -0
  87. package/flutter_app/tool/branding_source/neoagent-icon-16.png +0 -0
  88. package/flutter_app/tool/branding_source/neoagent-icon-180.png +0 -0
  89. package/flutter_app/tool/branding_source/neoagent-icon-192.png +0 -0
  90. package/flutter_app/tool/branding_source/neoagent-icon-256.png +0 -0
  91. package/flutter_app/tool/branding_source/neoagent-icon-32.png +0 -0
  92. package/flutter_app/tool/branding_source/neoagent-icon-48.png +0 -0
  93. package/flutter_app/tool/branding_source/neoagent-icon-512.png +0 -0
  94. package/flutter_app/tool/branding_source/neoagent-icon-64.png +0 -0
  95. package/flutter_app/tool/branding_source/neoagent-icon.svg +43 -0
  96. package/flutter_app/tool/generate_desktop_branding.py +154 -152
  97. package/flutter_app/web/favicon.png +0 -0
  98. package/flutter_app/web/favicon.svg +40 -9
  99. package/flutter_app/web/favicon_light.svg +43 -0
  100. package/flutter_app/web/icons/Icon-192-light.png +0 -0
  101. package/flutter_app/web/icons/Icon-192.png +0 -0
  102. package/flutter_app/web/icons/Icon-512-light.png +0 -0
  103. package/flutter_app/web/icons/Icon-512.png +0 -0
  104. package/flutter_app/web/icons/Icon-maskable-192-light.png +0 -0
  105. package/flutter_app/web/icons/Icon-maskable-192.png +0 -0
  106. package/flutter_app/web/icons/Icon-maskable-512-light.png +0 -0
  107. package/flutter_app/web/icons/Icon-maskable-512.png +0 -0
  108. package/flutter_app/windows/runner/main.cpp +7 -1
  109. package/flutter_app/windows/runner/resources/app_icon.ico +0 -0
  110. package/lib/manager.js +445 -83
  111. package/package.json +17 -3
  112. package/runtime/paths.js +3 -1
  113. package/server/admin/access.js +198 -0
  114. package/server/admin/admin.css +268 -0
  115. package/server/admin/admin.js +348 -0
  116. package/server/admin/analytics.js +128 -0
  117. package/server/admin/index.html +1015 -0
  118. package/server/admin/login.html +290 -0
  119. package/server/admin/logo.svg +43 -0
  120. package/server/admin/sql.js +134 -0
  121. package/server/admin/users.js +147 -0
  122. package/server/config/origins.js +3 -1
  123. package/server/db/database.js +92 -0
  124. package/server/db/ftsQuery.js +27 -0
  125. package/server/http/routes.js +1 -0
  126. package/server/http/static.js +23 -6
  127. package/server/index.js +1 -1
  128. package/server/middleware/adminAuth.js +48 -0
  129. package/server/middleware/auth.js +1 -40
  130. package/server/public/.last_build_id +1 -1
  131. package/server/public/app_icon.png +0 -0
  132. package/server/public/assets/AssetManifest.bin +1 -1
  133. package/server/public/assets/AssetManifest.bin.json +1 -1
  134. package/server/public/assets/assets/branding/app_icon_1024.png +0 -0
  135. package/server/public/assets/assets/branding/app_icon_256.png +0 -0
  136. package/server/public/assets/assets/branding/app_icon_512.png +0 -0
  137. package/server/public/assets/assets/branding/app_icon_light_1024.png +0 -0
  138. package/server/public/assets/assets/branding/app_icon_light_256.png +0 -0
  139. package/server/public/assets/assets/branding/app_icon_light_512.png +0 -0
  140. package/server/public/assets/assets/branding/onboarding_intro.mp4 +0 -0
  141. package/server/public/assets/assets/branding/tray_icon_light_template.png +0 -0
  142. package/server/public/assets/assets/branding/tray_icon_template.png +0 -0
  143. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  144. package/server/public/assets/web/icons/Icon-192.png +0 -0
  145. package/server/public/favicon.png +0 -0
  146. package/server/public/favicon.svg +40 -9
  147. package/server/public/favicon_light.svg +43 -0
  148. package/server/public/flutter_bootstrap.js +2 -2
  149. package/server/public/icons/Icon-192-light.png +0 -0
  150. package/server/public/icons/Icon-192.png +0 -0
  151. package/server/public/icons/Icon-512-light.png +0 -0
  152. package/server/public/icons/Icon-512.png +0 -0
  153. package/server/public/icons/Icon-maskable-192-light.png +0 -0
  154. package/server/public/icons/Icon-maskable-192.png +0 -0
  155. package/server/public/icons/Icon-maskable-512-light.png +0 -0
  156. package/server/public/icons/Icon-maskable-512.png +0 -0
  157. package/server/public/main.dart.js +88944 -85720
  158. package/server/routes/admin.js +632 -0
  159. package/server/routes/agent_profiles.js +3 -0
  160. package/server/routes/agents.js +1 -1
  161. package/server/routes/auth.js +3 -1
  162. package/server/routes/browser.js +14 -0
  163. package/server/routes/browser_extension.js +21 -4
  164. package/server/routes/desktop.js +10 -0
  165. package/server/routes/mcp.js +29 -13
  166. package/server/routes/memory.js +23 -1
  167. package/server/routes/messaging.js +2 -0
  168. package/server/routes/screenHistory.js +14 -8
  169. package/server/routes/settings.js +14 -2
  170. package/server/routes/stream.js +12 -3
  171. package/server/routes/triggers.js +4 -4
  172. package/server/routes/voice_assistant.js +36 -1
  173. package/server/routes/workspace.js +86 -0
  174. package/server/services/account/admin_two_factor.js +132 -0
  175. package/server/services/account/password_policy.js +6 -1
  176. package/server/services/ai/completion.js +44 -0
  177. package/server/services/ai/engine.js +321 -500
  178. package/server/services/ai/imageAnalysis.js +9 -5
  179. package/server/services/ai/logFormat.js +46 -0
  180. package/server/services/ai/loopPolicy.js +11 -0
  181. package/server/services/ai/messagingFallback.js +228 -0
  182. package/server/services/ai/models.js +194 -55
  183. package/server/services/ai/providerRetry.js +169 -0
  184. package/server/services/ai/providers/anthropic.js +15 -4
  185. package/server/services/ai/providers/google.js +38 -11
  186. package/server/services/ai/providers/grok.js +19 -68
  187. package/server/services/ai/providers/grokOauth.js +141 -0
  188. package/server/services/ai/providers/nvidia.js +154 -0
  189. package/server/services/ai/providers/ollama.js +76 -39
  190. package/server/services/ai/providers/openai.js +20 -31
  191. package/server/services/ai/providers/openaiCodex.js +6 -2
  192. package/server/services/ai/providers/openaiCompatible.js +70 -0
  193. package/server/services/ai/providers/openrouter.js +162 -0
  194. package/server/services/ai/settings.js +30 -0
  195. package/server/services/ai/systemPrompt.js +51 -29
  196. package/server/services/ai/taskAnalysis.js +60 -1
  197. package/server/services/ai/toolEvidence.js +207 -0
  198. package/server/services/ai/toolSelector.js +8 -1
  199. package/server/services/ai/tools.js +80 -15
  200. package/server/services/android/controller.js +36 -1
  201. package/server/services/browser/controller.js +18 -0
  202. package/server/services/browser/extension/manifest.js +33 -0
  203. package/server/services/browser/extension/provider.js +12 -6
  204. package/server/services/browser/extension/registry.js +229 -18
  205. package/server/services/desktop/protocol.js +1 -0
  206. package/server/services/desktop/provider.js +4 -0
  207. package/server/services/desktop/registry.js +104 -1
  208. package/server/services/desktop/screenRecorder.js +208 -98
  209. package/server/services/desktop/screen_recorder_support.js +46 -0
  210. package/server/services/integrations/google/provider.js +13 -0
  211. package/server/services/integrations/home_assistant/constants.js +88 -0
  212. package/server/services/integrations/home_assistant/network.js +207 -0
  213. package/server/services/integrations/home_assistant/provider.js +249 -0
  214. package/server/services/integrations/home_assistant/snapshot.js +98 -0
  215. package/server/services/integrations/home_assistant/tools.js +101 -0
  216. package/server/services/integrations/manager.js +5 -0
  217. package/server/services/integrations/registry.js +2 -0
  218. package/server/services/integrations/trello/provider.js +8 -2
  219. package/server/services/manager.js +51 -47
  220. package/server/services/mcp/client.js +208 -268
  221. package/server/services/mcp/client_support.js +172 -0
  222. package/server/services/mcp/recovery.js +116 -0
  223. package/server/services/mcp/tool_operations.js +123 -0
  224. package/server/services/memory/ingestion.js +185 -370
  225. package/server/services/memory/ingestion_coverage.js +129 -0
  226. package/server/services/memory/ingestion_documents.js +123 -0
  227. package/server/services/memory/ingestion_support.js +171 -0
  228. package/server/services/memory/intelligence.js +181 -0
  229. package/server/services/memory/manager.js +476 -32
  230. package/server/services/messaging/automation.js +71 -134
  231. package/server/services/messaging/formatting_guides.js +26 -1
  232. package/server/services/messaging/inbound_queue.js +111 -0
  233. package/server/services/messaging/manager.js +25 -8
  234. package/server/services/messaging/typing_keepalive.js +110 -0
  235. package/server/services/runtime/manager.js +7 -3
  236. package/server/services/runtime/settings.js +17 -0
  237. package/server/services/streaming/android-stream.js +296 -32
  238. package/server/services/streaming/stream-hub.js +1 -1
  239. package/server/services/tasks/integration_runtime.js +4 -1
  240. package/server/services/tasks/runtime.js +415 -56
  241. package/server/services/tasks/task_repository.js +48 -6
  242. package/server/services/websocket.js +8 -2
  243. package/server/services/widgets/service.js +17 -1
  244. package/server/services/workspace/manager.js +116 -0
  245. package/server/utils/logger.js +44 -6
  246. package/server/utils/security.js +3 -0
  247. package/server/utils/version.js +29 -19
  248. package/server/services/memory/openhuman_uplift.test.js +0 -98
package/.env.example CHANGED
@@ -2,6 +2,18 @@
2
2
  # Core runtime
3
3
  ########################################
4
4
 
5
+ # Admin dashboard credentials — set during `neoagent setup`.
6
+ # Access the dashboard at http://localhost:<PORT>/admin
7
+ # Run `neoagent admin` to display these credentials at any time.
8
+ ADMIN_USERNAME=admin
9
+ ADMIN_PASSWORD=
10
+
11
+ # Admin API key for programmatic access to /admin/api/* endpoints.
12
+ # Use: Authorization: Bearer <key>
13
+ # Generate or rotate via the admin dashboard → Access → Generate key.
14
+ # Leave blank to disable API key auth (dashboard session auth still works).
15
+ ADMIN_API_KEY=
16
+
5
17
  PORT=3333
6
18
  NODE_ENV=production
7
19
  # Auto-generated on first start or setup if left blank.
@@ -111,6 +123,17 @@ GOOGLE_AI_KEY=your-google-ai-key-here
111
123
  # • MiniMax-M2.7 via the Anthropic-compatible MiniMax endpoint
112
124
  MINIMAX_API_KEY=your-minimax-api-key-here
113
125
 
126
+ # NVIDIA NIM API key — used for:
127
+ # • Free-tier and paid models on NVIDIA's hosted inference (Nemotron, Kimi, Llama 4, DeepSeek, etc.)
128
+ # Get your key at: https://build.nvidia.com
129
+ NVIDIA_API_KEY=your-nvidia-nim-api-key-here
130
+
131
+ # OpenRouter API key — used for:
132
+ # • 300+ models from all major providers through a single API
133
+ # • Includes free-tier models (no credit needed for :free variants)
134
+ # Get your key at: https://openrouter.ai/keys
135
+ OPENROUTER_API_KEY=your-openrouter-api-key-here
136
+
114
137
  # GitHub Copilot OAuth token — used for:
115
138
  # • Copilot-backed chat/coding models (gpt-5.3, gpt-4.1)
116
139
  # Set via: neoagent login github-copilot
@@ -219,9 +242,16 @@ DEEPGRAM_LANGUAGE=multi
219
242
 
220
243
  OLLAMA_URL=http://localhost:11434
221
244
 
222
- # Local screen OCR capture (macOS desktop context recorder).
223
- # Set to false to disable background screen capture/OCR polling.
224
- NEOAGENT_SCREEN_RECORDER_ENABLED=true
245
+ # Connected integration memory refresh cadence in milliseconds.
246
+ NEOAGENT_MEMORY_INGESTION_INTERVAL_MS=600000
247
+
248
+ # Local screen OCR capture. This captures the backend host's macOS desktop,
249
+ # so enable it only when that host is the configured user's desktop.
250
+ NEOAGENT_SCREEN_RECORDER_ENABLED=false
251
+ # Existing NeoAgent user ID that owns all captured screen history. Required when enabled.
252
+ NEOAGENT_SCREEN_RECORDER_USER_ID=
253
+ NEOAGENT_SCREEN_RECORDER_INTERVAL_MS=10000
254
+ NEOAGENT_SCREEN_RECORDER_RETENTION_DAYS=7
225
255
 
226
256
  ########################################
227
257
  # Messaging and voice integrations
package/LICENSE CHANGED
@@ -1,5 +1,5 @@
1
- GNU AFFERO GENERAL PUBLIC LICENSE
2
- Version 3, 19 November 2007
1
+ GNU GENERAL PUBLIC LICENSE
2
+ Version 3, 29 June 2007
3
3
 
4
4
  Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
5
5
  Everyone is permitted to copy and distribute verbatim copies
@@ -7,15 +7,17 @@
7
7
 
8
8
  Preamble
9
9
 
10
- The GNU Affero General Public License is a free, copyleft license for
11
- software and other kinds of works, specifically designed to ensure
12
- cooperation with the community in the case of network server software.
10
+ The GNU General Public License is a free, copyleft license for
11
+ software and other kinds of works.
13
12
 
14
13
  The licenses for most software and other practical works are designed
15
14
  to take away your freedom to share and change the works. By contrast,
16
- our General Public Licenses are intended to guarantee your freedom to
15
+ the GNU General Public License is intended to guarantee your freedom to
17
16
  share and change all versions of a program--to make sure it remains free
18
- software for all its users.
17
+ software for all its users. We, the Free Software Foundation, use the
18
+ GNU General Public License for most of our software; it applies also to
19
+ any other work released this way by its authors. You can apply it to
20
+ your programs, too.
19
21
 
20
22
  When we speak of free software, we are referring to freedom, not
21
23
  price. Our General Public Licenses are designed to make sure that you
@@ -24,34 +26,44 @@ them if you wish), that you receive source code or can get it if you
24
26
  want it, that you can change the software or use pieces of it in new
25
27
  free programs, and that you know you can do these things.
26
28
 
27
- Developers that use our General Public Licenses protect your rights
28
- with two steps: (1) assert copyright on the software, and (2) offer
29
- you this License which gives you legal permission to copy, distribute
30
- and/or modify the software.
31
-
32
- A secondary benefit of defending all users' freedom is that
33
- improvements made in alternate versions of the program, if they
34
- receive widespread use, become available for other developers to
35
- incorporate. Many developers of free software are heartened and
36
- encouraged by the resulting cooperation. However, in the case of
37
- software used on network servers, this result may fail to come about.
38
- The GNU General Public License permits making a modified version and
39
- letting the public access it on a server without ever releasing its
40
- source code to the public.
41
-
42
- The GNU Affero General Public License is designed specifically to
43
- ensure that, in such cases, the modified source code becomes available
44
- to the community. It requires the operator of a network server to
45
- provide the source code of the modified version running there to the
46
- users of that server. Therefore, public use of a modified version, on
47
- a publicly accessible server, gives the public access to the source
48
- code of the modified version.
49
-
50
- An older license, called the Affero General Public License and
51
- published by Affero, was designed to accomplish similar goals. This is
52
- a different license, not a version of the Affero GPL, but Affero has
53
- released a new version of the Affero GPL which permits relicensing under
54
- this license.
29
+ To protect your rights, we need to prevent others from denying you
30
+ these rights or asking you to surrender the rights. Therefore, you have
31
+ certain responsibilities if you distribute copies of the software, or if
32
+ you modify it: responsibilities to respect the freedom of others.
33
+
34
+ For example, if you distribute copies of such a program, whether
35
+ gratis or for a fee, you must pass on to the recipients the same
36
+ freedoms that you received. You must make sure that they, too, receive
37
+ or can get the source code. And you must show them these terms so they
38
+ know their rights.
39
+
40
+ Developers that use the GNU GPL protect your rights with two steps:
41
+ (1) assert copyright on the software, and (2) offer you this License
42
+ giving you legal permission to copy, distribute and/or modify it.
43
+
44
+ For the developers' and authors' protection, the GPL clearly explains
45
+ that there is no warranty for this free software. For both users' and
46
+ authors' sake, the GPL requires that modified versions be marked as
47
+ changed, so that their problems will not be attributed erroneously to
48
+ authors of previous versions.
49
+
50
+ Some devices are designed to deny users access to install or run
51
+ modified versions of the software inside them, although the manufacturer
52
+ can do so. This is fundamentally incompatible with the aim of
53
+ protecting users' freedom to change the software. The systematic
54
+ pattern of such abuse occurs in the area of products for individuals to
55
+ use, which is precisely where it is most unacceptable. Therefore, we
56
+ have designed this version of the GPL to prohibit the practice for those
57
+ products. If such problems arise substantially in other domains, we
58
+ stand ready to extend this provision to those domains in future versions
59
+ of the GPL, as needed to protect the freedom of users.
60
+
61
+ Finally, every program is threatened constantly by software patents.
62
+ States should not allow patents to restrict development and use of
63
+ software on general-purpose computers, but in those that do, we wish to
64
+ avoid the special danger that patents applied to a free program could
65
+ make it effectively proprietary. To prevent this, the GPL assures that
66
+ patents cannot be used to render the program non-free.
55
67
 
56
68
  The precise terms and conditions for copying, distribution and
57
69
  modification follow.
@@ -60,7 +72,7 @@ modification follow.
60
72
 
61
73
  0. Definitions.
62
74
 
63
- "This License" refers to version 3 of the GNU Affero General Public License.
75
+ "This License" refers to version 3 of the GNU General Public License.
64
76
 
65
77
  "Copyright" also means copyright-like laws that apply to other kinds of
66
78
  works, such as semiconductor masks.
@@ -537,45 +549,35 @@ to collect a royalty for further conveying from those to whom you convey
537
549
  the Program, the only way you could satisfy both those terms and this
538
550
  License would be to refrain entirely from conveying the Program.
539
551
 
540
- 13. Remote Network Interaction; Use with the GNU General Public License.
541
-
542
- Notwithstanding any other provision of this License, if you modify the
543
- Program, your modified version must prominently offer all users
544
- interacting with it remotely through a computer network (if your version
545
- supports such interaction) an opportunity to receive the Corresponding
546
- Source of your version by providing access to the Corresponding Source
547
- from a network server at no charge, through some standard or customary
548
- means of facilitating copying of software. This Corresponding Source
549
- shall include the Corresponding Source for any work covered by version 3
550
- of the GNU General Public License that is incorporated pursuant to the
551
- following paragraph.
552
+ 13. Use with the GNU Affero General Public License.
552
553
 
553
554
  Notwithstanding any other provision of this License, you have
554
555
  permission to link or combine any covered work with a work licensed
555
- under version 3 of the GNU General Public License into a single
556
+ under version 3 of the GNU Affero General Public License into a single
556
557
  combined work, and to convey the resulting work. The terms of this
557
558
  License will continue to apply to the part which is the covered work,
558
- but the work with which it is combined will remain governed by version
559
- 3 of the GNU General Public License.
559
+ but the special requirements of the GNU Affero General Public License,
560
+ section 13, concerning interaction through a network will apply to the
561
+ combination as such.
560
562
 
561
563
  14. Revised Versions of this License.
562
564
 
563
565
  The Free Software Foundation may publish revised and/or new versions of
564
- the GNU Affero General Public License from time to time. Such new versions
565
- will be similar in spirit to the present version, but may differ in detail to
566
+ the GNU General Public License from time to time. Such new versions will
567
+ be similar in spirit to the present version, but may differ in detail to
566
568
  address new problems or concerns.
567
569
 
568
570
  Each version is given a distinguishing version number. If the
569
- Program specifies that a certain numbered version of the GNU Affero General
571
+ Program specifies that a certain numbered version of the GNU General
570
572
  Public License "or any later version" applies to it, you have the
571
573
  option of following the terms and conditions either of that numbered
572
574
  version or of any later version published by the Free Software
573
575
  Foundation. If the Program does not specify a version number of the
574
- GNU Affero General Public License, you may choose any version ever published
576
+ GNU General Public License, you may choose any version ever published
575
577
  by the Free Software Foundation.
576
578
 
577
579
  If the Program specifies that a proxy can decide which future
578
- versions of the GNU Affero General Public License can be used, that proxy's
580
+ versions of the GNU General Public License can be used, that proxy's
579
581
  public statement of acceptance of a version permanently authorizes you
580
582
  to choose that version for the Program.
581
583
 
@@ -617,3 +619,56 @@ Program, unless a warranty or assumption of liability accompanies a
617
619
  copy of the Program in return for a fee.
618
620
 
619
621
  END OF TERMS AND CONDITIONS
622
+
623
+ How to Apply These Terms to Your New Programs
624
+
625
+ If you develop a new program, and you want it to be of the greatest
626
+ possible use to the public, the best way to achieve this is to make it
627
+ free software which everyone can redistribute and change under these terms.
628
+
629
+ To do so, attach the following notices to the program. It is safest
630
+ to attach them to the start of each source file to most effectively
631
+ state the exclusion of warranty; and each file should have at least
632
+ the "copyright" line and a pointer to where the full notice is found.
633
+
634
+ <one line to give the program's name and a brief idea of what it does.>
635
+ Copyright (C) <year> <name of author>
636
+
637
+ This program is free software: you can redistribute it and/or modify
638
+ it under the terms of the GNU General Public License as published by
639
+ the Free Software Foundation, either version 3 of the License, or
640
+ (at your option) any later version.
641
+
642
+ This program is distributed in the hope that it will be useful,
643
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
644
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645
+ GNU General Public License for more details.
646
+
647
+ You should have received a copy of the GNU General Public License
648
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
649
+
650
+ Also add information on how to contact you by electronic and paper mail.
651
+
652
+ If the program does terminal interaction, make it output a short
653
+ notice like this when it starts in an interactive mode:
654
+
655
+ <program> Copyright (C) <year> <name of author>
656
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657
+ This is free software, and you are welcome to redistribute it
658
+ under certain conditions; type `show c' for details.
659
+
660
+ The hypothetical commands `show w' and `show c' should show the appropriate
661
+ parts of the General Public License. Of course, your program's commands
662
+ might be different; for a GUI interface, you would use an "about box".
663
+
664
+ You should also get your employer (if you work as a programmer) or school,
665
+ if any, to sign a "copyright disclaimer" for the program, if necessary.
666
+ For more information on this, and how to apply and follow the GNU GPL, see
667
+ <https://www.gnu.org/licenses/>.
668
+
669
+ The GNU General Public License does not permit incorporating your program
670
+ into proprietary programs. If your program is a subroutine library, you
671
+ may consider it more useful to permit linking proprietary applications with
672
+ the library. If this is what you want to do, use the GNU Lesser General
673
+ Public License instead of this License. But first, please read
674
+ <https://www.gnu.org/licenses/why-not-lgpl.html>.
package/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ <p align="center">
2
+ <img src="flutter_app/assets/branding/app_icon_128.png" width="80" alt="NeoAgent">
3
+ </p>
4
+
1
5
  <h1 align="center">NeoAgent</h1>
2
6
 
3
7
  <p align="center"><strong>Your agent. Your server. Your rules.</strong></p>
@@ -27,14 +31,15 @@
27
31
 
28
32
  ## Install
29
33
 
30
- Requires Node.js 20+ and QEMU — see [getting started](docs/getting-started.md) for details.
31
-
32
34
  ```bash
33
35
  npm install -g neoagent
34
36
  neoagent install
35
37
  ```
36
38
 
37
- Opens at **http://localhost:3333** when complete. Runs as a `launchd` user service on macOS and `systemd --user` on Linux.
39
+ `neoagent install` runs a preflight, writes a secure default config when setup
40
+ cannot prompt, installs Node dependencies, builds or uses the bundled web
41
+ client, bootstraps supported system tools where possible, and starts the
42
+ service. Available at **http://localhost:3333** when complete.
38
43
 
39
44
  ## Manage
40
45
 
@@ -4,6 +4,12 @@ NeoAgent reads server config from `~/.neoagent/.env`. Run `neoagent setup` to ge
4
4
 
5
5
  All AI provider credentials, OAuth client secrets, and deployment settings are server-side only — never sent to the client or exposed in the UI.
6
6
 
7
+ ## Admin Dashboard
8
+
9
+ The admin dashboard at `/admin` provides a web UI for operator tasks including AI provider key management, server logs, and runtime updates. Credentials are generated during `neoagent setup` (or run `neoagent admin` to view them).
10
+
11
+ Navigate to **Providers** in the sidebar to set or rotate API keys without editing `.env` manually — changes take effect immediately without a server restart.
12
+
7
13
  ## Minimal Config
8
14
 
9
15
  ```dotenv
@@ -40,6 +46,8 @@ At least one key is required unless you only use local Ollama.
40
46
  | `XAI_BASE_URL` | Optional xAI-compatible base URL override |
41
47
  | `GOOGLE_AI_KEY` | Gemini (Google) |
42
48
  | `MINIMAX_API_KEY` | MiniMax (including `MiniMax-M2.7`) |
49
+ | `NVIDIA_API_KEY` | NVIDIA NIM (free-tier + paid: Nemotron, Kimi, Llama 4, DeepSeek, etc.) |
50
+ | `OPENROUTER_API_KEY` | OpenRouter — access 300+ models from all providers through one API; free-tier models included |
43
51
  | `BRAVE_SEARCH_API_KEY` | Brave Search for the `web_search` tool |
44
52
  | `OPENAI_BASE_URL` | Optional OpenAI-compatible base URL override |
45
53
  | `ANTHROPIC_BASE_URL` | Optional Anthropic-compatible base URL override |
@@ -7,12 +7,16 @@ Install takes about 5 minutes. The first VM boot downloads and configures an Ubu
7
7
  | | |
8
8
  |---|---|
9
9
  | Node.js | 20 or newer |
10
- | QEMU | for VM-isolated browser and Android |
10
+ | QEMU | installed automatically when supported; used for VM-isolated browser and Android |
11
11
  | AI provider key | Anthropic, OpenAI, Gemini, Grok, MiniMax, or local Ollama |
12
12
 
13
13
  No API key is required if you only use local Ollama.
14
14
 
15
- ### Install QEMU
15
+ ### Optional manual QEMU install
16
+
17
+ `neoagent install` tries to install QEMU on supported macOS and Linux package
18
+ managers. If the machine does not have a supported package manager yet, install
19
+ QEMU manually and rerun `neoagent install`.
16
20
 
17
21
  ```bash
18
22
  # macOS
@@ -29,7 +33,9 @@ npm install -g neoagent
29
33
  neoagent install
30
34
  ```
31
35
 
32
- This runs setup (if no existing config), installs dependencies, and starts the service.
36
+ This runs a preflight, creates or updates config, installs dependencies, builds
37
+ or uses the bundled web client, starts the service, and prints any remaining
38
+ machine-specific action items.
33
39
 
34
40
  Open **http://localhost:3333** in your browser when the install finishes.
35
41
 
package/docs/index.md CHANGED
@@ -4,6 +4,10 @@ title: NeoAgent
4
4
  sidebar_label: Overview
5
5
  ---
6
6
 
7
+ <p align="center">
8
+ <img src="/img/app_icon.png" width="72" alt="NeoAgent" />
9
+ </p>
10
+
7
11
  # NeoAgent
8
12
 
9
13
  Self-hosted AI agent server. Runs as a system service, keeps all credentials on your machine, and provides a full operator UI for chat, automation, Android device control, recordings, integrations, and memory.
@@ -1,13 +1,15 @@
1
1
  import { createBrowserProtocol } from './protocol.mjs';
2
2
  import { DEFAULT_SERVER_URL } from './config.mjs';
3
3
 
4
- const STORAGE_KEYS = ['serverUrl', 'configuredServerUrl', 'token', 'pairingId', 'pairingSecret', 'approvalUrl', 'status'];
4
+ const STORAGE_KEYS = ['serverUrl', 'configuredServerUrl', 'token', 'pairingId', 'pairingSecret', 'approvalUrl', 'status', 'extensionName'];
5
5
  const protocol = createBrowserProtocol(chrome);
6
6
  let socket = null;
7
7
  let reconnectTimer = null;
8
8
  let suppressSocketClose = false;
9
9
  const DEFAULT_FETCH_TIMEOUT_MS = 10000;
10
10
  const DEFAULT_WS_CONNECT_TIMEOUT_MS = 10000;
11
+ const KEEPALIVE_ALARM_NAME = 'neoagent-extension-keepalive';
12
+ const KEEPALIVE_ALARM_MINUTES = 1;
11
13
  const EXTENSION_PROTOCOL_VERSION = 1;
12
14
 
13
15
  function getStorage(keys = STORAGE_KEYS) {
@@ -81,6 +83,12 @@ function clearReconnectTimer() {
81
83
  reconnectTimer = null;
82
84
  }
83
85
 
86
+ function ensureKeepaliveAlarm() {
87
+ chrome.alarms?.create?.(KEEPALIVE_ALARM_NAME, {
88
+ periodInMinutes: KEEPALIVE_ALARM_MINUTES,
89
+ });
90
+ }
91
+
84
92
  async function handleSocketDisconnected(ws) {
85
93
  if (socket !== ws) {
86
94
  return;
@@ -188,10 +196,12 @@ async function handleSocketMessage(raw) {
188
196
  async function startPairing(serverUrl) {
189
197
  const normalized = await resolveServerUrl(serverUrl);
190
198
  if (!normalized) throw new Error('NeoAgent server URL required.');
199
+ const { extensionName } = await getStorage(['extensionName']);
200
+ const nameToUse = String(extensionName || 'Chrome Extension').trim() || 'Chrome Extension';
191
201
  const response = await fetchWithTimeout(`${normalized}/api/browser-extension/pairing/request`, {
192
202
  method: 'POST',
193
203
  headers: { 'content-type': 'application/json' },
194
- body: JSON.stringify({ extensionName: 'NeoAgent Browser' }),
204
+ body: JSON.stringify({ extensionName: nameToUse }),
195
205
  });
196
206
  const payload = await response.json().catch(() => ({}));
197
207
  if (!response.ok) throw new Error(payload.error || `Pairing failed: ${response.status}`);
@@ -212,14 +222,15 @@ async function startPairing(serverUrl) {
212
222
  }
213
223
 
214
224
  async function claimPairing() {
215
- const { serverUrl, pairingId, pairingSecret } = await getStorage(['serverUrl', 'pairingId', 'pairingSecret']);
225
+ const { serverUrl, pairingId, pairingSecret, extensionName } = await getStorage(['serverUrl', 'pairingId', 'pairingSecret', 'extensionName']);
216
226
  if (!serverUrl || !pairingId || !pairingSecret) {
217
227
  throw new Error('No pending pairing request.');
218
228
  }
229
+ const nameToUse = String(extensionName || 'Chrome Extension').trim() || 'Chrome Extension';
219
230
  const response = await fetchWithTimeout(`${serverUrl}/api/browser-extension/pairing/${encodeURIComponent(pairingId)}/claim`, {
220
231
  method: 'POST',
221
232
  headers: { 'content-type': 'application/json' },
222
- body: JSON.stringify({ pairingSecret, extensionName: 'NeoAgent Browser' }),
233
+ body: JSON.stringify({ pairingSecret, extensionName: nameToUse }),
223
234
  });
224
235
  const payload = await response.json().catch(() => ({}));
225
236
  if (!response.ok) throw new Error(payload.error || `Claim failed: ${response.status}`);
@@ -250,12 +261,18 @@ async function checkForUpdates(preferredServerUrl) {
250
261
  const response = await fetchWithTimeout(`${serverUrl}/api/browser-extension/latest`);
251
262
  const latest = await response.json().catch(() => ({}));
252
263
  if (!response.ok) throw new Error(latest.error || `Update check failed: ${response.status}`);
253
- const currentVersion = chrome.runtime.getManifest().version;
264
+ const manifest = chrome.runtime.getManifest();
265
+ const currentVersion = manifest.version;
266
+ const currentVersionName = manifest.version_name || currentVersion;
267
+ const latestVersion = latest.version || currentVersion;
268
+ const latestVersionName = latest.versionName || latestVersion;
254
269
  return {
255
270
  currentVersion,
256
- latestVersion: latest.version || currentVersion,
271
+ currentVersionName,
272
+ latestVersion,
273
+ latestVersionName,
257
274
  downloadUrl: latest.downloadUrl || `${serverUrl}/api/browser-extension/download`,
258
- updateAvailable: compareVersions(latest.version, currentVersion) > 0,
275
+ updateAvailable: compareVersions(latestVersion, currentVersion) > 0,
259
276
  };
260
277
  }
261
278
 
@@ -279,6 +296,9 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
279
296
  return disconnect();
280
297
  case 'checkForUpdates':
281
298
  return checkForUpdates(message.serverUrl);
299
+ case 'saveExtensionName':
300
+ await setStorage({ extensionName: message.extensionName });
301
+ return { success: true };
282
302
  case 'openDownload':
283
303
  return openDownload(message.serverUrl);
284
304
  case 'getState':
@@ -296,4 +316,22 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
296
316
  return true;
297
317
  });
298
318
 
319
+ chrome.alarms?.onAlarm?.addListener((alarm) => {
320
+ if (alarm?.name !== KEEPALIVE_ALARM_NAME) return;
321
+ connect().catch((error) => {
322
+ console.error('NeoAgent keepalive reconnect failed', error);
323
+ });
324
+ });
325
+
326
+ chrome.runtime.onStartup?.addListener(() => {
327
+ ensureKeepaliveAlarm();
328
+ connect().catch(() => {});
329
+ });
330
+
331
+ chrome.runtime.onInstalled?.addListener(() => {
332
+ ensureKeepaliveAlarm();
333
+ connect().catch(() => {});
334
+ });
335
+
336
+ ensureKeepaliveAlarm();
299
337
  connect().catch(() => {});
@@ -1,12 +1,43 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 100 100">
2
2
  <defs>
3
- <linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
4
- <stop offset="0%" stop-color="#8f6d3e"/>
5
- <stop offset="100%" stop-color="#2f7d6e"/>
3
+ <linearGradient id="tile" x1="0%" y1="0%" x2="100%" y2="100%">
4
+ <stop offset="0%" stop-color="#705331"></stop>
5
+ <stop offset="100%" stop-color="#22564c"></stop>
6
6
  </linearGradient>
7
+ <radialGradient id="shine" cx="26%" cy="14%" r="72%">
8
+ <stop offset="0%" stop-color="#ffffff" stop-opacity="0.22"></stop>
9
+ <stop offset="100%" stop-color="#ffffff" stop-opacity="0"></stop>
10
+ </radialGradient>
11
+ <radialGradient id="core" cx="38%" cy="32%" r="75%">
12
+ <stop offset="0%" stop-color="#fffdf6"></stop>
13
+ <stop offset="48%" stop-color="#eee3cc"></stop>
14
+ <stop offset="100%" stop-color="#b5a888"></stop>
15
+ </radialGradient>
16
+ <radialGradient id="node" cx="36%" cy="30%" r="80%">
17
+ <stop offset="0%" stop-color="#f8dca8"></stop>
18
+ <stop offset="52%" stop-color="#d3a85f"></stop>
19
+ <stop offset="100%" stop-color="#9b6f2f"></stop>
20
+ </radialGradient>
21
+ <linearGradient id="tube" x1="0%" y1="0%" x2="0%" y2="100%">
22
+ <stop offset="0%" stop-color="#fffaf0"></stop>
23
+ <stop offset="52%" stop-color="#efe7d6"></stop>
24
+ <stop offset="100%" stop-color="#cabf9f"></stop>
25
+ </linearGradient>
26
+ <filter id="sh" x="-30%" y="-30%" width="160%" height="160%">
27
+ <feDropShadow dx="0" dy="1.1" stdDeviation="1.1" flood-color="#000000" flood-opacity="0.45"></feDropShadow>
28
+ </filter>
29
+ <clipPath id="tc"><rect x="0" y="0" width="100" height="100" rx="22.5"></rect></clipPath>
7
30
  </defs>
8
- <rect x="5.76" y="5.76" width="20.48" height="20.48" rx="6.96" fill="url(#bg)" stroke="#ffffff" stroke-opacity="0.16" stroke-width="1"/>
9
- <polygon points="16,9.76 9.35,13.12 16,16.48 22.65,13.12" fill="white"/>
10
- <polyline points="9.35,16.48 16,19.68 22.65,16.48" fill="none" stroke="white" stroke-width="1.44" stroke-linecap="round" stroke-linejoin="round"/>
11
- <polyline points="9.35,19.68 16,22.72 22.65,19.68" fill="none" stroke="white" stroke-width="1.44" stroke-linecap="round" stroke-linejoin="round"/>
31
+ <g clip-path="url(#tc)">
32
+ <rect x="0" y="0" width="100" height="100" fill="url(#tile)"></rect>
33
+ <rect x="0" y="0" width="100" height="100" fill="url(#shine)"></rect>
34
+ <g fill="none" stroke="url(#tube)" stroke-linecap="round" filter="url(#sh)">
35
+ <circle cx="50" cy="50" r="17" stroke-width="5" stroke-dasharray="78 29" transform="rotate(-35 50 50)" stroke-opacity="0.92"></circle>
36
+ <circle cx="50" cy="50" r="30" stroke-width="5" stroke-dasharray="150 39" transform="rotate(70 50 50)" stroke-opacity="0.8"></circle>
37
+ </g>
38
+ <circle cx="50" cy="50" r="7" fill="url(#core)"></circle>
39
+ <circle cx="50" cy="20" r="5.6" fill="url(#node)"></circle>
40
+ <circle cx="48" cy="18.4" r="1.7" fill="#fff7e6" fill-opacity="0.8"></circle>
41
+ </g>
42
+ <rect x="0.6" y="0.6" width="98.8" height="98.8" rx="22" fill="none" stroke="#ffffff" stroke-opacity="0.16" stroke-width="1.2"></rect>
12
43
  </svg>
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "NeoAgent Browser",
4
- "version": "1.0.0",
4
+ "version": "2.4.1.8",
5
+ "version_name": "2.4.1-beta.8",
5
6
  "description": "Connect this Chrome browser to NeoAgent for browser automation.",
6
7
  "minimum_chrome_version": "118",
7
- "permissions": ["debugger", "storage", "tabs"],
8
+ "permissions": ["alarms", "debugger", "storage", "tabs"],
8
9
  "host_permissions": ["http://*/*", "https://*/*"],
9
10
  "icons": {
10
11
  "16": "icons/icon16.png",
@@ -38,7 +38,11 @@
38
38
  Server URL
39
39
  <input id="serverUrl" type="url" placeholder="https://neoagent.example.com">
40
40
  </label>
41
- <div class="settings-actions">
41
+ <label style="margin-top: 8px;">
42
+ Extension Name
43
+ <input id="extensionName" type="text" placeholder="e.g. Work Laptop, Personal Mac" value="Chrome Extension">
44
+ </label>
45
+ <div class="settings-actions" style="margin-top: 12px;">
42
46
  <button id="checkUpdate" type="button" class="secondary">Check for update</button>
43
47
  <button id="download" type="button" class="secondary">Download latest ZIP</button>
44
48
  <button id="disconnect" type="button" class="secondary danger">Disconnect</button>
@@ -1,6 +1,7 @@
1
1
  const statusEl = document.querySelector('#status');
2
2
  const statusDotEl = document.querySelector('#statusDot');
3
3
  const serverUrlEl = document.querySelector('#serverUrl');
4
+ const extensionNameEl = document.querySelector('#extensionName');
4
5
  const serverLabelEl = document.querySelector('#serverLabel');
5
6
  const messageEl = document.querySelector('#message');
6
7
  const settingsEl = document.querySelector('#settings');
@@ -214,6 +215,10 @@ async function refresh() {
214
215
  if (serverUrl && document.activeElement !== serverUrlEl) {
215
216
  serverUrlEl.value = serverUrl;
216
217
  }
218
+ const extensionName = currentState.extensionName || 'Chrome Extension';
219
+ if (document.activeElement !== extensionNameEl) {
220
+ extensionNameEl.value = extensionName;
221
+ }
217
222
  if (!serverUrl) {
218
223
  settingsEl.open = true;
219
224
  }
@@ -237,6 +242,14 @@ function bindAsyncClick(element, handler) {
237
242
  }
238
243
 
239
244
  serverUrlEl.addEventListener('input', updateFlow);
245
+ extensionNameEl.addEventListener('input', async () => {
246
+ const name = String(extensionNameEl.value || '').trim();
247
+ try {
248
+ await send('saveExtensionName', { extensionName: name });
249
+ } catch (err) {
250
+ console.error('Failed to save extension name', err);
251
+ }
252
+ });
240
253
 
241
254
  bindAsyncClick(primaryActionEl, () => runAction(primaryActionEl.dataset.action));
242
255
  bindAsyncClick(secondaryActionEl, () => runAction(secondaryActionEl.dataset.action));
@@ -249,8 +262,8 @@ bindAsyncClick(checkUpdateEl, async () => {
249
262
  const result = await send('checkForUpdates', { serverUrl: effectiveServerUrl() });
250
263
  setMessage(
251
264
  result.updateAvailable
252
- ? `Update available: ${result.currentVersion} -> ${result.latestVersion}.`
253
- : `Current version ${result.currentVersion} is up to date.`,
265
+ ? `Update available: ${result.currentVersionName || result.currentVersion} -> ${result.latestVersionName || result.latestVersion}.`
266
+ : `Current version ${result.currentVersionName || result.currentVersion} is up to date.`,
254
267
  result.updateAvailable ? '' : 'success',
255
268
  );
256
269
  });
@@ -124,7 +124,8 @@
124
124
 
125
125
  <service
126
126
  android:name=".auto.NeoAgentCarAppService"
127
- android:exported="true">
127
+ android:exported="true"
128
+ android:permission="android.car.permission.BIND_CAR_APP_SERVICE">
128
129
  <intent-filter>
129
130
  <action android:name="androidx.car.app.CarAppService" />
130
131
  <category android:name="androidx.car.app.category.IOT" />