neoagent 3.2.1-beta.1 → 3.2.1-beta.11

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 (198) hide show
  1. package/extensions/chrome-browser/background.mjs +318 -88
  2. package/extensions/chrome-browser/http.mjs +136 -0
  3. package/extensions/chrome-browser/protocol.mjs +654 -90
  4. package/flutter_app/lib/main_chat.dart +118 -739
  5. package/flutter_app/lib/main_controller.dart +157 -24
  6. package/flutter_app/lib/main_integrations.dart +607 -8
  7. package/flutter_app/lib/main_models.dart +7 -2
  8. package/flutter_app/lib/main_operations.dart +334 -370
  9. package/flutter_app/lib/main_security.dart +266 -112
  10. package/flutter_app/lib/main_settings.dart +342 -3
  11. package/flutter_app/lib/main_shared.dart +14 -11
  12. package/flutter_app/lib/src/backend_client.dart +97 -0
  13. package/flutter_app/lib/src/desktop_companion_actions.dart +185 -31
  14. package/flutter_app/lib/src/desktop_companion_io.dart +319 -86
  15. package/flutter_app/windows/runner/flutter_window.cpp +143 -32
  16. package/landing/index.html +3 -1
  17. package/lib/manager.js +106 -89
  18. package/lib/schema_migrations.js +115 -13
  19. package/package.json +30 -15
  20. package/runtime/paths.js +49 -5
  21. package/server/db/database.js +2 -2
  22. package/server/guest-agent.cli.package.json +13 -0
  23. package/server/guest_agent.js +85 -40
  24. package/server/http/middleware.js +24 -0
  25. package/server/http/routes.js +12 -6
  26. package/server/public/.last_build_id +1 -1
  27. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  28. package/server/public/flutter_bootstrap.js +2 -2
  29. package/server/public/main.dart.js +81930 -80509
  30. package/server/routes/admin.js +1 -1
  31. package/server/routes/android.js +30 -34
  32. package/server/routes/behavior.js +80 -0
  33. package/server/routes/browser.js +23 -15
  34. package/server/routes/desktop.js +18 -1
  35. package/server/routes/integrations.js +107 -1
  36. package/server/routes/memory.js +1 -0
  37. package/server/routes/settings.js +20 -5
  38. package/server/routes/social_reach.js +12 -3
  39. package/server/routes/social_video.js +4 -0
  40. package/server/services/agents/manager.js +1 -1
  41. package/server/services/ai/capabilityHealth.js +62 -96
  42. package/server/services/ai/compaction.js +7 -2
  43. package/server/services/ai/history.js +45 -6
  44. package/server/services/ai/integrated_tools/http_request.js +8 -0
  45. package/server/services/ai/loop/agent_engine_core.js +452 -176
  46. package/server/services/ai/loop/blank_recovery.js +5 -4
  47. package/server/services/ai/loop/callbacks.js +1 -0
  48. package/server/services/ai/loop/completion_judge.js +291 -8
  49. package/server/services/ai/loop/conversation_loop.js +515 -342
  50. package/server/services/ai/loop/messaging_delivery.js +176 -57
  51. package/server/services/ai/loop/model_call_guard.js +91 -0
  52. package/server/services/ai/loop/model_io.js +20 -45
  53. package/server/services/ai/loop/progress_classification.js +2 -0
  54. package/server/services/ai/loop/tool_dispatch.js +19 -8
  55. package/server/services/ai/loopPolicy.js +48 -21
  56. package/server/services/ai/messagingFallback.js +17 -17
  57. package/server/services/ai/model_discovery.js +227 -0
  58. package/server/services/ai/model_failure_cache.js +108 -0
  59. package/server/services/ai/model_identity.js +71 -0
  60. package/server/services/ai/models.js +68 -163
  61. package/server/services/ai/providerRetry.js +17 -59
  62. package/server/services/ai/provider_selector.js +166 -0
  63. package/server/services/ai/providers/anthropic.js +2 -2
  64. package/server/services/ai/providers/claudeCode.js +21 -33
  65. package/server/services/ai/providers/githubCopilot.js +41 -20
  66. package/server/services/ai/providers/google.js +135 -97
  67. package/server/services/ai/providers/grok.js +4 -3
  68. package/server/services/ai/providers/grokOauth.js +19 -27
  69. package/server/services/ai/providers/nvidia.js +10 -5
  70. package/server/services/ai/providers/ollama.js +111 -84
  71. package/server/services/ai/providers/ollama_stream.js +142 -0
  72. package/server/services/ai/providers/openai.js +39 -5
  73. package/server/services/ai/providers/openaiCodex.js +11 -4
  74. package/server/services/ai/providers/openrouter.js +29 -7
  75. package/server/services/ai/providers/provider_error.js +36 -0
  76. package/server/services/ai/settings.js +26 -2
  77. package/server/services/ai/systemPrompt.js +32 -123
  78. package/server/services/ai/taskAnalysis.js +104 -11
  79. package/server/services/ai/toolEvidence.js +256 -29
  80. package/server/services/ai/tools.js +248 -117
  81. package/server/services/android/controller.js +770 -237
  82. package/server/services/android/process.js +140 -0
  83. package/server/services/android/sdk_download.js +143 -0
  84. package/server/services/android/uia.js +6 -5
  85. package/server/services/artifacts/store.js +24 -0
  86. package/server/services/behavior/config.js +251 -0
  87. package/server/services/behavior/defaults.js +68 -0
  88. package/server/services/behavior/delivery.js +176 -0
  89. package/server/services/behavior/index.js +43 -0
  90. package/server/services/behavior/model_client.js +35 -0
  91. package/server/services/behavior/modules/agent_identity.js +37 -0
  92. package/server/services/behavior/modules/channel_style.js +28 -0
  93. package/server/services/behavior/modules/index.js +29 -0
  94. package/server/services/behavior/modules/norms.js +101 -0
  95. package/server/services/behavior/modules/persona.js +48 -0
  96. package/server/services/behavior/modules/persona_prompt.js +33 -0
  97. package/server/services/behavior/modules/social_memory.js +86 -0
  98. package/server/services/behavior/modules/social_observability.js +94 -0
  99. package/server/services/behavior/modules/social_signals.js +41 -0
  100. package/server/services/behavior/modules/theory_of_mind.js +110 -0
  101. package/server/services/behavior/modules/turn_taking.js +237 -0
  102. package/server/services/behavior/pipeline.js +285 -0
  103. package/server/services/behavior/registry.js +78 -0
  104. package/server/services/behavior/signals.js +107 -0
  105. package/server/services/behavior/state.js +99 -0
  106. package/server/services/behavior/system_prompt.js +75 -0
  107. package/server/services/browser/controller.js +843 -385
  108. package/server/services/browser/extension/gateway.js +40 -16
  109. package/server/services/browser/extension/protocol.js +15 -1
  110. package/server/services/browser/extension/provider.js +71 -47
  111. package/server/services/browser/extension/registry.js +155 -34
  112. package/server/services/cli/executor.js +62 -9
  113. package/server/services/credentials/bitwarden_cli.js +322 -0
  114. package/server/services/credentials/broker.js +594 -0
  115. package/server/services/desktop/gateway.js +41 -4
  116. package/server/services/desktop/protocol.js +3 -0
  117. package/server/services/desktop/provider.js +39 -42
  118. package/server/services/desktop/registry.js +137 -52
  119. package/server/services/integrations/bitwarden/constants.js +14 -0
  120. package/server/services/integrations/bitwarden/provider.js +197 -0
  121. package/server/services/integrations/bitwarden/snapshot.js +65 -0
  122. package/server/services/integrations/figma/provider.js +78 -12
  123. package/server/services/integrations/github/common.js +11 -6
  124. package/server/services/integrations/github/provider.js +52 -53
  125. package/server/services/integrations/google/provider.js +55 -19
  126. package/server/services/integrations/home_assistant/network.js +17 -20
  127. package/server/services/integrations/home_assistant/provider.js +7 -5
  128. package/server/services/integrations/home_assistant/tools.js +17 -5
  129. package/server/services/integrations/http.js +51 -0
  130. package/server/services/integrations/manager.js +159 -53
  131. package/server/services/integrations/microsoft/provider.js +80 -13
  132. package/server/services/integrations/neoarchive/provider.js +55 -29
  133. package/server/services/integrations/neorecall/client.js +17 -10
  134. package/server/services/integrations/neorecall/provider.js +20 -11
  135. package/server/services/integrations/notion/provider.js +16 -13
  136. package/server/services/integrations/oauth_provider.js +115 -51
  137. package/server/services/integrations/registry.js +2 -0
  138. package/server/services/integrations/slack/provider.js +98 -9
  139. package/server/services/integrations/spotify/provider.js +67 -71
  140. package/server/services/integrations/trello/provider.js +21 -7
  141. package/server/services/integrations/weather/provider.js +18 -12
  142. package/server/services/integrations/whatsapp/provider.js +76 -16
  143. package/server/services/manager.js +110 -1
  144. package/server/services/memory/embedding_index.js +20 -8
  145. package/server/services/memory/embeddings.js +151 -90
  146. package/server/services/memory/ingestion.js +50 -9
  147. package/server/services/memory/ingestion_documents.js +13 -3
  148. package/server/services/memory/manager.js +66 -52
  149. package/server/services/messaging/access_policy.js +10 -6
  150. package/server/services/messaging/automation.js +240 -34
  151. package/server/services/messaging/discord.js +11 -1
  152. package/server/services/messaging/formatting_guides.js +5 -4
  153. package/server/services/messaging/http_platforms.js +37 -16
  154. package/server/services/messaging/inbound_queue.js +143 -28
  155. package/server/services/messaging/inbound_store.js +257 -0
  156. package/server/services/messaging/manager.js +344 -51
  157. package/server/services/messaging/telegram.js +10 -1
  158. package/server/services/messaging/typing_keepalive.js +5 -2
  159. package/server/services/messaging/whatsapp.js +33 -14
  160. package/server/services/network/http.js +210 -0
  161. package/server/services/network/safe_request.js +307 -0
  162. package/server/services/runtime/backends/local-vm.js +227 -67
  163. package/server/services/runtime/docker-vm-manager.js +9 -0
  164. package/server/services/runtime/guest_bootstrap.js +30 -4
  165. package/server/services/runtime/guest_image.js +43 -12
  166. package/server/services/runtime/manager.js +77 -23
  167. package/server/services/runtime/validation.js +7 -6
  168. package/server/services/security/tool_categories.js +6 -0
  169. package/server/services/social_reach/channels/github.js +10 -4
  170. package/server/services/social_reach/channels/reddit.js +4 -4
  171. package/server/services/social_reach/channels/rss.js +2 -2
  172. package/server/services/social_reach/channels/social_video.js +13 -8
  173. package/server/services/social_reach/channels/v2ex.js +21 -8
  174. package/server/services/social_reach/channels/x.js +2 -2
  175. package/server/services/social_reach/channels/xueqiu.js +5 -5
  176. package/server/services/social_reach/service.js +9 -6
  177. package/server/services/social_reach/utils.js +65 -14
  178. package/server/services/social_video/captions.js +2 -2
  179. package/server/services/social_video/service.js +343 -68
  180. package/server/services/tasks/integration_runtime.js +18 -8
  181. package/server/services/tasks/runtime.js +39 -4
  182. package/server/services/voice/agentBridge.js +17 -4
  183. package/server/services/voice/bufferedLiveRelayAdapter.js +5 -0
  184. package/server/services/voice/liveSession.js +31 -0
  185. package/server/services/voice/message.js +1 -1
  186. package/server/services/voice/openaiSpeech.js +33 -8
  187. package/server/services/voice/providers.js +233 -151
  188. package/server/services/voice/runtime.js +2 -2
  189. package/server/services/voice/runtimeManager.js +118 -20
  190. package/server/services/voice/turnRunner.js +6 -0
  191. package/server/services/wearable/firmware_manifest.js +51 -13
  192. package/server/services/wearable/service.js +1 -0
  193. package/server/utils/abort.js +96 -0
  194. package/server/utils/cloud-security.js +110 -3
  195. package/server/utils/files.js +31 -0
  196. package/server/utils/image_payload.js +95 -0
  197. package/server/utils/logger.js +19 -0
  198. package/server/utils/retry.js +107 -0
