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
@@ -63,6 +63,7 @@ class NeoAgentController extends ChangeNotifier {
63
63
  final Set<String> _backgroundRunIds = <String>{};
64
64
  final Set<String> _voiceRunIds = <String>{};
65
65
  final Set<String> _busyOfficialIntegrationKeys = <String>{};
66
+ final Set<String> _busyMessagingPlatformKeys = <String>{};
66
67
  final Map<String, DateTime> _manualRunCooldowns = <String, DateTime>{};
67
68
  static const Duration _manualRunCooldownDuration = Duration(seconds: 10);
68
69
  static const Duration _homeWidgetSyncCooldown = Duration(seconds: 5);
@@ -70,6 +71,7 @@ class NeoAgentController extends ChangeNotifier {
70
71
  int _authCycle = 0;
71
72
  bool _isPollingQrLogin = false;
72
73
  bool _socketHasConnectedOnce = false;
74
+ bool _onboardingManuallyReopened = false;
73
75
  List<LogEntry> _serverLogs = const <LogEntry>[];
74
76
  List<LogEntry> _clientLogs = const <LogEntry>[];
75
77
 
@@ -91,6 +93,8 @@ class NeoAgentController extends ChangeNotifier {
91
93
  bool isSavingReleaseChannel = false;
92
94
  bool isSyncingHealth = false;
93
95
  bool isRunningDeviceAction = false;
96
+ bool isLoadingWorkspaceFiles = false;
97
+ bool isSavingWorkspaceFile = false;
94
98
  bool isPreparingQrLogin = false;
95
99
  bool isApprovingQrLogin = false;
96
100
  bool isCheckingAppUpdate = false;
@@ -105,6 +109,8 @@ class NeoAgentController extends ChangeNotifier {
105
109
  bool _analyticsConsentResolved = false;
106
110
  bool _analyticsConsentGranted = false;
107
111
 
112
+ io.Socket? get streamSocket => socketConnected ? _socket : null;
113
+
108
114
  bool hasUser = true;
109
115
  bool registrationOpen = false;
110
116
  bool serviceEmailConfigured = false;
@@ -168,6 +174,8 @@ class NeoAgentController extends ChangeNotifier {
168
174
  List<McpServerItem> mcpServers = const <McpServerItem>[];
169
175
  Map<String, dynamic> browserRuntime = const <String, dynamic>{};
170
176
  Map<String, dynamic> browserExtensionStatus = const <String, dynamic>{};
177
+ List<Map<String, dynamic>> browserExtensionTokens =
178
+ const <Map<String, dynamic>>[];
171
179
  Map<String, dynamic> androidRuntime = const <String, dynamic>{};
172
180
  Map<String, dynamic> desktopRuntime = const <String, dynamic>{};
173
181
  List<String> androidInstalledApps = const <String>[];
@@ -176,6 +184,7 @@ class NeoAgentController extends ChangeNotifier {
176
184
  List<Map<String, dynamic>> desktopDisplays = const <Map<String, dynamic>>[];
177
185
  Map<String, dynamic> desktopPermissions = const <String, dynamic>{};
178
186
  String? selectedDesktopDeviceId;
187
+ String? selectedBrowserExtensionTokenId;
179
188
  String? browserScreenshotPath;
180
189
  String? androidScreenshotPath;
181
190
  String? desktopScreenshotPath;
@@ -183,6 +192,10 @@ class NeoAgentController extends ChangeNotifier {
183
192
  String? androidLastResult;
184
193
  String? desktopLastResult;
185
194
  String? androidUiDumpPath;
195
+ String workspaceCurrentPath = '';
196
+ String? workspaceSelectedFilePath;
197
+ String workspaceEditorContent = '';
198
+ List<Map<String, dynamic>> workspaceEntries = const <Map<String, dynamic>>[];
186
199
  final Map<String, RunDetailSnapshot> _runDetailsCache =
187
200
  <String, RunDetailSnapshot>{};
188
201
  String? _selectedWidgetId;
@@ -231,6 +244,8 @@ class NeoAgentController extends ChangeNotifier {
231
244
 
232
245
  bool isOfficialIntegrationBusy(String key) =>
233
246
  _busyOfficialIntegrationKeys.contains(key);
247
+ bool isMessagingPlatformBusy(String platform, String action) =>
248
+ _busyMessagingPlatformKeys.contains('$platform:$action');
234
249
 
235
250
  String get chatComposerHint => hasLiveRun
236
251
  ? 'Send a steering update or next-up note for the current run...'
@@ -443,6 +458,7 @@ class NeoAgentController extends ChangeNotifier {
443
458
  required String role,
444
459
  required String platform,
445
460
  bool transient = false,
461
+ Map<String, dynamic> metadata = const <String, dynamic>{},
446
462
  }) {
447
463
  final trimmed = content.trim();
448
464
  if (trimmed.isEmpty) {
@@ -453,7 +469,8 @@ class NeoAgentController extends ChangeNotifier {
453
469
  if (previous != null &&
454
470
  previous.role == role &&
455
471
  previous.platform == platform &&
456
- previous.content.trim() == trimmed) {
472
+ previous.content.trim() == trimmed &&
473
+ metadata.isEmpty) {
457
474
  return;
458
475
  }
459
476
 
@@ -466,6 +483,7 @@ class NeoAgentController extends ChangeNotifier {
466
483
  platform: platform,
467
484
  createdAt: DateTime.now(),
468
485
  transient: transient,
486
+ metadata: metadata,
469
487
  ),
470
488
  ];
471
489
  }
@@ -807,6 +825,7 @@ class NeoAgentController extends ChangeNotifier {
807
825
  status['user'] as Map<String, dynamic>,
808
826
  );
809
827
  isAuthenticated = true;
828
+ _syncOnboardingFromAccount();
810
829
  }
811
830
  if (isAuthenticated) {
812
831
  unawaited(refresh());
@@ -893,8 +912,9 @@ class NeoAgentController extends ChangeNotifier {
893
912
  // On web, require explicit opt-in (GDPR). On native, consent is implicit
894
913
  // unless the user has previously declined.
895
914
  _analyticsConsentResolved = consentState != null || !kIsWeb;
896
- _analyticsConsentGranted =
897
- kIsWeb ? consentState == true : consentState != false;
915
+ _analyticsConsentGranted = kIsWeb
916
+ ? consentState == true
917
+ : consentState != false;
898
918
  await _analytics.initialize(
899
919
  token: token,
900
920
  consentGranted: _analyticsConsentGranted,
@@ -1344,9 +1364,7 @@ class NeoAgentController extends ChangeNotifier {
1344
1364
  pendingTwoFactorUsername = '';
1345
1365
  password = '';
1346
1366
 
1347
- final bool backendCompletedOnboarding =
1348
- user?['hasCompletedOnboarding'] == true;
1349
- showOnboarding = isRegistration || !backendCompletedOnboarding;
1367
+ _syncOnboardingFromAccount();
1350
1368
 
1351
1369
  _clearQrLoginChallenge();
1352
1370
  await _persistCredentials();
@@ -1590,6 +1608,7 @@ class NeoAgentController extends ChangeNotifier {
1590
1608
  }
1591
1609
 
1592
1610
  Future<void> dismissOnboarding() async {
1611
+ _onboardingManuallyReopened = false;
1593
1612
  showOnboarding = false;
1594
1613
  notifyListeners();
1595
1614
  try {
@@ -1606,10 +1625,23 @@ class NeoAgentController extends ChangeNotifier {
1606
1625
  }
1607
1626
 
1608
1627
  void reopenOnboarding() {
1628
+ _onboardingManuallyReopened = true;
1609
1629
  showOnboarding = true;
1610
1630
  notifyListeners();
1611
1631
  }
1612
1632
 
1633
+ void _syncOnboardingFromAccount() {
1634
+ final hasCompletedOnboarding = user?['hasCompletedOnboarding'] == true;
1635
+ if (hasCompletedOnboarding) {
1636
+ if (!_onboardingManuallyReopened) {
1637
+ showOnboarding = false;
1638
+ }
1639
+ return;
1640
+ }
1641
+ _onboardingManuallyReopened = false;
1642
+ showOnboarding = true;
1643
+ }
1644
+
1613
1645
  void _clearAuthenticatedState() {
1614
1646
  _disconnectSocket();
1615
1647
  _updatePollTimer?.cancel();
@@ -1617,6 +1649,8 @@ class NeoAgentController extends ChangeNotifier {
1617
1649
  _clearQrLoginChallenge();
1618
1650
  isAuthenticated = false;
1619
1651
  isRefreshing = false;
1652
+ _onboardingManuallyReopened = false;
1653
+ _busyMessagingPlatformKeys.clear();
1620
1654
  isAwaitingTwoFactor = false;
1621
1655
  pendingTwoFactorUsername = '';
1622
1656
  errorMessage = null;
@@ -1653,6 +1687,7 @@ class NeoAgentController extends ChangeNotifier {
1653
1687
  mcpServers = const <McpServerItem>[];
1654
1688
  browserRuntime = const <String, dynamic>{};
1655
1689
  browserExtensionStatus = const <String, dynamic>{};
1690
+ browserExtensionTokens = const <Map<String, dynamic>>[];
1656
1691
  androidRuntime = const <String, dynamic>{};
1657
1692
  desktopRuntime = const <String, dynamic>{};
1658
1693
  androidInstalledApps = const <String>[];
@@ -1661,6 +1696,7 @@ class NeoAgentController extends ChangeNotifier {
1661
1696
  desktopDisplays = const <Map<String, dynamic>>[];
1662
1697
  desktopPermissions = const <String, dynamic>{};
1663
1698
  selectedDesktopDeviceId = null;
1699
+ selectedBrowserExtensionTokenId = null;
1664
1700
  browserScreenshotPath = null;
1665
1701
  androidScreenshotPath = null;
1666
1702
  desktopScreenshotPath = null;
@@ -2072,6 +2108,7 @@ class NeoAgentController extends ChangeNotifier {
2072
2108
  user = Map<String, dynamic>.from(
2073
2109
  authStatus['user'] as Map<String, dynamic>,
2074
2110
  );
2111
+ _syncOnboardingFromAccount();
2075
2112
 
2076
2113
  final profilesResponse = await _backendClient.fetchAgentProfiles(
2077
2114
  backendUrl,
@@ -2342,6 +2379,13 @@ class NeoAgentController extends ChangeNotifier {
2342
2379
  browserExtensionStatus = Map<String, dynamic>.from(
2343
2380
  browserExtensionResponse,
2344
2381
  );
2382
+ browserExtensionTokens = _jsonMapList(
2383
+ browserExtensionStatus['tokens'],
2384
+ fallbackToMapValues: true,
2385
+ );
2386
+ selectedBrowserExtensionTokenId = _optionalIdFrom(
2387
+ browserExtensionStatus['selectedTokenId'],
2388
+ );
2345
2389
  androidRuntime = Map<String, dynamic>.from(androidResponse);
2346
2390
  desktopRuntime = Map<String, dynamic>.from(desktopResponse);
2347
2391
  selectedDesktopDeviceId =
@@ -2437,6 +2481,11 @@ class NeoAgentController extends ChangeNotifier {
2437
2481
  return parsed;
2438
2482
  }
2439
2483
 
2484
+ String? _optionalIdFrom(dynamic value) {
2485
+ final normalized = value?.toString().trim() ?? '';
2486
+ return normalized.isEmpty || normalized == 'null' ? null : normalized;
2487
+ }
2488
+
2440
2489
  Future<void> refreshRunsOnly() async {
2441
2490
  try {
2442
2491
  final runsResponse = await _backendClient.fetchRuns(
@@ -2458,31 +2507,6 @@ class NeoAgentController extends ChangeNotifier {
2458
2507
  );
2459
2508
  notifyListeners();
2460
2509
  } catch (_) {}
2461
-
2462
- Future<String> fetchMemoryTransferPrompt() async {
2463
- final response = await _backendClient.fetchMemoryTransferPrompt(
2464
- backendUrl,
2465
- agentId: _scopedAgentId,
2466
- );
2467
- return response['prompt']?.toString() ?? '';
2468
- }
2469
-
2470
- Future<MemoryTransferImportResult> importMemoryTransfer(
2471
- String text, {
2472
- bool applyBehaviorNotes = true,
2473
- bool applyCoreMemory = true,
2474
- }) async {
2475
- final response = await _backendClient.importMemoryTransfer(
2476
- backendUrl,
2477
- text: text,
2478
- applyBehaviorNotes: applyBehaviorNotes,
2479
- applyCoreMemory: applyCoreMemory,
2480
- agentId: _scopedAgentId,
2481
- );
2482
- final result = MemoryTransferImportResult.fromJson(response);
2483
- await refreshMemory();
2484
- return result;
2485
- }
2486
2510
  }
2487
2511
 
2488
2512
  Future<void> refreshMessaging() async {
@@ -2740,6 +2764,13 @@ class NeoAgentController extends ChangeNotifier {
2740
2764
  browserExtensionStatus = Map<String, dynamic>.from(
2741
2765
  browserExtensionResponse,
2742
2766
  );
2767
+ browserExtensionTokens = _jsonMapList(
2768
+ browserExtensionStatus['tokens'],
2769
+ fallbackToMapValues: true,
2770
+ );
2771
+ selectedBrowserExtensionTokenId = _optionalIdFrom(
2772
+ browserExtensionStatus['selectedTokenId'],
2773
+ );
2743
2774
  androidRuntime = Map<String, dynamic>.from(androidResponse);
2744
2775
  desktopRuntime = Map<String, dynamic>.from(desktopResponse);
2745
2776
  selectedDesktopDeviceId =
@@ -2767,6 +2798,13 @@ class NeoAgentController extends ChangeNotifier {
2767
2798
  backendUrl,
2768
2799
  );
2769
2800
  browserExtensionStatus = Map<String, dynamic>.from(response);
2801
+ browserExtensionTokens = _jsonMapList(
2802
+ browserExtensionStatus['tokens'],
2803
+ fallbackToMapValues: true,
2804
+ );
2805
+ selectedBrowserExtensionTokenId = _optionalIdFrom(
2806
+ browserExtensionStatus['selectedTokenId'],
2807
+ );
2770
2808
  notifyListeners();
2771
2809
  } catch (error) {
2772
2810
  errorMessage = _friendlyErrorMessage(error);
@@ -2911,6 +2949,15 @@ class NeoAgentController extends ChangeNotifier {
2911
2949
  );
2912
2950
  }
2913
2951
 
2952
+ Future<void> hoverBrowserPointRuntime({
2953
+ required int x,
2954
+ required int y,
2955
+ }) async {
2956
+ try {
2957
+ await _backendClient.hoverBrowserPoint(backendUrl, x: x, y: y);
2958
+ } catch (_) {}
2959
+ }
2960
+
2914
2961
  Future<void> fillBrowserRuntime({
2915
2962
  required String selector,
2916
2963
  required String value,
@@ -3036,6 +3083,40 @@ class NeoAgentController extends ChangeNotifier {
3036
3083
  } catch (_) {}
3037
3084
  }
3038
3085
 
3086
+ Future<void> startStreamRuntime({
3087
+ required String platform,
3088
+ required String deviceId,
3089
+ int fps = 10,
3090
+ int quality = 70,
3091
+ }) async {
3092
+ final normalizedDeviceId = deviceId.trim();
3093
+ if (normalizedDeviceId.isEmpty) {
3094
+ return;
3095
+ }
3096
+ await _backendClient.startStream(
3097
+ backendUrl,
3098
+ platform: platform,
3099
+ deviceId: normalizedDeviceId,
3100
+ fps: fps,
3101
+ quality: quality,
3102
+ );
3103
+ }
3104
+
3105
+ Future<void> stopStreamRuntime({
3106
+ required String platform,
3107
+ required String deviceId,
3108
+ }) async {
3109
+ final normalizedDeviceId = deviceId.trim();
3110
+ if (normalizedDeviceId.isEmpty) {
3111
+ return;
3112
+ }
3113
+ await _backendClient.stopStream(
3114
+ backendUrl,
3115
+ platform: platform,
3116
+ deviceId: normalizedDeviceId,
3117
+ );
3118
+ }
3119
+
3039
3120
  Future<void> dumpAndroidUiRuntime() async {
3040
3121
  await _runDeviceAction(
3041
3122
  () => _backendClient.dumpAndroidUi(backendUrl),
@@ -3203,6 +3284,44 @@ class NeoAgentController extends ChangeNotifier {
3203
3284
  await refreshDesktopFrameRuntime();
3204
3285
  }
3205
3286
 
3287
+ Future<void> selectBrowserExtensionRuntime(String tokenId) async {
3288
+ isRunningDeviceAction = true;
3289
+ errorMessage = null;
3290
+ notifyListeners();
3291
+ try {
3292
+ final response = await _backendClient.selectBrowserExtensionToken(
3293
+ backendUrl,
3294
+ tokenId: tokenId,
3295
+ );
3296
+ final status = response['status'] is Map
3297
+ ? Map<String, dynamic>.from(response['status'] as Map)
3298
+ : await _backendClient.fetchBrowserExtensionStatus(backendUrl);
3299
+ browserExtensionStatus = Map<String, dynamic>.from(status);
3300
+ browserExtensionTokens = _jsonMapList(
3301
+ browserExtensionStatus['tokens'],
3302
+ fallbackToMapValues: true,
3303
+ );
3304
+ selectedBrowserExtensionTokenId = _optionalIdFrom(
3305
+ browserExtensionStatus['selectedTokenId'],
3306
+ );
3307
+ if (selectedBrowserExtensionTokenId != null) {
3308
+ settings = <String, dynamic>{
3309
+ ...settings,
3310
+ 'browser_extension_token_id': selectedBrowserExtensionTokenId,
3311
+ 'selected_browser_extension_token_id':
3312
+ selectedBrowserExtensionTokenId,
3313
+ };
3314
+ }
3315
+ browserScreenshotPath = null;
3316
+ await refreshBrowserFrameRuntime();
3317
+ } catch (error) {
3318
+ errorMessage = _friendlyErrorMessage(error);
3319
+ } finally {
3320
+ isRunningDeviceAction = false;
3321
+ notifyListeners();
3322
+ }
3323
+ }
3324
+
3206
3325
  Future<void> openDesktopSelectionRuntime() async {
3207
3326
  await refreshDevices();
3208
3327
  errorMessage =
@@ -3281,6 +3400,17 @@ class NeoAgentController extends ChangeNotifier {
3281
3400
  );
3282
3401
  }
3283
3402
 
3403
+ Future<void> hoverDesktopRuntime({required int x, required int y}) async {
3404
+ try {
3405
+ await _backendClient.hoverDesktop(
3406
+ backendUrl,
3407
+ deviceId: selectedDesktopDeviceId,
3408
+ x: x,
3409
+ y: y,
3410
+ );
3411
+ } catch (_) {}
3412
+ }
3413
+
3284
3414
  Future<void> dragDesktopRuntime({
3285
3415
  required int x1,
3286
3416
  required int y1,
@@ -3367,6 +3497,102 @@ class NeoAgentController extends ChangeNotifier {
3367
3497
  );
3368
3498
  }
3369
3499
 
3500
+ String workspaceDownloadUrl(String path) {
3501
+ return '${_socketOrigin()}/${_backendClient.workspaceDownloadPath(path).replaceFirst(RegExp(r'^/'), '')}';
3502
+ }
3503
+
3504
+ Future<void> refreshWorkspaceFiles({String? path}) async {
3505
+ if (!isAuthenticated || isLoadingWorkspaceFiles) {
3506
+ return;
3507
+ }
3508
+ isLoadingWorkspaceFiles = true;
3509
+ errorMessage = null;
3510
+ notifyListeners();
3511
+ try {
3512
+ final response = await _backendClient.fetchWorkspaceDirectory(
3513
+ backendUrl,
3514
+ path: path ?? workspaceCurrentPath,
3515
+ );
3516
+ workspaceCurrentPath = response['path']?.toString() ?? '';
3517
+ workspaceEntries = _jsonMapList(
3518
+ response['entries'],
3519
+ fallbackToMapValues: true,
3520
+ );
3521
+ } catch (error) {
3522
+ errorMessage = _friendlyErrorMessage(error);
3523
+ } finally {
3524
+ isLoadingWorkspaceFiles = false;
3525
+ notifyListeners();
3526
+ }
3527
+ }
3528
+
3529
+ Future<void> openWorkspaceDirectory(String path) async {
3530
+ workspaceSelectedFilePath = null;
3531
+ workspaceEditorContent = '';
3532
+ await refreshWorkspaceFiles(path: path);
3533
+ }
3534
+
3535
+ Future<void> openWorkspaceFile(String path) async {
3536
+ if (isLoadingWorkspaceFiles) {
3537
+ return;
3538
+ }
3539
+ isLoadingWorkspaceFiles = true;
3540
+ errorMessage = null;
3541
+ notifyListeners();
3542
+ try {
3543
+ final response = await _backendClient.fetchWorkspaceFile(
3544
+ backendUrl,
3545
+ path: path,
3546
+ );
3547
+ workspaceSelectedFilePath = response['path']?.toString() ?? path;
3548
+ workspaceEditorContent = response['content']?.toString() ?? '';
3549
+ } catch (error) {
3550
+ errorMessage = _friendlyErrorMessage(error);
3551
+ } finally {
3552
+ isLoadingWorkspaceFiles = false;
3553
+ notifyListeners();
3554
+ }
3555
+ }
3556
+
3557
+ Future<void> saveWorkspaceFile(String content) async {
3558
+ final path = workspaceSelectedFilePath?.trim() ?? '';
3559
+ if (path.isEmpty || isSavingWorkspaceFile) {
3560
+ return;
3561
+ }
3562
+ isSavingWorkspaceFile = true;
3563
+ errorMessage = null;
3564
+ notifyListeners();
3565
+ try {
3566
+ await _backendClient.saveWorkspaceFile(
3567
+ backendUrl,
3568
+ path: path,
3569
+ content: content,
3570
+ );
3571
+ workspaceEditorContent = content;
3572
+ await refreshWorkspaceFiles(path: workspaceCurrentPath);
3573
+ } catch (error) {
3574
+ errorMessage = _friendlyErrorMessage(error);
3575
+ } finally {
3576
+ isSavingWorkspaceFile = false;
3577
+ notifyListeners();
3578
+ }
3579
+ }
3580
+
3581
+ Future<void> downloadWorkspaceFile(String path) async {
3582
+ final normalized = path.trim();
3583
+ if (normalized.isEmpty) {
3584
+ return;
3585
+ }
3586
+ final result = await _oauthLauncher.openExternal(
3587
+ url: workspaceDownloadUrl(normalized),
3588
+ label: 'neoagent_workspace_file_download',
3589
+ );
3590
+ if (!result.launched) {
3591
+ errorMessage = result.error ?? 'Could not open workspace file download.';
3592
+ notifyListeners();
3593
+ }
3594
+ }
3595
+
3370
3596
  Uri resolveRuntimeAsset(String path) {
3371
3597
  final separator = path.contains('?') ? '&' : '?';
3372
3598
  return _backendClient.resolveAssetUri(
@@ -4410,12 +4636,14 @@ class NeoAgentController extends ChangeNotifier {
4410
4636
  String content, {
4411
4637
  required String platform,
4412
4638
  bool transient = false,
4639
+ Map<String, dynamic> metadata = const <String, dynamic>{},
4413
4640
  }) {
4414
4641
  _appendChatMessage(
4415
4642
  content,
4416
4643
  role: 'assistant',
4417
4644
  platform: platform,
4418
4645
  transient: transient,
4646
+ metadata: metadata,
4419
4647
  );
4420
4648
  }
4421
4649
 
@@ -4476,6 +4704,18 @@ class NeoAgentController extends ChangeNotifier {
4476
4704
  }
4477
4705
  }
4478
4706
 
4707
+ Future<String> transcribeDictationAudio({
4708
+ required String audioBase64,
4709
+ String mimeType = 'audio/pcm;rate=16000;channels=1',
4710
+ }) async {
4711
+ final result = await _backendClient.transcribeAudio(
4712
+ backendUrl,
4713
+ audioBase64: audioBase64,
4714
+ mimeType: mimeType,
4715
+ );
4716
+ return result['transcript']?.toString() ?? '';
4717
+ }
4718
+
4479
4719
  Future<void> sendMessage(
4480
4720
  String task, {
4481
4721
  List<SharedChatAttachment> sharedAttachments =
@@ -4573,6 +4813,7 @@ class NeoAgentController extends ChangeNotifier {
4573
4813
 
4574
4814
  Future<void> saveSettings({
4575
4815
  required String browserBackend,
4816
+ String? browserExtensionTokenId,
4576
4817
  required String cliBackend,
4577
4818
  String? cliDesktopDeviceId,
4578
4819
  required bool smarterSelector,
@@ -4603,8 +4844,11 @@ class NeoAgentController extends ChangeNotifier {
4603
4844
  final payload = <String, dynamic>{
4604
4845
  'headless_browser': true,
4605
4846
  'browser_backend': browserBackend,
4847
+ if (browserExtensionTokenId != null)
4848
+ 'browser_extension_token_id': browserExtensionTokenId,
4606
4849
  'cli_backend': cliBackend,
4607
- if (cliDesktopDeviceId != null) 'cli_desktop_device_id': cliDesktopDeviceId,
4850
+ if (cliDesktopDeviceId != null)
4851
+ 'cli_desktop_device_id': cliDesktopDeviceId,
4608
4852
  'smarter_model_selector': smarterSelector,
4609
4853
  'enabled_models': enabledModels,
4610
4854
  'default_chat_model': defaultChatModel,
@@ -5382,12 +5626,28 @@ class NeoAgentController extends ChangeNotifier {
5382
5626
  }
5383
5627
 
5384
5628
  Future<void> disconnectMessagingPlatform(String platform) async {
5385
- await _backendClient.disconnectMessagingPlatform(
5386
- backendUrl,
5387
- platform: platform,
5388
- agentId: _scopedAgentId,
5389
- );
5390
- await refreshMessaging();
5629
+ final busyKey = '$platform:disconnect';
5630
+ if (_busyMessagingPlatformKeys.contains(busyKey)) {
5631
+ return;
5632
+ }
5633
+
5634
+ _busyMessagingPlatformKeys.add(busyKey);
5635
+ errorMessage = null;
5636
+ notifyListeners();
5637
+
5638
+ try {
5639
+ await _backendClient.disconnectMessagingPlatform(
5640
+ backendUrl,
5641
+ platform: platform,
5642
+ agentId: _scopedAgentId,
5643
+ );
5644
+ await refreshMessaging();
5645
+ } catch (error) {
5646
+ errorMessage = _friendlyErrorMessage(error);
5647
+ } finally {
5648
+ _busyMessagingPlatformKeys.remove(busyKey);
5649
+ notifyListeners();
5650
+ }
5391
5651
  }
5392
5652
 
5393
5653
  Future<void> logoutMessagingPlatform(String platform) async {
@@ -5857,18 +6117,36 @@ class NeoAgentController extends ChangeNotifier {
5857
6117
  }
5858
6118
 
5859
6119
  Future<void> startMcpServer(int id) async {
5860
- await _backendClient.startMcpServer(backendUrl, id);
5861
- await refreshMcp();
6120
+ try {
6121
+ await _backendClient.startMcpServer(backendUrl, id);
6122
+ await refreshMcp();
6123
+ } catch (error) {
6124
+ errorMessage = _friendlyErrorMessage(error);
6125
+ await refreshMcp();
6126
+ notifyListeners();
6127
+ }
5862
6128
  }
5863
6129
 
5864
6130
  Future<void> stopMcpServer(int id) async {
5865
- await _backendClient.stopMcpServer(backendUrl, id);
5866
- await refreshMcp();
6131
+ try {
6132
+ await _backendClient.stopMcpServer(backendUrl, id);
6133
+ await refreshMcp();
6134
+ } catch (error) {
6135
+ errorMessage = _friendlyErrorMessage(error);
6136
+ await refreshMcp();
6137
+ notifyListeners();
6138
+ }
5867
6139
  }
5868
6140
 
5869
6141
  Future<void> deleteMcpServer(int id) async {
5870
- await _backendClient.deleteMcpServer(backendUrl, id);
5871
- await refreshMcp();
6142
+ try {
6143
+ await _backendClient.deleteMcpServer(backendUrl, id);
6144
+ await refreshMcp();
6145
+ } catch (error) {
6146
+ errorMessage = _friendlyErrorMessage(error);
6147
+ await refreshMcp();
6148
+ notifyListeners();
6149
+ }
5872
6150
  }
5873
6151
 
5874
6152
  Future<void> requestHealthPermissions() async {
@@ -6170,6 +6448,11 @@ class NeoAgentController extends ChangeNotifier {
6170
6448
  bool get browserExtensionConnected =>
6171
6449
  browserExtensionStatus['connected'] == true;
6172
6450
 
6451
+ String? get browserExtensionTokenId {
6452
+ final v = settings['browser_extension_token_id']?.toString().trim();
6453
+ return (v == null || v.isEmpty || v == 'null') ? null : v;
6454
+ }
6455
+
6173
6456
  bool get smarterSelector => settings['smarter_model_selector'] != false;
6174
6457
 
6175
6458
  Map<String, AiProviderConfig> get aiProviderConfigs {
@@ -6352,7 +6635,9 @@ class NeoAgentController extends ChangeNotifier {
6352
6635
 
6353
6636
  List<ChatEntry> get visibleChatMessages {
6354
6637
  final entries = <ChatEntry>[...chatMessages];
6355
- if (isSendingMessage && activeRun != null && streamingAssistant.trim().isEmpty) {
6638
+ if (isSendingMessage &&
6639
+ activeRun != null &&
6640
+ streamingAssistant.trim().isEmpty) {
6356
6641
  entries.add(
6357
6642
  ChatEntry(
6358
6643
  id: '',
@@ -7138,7 +7423,14 @@ class NeoAgentController extends ChangeNotifier {
7138
7423
  }
7139
7424
  final content = payload['content']?.toString().trim() ?? '';
7140
7425
  if (content.isNotEmpty) {
7141
- _appendAssistantChatMessage(content, platform: 'web');
7426
+ final schema = payload['schema'];
7427
+ _appendAssistantChatMessage(
7428
+ content,
7429
+ platform: 'web',
7430
+ metadata: schema != null
7431
+ ? <String, dynamic>{'schema': schema}
7432
+ : const <String, dynamic>{},
7433
+ );
7142
7434
  }
7143
7435
  streamingAssistant = '';
7144
7436
  isSendingMessage = false;