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
@@ -274,6 +274,42 @@ function markProactiveMessageSent({ runState, deliveryState, content }) {
274
274
  }
275
275
  }
276
276
 
277
+ function markProactiveNoResponse({ runState, deliveryState }) {
278
+ if (runState) {
279
+ runState.noResponse = true;
280
+ }
281
+ if (deliveryState) {
282
+ deliveryState.noResponse = true;
283
+ }
284
+ }
285
+
286
+ function normalizeStoredSettingString(value) {
287
+ if (value == null) return '';
288
+ if (typeof value !== 'string') return String(value || '').trim();
289
+ let current = value.trim();
290
+ for (let i = 0; i < 2; i += 1) {
291
+ if (!current) return '';
292
+ try {
293
+ const parsed = JSON.parse(current);
294
+ if (typeof parsed === 'string') {
295
+ current = parsed.trim();
296
+ continue;
297
+ }
298
+ return '';
299
+ } catch {
300
+ return current;
301
+ }
302
+ }
303
+ return current;
304
+ }
305
+
306
+ function normalizeMessagingTarget(target = {}) {
307
+ const platform = normalizeStoredSettingString(target.platform);
308
+ const to = normalizeStoredSettingString(target.to);
309
+ if (!platform || !to) return null;
310
+ return { platform, to };
311
+ }
312
+
277
313
  function buildAndroidUiMatchProperties(extra = {}) {
278
314
  return {
279
315
  x: { type: 'number', description: 'Absolute X coordinate' },
@@ -1289,7 +1325,7 @@ function getAvailableTools(app, options = {}) {
1289
1325
  // mechanism and gives the AI real agency over when it's finished.
1290
1326
  tools.push({
1291
1327
  name: 'task_complete',
1292
- description: 'Signal that the task is fully complete and provide the final response. Call this exactly once when all steps are done and you have a complete answer ready. Do NOT call it if you still have work to do.',
1328
+ description: 'Signal that the task is fully complete and provide the final response. Call this exactly once when all steps are done and you have a complete answer ready. Do NOT call it if you still have work to do, unverified claims, unresolved tool failures, or confidence below the current run requirement.',
1293
1329
  parameters: {
1294
1330
  type: 'object',
1295
1331
  properties: {
@@ -1300,10 +1336,10 @@ function getAvailableTools(app, options = {}) {
1300
1336
  confidence: {
1301
1337
  type: 'string',
1302
1338
  enum: ['high', 'medium', 'low'],
1303
- description: 'How confident are you the task is fully and correctly complete? Use "low" if you had to make assumptions.'
1339
+ description: 'How confident are you the task is fully and correctly complete? Use "low" only when the final answer is intentionally limited or incomplete; low confidence may be rejected so the run can keep working.'
1304
1340
  }
1305
1341
  },
1306
- required: ['message']
1342
+ required: ['message', 'confidence']
1307
1343
  }
1308
1344
  });
1309
1345
 
@@ -1405,7 +1441,7 @@ async function executeTool(toolName, args, context, engine) {
1405
1441
  const manager = runtime();
1406
1442
  if (manager && typeof manager.getBrowserProviderForUser === 'function') {
1407
1443
  const backend = typeof manager.getActiveBrowserBackend === 'function'
1408
- ? manager.getActiveBrowserBackend(userId)
1444
+ ? await Promise.resolve(manager.getActiveBrowserBackend(userId))
1409
1445
  : 'vm';
1410
1446
  return { provider: await manager.getBrowserProviderForUser(userId), backend };
1411
1447
  }
@@ -1489,7 +1525,7 @@ async function executeTool(toolName, args, context, engine) {
1489
1525
  timeout: args.timeout || (args.pty ? 20 * 60 * 1000 : 15 * 60 * 1000),
1490
1526
  stdinInput: args.stdin_input,
1491
1527
  pty: args.pty === true,
1492
- inputs: args.inputs || [],
1528
+ inputs: Array.isArray(args.inputs) ? args.inputs : [],
1493
1529
  };
1494
1530
  if (typeof runtimeManager.executeCliCommand === 'function') {
1495
1531
  return await runtimeManager.executeCliCommand(userId, args.command, execOptions);
@@ -2112,6 +2148,9 @@ async function executeTool(toolName, args, context, engine) {
2112
2148
  error: proactiveValidation.error,
2113
2149
  };
2114
2150
  }
2151
+ if (proactiveValidation.reason === 'no_response') {
2152
+ markProactiveNoResponse({ runState, deliveryState });
2153
+ }
2115
2154
  return {
2116
2155
  sent: false,
2117
2156
  suppressed: proactiveValidation.suppressed === true,
@@ -2323,8 +2362,8 @@ async function executeTool(toolName, args, context, engine) {
2323
2362
  || null
2324
2363
  );
2325
2364
  const loadDefaultTarget = () => ({
2326
- platform: loadAgentSetting('last_platform'),
2327
- to: loadAgentSetting('last_chat_id')
2365
+ platform: normalizeStoredSettingString(loadAgentSetting('last_platform')),
2366
+ to: normalizeStoredSettingString(loadAgentSetting('last_chat_id'))
2328
2367
  });
2329
2368
 
2330
2369
  let taskConfig = null;
@@ -2336,26 +2375,43 @@ async function executeTool(toolName, args, context, engine) {
2336
2375
  try {
2337
2376
  taskConfig = JSON.parse(task.task_config || '{}');
2338
2377
  taskTarget = {
2339
- platform: taskConfig.notifyPlatform || null,
2340
- to: taskConfig.notifyTo || null
2378
+ platform: normalizeStoredSettingString(taskConfig.notifyPlatform),
2379
+ to: normalizeStoredSettingString(taskConfig.notifyTo)
2341
2380
  };
2342
2381
  } catch { }
2343
2382
  }
2344
2383
  }
2345
2384
 
2346
2385
  const fallbackTarget = loadDefaultTarget();
2386
+ const recentTargets = db.prepare(
2387
+ `SELECT platform, platform_chat_id
2388
+ FROM messages
2389
+ WHERE user_id = ?
2390
+ AND agent_id = ?
2391
+ AND platform IS NOT NULL
2392
+ AND platform_chat_id IS NOT NULL
2393
+ ORDER BY id DESC
2394
+ LIMIT 20`
2395
+ ).all(userId, agentId);
2347
2396
  const candidateTargets = [];
2348
2397
  const seenTargets = new Set();
2349
2398
  const addCandidate = (target) => {
2350
- if (!target?.platform || !target?.to) return;
2351
- const key = `${target.platform}:${target.to}`;
2399
+ const normalizedTarget = normalizeMessagingTarget(target);
2400
+ if (!normalizedTarget) return;
2401
+ const key = `${normalizedTarget.platform}:${normalizedTarget.to}`;
2352
2402
  if (seenTargets.has(key)) return;
2353
2403
  seenTargets.add(key);
2354
- candidateTargets.push(target);
2404
+ candidateTargets.push(normalizedTarget);
2355
2405
  };
2356
2406
 
2357
2407
  addCandidate(taskTarget);
2358
2408
  addCandidate(fallbackTarget);
2409
+ for (const row of recentTargets) {
2410
+ addCandidate({
2411
+ platform: row.platform,
2412
+ to: row.platform_chat_id
2413
+ });
2414
+ }
2359
2415
 
2360
2416
  if (candidateTargets.length === 0) {
2361
2417
  throw new Error('No messaging target is configured for this task run. Connect a platform and send at least one message on this server, or recreate the task after reconnecting.');
@@ -2611,8 +2667,14 @@ async function executeTool(toolName, args, context, engine) {
2611
2667
  let tools = [];
2612
2668
  if (autoStart) {
2613
2669
  try {
2614
- await mcpClient.startServer(serverId, args.command, args.name, userId, { agentId });
2615
- tools = await mcpClient.listTools(serverId, userId);
2670
+ const startResult = await mcpClient.startServer(
2671
+ serverId,
2672
+ args.command,
2673
+ args.name,
2674
+ userId,
2675
+ { agentId }
2676
+ );
2677
+ tools = startResult.tools || [];
2616
2678
  } catch (startErr) {
2617
2679
  return { registered: true, id: serverId, started: false, error: `Registered but failed to start: ${startErr.message}` };
2618
2680
  }
@@ -2635,7 +2697,10 @@ async function executeTool(toolName, args, context, engine) {
2635
2697
  args: JSON.parse(s.config || '{}').args || [],
2636
2698
  enabled: !!s.enabled,
2637
2699
  status: liveStatuses[s.id]?.status || 'stopped',
2638
- toolCount: liveStatuses[s.id]?.toolCount || 0
2700
+ toolCount: liveStatuses[s.id]?.toolCount || 0,
2701
+ error: liveStatuses[s.id]?.error || null,
2702
+ consecutiveFails: liveStatuses[s.id]?.consecutiveFails || 0,
2703
+ nextRetryAt: liveStatuses[s.id]?.nextRetryAt || null
2639
2704
  }))
2640
2705
  };
2641
2706
  }
@@ -354,7 +354,7 @@ class AndroidController {
354
354
 
355
355
  async capturePng(_opts = {}) {
356
356
  const serial = this.#requireSerial();
357
- return this.#adbCapture(serial, ['exec-out', 'screencap', '-p']);
357
+ return this.#adbCaptureAsync(serial, ['exec-out', 'screencap', '-p']);
358
358
  }
359
359
 
360
360
  async observe(_opts = {}) { return this.screenshot(); }
@@ -473,6 +473,41 @@ class AndroidController {
473
473
  return (r.status === 0 && r.stdout?.length) ? r.stdout : null;
474
474
  }
475
475
 
476
+ #adbCaptureAsync(serial, args) {
477
+ return new Promise((resolve, reject) => {
478
+ const proc = spawn(adbBin(this.sdkDir), ['-s', serial, ...args]);
479
+ const chunks = [];
480
+ const errors = [];
481
+ let settled = false;
482
+ const timer = setTimeout(() => {
483
+ if (settled) return;
484
+ settled = true;
485
+ try { proc.kill('SIGKILL'); } catch {}
486
+ reject(new Error('adb capture timed out'));
487
+ }, 15000);
488
+ timer.unref?.();
489
+ proc.stdout?.on('data', (chunk) => chunks.push(chunk));
490
+ proc.stderr?.on('data', (chunk) => errors.push(chunk));
491
+ proc.on('error', (error) => {
492
+ if (settled) return;
493
+ settled = true;
494
+ clearTimeout(timer);
495
+ reject(error);
496
+ });
497
+ proc.on('close', (code) => {
498
+ if (settled) return;
499
+ settled = true;
500
+ clearTimeout(timer);
501
+ if (code === 0 && chunks.length) {
502
+ resolve(Buffer.concat(chunks));
503
+ return;
504
+ }
505
+ const message = Buffer.concat(errors).toString('utf8').trim();
506
+ reject(new Error(message || `adb capture exit ${code}`));
507
+ });
508
+ });
509
+ }
510
+
476
511
  #saveArtifact(data) {
477
512
  if (!data || !this.artifactStore) return null;
478
513
  const alloc = this.artifactStore.allocateFile(this.userId, { kind: 'screenshot', extension: 'png', contentType: 'image/png' });
@@ -600,6 +600,24 @@ class BrowserController {
600
600
  }
601
601
  }
602
602
 
603
+ async hoverPoint(x, y, options = {}) {
604
+ const page = await this.ensurePage();
605
+ try {
606
+ const px = Math.max(0, Math.round(Number(x) || 0));
607
+ const py = Math.max(0, Math.round(Number(y) || 0));
608
+ await page.mouse.move(px, py, { steps: options.steps || 1 });
609
+ return {
610
+ success: true,
611
+ x: px,
612
+ y: py,
613
+ url: page.url(),
614
+ title: await page.title()
615
+ };
616
+ } catch (err) {
617
+ return { error: err.message };
618
+ }
619
+ }
620
+
603
621
  async scroll(deltaX = 0, deltaY = 0, screenshot = true) {
604
622
  const page = await this.ensurePage();
605
623
 
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ const BASE_MANIFEST = require('../../../../extensions/chrome-browser/manifest.json');
4
+ const { getVersionInfo } = require('../../../utils/version');
5
+
6
+ function chromeVersionFor(packageVersion) {
7
+ const parts = String(packageVersion || '')
8
+ .match(/\d+/g)
9
+ ?.slice(0, 4)
10
+ .map((part) => Math.min(Number(part) || 0, 65535)) || [];
11
+
12
+ while (parts.length < 3) {
13
+ parts.push(0);
14
+ }
15
+
16
+ return parts.join('.');
17
+ }
18
+
19
+ function getExtensionManifest() {
20
+ const { packageVersion } = getVersionInfo();
21
+ const displayVersion = packageVersion || BASE_MANIFEST.version_name || BASE_MANIFEST.version;
22
+
23
+ return {
24
+ ...BASE_MANIFEST,
25
+ version: chromeVersionFor(displayVersion),
26
+ version_name: displayVersion,
27
+ };
28
+ }
29
+
30
+ module.exports = {
31
+ chromeVersionFor,
32
+ getExtensionManifest,
33
+ };
@@ -17,6 +17,7 @@ class ExtensionBrowserProvider {
17
17
  constructor(options = {}) {
18
18
  this.registry = options.registry;
19
19
  this.userId = options.userId != null ? String(options.userId) : null;
20
+ this.tokenId = options.tokenId ? String(options.tokenId) : null;
20
21
  this.artifactStore = options.artifactStore || null;
21
22
  this.headless = false;
22
23
  }
@@ -29,13 +30,16 @@ class ExtensionBrowserProvider {
29
30
 
30
31
  async #dispatch(command, payload = {}, options = {}) {
31
32
  this.#assertReady();
32
- const result = await this.registry.dispatch(this.userId, command, payload, options);
33
+ const result = await this.registry.dispatch(this.userId, command, payload, {
34
+ ...options,
35
+ tokenId: options.tokenId || this.tokenId,
36
+ });
33
37
  return this.#materialize(result);
34
38
  }
35
39
 
36
40
  #disconnect() {
37
41
  if (!this.registry || this.userId == null) return;
38
- const connection = this.registry.getConnection(this.userId);
42
+ const connection = this.registry.getConnection(this.userId, this.tokenId);
39
43
  if (connection) {
40
44
  connection.close('browser extension provider closed');
41
45
  }
@@ -131,7 +135,7 @@ class ExtensionBrowserProvider {
131
135
  }
132
136
 
133
137
  async closeBrowser() {
134
- if (!this.registry || this.userId == null || !this.registry.isConnected(this.userId)) {
138
+ if (!this.registry || this.userId == null || !this.registry.isConnected(this.userId, this.tokenId)) {
135
139
  return { success: true, extensionConnected: false };
136
140
  }
137
141
  const result = await this.#dispatch(EXTENSION_COMMANDS.CLOSE, {});
@@ -152,14 +156,16 @@ class ExtensionBrowserProvider {
152
156
  }
153
157
 
154
158
  async getPageInfo() {
155
- if (!this.registry || this.userId == null || !this.registry.isConnected(this.userId)) {
159
+ if (!this.registry || this.userId == null || !this.registry.isConnected(this.userId, this.tokenId)) {
156
160
  return { url: null, title: null, extensionConnected: false };
157
161
  }
158
- return this.registry.dispatch(this.userId, EXTENSION_COMMANDS.GET_PAGE_INFO, {});
162
+ return this.registry.dispatch(this.userId, EXTENSION_COMMANDS.GET_PAGE_INFO, {}, {
163
+ tokenId: this.tokenId,
164
+ });
159
165
  }
160
166
 
161
167
  isLaunched() {
162
- return Boolean(this.registry && this.userId != null && this.registry.isConnected(this.userId));
168
+ return Boolean(this.registry && this.userId != null && this.registry.isConnected(this.userId, this.tokenId));
163
169
  }
164
170
 
165
171
  getPageCount() {