@@ -1,7 +1,9 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { analyzeImageForUser } = require('./imageAnalysis');
4
- const { isPrivateHost, validateCloudUrl } = require('../../utils/cloud-security');
4
+ const { validateCloudUrlWithDns } = require('../../utils/cloud-security');
5
+ const { isAbortError } = require('../../utils/abort');
6
+ const { fetchResponseText } = require('../network/http');
5
7
  const db = require('../../db/database');
6
8
  const { DATA_DIR } = require('../../../runtime/paths');
7
9
  const { isMainAgent } = require('../agents/manager');
@@ -15,6 +17,7 @@ const {
15
17
  executeIntegratedTool,
16
18
  getIntegratedToolDefinitions,
17
19
  } = require('./integrated_tools');
20
+ const { executeHttpRequest } = require('./integrated_tools/http_request');
18
21
 
19
22
  function compactText(text, maxChars = 120) {
20
23
  const str = String(text || '').replace(/\s+/g, ' ').trim();
@@ -34,6 +37,14 @@ function compactToolDefinition(tool, options = {}) {
34
37
  }
35
38
  };
36
39
 
40
+ // Keep execution-only access metadata on the internal tool definition. The
41
+ // provider adapters serialize only name/description/parameters, so this is
42
+ // never sent as part of an LLM tool schema. The loop uses it to distinguish
43
+ // official-integration reads from writes without hard-coding provider names.
44
+ if (typeof tool.access === 'string' && tool.access.trim()) {
45
+ compact.access = tool.access.trim().toLowerCase();
46
+ }
47
+
37
48
  if (options.includeDescriptions) {
38
49
  compact.description = compactText(tool.description, 320);
39
50
  }
