neoagent 3.2.1-beta.2 → 3.2.1-beta.3

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 (144) 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 +511 -89
  4. package/flutter_app/lib/main_chat.dart +118 -739
  5. package/flutter_app/lib/main_controller.dart +29 -20
  6. package/flutter_app/lib/main_models.dart +3 -0
  7. package/flutter_app/lib/main_operations.dart +334 -321
  8. package/flutter_app/lib/main_settings.dart +4 -3
  9. package/flutter_app/lib/main_shared.dart +14 -11
  10. package/flutter_app/lib/src/desktop_companion_actions.dart +185 -31
  11. package/flutter_app/lib/src/desktop_companion_io.dart +319 -86
  12. package/flutter_app/windows/runner/flutter_window.cpp +143 -32
  13. package/lib/manager.js +106 -89
  14. package/lib/schema_migrations.js +67 -13
  15. package/package.json +20 -13
  16. package/runtime/paths.js +49 -5
  17. package/server/guest_agent.js +52 -30
  18. package/server/http/middleware.js +24 -0
  19. package/server/http/routes.js +4 -6
  20. package/server/public/.last_build_id +1 -1
  21. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  22. package/server/public/flutter_bootstrap.js +1 -1
  23. package/server/public/main.dart.js +30803 -30805
  24. package/server/routes/admin.js +1 -1
  25. package/server/routes/android.js +30 -34
  26. package/server/routes/browser.js +23 -15
  27. package/server/routes/desktop.js +18 -1
  28. package/server/routes/integrations.js +5 -1
  29. package/server/routes/memory.js +1 -0
  30. package/server/routes/settings.js +16 -5
  31. package/server/routes/social_reach.js +12 -3
  32. package/server/routes/social_video.js +4 -0
  33. package/server/services/ai/compaction.js +7 -2
  34. package/server/services/ai/history.js +44 -5
  35. package/server/services/ai/integrated_tools/http_request.js +8 -0
  36. package/server/services/ai/loop/agent_engine_core.js +347 -162
  37. package/server/services/ai/loop/callbacks.js +1 -0
  38. package/server/services/ai/loop/completion_judge.js +12 -0
  39. package/server/services/ai/loop/conversation_loop.js +348 -242
  40. package/server/services/ai/loop/messaging_delivery.js +129 -57
  41. package/server/services/ai/loop/model_call_guard.js +91 -0
  42. package/server/services/ai/loop/model_io.js +8 -41
  43. package/server/services/ai/loop/tool_dispatch.js +19 -8
  44. package/server/services/ai/loopPolicy.js +24 -19
  45. package/server/services/ai/model_discovery.js +227 -0
  46. package/server/services/ai/model_identity.js +71 -0
  47. package/server/services/ai/models.js +67 -162
  48. package/server/services/ai/providerRetry.js +17 -59
  49. package/server/services/ai/provider_selector.js +111 -0
  50. package/server/services/ai/providers/anthropic.js +2 -2
  51. package/server/services/ai/providers/claudeCode.js +15 -28
  52. package/server/services/ai/providers/githubCopilot.js +36 -16
  53. package/server/services/ai/providers/google.js +23 -5
  54. package/server/services/ai/providers/grok.js +4 -3
  55. package/server/services/ai/providers/grokOauth.js +13 -22
  56. package/server/services/ai/providers/nvidia.js +10 -5
  57. package/server/services/ai/providers/ollama.js +102 -82
  58. package/server/services/ai/providers/ollama_stream.js +142 -0
  59. package/server/services/ai/providers/openai.js +39 -5
  60. package/server/services/ai/providers/openaiCodex.js +11 -4
  61. package/server/services/ai/providers/openrouter.js +29 -7
  62. package/server/services/ai/providers/provider_error.js +36 -0
  63. package/server/services/ai/settings.js +26 -2
  64. package/server/services/ai/taskAnalysis.js +5 -1
  65. package/server/services/ai/terminal_reply.js +45 -0
  66. package/server/services/ai/toolEvidence.js +58 -29
  67. package/server/services/ai/tools.js +124 -111
  68. package/server/services/android/controller.js +770 -237
  69. package/server/services/android/process.js +140 -0
  70. package/server/services/android/sdk_download.js +143 -0
  71. package/server/services/android/uia.js +6 -5
  72. package/server/services/artifacts/store.js +24 -0
  73. package/server/services/browser/controller.js +736 -385
  74. package/server/services/browser/extension/gateway.js +40 -16
  75. package/server/services/browser/extension/protocol.js +12 -1
  76. package/server/services/browser/extension/provider.js +59 -47
  77. package/server/services/browser/extension/registry.js +155 -34
  78. package/server/services/cli/executor.js +62 -9
  79. package/server/services/desktop/gateway.js +41 -4
  80. package/server/services/desktop/protocol.js +3 -0
  81. package/server/services/desktop/provider.js +39 -42
  82. package/server/services/desktop/registry.js +137 -52
  83. package/server/services/integrations/figma/provider.js +78 -12
  84. package/server/services/integrations/github/common.js +11 -6
  85. package/server/services/integrations/github/provider.js +52 -53
  86. package/server/services/integrations/google/provider.js +55 -19
  87. package/server/services/integrations/home_assistant/network.js +17 -20
  88. package/server/services/integrations/home_assistant/provider.js +7 -5
  89. package/server/services/integrations/home_assistant/tools.js +17 -5
  90. package/server/services/integrations/http.js +51 -0
  91. package/server/services/integrations/manager.js +158 -53
  92. package/server/services/integrations/microsoft/provider.js +80 -13
  93. package/server/services/integrations/neoarchive/provider.js +55 -29
  94. package/server/services/integrations/neorecall/client.js +17 -10
  95. package/server/services/integrations/neorecall/provider.js +20 -11
  96. package/server/services/integrations/notion/provider.js +16 -13
  97. package/server/services/integrations/oauth_provider.js +115 -51
  98. package/server/services/integrations/slack/provider.js +98 -9
  99. package/server/services/integrations/spotify/provider.js +67 -71
  100. package/server/services/integrations/trello/provider.js +21 -7
  101. package/server/services/integrations/weather/provider.js +18 -12
  102. package/server/services/integrations/whatsapp/provider.js +76 -16
  103. package/server/services/manager.js +87 -1
  104. package/server/services/memory/embedding_index.js +20 -8
  105. package/server/services/memory/embeddings.js +151 -90
  106. package/server/services/memory/ingestion.js +50 -9
  107. package/server/services/memory/ingestion_documents.js +13 -3
  108. package/server/services/memory/manager.js +52 -19
  109. package/server/services/messaging/automation.js +84 -9
  110. package/server/services/messaging/http_platforms.js +33 -13
  111. package/server/services/messaging/inbound_queue.js +78 -24
  112. package/server/services/messaging/inbound_store.js +224 -0
  113. package/server/services/messaging/manager.js +326 -51
  114. package/server/services/messaging/typing_keepalive.js +5 -2
  115. package/server/services/network/http.js +210 -0
  116. package/server/services/network/safe_request.js +307 -0
  117. package/server/services/runtime/backends/local-vm.js +214 -66
  118. package/server/services/runtime/manager.js +17 -12
  119. package/server/services/social_reach/channels/github.js +10 -4
  120. package/server/services/social_reach/channels/reddit.js +4 -4
  121. package/server/services/social_reach/channels/rss.js +2 -2
  122. package/server/services/social_reach/channels/social_video.js +12 -7
  123. package/server/services/social_reach/channels/v2ex.js +21 -8
  124. package/server/services/social_reach/channels/x.js +2 -2
  125. package/server/services/social_reach/channels/xueqiu.js +5 -5
  126. package/server/services/social_reach/service.js +9 -6
  127. package/server/services/social_reach/utils.js +65 -14
  128. package/server/services/social_video/service.js +160 -50
  129. package/server/services/tasks/integration_runtime.js +18 -8
  130. package/server/services/tasks/runtime.js +39 -4
  131. package/server/services/voice/agentBridge.js +17 -4
  132. package/server/services/voice/bufferedLiveRelayAdapter.js +5 -0
  133. package/server/services/voice/liveSession.js +31 -0
  134. package/server/services/voice/openaiSpeech.js +33 -8
  135. package/server/services/voice/providers.js +233 -151
  136. package/server/services/voice/runtimeManager.js +118 -20
  137. package/server/services/voice/turnRunner.js +6 -0
  138. package/server/services/wearable/firmware_manifest.js +51 -13
  139. package/server/services/wearable/service.js +1 -0
  140. package/server/utils/abort.js +96 -0
  141. package/server/utils/cloud-security.js +110 -3
  142. package/server/utils/files.js +31 -0
  143. package/server/utils/image_payload.js +95 -0
  144. package/server/utils/retry.js +107 -0
