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
@@ -0,0 +1,172 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+ const db = require('../../db/database');
5
+
6
+ const OAUTH_STATE_TTL_MS = 10 * 60_000;
7
+
8
+ function hashOAuthState(value) {
9
+ return crypto.createHash('sha256').update(String(value || '')).digest('hex');
10
+ }
11
+
12
+ class DBAuthProvider {
13
+ constructor(serverId, clientId, authServerUrl) {
14
+ this.serverId = serverId;
15
+ this.clientId = clientId;
16
+ this.authServerUrl = authServerUrl;
17
+ }
18
+
19
+ get redirectUrl() {
20
+ const baseUrl = process.env.PUBLIC_URL || `http://localhost:${process.env.PORT || 3333}`;
21
+ return `${baseUrl}/api/mcp/oauth/callback`;
22
+ }
23
+
24
+ get clientMetadata() {
25
+ return { client_id: this.clientId };
26
+ }
27
+
28
+ state() {
29
+ const state = `${this.serverId}::${crypto.randomBytes(16).toString('hex')}`;
30
+ const config = this._getConfig();
31
+ config.auth = config.auth || {};
32
+ config.auth.oauthStateHash = hashOAuthState(state);
33
+ config.auth.oauthStateCreatedAt = new Date().toISOString();
34
+ this._saveConfig(config);
35
+ return state;
36
+ }
37
+
38
+ clientInformation() {
39
+ return { client_id: this.clientId };
40
+ }
41
+
42
+ _getConfig() {
43
+ const row = db.prepare('SELECT config FROM mcp_servers WHERE id = ?').get(this.serverId);
44
+ return row ? JSON.parse(row.config || '{}') : {};
45
+ }
46
+
47
+ _saveConfig(config) {
48
+ db.prepare('UPDATE mcp_servers SET config = ? WHERE id = ?')
49
+ .run(JSON.stringify(config), this.serverId);
50
+ }
51
+
52
+ tokens() {
53
+ return this._getConfig().auth?.tokens;
54
+ }
55
+
56
+ saveTokens(tokens) {
57
+ const config = this._getConfig();
58
+ config.auth = config.auth || {};
59
+ config.auth.tokens = tokens;
60
+ this._saveConfig(config);
61
+ }
62
+
63
+ redirectToAuthorization(authorizationUrl) {
64
+ throw new Error(`OAUTH_REDIRECT:${authorizationUrl.toString()}`);
65
+ }
66
+
67
+ saveCodeVerifier(codeVerifier) {
68
+ const config = this._getConfig();
69
+ config.auth = config.auth || {};
70
+ config.auth.codeVerifier = codeVerifier;
71
+ this._saveConfig(config);
72
+ }
73
+
74
+ codeVerifier() {
75
+ return this._getConfig().auth?.codeVerifier;
76
+ }
77
+ }
78
+
79
+ function consumeOAuthState(serverId, state) {
80
+ const row = db.prepare('SELECT config FROM mcp_servers WHERE id = ?').get(serverId);
81
+ if (!row) return false;
82
+
83
+ let config;
84
+ try {
85
+ config = JSON.parse(row.config || '{}');
86
+ } catch {
87
+ return false;
88
+ }
89
+
90
+ const expectedHash = String(config.auth?.oauthStateHash || '');
91
+ const createdAt = Date.parse(config.auth?.oauthStateCreatedAt || '');
92
+ if (
93
+ !expectedHash
94
+ || !Number.isFinite(createdAt)
95
+ || Date.now() - createdAt > OAUTH_STATE_TTL_MS
96
+ || createdAt > Date.now() + 60_000
97
+ ) {
98
+ return false;
99
+ }
100
+
101
+ const actualHash = hashOAuthState(state);
102
+ const expectedBuffer = Buffer.from(expectedHash, 'hex');
103
+ const actualBuffer = Buffer.from(actualHash, 'hex');
104
+ if (
105
+ expectedBuffer.length !== actualBuffer.length
106
+ || !crypto.timingSafeEqual(expectedBuffer, actualBuffer)
107
+ ) {
108
+ return false;
109
+ }
110
+
111
+ delete config.auth.oauthStateHash;
112
+ delete config.auth.oauthStateCreatedAt;
113
+ db.prepare('UPDATE mcp_servers SET config = ? WHERE id = ?')
114
+ .run(JSON.stringify(config), serverId);
115
+ return true;
116
+ }
117
+
118
+ function extractErrorMessage(err) {
119
+ const raw = err?.message || String(err || 'Unknown error');
120
+ if (raw.includes('<!doctype') || raw.includes('<html') || raw.includes('<!DOCTYPE')) {
121
+ const httpMatch = raw.match(/HTTP (\d+)/i);
122
+ return httpMatch
123
+ ? `Server returned HTTP ${httpMatch[1]} - the MCP endpoint may be down or misconfigured`
124
+ : 'Server returned an HTML error page - the MCP endpoint may be down or misconfigured';
125
+ }
126
+ if (err?.code === 'ECONNREFUSED' || raw.includes('ECONNREFUSED')) {
127
+ const addrMatch = raw.match(/connect ECONNREFUSED ([^\s,]+)/);
128
+ return addrMatch
129
+ ? `Connection refused at ${addrMatch[1]} - is the MCP server running?`
130
+ : 'Connection refused - the MCP server is not reachable';
131
+ }
132
+ return raw.split('\n')[0].trim();
133
+ }
134
+
135
+ function normalizeServerId(value) {
136
+ const text = String(value ?? '').trim();
137
+ if (!text) return value;
138
+ const numeric = Number(text);
139
+ return Number.isSafeInteger(numeric) && numeric > 0 ? numeric : value;
140
+ }
141
+
142
+ function buildTransportOptions(serverId) {
143
+ const row = db.prepare('SELECT config FROM mcp_servers WHERE id = ?').get(serverId);
144
+ const config = row ? JSON.parse(row.config || '{}') : {};
145
+ const auth = config.auth || {};
146
+ const transportOptions = {
147
+ requestInit: { headers: {} },
148
+ eventSourceInit: { headers: {} },
149
+ };
150
+
151
+ if (auth.type === 'bearer' && auth.token) {
152
+ const authorization = `Bearer ${auth.token}`;
153
+ transportOptions.requestInit.headers.Authorization = authorization;
154
+ transportOptions.eventSourceInit.headers.Authorization = authorization;
155
+ } else if (auth.type === 'oauth') {
156
+ transportOptions.authProvider = new DBAuthProvider(
157
+ serverId,
158
+ auth.clientId,
159
+ auth.authServerUrl,
160
+ );
161
+ }
162
+
163
+ return transportOptions;
164
+ }
165
+
166
+ module.exports = {
167
+ DBAuthProvider,
168
+ buildTransportOptions,
169
+ consumeOAuthState,
170
+ extractErrorMessage,
171
+ normalizeServerId,
172
+ };
@@ -0,0 +1,116 @@
1
+ 'use strict';
2
+
3
+ const db = require('../../db/database');
4
+ const { normalizeServerId } = require('./client_support');
5
+
6
+ function scheduleReconnect(manager, serverId, userId, options) {
7
+ serverId = normalizeServerId(serverId);
8
+ if (manager._isShuttingDown || manager._reconnectTimers.has(serverId)) return;
9
+ const server = manager.servers.get(serverId);
10
+ if (!server || server.status === 'auth_required') return;
11
+
12
+ const attempts = Math.max(1, manager._reconnectAttempts.get(serverId) || 1);
13
+ const delayMs = Math.min(
14
+ manager._reconnectDelayMs * (2 ** Math.min(attempts - 1, 20)),
15
+ manager._maxReconnectDelayMs,
16
+ );
17
+ server.nextRetryAt = new Date(Date.now() + delayMs).toISOString();
18
+ manager._setServerStatus(server, 'reconnecting');
19
+
20
+ const timer = setTimeout(async () => {
21
+ manager._reconnectTimers.delete(serverId);
22
+ if (manager._isShuttingDown) return;
23
+ const currentServer = manager.servers.get(serverId);
24
+ if (!currentServer || currentServer.status === 'running') return;
25
+
26
+ const row = db.prepare(
27
+ 'SELECT * FROM mcp_servers WHERE id = ? AND enabled = 1'
28
+ ).get(serverId);
29
+ if (!row) {
30
+ await manager.stopServer(serverId);
31
+ return;
32
+ }
33
+
34
+ try {
35
+ await manager.startServer(serverId, row.command, row.name, userId, {
36
+ ...options,
37
+ _isReconnect: true,
38
+ });
39
+ console.log(`[MCP] Reconnected to ${row.name}`);
40
+ } catch {
41
+ const failedServer = manager.servers.get(serverId);
42
+ if (
43
+ failedServer
44
+ && failedServer.status !== 'auth_required'
45
+ && !manager._isShuttingDown
46
+ ) {
47
+ scheduleReconnect(manager, serverId, userId, options);
48
+ }
49
+ }
50
+ }, delayMs);
51
+ timer.unref?.();
52
+
53
+ manager._reconnectTimers.set(serverId, timer);
54
+ }
55
+
56
+ async function loadFromDB(manager, userId) {
57
+ const servers = db.prepare(
58
+ 'SELECT * FROM mcp_servers WHERE user_id = ? AND enabled = 1'
59
+ ).all(userId);
60
+ const results = [];
61
+
62
+ for (const serverRow of servers) {
63
+ try {
64
+ await manager.startServer(
65
+ serverRow.id,
66
+ serverRow.command,
67
+ serverRow.name,
68
+ userId,
69
+ { agentId: serverRow.agent_id },
70
+ );
71
+ results.push({
72
+ id: serverRow.id,
73
+ name: serverRow.name,
74
+ status: 'running',
75
+ });
76
+ } catch (err) {
77
+ const message = err.message;
78
+ console.error(`[MCP] Failed to start "${serverRow.name}":`, message);
79
+ const server = manager.servers.get(serverRow.id);
80
+ if (server && server.status !== 'auth_required') {
81
+ scheduleReconnect(
82
+ manager,
83
+ serverRow.id,
84
+ userId,
85
+ { agentId: serverRow.agent_id },
86
+ );
87
+ }
88
+ results.push({
89
+ id: serverRow.id,
90
+ name: serverRow.name,
91
+ status: server?.status || 'error',
92
+ error: message,
93
+ });
94
+ }
95
+ }
96
+
97
+ return results;
98
+ }
99
+
100
+ async function shutdown(manager) {
101
+ manager._isShuttingDown = true;
102
+ for (const timer of manager._reconnectTimers.values()) {
103
+ clearTimeout(timer);
104
+ }
105
+ manager._reconnectTimers.clear();
106
+
107
+ const stops = Array.from(manager.servers.keys())
108
+ .map((serverId) => manager.stopServer(serverId));
109
+ await Promise.allSettled(stops);
110
+ }
111
+
112
+ module.exports = {
113
+ loadFromDB,
114
+ scheduleReconnect,
115
+ shutdown,
116
+ };
@@ -0,0 +1,123 @@
1
+ 'use strict';
2
+
3
+ const { extractErrorMessage, normalizeServerId } = require('./client_support');
4
+
5
+ async function listTools(manager, serverId, userId = null) {
6
+ serverId = normalizeServerId(serverId);
7
+ const server = manager._getOwnedServer(serverId, userId);
8
+ if (!server || server.status !== 'running') {
9
+ throw new Error(`Server ${serverId} not running`);
10
+ }
11
+
12
+ try {
13
+ const response = await manager._runWithTimeout(
14
+ () => server.client.listTools(),
15
+ `Discovering tools from MCP server "${server.name}"`,
16
+ );
17
+ server.tools = response.tools || [];
18
+ server.consecutiveFails = 0;
19
+ server.lastError = null;
20
+ manager._reconnectAttempts.delete(serverId);
21
+ return server.tools;
22
+ } catch (err) {
23
+ const message = manager._recordFailure(server, err);
24
+ manager._scheduleReconnect(serverId, server.userId, { agentId: server.agentId });
25
+ throw new Error(`Failed to list tools for MCP server "${server.name}": ${message}`);
26
+ }
27
+ }
28
+
29
+ async function callTool(manager, serverId, toolName, args = {}, userId = null) {
30
+ serverId = normalizeServerId(serverId);
31
+ const server = manager._getOwnedServer(serverId, userId);
32
+ if (!server) throw new Error(`Server ${serverId} not found`);
33
+
34
+ if (server.status !== 'running') {
35
+ const hint = server.lastError ? ` (${server.lastError})` : '';
36
+ throw new Error(`MCP server "${server.name}" is not available${hint}`);
37
+ }
38
+
39
+ try {
40
+ const result = await manager._runWithTimeout(
41
+ () => server.client.callTool({ name: toolName, arguments: args }),
42
+ `Calling MCP tool "${toolName}" on "${server.name}"`,
43
+ );
44
+ server.consecutiveFails = 0;
45
+ server.lastError = null;
46
+ manager._reconnectAttempts.delete(serverId);
47
+ return result;
48
+ } catch (err) {
49
+ const message = extractErrorMessage(err);
50
+ const consecutiveFails = (server.consecutiveFails || 0) + 1;
51
+ server.consecutiveFails = consecutiveFails;
52
+ server.lastError = message;
53
+
54
+ if (consecutiveFails >= manager._toolFailureThreshold) {
55
+ manager._reconnectAttempts.set(serverId, consecutiveFails);
56
+ server.nextRetryAt = null;
57
+ manager._setServerStatus(server, 'error');
58
+ manager._scheduleReconnect(serverId, server.userId, { agentId: server.agentId });
59
+ }
60
+
61
+ throw new Error(`MCP tool "${toolName}" failed: ${message}`);
62
+ }
63
+ }
64
+
65
+ async function callToolByName(manager, fullName, args = {}, userId = null, options = {}) {
66
+ for (const [serverId, server] of manager.servers) {
67
+ if (userId != null && server.userId !== userId) continue;
68
+ if (options.agentId && server.agentId && server.agentId !== options.agentId) continue;
69
+ const prefix = `mcp_${server.slug}_`;
70
+ if (!fullName.startsWith(prefix)) continue;
71
+
72
+ const originalName = fullName.substring(prefix.length);
73
+ return callTool(manager, serverId, originalName, args, userId);
74
+ }
75
+ return null;
76
+ }
77
+
78
+ function getAllTools(manager, userId = null, options = {}) {
79
+ const allTools = [];
80
+ for (const [serverId, server] of manager.servers) {
81
+ if (userId != null && server.userId !== userId) continue;
82
+ if (options.agentId && server.agentId && server.agentId !== options.agentId) continue;
83
+ if (server.status !== 'running') continue;
84
+ for (const tool of server.tools) {
85
+ allTools.push({
86
+ ...tool,
87
+ name: `mcp_${server.slug}_${tool.name}`,
88
+ originalName: tool.name,
89
+ parameters: tool.inputSchema || tool.parameters,
90
+ serverId,
91
+ });
92
+ }
93
+ }
94
+ return allTools;
95
+ }
96
+
97
+ function getStatus(manager, userId = null, options = {}) {
98
+ const statuses = {};
99
+ const agentId = options.agentId || options.agent_id || null;
100
+ for (const [serverId, server] of manager.servers) {
101
+ if (userId != null && server.userId !== userId) continue;
102
+ if (agentId && server.agentId && server.agentId !== agentId) continue;
103
+ statuses[serverId] = {
104
+ status: server.status,
105
+ command: server.url,
106
+ args: [],
107
+ toolCount: server.tools.length,
108
+ error: server.lastError || null,
109
+ consecutiveFails: server.consecutiveFails || 0,
110
+ nextRetryAt: server.nextRetryAt || null,
111
+ serverInfo: null,
112
+ };
113
+ }
114
+ return statuses;
115
+ }
116
+
117
+ module.exports = {
118
+ callTool,
119
+ callToolByName,
120
+ getAllTools,
121
+ getStatus,
122
+ listTools,
123
+ };