@@ -464,6 +475,58 @@ function getAvailableTools(app, options = {}) {
464
475
  required: ['script']
465
476
  }
466
477
  },
478
+ {
479
+ name: 'credential_fill_browser',
480
+ description: 'Fill a configured credential binding into the current HTTPS login page without revealing the username or password. This enters protected mode; call credential_submit_browser or credential_cancel_browser next.',
481
+ parameters: {
482
+ type: 'object',
483
+ properties: {
484
+ binding_id: { type: 'string', description: 'Opaque browser credential binding ID from the Bitwarden integration summary.' },
485
+ stage: { type: 'string', enum: ['both', 'username', 'password'], description: 'Use username then password in separate protected fills for multi-step sign-in pages; default both.' },
486
+ username_selector: { type: 'string', description: 'CSS selector for the username or email input. Required for both or username stage.' },
487
+ password_selector: { type: 'string', description: 'CSS selector for the password input. Required for both or password stage.' }
488
+ },
489
+ required: ['binding_id']
490
+ }
491
+ },
492
+ {
493
+ name: 'credential_submit_browser',
494
+ description: 'Submit an active protected credential fill. Normal browser capabilities resume after this call.',
495
+ parameters: {
496
+ type: 'object',
497
+ properties: {
498
+ protected_fill_id: { type: 'string', description: 'Opaque protected fill ID returned by credential_fill_browser.' }
499
+ },
500
+ required: ['protected_fill_id']
501
+ }
502
+ },
503
+ {
504
+ name: 'credential_cancel_browser',
505
+ description: 'Clear and cancel an active protected credential fill. Normal browser capabilities resume after this call.',
506
+ parameters: {
507
+ type: 'object',
508
+ properties: {
509
+ protected_fill_id: { type: 'string', description: 'Opaque protected fill ID returned by credential_fill_browser.' }
510
+ },
511
+ required: ['protected_fill_id']
512
+ }
513
+ },
514
+ {
515
+ name: 'credential_http_request',
516
+ description: 'Make an HTTPS request through a configured credential binding. Authentication is injected by the host and never exposed; only the bounded origin, path prefix, and methods are allowed.',
517
+ parameters: {
518
+ type: 'object',
519
+ properties: {
520
+ binding_id: { type: 'string', description: 'Opaque HTTP credential binding ID from the Bitwarden integration summary.' },
521
+ method: { type: 'string', enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] },
522
+ url: { type: 'string', description: 'Full HTTPS URL allowed by the binding.' },
523
+ headers: { type: 'object', description: 'Optional non-authentication request headers.' },
524
+ body: { type: 'string', description: 'Optional request body.' },
525
+ timeout_ms: { type: 'number', description: 'Optional timeout from 1000 to 120000 milliseconds.' }
526
+ },
527
+ required: ['binding_id', 'method', 'url']
528
+ }
529
+ },
467
530
  {
468
531
  name: 'desktop_list_devices',
469
532
  description: 'List logged-in desktop companion devices available for local PC control.',
@@ -878,7 +941,7 @@ function getAvailableTools(app, options = {}) {
878
941
  },
879
942
  {
880
943
  name: 'send_message',
881
- description: `Send a message on a connected messaging platform. Supports WhatsApp (text/media), Telnyx Voice (phone calls — TTS), Discord, Telegram, Slack, Google Chat, Microsoft Teams, Matrix, Signal, iMessage/BlueBubbles, IRC, Feishu, LINE, Mattermost, Nextcloud Talk, Nostr, Synology Chat, Tlon, Twitch, Zalo, WeChat, WebChat, and configurable webhook bridges. ${buildSendMessageFormattingReference()} For WhatsApp: use media_path to attach files. Use content "[NO RESPONSE]" only when the user explicitly asked for silence/no reply, or when a background task intentionally decides no user-visible update is needed with purpose="no_response". For background task or schedule runs, set purpose to final_result, blocker, or no_response.`,
944
+ description: `Send a final message on a connected messaging platform. Use send_interim_update, not this tool, for an ongoing status reply to the originating chat. Supports WhatsApp (text/media), Telnyx Voice (phone calls — TTS), Discord, Telegram, Slack, Google Chat, Microsoft Teams, Matrix, Signal, iMessage/BlueBubbles, IRC, Feishu, LINE, Mattermost, Nextcloud Talk, Nostr, Synology Chat, Tlon, Twitch, Zalo, WeChat, WebChat, and configurable webhook bridges. ${buildSendMessageFormattingReference()} For WhatsApp: use media_path to attach files. Use content "[NO RESPONSE]" only when the user explicitly asked for silence/no reply, or when a background task intentionally decides no user-visible update is needed with purpose="no_response". For background task or schedule runs, set purpose to final_result, blocker, or no_response.`,
882
945
  parameters: {
883
946
  type: 'object',
884
947
  properties: {
@@ -1632,7 +1695,8 @@ async function executeTool(toolName, args, context, engine) {
1632
1695
  taskId,
1633
1696
  widgetId,
1634
1697
  deliveryState = null,
1635
- allowMultipleProactiveMessages = false
1698
+ allowMultipleProactiveMessages = false,
1699
+ signal = null,
1636
1700
  } = context;
1637
1701
  const runtime = () => app?.locals?.runtimeManager || engine.runtimeManager || null;
1638
1702
  const bc = async () => {
@@ -1641,7 +1705,10 @@ async function executeTool(toolName, args, context, engine) {
1641
1705
  const backend = typeof manager.getActiveBrowserBackend === 'function'
1642
1706
  ? await Promise.resolve(manager.getActiveBrowserBackend(userId))
1643
1707
  : 'vm';
1644
- return { provider: await manager.getBrowserProviderForUser(userId), backend };
1708
+ return {
1709
+ provider: await manager.getBrowserProviderForUser(userId, { signal }),
1710
+ backend,
1711
+ };
1645
1712
  }
1646
1713
  throw new Error('Browser provider is unavailable. VM runtime is required.');
1647
1714
  };
@@ -1668,11 +1735,18 @@ async function executeTool(toolName, args, context, engine) {
1668
1735
  const socialVideo = () => app?.locals?.socialVideoService || null;
1669
1736
  const socialReach = () => app?.locals?.socialReachService || null;
1670
1737
  const widgets = () => app?.locals?.widgetService || null;
1738
+ const credentials = () => app?.locals?.credentialBroker || null;
1671
1739
  const artifactStore = app?.locals?.artifactStore || null;
1672
1740
 
1673
1741
  const integrationManager = integrations();
1674
1742
  if (integrationManager) {
1675
- const integrationResult = await integrationManager.executeTool(userId, toolName, args, agentId);
1743
+ const integrationResult = await integrationManager.executeTool(
1744
+ userId,
1745
+ toolName,
1746
+ args,
1747
+ agentId,
1748
+ { signal },
1749
+ );
1676
1750
  if (
1677
1751
  integrationResult &&
1678
1752
  typeof integrationResult === 'object' &&
@@ -1724,6 +1798,7 @@ async function executeTool(toolName, args, context, engine) {
1724
1798
  stdinInput: args.stdin_input,
1725
1799
  pty: args.pty === true,
1726
1800
  inputs: Array.isArray(args.inputs) ? args.inputs : [],
1801
+ signal,
1727
1802
  };
1728
1803
  if (typeof runtimeManager.executeCliCommand === 'function') {
1729
1804
  return await runtimeManager.executeCliCommand(userId, args.command, execOptions);
@@ -1736,7 +1811,7 @@ async function executeTool(toolName, args, context, engine) {
1736
1811
  }
1737
1812
 
1738
1813
  case 'browser_navigate': {
1739
- const urlCheck = validateCloudUrl(args.url);
1814
+ const urlCheck = await validateCloudUrlWithDns(args.url, { signal });
1740
1815
  if (!urlCheck.allowed) return { error: 'URL is not allowed: blocked scheme or private/internal network address.' };
1741
1816
  const { provider, backend } = await bc();
1742
1817
  if (!provider) return { error: 'Browser controller not available' };
@@ -1745,14 +1820,15 @@ async function executeTool(toolName, args, context, engine) {
1745
1820
  waitFor: args.waitFor,
1746
1821
  fullPage: args.fullPage,
1747
1822
  referrerMode: args.referrerMode,
1748
- challengeRetry: args.challengeRetry
1823
+ challengeRetry: args.challengeRetry,
1824
+ signal,
1749
1825
  }), backend };
1750
1826
  }
1751
1827
 
1752
1828
  case 'browser_click': {
1753
1829
  const { provider, backend } = await bc();
1754
1830
  if (!provider) return { error: 'Browser controller not available' };
1755
- return { ...await provider.click(args.selector, args.text, args.screenshot !== false), backend };
1831
+ return { ...await provider.click(args.selector, args.text, args.screenshot !== false, { signal }), backend };
1756
1832
  }
1757
1833
 
1758
1834
  case 'browser_type': {
@@ -1760,20 +1836,21 @@ async function executeTool(toolName, args, context, engine) {
1760
1836
  if (!provider) return { error: 'Browser controller not available' };
1761
1837
  return { ...await provider.type(args.selector, args.text, {
1762
1838
  clear: args.clear !== false,
1763
- pressEnter: args.pressEnter
1839
+ pressEnter: args.pressEnter,
1840
+ signal,
1764
1841
  }), backend };
1765
1842
  }
1766
1843
 
1767
1844
  case 'browser_extract': {
1768
1845
  const { provider, backend } = await bc();
1769
1846
  if (!provider) return { error: 'Browser controller not available' };
1770
- return { ...await provider.extract(args.selector, args.attribute, args.all), backend };
1847
+ return { ...await provider.extract(args.selector, args.attribute, args.all, { signal }), backend };
1771
1848
  }
1772
1849
 
1773
1850
  case 'browser_screenshot': {
1774
1851
  const { provider, backend } = await bc();
1775
1852
  if (!provider) return { error: 'Browser controller not available' };
1776
- return { ...await provider.screenshot({ fullPage: args.fullPage, selector: args.selector }), backend };
1853
+ return { ...await provider.screenshot({ fullPage: args.fullPage, selector: args.selector, signal }), backend };
1777
1854
  }
1778
1855
 
1779
1856
  case 'browser_evaluate': {
@@ -1781,13 +1858,37 @@ async function executeTool(toolName, args, context, engine) {
1781
1858
  if (!provider) return { error: 'Browser controller not available' };
1782
1859
  const script = args.script ?? args.javascript;
1783
1860
  if (!script) return { error: 'browser_evaluate requires a "script" argument' };
1784
- return { ...await provider.evaluate(script), backend };
1861
+ return { ...await provider.evaluate(script, { signal }), backend };
1862
+ }
1863
+
1864
+ case 'credential_fill_browser': {
1865
+ const broker = credentials();
1866
+ if (!broker) return { error: 'Credential broker is unavailable.' };
1867
+ return broker.fillBrowser(userId, agentId, args, { runId, signal });
1868
+ }
1869
+
1870
+ case 'credential_submit_browser': {
1871
+ const broker = credentials();
1872
+ if (!broker) return { error: 'Credential broker is unavailable.' };
1873
+ return broker.submitProtected(userId, agentId, args.protected_fill_id, { runId, signal });
1874
+ }
1875
+
1876
+ case 'credential_cancel_browser': {
1877
+ const broker = credentials();
1878
+ if (!broker) return { error: 'Credential broker is unavailable.' };
1879
+ return broker.cancelProtected(userId, agentId, args.protected_fill_id, { runId, signal });
1880
+ }
1881
+
1882
+ case 'credential_http_request': {
1883
+ const broker = credentials();
1884
+ if (!broker) return { error: 'Credential broker is unavailable.' };
1885
+ return broker.httpRequest(userId, agentId, args, { runId, signal });
1785
1886
  }
1786
1887
 
1787
1888
  case 'android_start_emulator': {
1788
1889
  const controller = await ac();
1789
1890
  if (!controller) return { error: 'Android controller not available' };
1790
- return await controller.startEmulator(args || {});
1891
+ return await controller.startEmulator({ ...(args || {}), signal });
1791
1892
  }
1792
1893
 
1793
1894
  case 'desktop_list_devices': {
@@ -1814,6 +1915,7 @@ async function executeTool(toolName, args, context, engine) {
1814
1915
  return await controller.observe({
1815
1916
  deviceId: args.device_id,
1816
1917
  includeTree: args.includeTree === true,
1918
+ signal,
1817
1919
  });
1818
1920
  }
1819
1921
 
@@ -1823,6 +1925,7 @@ async function executeTool(toolName, args, context, engine) {
1823
1925
  return await controller.clickPoint(args.x, args.y, {
1824
1926
  deviceId: args.device_id,
1825
1927
  button: args.button,
1928
+ signal,
1826
1929
  });
1827
1930
  }
1828
1931
 
@@ -1836,6 +1939,7 @@ async function executeTool(toolName, args, context, engine) {
1836
1939
  x2: args.x2,
1837
1940
  y2: args.y2,
1838
1941
  durationMs: args.durationMs,
1942
+ signal,
1839
1943
  });
1840
1944
  }
1841
1945
 
@@ -1846,6 +1950,7 @@ async function executeTool(toolName, args, context, engine) {
1846
1950
  deviceId: args.device_id,
1847
1951
  deltaX: args.deltaX,
1848
1952
  deltaY: args.deltaY,
1953
+ signal,
1849
1954
  });
1850
1955
  }
1851
1956
 
@@ -1855,6 +1960,7 @@ async function executeTool(toolName, args, context, engine) {
1855
1960
  return await controller.typeText(args.text, {
1856
1961
  deviceId: args.device_id,
1857
1962
  pressEnter: args.pressEnter === true,
1963
+ signal,
1858
1964
  });
1859
1965
  }
1860
1966
 
@@ -1863,6 +1969,7 @@ async function executeTool(toolName, args, context, engine) {
1863
1969
  if (!controller) return { error: 'Desktop provider not available' };
1864
1970
  return await controller.pressKey(args.key, {
1865
1971
  deviceId: args.device_id,
1972
+ signal,
1866
1973
  });
1867
1974
  }
1868
1975
 
@@ -1872,6 +1979,7 @@ async function executeTool(toolName, args, context, engine) {
1872
1979
  return await controller.launchApp({
1873
1980
  deviceId: args.device_id,
1874
1981
  app: args.app,
1982
+ signal,
1875
1983
  });
1876
1984
  }
1877
1985
 
@@ -1880,112 +1988,111 @@ async function executeTool(toolName, args, context, engine) {
1880
1988
  if (!controller) return { error: 'Desktop provider not available' };
1881
1989
  return await controller.getAccessibilityTree({
1882
1990
  deviceId: args.device_id,
1991
+ signal,
1883
1992
  });
1884
1993
  }
1885
1994
 
1886
1995
  case 'android_stop_emulator': {
1887
1996
  const controller = await ac();
1888
1997
  if (!controller) return { error: 'Android controller not available' };
1889
- return await controller.stopEmulator();
1998
+ return await controller.stopEmulator({ signal });
1890
1999
  }
1891
2000
 
1892
2001
  case 'android_list_devices': {
1893
2002
  const controller = await ac();
1894
2003
  if (!controller) return { error: 'Android controller not available' };
1895
- return { devices: await controller.listDevices() };
2004
+ return { devices: await controller.listDevices({ signal }) };
1896
2005
  }
1897
2006
 
1898
2007
  case 'android_open_app': {
1899
2008
  const controller = await ac();
1900
2009
  if (!controller) return { error: 'Android controller not available' };
1901
- return await controller.openApp(args || {});
2010
+ return await controller.openApp({ ...(args || {}), signal });
1902
2011
  }
1903
2012
 
1904
2013
  case 'android_open_intent': {
1905
2014
  const controller = await ac();
1906
2015
  if (!controller) return { error: 'Android controller not available' };
1907
- return await controller.openIntent(args || {});
2016
+ return await controller.openIntent({ ...(args || {}), signal });
1908
2017
  }
1909
2018
 
1910
2019
  case 'android_tap': {
1911
2020
  const controller = await ac();
1912
2021
  if (!controller) return { error: 'Android controller not available' };
1913
- return await controller.tap(args || {});
2022
+ return await controller.tap({ ...(args || {}), signal });
1914
2023
  }
1915
2024
 
1916
2025
  case 'android_long_press': {
1917
2026
  const controller = await ac();
1918
2027
  if (!controller) return { error: 'Android controller not available' };
1919
- return await controller.longPress(args || {});
2028
+ return await controller.longPress({ ...(args || {}), signal });
1920
2029
  }
1921
2030
 
1922
2031
  case 'android_type': {
1923
2032
  const controller = await ac();
1924
2033
  if (!controller) return { error: 'Android controller not available' };
1925
- return await controller.type(args || {});
2034
+ return await controller.type({ ...(args || {}), signal });
1926
2035
  }
1927
2036
 
1928
2037
  case 'android_swipe': {
1929
2038
  const controller = await ac();
1930
2039
  if (!controller) return { error: 'Android controller not available' };
1931
- return await controller.swipe(args || {});
2040
+ return await controller.swipe({ ...(args || {}), signal });
1932
2041
  }
1933
2042
 
1934
2043
  case 'android_press_key': {
1935
2044
  const controller = await ac();
1936
2045
  if (!controller) return { error: 'Android controller not available' };
1937
- return await controller.pressKey(args || {});
2046
+ return await controller.pressKey({ ...(args || {}), signal });
1938
2047
  }
1939
2048
 
1940
2049
  case 'android_wait_for': {
1941
2050
  const controller = await ac();
1942
2051
  if (!controller) return { error: 'Android controller not available' };
1943
- return await controller.waitFor(args || {});
2052
+ return await controller.waitFor({ ...(args || {}), signal });
1944
2053
  }
1945
2054
 
1946
2055
  case 'android_observe': {
1947
2056
  const controller = await ac();
1948
2057
  if (!controller) return { error: 'Android controller not available' };
1949
- return await controller.observe(args || {});
2058
+ return await controller.observe({ ...(args || {}), signal });
1950
2059
  }
1951
2060
 
1952
2061
  case 'android_dump_ui': {
1953
2062
  const controller = await ac();
1954
2063
  if (!controller) return { error: 'Android controller not available' };
1955
- return await controller.dumpUi(args || {});
2064
+ return await controller.dumpUi({ ...(args || {}), signal });
1956
2065
  }
1957
2066
 
1958
2067
  case 'android_screenshot': {
1959
2068
  const controller = await ac();
1960
2069
  if (!controller) return { error: 'Android controller not available' };
1961
- return await controller.screenshot(args || {});
2070
+ return await controller.screenshot({ ...(args || {}), signal });
1962
2071
  }
1963
2072
 
1964
2073
  case 'android_list_apps': {
1965
2074
  const controller = await ac();
1966
2075
  if (!controller) return { error: 'Android controller not available' };
1967
- return await controller.listApps(args || {});
2076
+ return await controller.listApps({ ...(args || {}), signal });
1968
2077
  }
1969
2078
 
1970
2079
  case 'android_install_apk': {
1971
2080
  const controller = await ac();
1972
2081
  if (!controller) return { error: 'Android controller not available' };
1973
- return await controller.installApk(args || {});
2082
+ return await controller.installApk({ ...(args || {}), signal });
1974
2083
  }
1975
2084
 
1976
2085
  case 'android_shell': {
1977
2086
  const controller = await ac();
1978
2087
  if (!controller) return { error: 'Android controller not available' };
1979
- return await controller.shell(args || {});
2088
+ return await controller.shell({ ...(args || {}), signal });
1980
2089
  }
1981
2090
 
1982
2091
  case 'web_search': {
1983
2092
  const apiKey = process.env.BRAVE_SEARCH_API_KEY;
1984
2093
  if (!apiKey) return { error: 'BRAVE_SEARCH_API_KEY is not configured' };
1985
2094
 
1986
- const controller = new AbortController();
1987
2095
  const timeoutMs = 20000;
1988
- const timer = setTimeout(() => controller.abort(), timeoutMs);
1989
2096
 
1990
2097
  try {
1991
2098
  const limit = Math.max(1, Math.min(Number(args.count) || 5, 10));
@@ -2000,15 +2107,20 @@ async function executeTool(toolName, args, context, engine) {
2000
2107
  if (args.search_lang) params.set('search_lang', String(args.search_lang).toLowerCase());
2001
2108
  if (args.freshness) params.set('freshness', args.freshness);
2002
2109
 
2003
- const res = await fetch(`https://api.search.brave.com/res/v1/web/search?${params.toString()}`, {
2110
+ const { response, text } = await fetchResponseText(
2111
+ `https://api.search.brave.com/res/v1/web/search?${params.toString()}`,
2112
+ {
2004
2113
  headers: {
2005
2114
  Accept: 'application/json',
2006
2115
  'X-Subscription-Token': apiKey
2007
2116
  },
2008
- signal: controller.signal
2009
- });
2117
+ maxResponseBytes: 2 * 1024 * 1024,
2118
+ serviceName: 'Brave Search',
2119
+ signal,
2120
+ timeoutMs,
2121
+ },
2122
+ );
2010
2123
 
2011
- const text = await res.text();
2012
2124
  let data = null;
2013
2125
  try {
2014
2126
  data = JSON.parse(text);
@@ -2016,9 +2128,9 @@ async function executeTool(toolName, args, context, engine) {
2016
2128
  data = null;
2017
2129
  }
2018
2130
 
2019
- if (!res.ok) {
2131
+ if (!response.ok) {
2020
2132
  return {
2021
- error: `Brave Search API request failed with status ${res.status}`,
2133
+ error: `Brave Search API request failed with status ${response.status}`,
2022
2134
  details: data || text.slice(0, 1000)
2023
2135
  };
2024
2136
  }
@@ -2040,10 +2152,11 @@ async function executeTool(toolName, args, context, engine) {
2040
2152
  results
2041
2153
  };
2042
2154
  } catch (err) {
2043
- if (err.name === 'AbortError') return { error: `Brave Search API request timed out after ${timeoutMs} ms` };
2155
+ if (isAbortError(err, signal)) throw err;
2156
+ if (err.code === 'HTTP_TIMEOUT') {
2157
+ return { error: `Brave Search API request timed out after ${timeoutMs}ms` };
2158
+ }
2044
2159
  return { error: err.message };
2045
- } finally {
2046
- clearTimeout(timer);
2047
2160
  }
2048
2161
  }
2049
2162
 
@@ -2051,7 +2164,13 @@ async function executeTool(toolName, args, context, engine) {
2051
2164
  const { MemoryManager } = require('../memory/manager');
2052
2165
  const mm = new MemoryManager();
2053
2166
  const content = typeof args.content === 'string' ? args.content : args.value;
2054
- const id = await mm.saveMemory(userId, content, args.category || 'episodic', args.importance || 5, { agentId });
2167
+ const id = await mm.saveMemory(
2168
+ userId,
2169
+ content,
2170
+ args.category || 'episodic',
2171
+ args.importance || 5,
2172
+ { agentId, signal },
2173
+ );
2055
2174
  if (!id) {
2056
2175
  return {
2057
2176
  success: true,
@@ -2065,7 +2184,12 @@ async function executeTool(toolName, args, context, engine) {
2065
2184
  case 'memory_recall': {
2066
2185
  const { MemoryManager } = require('../memory/manager');
2067
2186
  const mm = new MemoryManager();
2068
- const results = await mm.recallMemory(userId, args.query, args.limit || 6, { agentId });
2187
+ const results = await mm.recallMemory(
2188
+ userId,
2189
+ args.query,
2190
+ args.limit || 6,
2191
+ { agentId, signal },
2192
+ );
2069
2193
  if (!results.length) return { results: [], message: 'Nothing found' };
2070
2194
  return { results };
2071
2195
  }
@@ -2198,6 +2322,7 @@ async function executeTool(toolName, args, context, engine) {
2198
2322
  includeFrame: args.include_frame !== false,
2199
2323
  forceStt: args.force_stt === true,
2200
2324
  agentId,
2325
+ signal,
2201
2326
  });
2202
2327
  }
2203
2328
 
@@ -2206,7 +2331,7 @@ async function executeTool(toolName, args, context, engine) {
2206
2331
  if (!service || typeof service.getStatus !== 'function') {
2207
2332
  return { error: 'Social reach service is unavailable.' };
2208
2333
  }
2209
- return await service.getStatus(userId);
2334
+ return await service.getStatus(userId, { signal });
2210
2335
  }
2211
2336
 
2212
2337
  case 'social_reach_read': {
@@ -2214,7 +2339,7 @@ async function executeTool(toolName, args, context, engine) {
2214
2339
  if (!service || typeof service.read !== 'function') {
2215
2340
  return { error: 'Social reach service is unavailable.' };
2216
2341
  }
2217
- return await service.read(userId, args || {});
2342
+ return await service.read(userId, args || {}, { signal });
2218
2343
  }
2219
2344
 
2220
2345
  case 'social_reach_search': {
@@ -2222,7 +2347,7 @@ async function executeTool(toolName, args, context, engine) {
2222
2347
  if (!service || typeof service.search !== 'function') {
2223
2348
  return { error: 'Social reach service is unavailable.' };
2224
2349
  }
2225
- return await service.search(userId, args || {});
2350
+ return await service.search(userId, args || {}, { signal });
2226
2351
  }
2227
2352
 
2228
2353
  case 'memory_write': {
@@ -2275,6 +2400,13 @@ async function executeTool(toolName, args, context, engine) {
2275
2400
  if (!engine || !runId) {
2276
2401
  return { error: 'Interim updates require an active run.' };
2277
2402
  }
2403
+ if (engine.getRunMeta(runId)?.messagingContext?.behavior?.isGroup === true) {
2404
+ return {
2405
+ sent: false,
2406
+ skipped: true,
2407
+ reason: 'Interim progress updates are suppressed in shared rooms.',
2408
+ };
2409
+ }
2278
2410
  const interimContent = typeof args.content === 'string' ? args.content : '';
2279
2411
  const expectsReply = args.expects_reply === true;
2280
2412
  const deferFollowUp = args.defer_follow_up === true;
@@ -2305,6 +2437,13 @@ async function executeTool(toolName, args, context, engine) {
2305
2437
  stripNoResponseMarker: false
2306
2438
  });
2307
2439
  const suppressReply = normalizedMessage === '[NO RESPONSE]';
2440
+ const originDelivery = isOriginMessagingDelivery({
2441
+ triggerSource,
2442
+ source: context.source,
2443
+ chatId: context.chatId,
2444
+ platform: args.platform,
2445
+ to: args.to,
2446
+ });
2308
2447
  if (isProactiveTrigger(triggerSource)) {
2309
2448
  const proactiveValidation = validateProactiveSendMessageArgs({
2310
2449
  purpose: args.purpose,
@@ -2360,26 +2499,69 @@ async function executeTool(toolName, args, context, engine) {
2360
2499
  };
2361
2500
  }
2362
2501
 
2363
- const sendResult = await manager.sendMessage(userId, args.platform, args.to, args.content, {
2364
- agentId,
2365
- mediaPath: args.media_path,
2366
- runId,
2367
- persistConversation: triggerSource === 'schedule' || triggerSource === 'tasks'
2368
- });
2502
+ if (triggerSource === 'messaging' && originDelivery) {
2503
+ await engine?.stopMessagingProgressSupervisor?.(runId);
2504
+ }
2505
+ const behavior = runState?.messagingContext?.behavior;
2506
+ const behaviorPipeline = app?.locals?.behaviorPipeline;
2507
+ let deliveredContent = normalizedMessage;
2508
+ let sendResult;
2509
+ if (
2510
+ originDelivery
2511
+ && behavior
2512
+ && behavior.enabled !== false
2513
+ && behaviorPipeline
2514
+ && typeof behaviorPipeline.refineAndMaybeDeliver === 'function'
2515
+ ) {
2516
+ const behaviorResult = await behaviorPipeline.refineAndMaybeDeliver({
2517
+ userId,
2518
+ agentId,
2519
+ msg: behavior.message,
2520
+ config: behavior.config,
2521
+ draft: normalizedMessage,
2522
+ messagingManager: manager,
2523
+ runId,
2524
+ signal,
2525
+ mediaPath: args.media_path,
2526
+ turnEpoch: behavior.turnEpoch,
2527
+ deliver: true,
2528
+ });
2529
+ deliveredContent = behaviorResult.content;
2530
+ if (behaviorResult.delivered) {
2531
+ sendResult = { success: true, behavior: true, result: behaviorResult.delivery };
2532
+ } else if (behaviorResult.suppressed) {
2533
+ markProactiveNoResponse({ runState, deliveryState });
2534
+ sendResult = {
2535
+ success: true,
2536
+ sent: false,
2537
+ suppressed: true,
2538
+ reason: behaviorResult.reasonCodes?.[0] || 'behavior_suppressed',
2539
+ };
2540
+ } else {
2541
+ sendResult = {
2542
+ success: false,
2543
+ error: behaviorResult.delivery?.error
2544
+ || behaviorResult.delivery?.reason
2545
+ || 'Behavior delivery was not confirmed.',
2546
+ };
2547
+ }
2548
+ } else {
2549
+ sendResult = await manager.sendMessage(userId, args.platform, args.to, args.content, {
2550
+ agentId,
2551
+ mediaPath: args.media_path,
2552
+ runId,
2553
+ persistConversation: triggerSource === 'schedule' || triggerSource === 'tasks',
2554
+ signal,
2555
+ });
2556
+ }
2369
2557
  // Track that the agent explicitly sent a message during this run
2370
2558
  if (
2371
2559
  !suppressReply
2372
2560
  && sendResult?.success === true
2373
2561
  && sendResult?.suppressed !== true
2374
- && isOriginMessagingDelivery({
2375
- triggerSource,
2376
- source: context.source,
2377
- chatId: context.chatId,
2378
- platform: args.platform,
2379
- to: args.to,
2380
- })
2562
+ && originDelivery
2381
2563
  ) {
2382
- markProactiveMessageSent({ runState, deliveryState, content: normalizedMessage });
2564
+ markProactiveMessageSent({ runState, deliveryState, content: deliveredContent });
2383
2565
  if (runState && triggerSource === 'messaging') {
2384
2566
  runState.explicitMessageSent = true;
2385
2567
  }
@@ -2557,66 +2739,14 @@ async function executeTool(toolName, args, context, engine) {
2557
2739
  }
2558
2740
 
2559
2741
  case 'http_request': {
2560
- let parsedUrl;
2561
- try { parsedUrl = new URL(args.url); } catch {
2562
- return { error: 'Invalid URL' };
2563
- }
2564
- const scheme = parsedUrl.protocol.replace(/:$/, '').toLowerCase();
2565
- if (!['http', 'https'].includes(scheme)) {
2566
- return { error: 'URL scheme not allowed. Only http and https are permitted.' };
2567
- }
2568
- const h = parsedUrl.hostname.toLowerCase().replace(/^\[|\]$/g, '');
2569
- if (h === 'localhost' || h === '127.0.0.1' || h === '::1' || h.endsWith('.localhost')) {
2570
- return { error: 'Loopback addresses are not permitted.' };
2571
- }
2572
- const allowPrivate = process.env.NEOAGENT_HTTP_ALLOW_PRIVATE !== 'false';
2573
- if (!allowPrivate && isPrivateHost(parsedUrl.hostname)) {
2574
- return { error: 'Private/internal network addresses are not permitted.' };
2575
- }
2576
- const controller = new AbortController();
2577
- const timeoutMs = args.timeout_ms || 30000;
2578
- const timer = setTimeout(() => controller.abort(), timeoutMs);
2579
2742
  try {
2580
- const options = {
2581
- method: args.method || 'GET',
2582
- headers: args.headers || {},
2583
- signal: controller.signal
2584
- };
2585
- if (args.body && ['POST', 'PUT', 'PATCH'].includes(options.method)) {
2586
- options.body = args.body;
2587
- if (!options.headers['Content-Type']) {
2588
- options.headers['Content-Type'] = 'application/json';
2589
- }
2590
- }
2591
- const res = await fetch(args.url, options);
2592
- const MAX_BODY = 512 * 1024;
2593
- const reader = res.body.getReader();
2594
- const chunks = [];
2595
- let total = 0;
2596
- let truncated = false;
2597
- while (true) {
2598
- const { done, value } = await reader.read();
2599
- if (done) break;
2600
- total += value.length;
2601
- if (total > MAX_BODY) {
2602
- const take = MAX_BODY - (total - value.length);
2603
- if (take > 0) chunks.push(value.slice(0, take));
2604
- truncated = true;
2605
- break;
2606
- }
2607
- chunks.push(value);
2608
- }
2609
- const text = Buffer.concat(chunks.map(c => Buffer.from(c))).toString('utf-8');
2610
- return {
2611
- status: res.status,
2612
- headers: Object.fromEntries(res.headers.entries()),
2613
- body: truncated ? text + '\n...[truncated]' : text,
2614
- };
2743
+ return await executeHttpRequest(args, {
2744
+ allowPrivate: process.env.NEOAGENT_HTTP_ALLOW_PRIVATE === 'true',
2745
+ signal,
2746
+ });
2615
2747
  } catch (err) {
2616
- if (err.name === 'AbortError') return { error: `Request timed out after ${timeoutMs} ms` };
2748
+ if (isAbortError(err, signal)) throw err;
2617
2749
  return { error: err.message };
2618
- } finally {
2619
- clearTimeout(timer);
2620
2750
  }
2621
2751
  }
2622
2752
 
@@ -2762,7 +2892,8 @@ async function executeTool(toolName, args, context, engine) {
2762
2892
  const sendResult = await manager.sendMessage(userId, target.platform, target.to, message, {
2763
2893
  agentId,
2764
2894
  runId,
2765
- persistConversation: true
2895
+ persistConversation: true,
2896
+ signal,
2766
2897
  });
2767
2898
  if (taskId && taskConfig && (taskConfig.notifyPlatform !== target.platform || taskConfig.notifyTo !== target.to)) {
2768
2899
  taskConfig.notifyPlatform = target.platform;