@@ -3,6 +3,7 @@
3
3
  const { getPlatformDefinition } = require('../platforms');
4
4
  const { SocialReachChannel } = require('./base');
5
5
  const { normalizeAndDetectPlatform } = require('../../social_video/url');
6
+ const { createAbortError } = require('../../../utils/abort');
6
7
 
7
8
  const PLATFORM_LABELS = Object.freeze({
8
9
  youtube: 'YouTube',
@@ -26,7 +27,7 @@ class SocialVideoReachChannel extends SocialReachChannel {
26
27
  }
27
28
  }
28
29
 
29
- async check() {
30
+ async check({ signal } = {}) {
30
31
  if (!this.socialVideoService || typeof this.socialVideoService.getHealthStatus !== 'function') {
31
32
  return {
32
33
  ...(await super.check()),
@@ -37,11 +38,14 @@ class SocialVideoReachChannel extends SocialReachChannel {
37
38
  };
38
39
  }
39
40
 
40
- const health = await this.socialVideoService.getHealthStatus().catch((error) => ({
41
- ready: false,
42
- dependencies: [],
43
- error: error.message || String(error),
44
- }));
41
+ const health = await this.socialVideoService.getHealthStatus({ signal }).catch((error) => {
42
+ if (signal?.aborted) throw createAbortError(signal);
43
+ return {
44
+ ready: false,
45
+ dependencies: [],
46
+ error: error.message || String(error),
47
+ };
48
+ });
45
49
  const missing = (health.dependencies || [])
46
50
  .filter((item) => !item.available)
47
51
  .map((item) => item.name)
@@ -59,7 +63,7 @@ class SocialVideoReachChannel extends SocialReachChannel {
59
63
  };
60
64
  }
61
65
 
62
- async read({ userId, url, include_frame: includeFrame, force_stt: forceStt, agentId }) {
66
+ async read({ userId, url, include_frame: includeFrame, force_stt: forceStt, agentId, signal }) {
63
67
  if (!this.socialVideoService || typeof this.socialVideoService.extractFromUrl !== 'function') {
64
68
  const error = new Error('Social video extraction is not connected right now.');
65
69
  error.status = 503;
@@ -70,6 +74,7 @@ class SocialVideoReachChannel extends SocialReachChannel {
70
74
  includeFrame: includeFrame !== false,
71
75
  forceStt: forceStt === true,
72
76
  agentId: agentId || null,
77
+ signal,
73
78
  });
74
79
  return {
75
80
  ...result,
@@ -3,6 +3,7 @@
3
3
  const { SocialReachChannel } = require('./base');
4
4
  const { getPlatformDefinition } = require('../platforms');
5
5
  const { assertHttpUrl, fetchJson, normalizeLimit } = require('../utils');
6
+ const { createAbortError } = require('../../../utils/abort');
6
7
 
7
8
  function shapeTopic(item = {}) {
8
9
  const node = item.node || {};
@@ -33,17 +34,26 @@ class V2exChannel extends SocialReachChannel {
33
34
  };
34
35
  }
35
36
 
36
- async read({ url, limit }) {
37
+ async read({ url, limit, signal }) {
37
38
  const parsed = assertHttpUrl(url);
38
39
  const match = parsed.pathname.match(/^\/t\/(\d+)/);
39
40
  if (!match) {
40
- return this.search({ query: parsed.searchParams.get('q') || 'hot', limit });
41
+ return this.search({ query: parsed.searchParams.get('q') || 'hot', limit, signal });
41
42
  }
42
43
  const topicId = match[1];
43
- const topicData = await fetchJson(`https://www.v2ex.com/api/topics/show.json?id=${encodeURIComponent(topicId)}`);
44
+ const topicData = await fetchJson(
45
+ `https://www.v2ex.com/api/topics/show.json?id=${encodeURIComponent(topicId)}`,
46
+ { signal },
47
+ );
44
48
  const topic = Array.isArray(topicData) ? topicData[0] || {} : topicData || {};
45
- const replies = await fetchJson(`https://www.v2ex.com/api/replies/show.json?topic_id=${encodeURIComponent(topicId)}&page=1`)
46
- .catch(() => []);
49
+ const replies = await fetchJson(
50
+ `https://www.v2ex.com/api/replies/show.json?topic_id=${encodeURIComponent(topicId)}&page=1`,
51
+ { signal },
52
+ )
53
+ .catch((error) => {
54
+ if (signal?.aborted) throw createAbortError(signal);
55
+ return [];
56
+ });
47
57
  return {
48
58
  platform: this.id,
49
59
  ...shapeTopic(topic),
@@ -58,11 +68,14 @@ class V2exChannel extends SocialReachChannel {
58
68
  };
59
69
  }
60
70
 
61
- async search({ query, limit }) {
71
+ async search({ query, limit, signal }) {
62
72
  const normalized = String(query || '').trim().toLowerCase();
63
73
  const capped = normalizeLimit(limit, 20, 100);
64
74
  if (normalized && normalized !== 'hot') {
65
- const data = await fetchJson(`https://www.v2ex.com/api/topics/show.json?node_name=${encodeURIComponent(normalized)}&page=1`);
75
+ const data = await fetchJson(
76
+ `https://www.v2ex.com/api/topics/show.json?node_name=${encodeURIComponent(normalized)}&page=1`,
77
+ { signal },
78
+ );
66
79
  return {
67
80
  platform: this.id,
68
81
  query: normalized,
@@ -70,7 +83,7 @@ class V2exChannel extends SocialReachChannel {
70
83
  source: 'v2ex_public_api',
71
84
  };
72
85
  }
73
- const data = await fetchJson('https://www.v2ex.com/api/topics/hot.json');
86
+ const data = await fetchJson('https://www.v2ex.com/api/topics/hot.json', { signal });
74
87
  return {
75
88
  platform: this.id,
76
89
  query: 'hot',
@@ -43,7 +43,7 @@ class XChannel extends SocialReachChannel {
43
43
  };
44
44
  }
45
45
 
46
- async read({ userId, url }) {
46
+ async read({ userId, url, signal }) {
47
47
  const id = extractStatusId(url);
48
48
  if (!id) {
49
49
  const error = new Error('A public X post URL is required.');
@@ -54,7 +54,7 @@ class XChannel extends SocialReachChannel {
54
54
  const source = cookieHeaderForPlatform(userId, this.id) ? 'x_syndication_cookies' : 'x_syndication_public';
55
55
  const data = await fetchJson(
56
56
  `https://cdn.syndication.twimg.com/tweet-result?id=${encodeURIComponent(id)}&lang=en`,
57
- { headers: this.#headers(userId) },
57
+ { headers: this.#headers(userId), signal },
58
58
  );
59
59
  return {
60
60
  platform: this.id,
@@ -37,7 +37,7 @@ class XueqiuChannel extends SocialReachChannel {
37
37
  };
38
38
  }
39
39
 
40
- async search({ userId, query, limit }) {
40
+ async search({ userId, query, limit, signal }) {
41
41
  const q = String(query || '').trim();
42
42
  if (!q) {
43
43
  const error = new Error('query is required.');
@@ -45,7 +45,7 @@ class XueqiuChannel extends SocialReachChannel {
45
45
  throw error;
46
46
  }
47
47
  const url = `https://xueqiu.com/stock/search.json?code=${encodeURIComponent(q)}&size=${normalizeLimit(limit, 10, 50)}`;
48
- const data = await fetchJson(url, { headers: this.#headers(userId) });
48
+ const data = await fetchJson(url, { headers: this.#headers(userId), signal });
49
49
  return {
50
50
  platform: this.id,
51
51
  query: q,
@@ -58,12 +58,12 @@ class XueqiuChannel extends SocialReachChannel {
58
58
  };
59
59
  }
60
60
 
61
- async read({ userId, symbol, limit }) {
61
+ async read({ userId, symbol, limit, signal }) {
62
62
  const raw = String(symbol || '').trim().toUpperCase();
63
63
  if (raw) {
64
64
  const data = await fetchJson(
65
65
  `https://stock.xueqiu.com/v5/stock/batch/quote.json?symbol=${encodeURIComponent(raw)}`,
66
- { headers: this.#headers(userId) },
66
+ { headers: this.#headers(userId), signal },
67
67
  );
68
68
  const quote = data.data?.items?.[0]?.quote || {};
69
69
  return {
@@ -76,7 +76,7 @@ class XueqiuChannel extends SocialReachChannel {
76
76
  }
77
77
  const data = await fetchJson(
78
78
  'https://xueqiu.com/v4/statuses/public_timeline_by_category.json?since_id=-1&max_id=-1&count=20&category=-1',
79
- { headers: this.#headers(userId) },
79
+ { headers: this.#headers(userId), signal },
80
80
  );
81
81
  return {
82
82
  platform: this.id,
@@ -5,6 +5,7 @@ const { createChannels } = require('./channels');
5
5
  const { deleteCookieBundle, getCookieSummary, writeCookieBundle } = require('./store');
6
6
  const { domainsForPlatform, getPlatformDefinition, normalizePlatformId } = require('./platforms');
7
7
  const { assertHttpUrl } = require('./utils');
8
+ const { createAbortError } = require('../../utils/abort');
8
9
 
9
10
  const COOKIE_IMPORT_PLATFORMS = new Set(['xueqiu', 'x']);
10
11
  const MAX_IMPORTED_COOKIES = 80;
@@ -59,16 +60,17 @@ class SocialReachService {
59
60
  return this.channels.find((channel) => channel.canHandleUrl(parsed.toString())) || null;
60
61
  }
61
62
 
62
- async getStatus(userId) {
63
+ async getStatus(userId, options = {}) {
63
64
  const statuses = [];
64
65
  for (const channel of this.channels) {
65
66
  try {
66
- const status = await channel.check({ userId });
67
+ const status = await channel.check({ userId, signal: options.signal });
67
68
  if (!status.cookie && channel.setupKind === 'cookies') {
68
69
  status.cookie = getCookieSummary(userId, channel.id);
69
70
  }
70
71
  statuses.push(status);
71
72
  } catch (error) {
73
+ if (options.signal?.aborted) throw createAbortError(options.signal);
72
74
  statuses.push({
73
75
  platform: channel.id,
74
76
  label: channel.label,
@@ -87,7 +89,7 @@ class SocialReachService {
87
89
  };
88
90
  }
89
91
 
90
- async read(userId, args = {}) {
92
+ async read(userId, args = {}, options = {}) {
91
93
  const platform = normalizePlatformId(args.platform || '');
92
94
  const channel = platform ? this.getChannel(platform) : this.detectChannelForUrl(args.url);
93
95
  if (!channel) {
@@ -97,10 +99,10 @@ class SocialReachService {
97
99
  error.status = 400;
98
100
  throw error;
99
101
  }
100
- return channel.read({ ...args, userId });
102
+ return channel.read({ ...args, userId, signal: options.signal });
101
103
  }
102
104
 
103
- async search(userId, args = {}) {
105
+ async search(userId, args = {}, options = {}) {
104
106
  const platform = normalizePlatformId(args.platform || '');
105
107
  const channel = this.getChannel(platform);
106
108
  if (!channel) {
@@ -108,7 +110,7 @@ class SocialReachService {
108
110
  error.status = 400;
109
111
  throw error;
110
112
  }
111
- return channel.search({ ...args, userId });
113
+ return channel.search({ ...args, userId, signal: options.signal });
112
114
  }
113
115
 
114
116
  async importCookiesFromExtension(userId, platform, options = {}) {
@@ -132,6 +134,7 @@ class SocialReachService {
132
134
  }, {
133
135
  tokenId: options.tokenId || null,
134
136
  timeoutMs: 15000,
137
+ signal: options.signal,
135
138
  });
136
139
  const cookies = sanitizeCookies(response?.cookies || [], domains);
137
140
  if (cookies.length === 0) {
@@ -1,9 +1,13 @@
1
1
  'use strict';
2
2
 
3
3
  const cheerio = require('cheerio');
4
+ const { fetchResponseText } = require('../network/http');
5
+ const { executeSafeHttpRequest } = require('../network/safe_request');
4
6
 
5
7
  const DEFAULT_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 '
6
8
  + '(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36';
9
+ const DEFAULT_TIMEOUT_MS = 20000;
10
+ const MAX_RESPONSE_BYTES = 2 * 1024 * 1024;
7
11
 
8
12
  function normalizeLimit(value, fallback = 10, max = 50) {
9
13
  const n = Number(value);
@@ -31,19 +35,60 @@ function assertHttpUrl(value) {
31
35
  }
32
36
 
33
37
  async function fetchText(url, options = {}) {
34
- const response = await fetch(url, {
35
- redirect: 'follow',
36
- ...options,
37
- headers: {
38
- 'user-agent': DEFAULT_UA,
39
- accept: 'text/plain,text/html,application/xml,application/rss+xml,application/atom+xml,*/*',
40
- ...(options.headers || {}),
41
- },
42
- });
43
- const text = await response.text();
44
- if (!response.ok) {
45
- const error = new Error(`Request failed with HTTP ${response.status}`);
46
- error.status = response.status;
38
+ const {
39
+ lookup,
40
+ maxResponseBytes = MAX_RESPONSE_BYTES,
41
+ publicOnly = false,
42
+ requestImpl,
43
+ signal,
44
+ timeoutMs = DEFAULT_TIMEOUT_MS,
45
+ ...requestOptions
46
+ } = options;
47
+ const headers = {
48
+ 'user-agent': DEFAULT_UA,
49
+ accept: 'text/plain,text/html,application/xml,application/rss+xml,application/atom+xml,*/*',
50
+ ...(options.headers || {}),
51
+ };
52
+ let status;
53
+ let text;
54
+ if (publicOnly) {
55
+ const result = await executeSafeHttpRequest({
56
+ url,
57
+ method: requestOptions.method || 'GET',
58
+ headers,
59
+ body: requestOptions.body,
60
+ timeout_ms: timeoutMs,
61
+ }, {
62
+ signal,
63
+ lookup,
64
+ requestImpl,
65
+ maxResponseBytes,
66
+ });
67
+ if (result.truncated) {
68
+ const error = new Error('Response exceeded the Social Reach safety limit.');
69
+ error.code = 'SOCIAL_REACH_RESPONSE_TOO_LARGE';
70
+ error.status = 502;
71
+ throw error;
72
+ }
73
+ status = result.status;
74
+ text = result.body;
75
+ } else {
76
+ const result = await fetchResponseText(url, {
77
+ ...requestOptions,
78
+ headers,
79
+ signal,
80
+ timeoutMs,
81
+ maxResponseBytes,
82
+ serviceName: 'Social Reach',
83
+ timeoutCode: 'SOCIAL_REACH_TIMEOUT',
84
+ tooLargeCode: 'SOCIAL_REACH_RESPONSE_TOO_LARGE',
85
+ });
86
+ status = result.response.status;
87
+ text = result.text;
88
+ }
89
+ if (status < 200 || status >= 300) {
90
+ const error = new Error(`Request failed with HTTP ${status}`);
91
+ error.status = status;
47
92
  error.body = text.slice(0, 500);
48
93
  throw error;
49
94
  }
@@ -58,7 +103,13 @@ async function fetchJson(url, options = {}) {
58
103
  ...(options.headers || {}),
59
104
  },
60
105
  });
61
- return JSON.parse(text);
106
+ try {
107
+ return JSON.parse(text);
108
+ } catch (cause) {
109
+ const error = new Error('Social Reach returned malformed JSON.', { cause });
110
+ error.status = 502;
111
+ throw error;
112
+ }
62
113
  }
63
114
 
64
115
  function htmlToText(html, maxChars = 20000) {