funolio-agent 1.0.47 → 1.0.49

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 (173) hide show
  1. package/dist/agent-config.d.ts +9 -1
  2. package/dist/agent-config.d.ts.map +1 -1
  3. package/dist/agent-config.js +4 -1
  4. package/dist/agent-config.js.map +1 -1
  5. package/dist/auth/auto-detect.d.ts +1 -0
  6. package/dist/auth/auto-detect.d.ts.map +1 -1
  7. package/dist/auth/auto-detect.js +16 -13
  8. package/dist/auth/auto-detect.js.map +1 -1
  9. package/dist/auto-organizer.d.ts.map +1 -1
  10. package/dist/auto-organizer.js +4 -3
  11. package/dist/auto-organizer.js.map +1 -1
  12. package/dist/backfill.d.ts.map +1 -1
  13. package/dist/backfill.js +3 -2
  14. package/dist/backfill.js.map +1 -1
  15. package/dist/bot-manager.d.ts +8 -23
  16. package/dist/bot-manager.d.ts.map +1 -1
  17. package/dist/bot-manager.js +61 -388
  18. package/dist/bot-manager.js.map +1 -1
  19. package/dist/clerk-model.d.ts +5 -1
  20. package/dist/clerk-model.d.ts.map +1 -1
  21. package/dist/clerk-model.js +40 -28
  22. package/dist/clerk-model.js.map +1 -1
  23. package/dist/cli-session-epoch.d.ts +10 -0
  24. package/dist/cli-session-epoch.d.ts.map +1 -0
  25. package/dist/cli-session-epoch.js +61 -0
  26. package/dist/cli-session-epoch.js.map +1 -0
  27. package/dist/commands/init.d.ts.map +1 -1
  28. package/dist/commands/init.js +30 -1
  29. package/dist/commands/init.js.map +1 -1
  30. package/dist/commands/pool.js +1 -1
  31. package/dist/commands/pool.js.map +1 -1
  32. package/dist/commands/setup.d.ts +37 -0
  33. package/dist/commands/setup.d.ts.map +1 -1
  34. package/dist/commands/setup.js +154 -43
  35. package/dist/commands/setup.js.map +1 -1
  36. package/dist/commands/start.d.ts.map +1 -1
  37. package/dist/commands/start.js +195 -164
  38. package/dist/commands/start.js.map +1 -1
  39. package/dist/config-cleanup.d.ts.map +1 -1
  40. package/dist/config-cleanup.js +2 -1
  41. package/dist/config-cleanup.js.map +1 -1
  42. package/dist/config.d.ts +6 -9
  43. package/dist/config.d.ts.map +1 -1
  44. package/dist/config.js +8 -30
  45. package/dist/config.js.map +1 -1
  46. package/dist/context-window.d.ts +33 -5
  47. package/dist/context-window.d.ts.map +1 -1
  48. package/dist/context-window.js +121 -20
  49. package/dist/context-window.js.map +1 -1
  50. package/dist/eval/orchestrator-front-door-replay.js +1 -1
  51. package/dist/eval/orchestrator-front-door-replay.js.map +1 -1
  52. package/dist/eval/policy-detection-replay.js +1 -1
  53. package/dist/eval/policy-detection-replay.js.map +1 -1
  54. package/dist/integration-tokens.d.ts +1 -6
  55. package/dist/integration-tokens.d.ts.map +1 -1
  56. package/dist/integration-tokens.js +38 -40
  57. package/dist/integration-tokens.js.map +1 -1
  58. package/dist/local-cli-pty-manager.d.ts +50 -0
  59. package/dist/local-cli-pty-manager.d.ts.map +1 -0
  60. package/dist/local-cli-pty-manager.js +645 -0
  61. package/dist/local-cli-pty-manager.js.map +1 -0
  62. package/dist/local-data.d.ts +30 -0
  63. package/dist/local-data.d.ts.map +1 -1
  64. package/dist/local-data.js +56 -1
  65. package/dist/local-data.js.map +1 -1
  66. package/dist/local-db.d.ts.map +1 -1
  67. package/dist/local-db.js +54 -1
  68. package/dist/local-db.js.map +1 -1
  69. package/dist/local-funnel.d.ts.map +1 -1
  70. package/dist/local-funnel.js +3 -2
  71. package/dist/local-funnel.js.map +1 -1
  72. package/dist/local-memory-search.d.ts +1 -0
  73. package/dist/local-memory-search.d.ts.map +1 -1
  74. package/dist/local-memory-search.js +101 -18
  75. package/dist/local-memory-search.js.map +1 -1
  76. package/dist/local-server.d.ts +0 -16
  77. package/dist/local-server.d.ts.map +1 -1
  78. package/dist/local-server.js +339 -287
  79. package/dist/local-server.js.map +1 -1
  80. package/dist/mcp/bridge-server.d.ts.map +1 -1
  81. package/dist/mcp/bridge-server.js +2 -1
  82. package/dist/mcp/bridge-server.js.map +1 -1
  83. package/dist/mcp/local-memory-server.d.ts +5 -0
  84. package/dist/mcp/local-memory-server.d.ts.map +1 -1
  85. package/dist/mcp/local-memory-server.js +15 -2
  86. package/dist/mcp/local-memory-server.js.map +1 -1
  87. package/dist/mcp/manager.d.ts +3 -22
  88. package/dist/mcp/manager.d.ts.map +1 -1
  89. package/dist/mcp/manager.js +66 -388
  90. package/dist/mcp/manager.js.map +1 -1
  91. package/dist/memory-extraction.d.ts +2 -0
  92. package/dist/memory-extraction.d.ts.map +1 -1
  93. package/dist/memory-extraction.js +3 -1
  94. package/dist/memory-extraction.js.map +1 -1
  95. package/dist/message-loop.d.ts +10 -6
  96. package/dist/message-loop.d.ts.map +1 -1
  97. package/dist/message-loop.js +241 -540
  98. package/dist/message-loop.js.map +1 -1
  99. package/dist/mqtt-client.d.ts +2 -31
  100. package/dist/mqtt-client.d.ts.map +1 -1
  101. package/dist/mqtt-client.js +2 -2
  102. package/dist/mqtt-client.js.map +1 -1
  103. package/dist/oauth.d.ts +6 -0
  104. package/dist/oauth.d.ts.map +1 -1
  105. package/dist/oauth.js +91 -0
  106. package/dist/oauth.js.map +1 -1
  107. package/dist/orchestration/front-door-policy.d.ts +5 -2
  108. package/dist/orchestration/front-door-policy.d.ts.map +1 -1
  109. package/dist/orchestration/front-door-policy.js +25 -28
  110. package/dist/orchestration/front-door-policy.js.map +1 -1
  111. package/dist/orchestration/orchestrator-blocked-prompt.js +1 -1
  112. package/dist/orchestration/orchestrator-final-response-prompt.js +1 -1
  113. package/dist/orchestration/orchestrator-operating-prompt.d.ts +11 -0
  114. package/dist/orchestration/orchestrator-operating-prompt.d.ts.map +1 -1
  115. package/dist/orchestration/orchestrator-operating-prompt.js +67 -44
  116. package/dist/orchestration/orchestrator-operating-prompt.js.map +1 -1
  117. package/dist/orchestration/worker-operating-prompt.js +3 -3
  118. package/dist/orchestration/worker-operating-prompt.js.map +1 -1
  119. package/dist/orchestrator.d.ts +5 -1
  120. package/dist/orchestrator.d.ts.map +1 -1
  121. package/dist/orchestrator.js +141 -81
  122. package/dist/orchestrator.js.map +1 -1
  123. package/dist/prompt-template.js +3 -3
  124. package/dist/prompt-template.js.map +1 -1
  125. package/dist/providers/claude-cli-prompt.d.ts.map +1 -1
  126. package/dist/providers/claude-cli-prompt.js +22 -6
  127. package/dist/providers/claude-cli-prompt.js.map +1 -1
  128. package/dist/providers/claude-cli.d.ts.map +1 -1
  129. package/dist/providers/claude-cli.js +20 -2
  130. package/dist/providers/claude-cli.js.map +1 -1
  131. package/dist/providers/codex-cli.d.ts.map +1 -1
  132. package/dist/providers/codex-cli.js +71 -16
  133. package/dist/providers/codex-cli.js.map +1 -1
  134. package/dist/providers/index.d.ts +11 -0
  135. package/dist/providers/index.d.ts.map +1 -1
  136. package/dist/providers/index.js.map +1 -1
  137. package/dist/runtime-context.d.ts +10 -0
  138. package/dist/runtime-context.d.ts.map +1 -0
  139. package/dist/runtime-context.js +30 -0
  140. package/dist/runtime-context.js.map +1 -0
  141. package/dist/subagent/queue.d.ts.map +1 -1
  142. package/dist/subagent/queue.js +1 -0
  143. package/dist/subagent/queue.js.map +1 -1
  144. package/dist/summarization-pipeline.d.ts +1 -0
  145. package/dist/summarization-pipeline.d.ts.map +1 -1
  146. package/dist/summarization-pipeline.js +94 -25
  147. package/dist/summarization-pipeline.js.map +1 -1
  148. package/dist/tool-permissions.d.ts +2 -0
  149. package/dist/tool-permissions.d.ts.map +1 -0
  150. package/dist/tool-permissions.js +25 -0
  151. package/dist/tool-permissions.js.map +1 -0
  152. package/dist/tools/index.d.ts +7 -8
  153. package/dist/tools/index.d.ts.map +1 -1
  154. package/dist/tools/index.js +70 -60
  155. package/dist/tools/index.js.map +1 -1
  156. package/dist/tools/search-memory.d.ts.map +1 -1
  157. package/dist/tools/search-memory.js +9 -3
  158. package/dist/tools/search-memory.js.map +1 -1
  159. package/dist/tools/spawn-subagent.d.ts.map +1 -1
  160. package/dist/tools/spawn-subagent.js +1 -0
  161. package/dist/tools/spawn-subagent.js.map +1 -1
  162. package/dist/types.d.ts +3 -0
  163. package/dist/types.d.ts.map +1 -1
  164. package/dist/types.js +0 -3
  165. package/dist/types.js.map +1 -1
  166. package/dist/wizard-support.d.ts.map +1 -1
  167. package/dist/wizard-support.js +8 -6
  168. package/dist/wizard-support.js.map +1 -1
  169. package/dist/workflow-engine.d.ts +6 -2
  170. package/dist/workflow-engine.d.ts.map +1 -1
  171. package/dist/workflow-engine.js +254 -77
  172. package/dist/workflow-engine.js.map +1 -1
  173. package/package.json +2 -1
@@ -40,20 +40,22 @@ var __importStar = (this && this.__importStar) || (function () {
40
40
  return result;
41
41
  };
42
42
  })();
43
- var __importDefault = (this && this.__importDefault) || function (mod) {
44
- return (mod && mod.__esModule) ? mod : { "default": mod };
45
- };
46
43
  Object.defineProperty(exports, "__esModule", { value: true });
47
44
  exports.MCPManager = void 0;
48
45
  const child_process_1 = require("child_process");
49
46
  const fs = __importStar(require("fs"));
50
47
  const path = __importStar(require("path"));
51
48
  const events_1 = require("events");
52
- const chalk_1 = __importDefault(require("chalk"));
53
49
  const config_1 = require("../config");
54
50
  const registry_1 = require("./registry");
55
51
  const stdio_protocol_1 = require("./stdio-protocol");
56
- const tool_grouping_1 = require("./tool-grouping");
52
+ const GOOGLE_INTEGRATION_SERVER_IDS = new Set([
53
+ 'google-workspace',
54
+ 'google-drive',
55
+ 'google-calendar',
56
+ 'google-chat',
57
+ 'gmail',
58
+ ]);
57
59
  /**
58
60
  * MCP Manager — singleton per agent instance.
59
61
  */
@@ -140,104 +142,16 @@ class MCPManager extends events_1.EventEmitter {
140
142
  ? ['-y', config.npmPackage, ...config.args]
141
143
  : ['run', config.pipPackage, ...config.args];
142
144
  // Inject integration tokens for known MCP servers (e.g. google-drive → Google OAuth tokens)
143
- const GOOGLE_MCP_IDS = new Set(['google-workspace', 'google-drive', 'google-calendar', 'gmail']);
144
145
  let integrationEnv = {};
145
146
  try {
146
- const { getIntegrationEnvVars } = require('../integration-tokens');
147
- if (GOOGLE_MCP_IDS.has(config.id)) {
147
+ const { getIntegrationEnvVars, writeGoogleWorkspaceFiles } = require('../integration-tokens');
148
+ if (GOOGLE_INTEGRATION_SERVER_IDS.has(config.id)) {
149
+ writeGoogleWorkspaceFiles();
148
150
  integrationEnv = getIntegrationEnvVars('google');
149
151
  }
150
152
  }
151
153
  catch { }
152
- // Merge all env var sources: process.env < integration-tokens.json < marketplace envVars
153
- const mergedEnv = { ...process.env, ...integrationEnv, ...config.envVars };
154
- // For Google MCP servers that use file-based auth (e.g. @piotr-agier/google-drive-mcp),
155
- // write the credentials and tokens to the expected file locations. These packages read
156
- // from gcp-oauth.keys.json + ~/.config/google-drive-mcp/tokens.json rather than env vars.
157
- if (GOOGLE_MCP_IDS.has(config.id)) {
158
- const clientId = mergedEnv.GOOGLE_CLIENT_ID;
159
- const clientSecret = mergedEnv.GOOGLE_CLIENT_SECRET;
160
- const refreshToken = mergedEnv.GOOGLE_REFRESH_TOKEN;
161
- if (clientId && clientSecret && refreshToken) {
162
- try {
163
- const os = require('os');
164
- const configDir = path.join(os.homedir(), '.config', 'google-drive-mcp');
165
- if (!fs.existsSync(configDir)) {
166
- fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
167
- }
168
- // Write credentials file (gcp-oauth.keys.json)
169
- const keysFile = path.join(configDir, 'gcp-oauth.keys.json');
170
- fs.writeFileSync(keysFile, JSON.stringify({
171
- installed: {
172
- client_id: clientId,
173
- client_secret: clientSecret,
174
- redirect_uris: ['http://localhost:3000/oauth2callback'],
175
- },
176
- }, null, 2), { mode: 0o600 });
177
- // Write tokens file — preserve existing valid access_token but
178
- // always ensure scopes and refresh_token are up to date.
179
- //
180
- // The configured scopes are the source of truth. The tokens.json
181
- // scope field is used by the MCP for internal checks (authGetStatus),
182
- // so it must always match the configured scopes — not whatever the
183
- // token file happened to have from a previous OAuth consent.
184
- const tokensFile = path.join(configDir, 'tokens.json');
185
- const incomingAccessToken = mergedEnv.GOOGLE_ACCESS_TOKEN;
186
- const CONFIGURED_SCOPES = 'https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/gmail.modify https://www.googleapis.com/auth/chat.spaces https://www.googleapis.com/auth/chat.messages';
187
- let tokenPayload = {
188
- access_token: incomingAccessToken || 'needs-refresh',
189
- refresh_token: refreshToken,
190
- scope: CONFIGURED_SCOPES,
191
- token_type: 'Bearer',
192
- expiry_date: incomingAccessToken ? (Date.now() + 3600 * 1000) : (Date.now() - 1000),
193
- };
194
- // If we have no server-side token and an existing file has a real
195
- // access_token, preserve it — but always update scope and refresh_token
196
- // to match the current configuration.
197
- if (!incomingAccessToken && fs.existsSync(tokensFile)) {
198
- try {
199
- const existing = JSON.parse(fs.readFileSync(tokensFile, 'utf-8'));
200
- if (existing.access_token && existing.access_token !== 'needs-refresh' && existing.refresh_token) {
201
- tokenPayload = {
202
- ...existing,
203
- refresh_token: refreshToken, // Always use configured refresh token
204
- scope: CONFIGURED_SCOPES, // Always use configured scopes
205
- token_type: 'Bearer',
206
- };
207
- console.log(chalk_1.default.gray(` ℹ Preserving existing Google access_token, updated scope & refresh_token`));
208
- }
209
- }
210
- catch {
211
- // Can't parse existing file — use fresh payload
212
- }
213
- }
214
- fs.writeFileSync(tokensFile, JSON.stringify(tokenPayload, null, 2), { mode: 0o600 });
215
- // Also set the env var so the package can find its config
216
- mergedEnv.GOOGLE_DRIVE_MCP_TOKEN_PATH = tokensFile;
217
- mergedEnv.GOOGLE_CLIENT_SECRET_PATH = keysFile;
218
- // Gmail MCP (@gongrzhe/server-gmail-autoauth-mcp) reads from ~/.gmail-mcp/
219
- // instead of ~/.config/google-drive-mcp/, so duplicate the files there.
220
- if (config.id === 'gmail') {
221
- const gmailDir = path.join(os.homedir(), '.gmail-mcp');
222
- if (!fs.existsSync(gmailDir)) {
223
- fs.mkdirSync(gmailDir, { recursive: true, mode: 0o700 });
224
- }
225
- fs.copyFileSync(keysFile, path.join(gmailDir, 'gcp-oauth.keys.json'));
226
- fs.copyFileSync(tokensFile, path.join(gmailDir, 'tokens.json'));
227
- console.log(chalk_1.default.green(` ✓ Also wrote Gmail credentials to ${gmailDir}`));
228
- }
229
- console.log(chalk_1.default.green(` ✓ Wrote Google credentials for ${config.id} to ${configDir}`));
230
- }
231
- catch (err) {
232
- console.error(chalk_1.default.red(` ✗ Failed to write Google credential files: ${err.message}`));
233
- }
234
- }
235
- else {
236
- console.warn(chalk_1.default.yellow(` ⚠ Missing Google credentials for ${config.id} (need CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN)`));
237
- console.warn(chalk_1.default.yellow(` Install via the Funolio marketplace and provide your Google OAuth credentials.`));
238
- }
239
- }
240
- const env = mergedEnv;
154
+ const env = { ...process.env, ...integrationEnv, ...config.envVars };
241
155
  const child = (0, child_process_1.spawn)(cmd, args, {
242
156
  stdio: ['pipe', 'pipe', 'pipe'],
243
157
  env,
@@ -250,7 +164,6 @@ class MCPManager extends events_1.EventEmitter {
250
164
  ready: false,
251
165
  requestId: 0,
252
166
  pendingRequests: new Map(),
253
- stderrBuffer: [],
254
167
  };
255
168
  this.servers.set(config.id, instance);
256
169
  const reader = new stdio_protocol_1.McpStdioMessageReader((msg) => {
@@ -268,19 +181,11 @@ class MCPManager extends events_1.EventEmitter {
268
181
  child.stdout?.on('data', (chunk) => {
269
182
  reader.feed(chunk);
270
183
  });
271
- // Capture recent stderr for error enrichment (ring buffer, last 5 messages)
272
- const stderrBuffer = [];
273
- const MAX_STDERR_LINES = 5;
274
184
  child.stderr?.on('data', (data) => {
275
185
  const msg = data.toString().trim();
276
- if (msg) {
186
+ if (msg)
277
187
  console.error(`[MCP:${config.id}] ${msg}`);
278
- stderrBuffer.push(msg);
279
- if (stderrBuffer.length > MAX_STDERR_LINES)
280
- stderrBuffer.shift();
281
- }
282
188
  });
283
- instance.stderrBuffer = stderrBuffer;
284
189
  child.on('exit', (code) => {
285
190
  const error = new Error(`MCP server ${config.id} exited with code ${code}`);
286
191
  for (const [requestId, pending] of instance.pendingRequests.entries()) {
@@ -291,36 +196,6 @@ class MCPManager extends events_1.EventEmitter {
291
196
  this.servers.delete(config.id);
292
197
  this.rebuildToolList();
293
198
  this.emit('server-stopped', config.id);
294
- // Fix 2: MCP crash recovery — attempt one restart for always-on servers
295
- // Track restart attempts to prevent infinite crash loops
296
- if (config.alwaysOn && entry) {
297
- const key = `restart_${config.id}`;
298
- const now = Date.now();
299
- const restartState = this[key] || { count: 0, firstAttempt: now };
300
- restartState.count++;
301
- this[key] = restartState;
302
- // Max 3 restart attempts within 5 minutes, then give up
303
- const MAX_RESTARTS = 3;
304
- const RESTART_WINDOW_MS = 5 * 60 * 1000;
305
- if (now - restartState.firstAttempt > RESTART_WINDOW_MS) {
306
- restartState.count = 1;
307
- restartState.firstAttempt = now;
308
- }
309
- if (restartState.count <= MAX_RESTARTS) {
310
- console.log(`[MCP] Attempting auto-restart of ${config.id} (${restartState.count}/${MAX_RESTARTS})...`);
311
- this.launchServer(config, entry).then((tools) => {
312
- console.log(`[MCP] ✓ Auto-restarted ${config.id} — ${tools.length} tools recovered`);
313
- restartState.count = 0; // Reset on success
314
- this.emit('server-restarted', config.id, tools);
315
- }).catch((err) => {
316
- console.error(`[MCP] ✗ Auto-restart of ${config.id} failed: ${err.message}`);
317
- this.emit('server-restart-failed', config.id, err.message);
318
- });
319
- }
320
- else {
321
- console.error(`[MCP] ✗ ${config.id} crashed ${restartState.count} times in ${Math.round((now - restartState.firstAttempt) / 1000)}s — giving up on auto-restart`);
322
- }
323
- }
324
199
  });
325
200
  // MCP handshake: initialize
326
201
  try {
@@ -339,15 +214,10 @@ class MCPManager extends events_1.EventEmitter {
339
214
  inputSchema: t.inputSchema || {},
340
215
  serverId: config.id,
341
216
  }));
342
- // Apply smart tool grouping for servers with many tools
343
- const groupedTools = (0, tool_grouping_1.groupMcpTools)(tools, config.id, entry.name);
344
- if (groupedTools.length < tools.length) {
345
- console.log(`[MCP] ${entry.name}: grouped ${tools.length} tools into ${groupedTools.length} meta-tool(s)`);
346
- }
347
- instance.tools = groupedTools;
217
+ instance.tools = tools;
348
218
  instance.ready = true;
349
219
  this.rebuildToolList();
350
- console.log(`[MCP] ${entry.name} launched — ${tools.length} tools discovered (${groupedTools.length} after grouping)`);
220
+ console.log(`[MCP] ${entry.name} launched — ${tools.length} tools discovered`);
351
221
  this.emit('server-ready', config.id, tools);
352
222
  return tools;
353
223
  }
@@ -365,14 +235,13 @@ class MCPManager extends events_1.EventEmitter {
365
235
  const msg = (0, stdio_protocol_1.encodeMcpStdioMessage)({ jsonrpc: '2.0', id, method, params });
366
236
  instance.pendingRequests.set(id, { resolve, reject });
367
237
  instance.process.stdin.write(msg);
368
- // Timeout: 120s for initialize (first launch may download packages), 30s for other requests
369
- const timeoutMs = method === 'initialize' ? 120_000 : 30_000;
238
+ // Timeout after 30s
370
239
  setTimeout(() => {
371
240
  if (instance.pendingRequests.has(id)) {
372
241
  instance.pendingRequests.delete(id);
373
- reject(new Error(`MCP request ${method} timed out after ${timeoutMs / 1000}s`));
242
+ reject(new Error(`MCP request ${method} timed out`));
374
243
  }
375
- }, timeoutMs);
244
+ }, 30_000);
376
245
  });
377
246
  }
378
247
  /** Send a JSON-RPC notification (no response expected) */
@@ -380,117 +249,8 @@ class MCPManager extends events_1.EventEmitter {
380
249
  const msg = (0, stdio_protocol_1.encodeMcpStdioMessage)({ jsonrpc: '2.0', method, params });
381
250
  instance.process.stdin.write(msg);
382
251
  }
383
- /** Enrich MCP error results with actionable hints */
384
- enrichErrorResult(result, toolName, args) {
385
- if (!result?.isError)
386
- return result;
387
- const contentText = result.content
388
- ? result.content.map((c) => c.text || JSON.stringify(c)).join('\n')
389
- : '';
390
- // Detect unhelpful errors and add context
391
- const isUnhelpful = !contentText || contentText === 'Unknown error' || contentText.length < 20;
392
- // Check for file-type-specific hints
393
- const fileId = (args.fileId || args.file_id || '');
394
- const mimeType = (args.mimeType || args.mime_type || '');
395
- const actionName = toolName.replace(/^mcp_[^_]+_/, '');
396
- let hint = '';
397
- if (actionName === 'downloadFile' || actionName === 'readSpreadsheet' || actionName === 'readDocument') {
398
- const uploadedFormats = ['.xlsx', '.docx', '.pptx', '.csv', '.pdf'];
399
- const uploadedMimes = ['application/vnd.openxmlformats', 'application/msword', 'application/pdf'];
400
- const isUploaded = uploadedFormats.some(f => mimeType.includes(f)) ||
401
- uploadedMimes.some(m => mimeType.includes(m));
402
- if (isUploaded && actionName === 'readSpreadsheet') {
403
- hint = '\nHINT: This file appears to be an uploaded .xlsx file, NOT a native Google Sheet. ' +
404
- 'Use downloadFile to save it locally, then read with run_command (e.g., python with openpyxl).';
405
- }
406
- else if (isUploaded && actionName === 'downloadFile' && isUnhelpful) {
407
- hint = '\nHINT: downloadFile failed. Try using copyFile with convert=true to convert to native Google format first, then use the appropriate read tool.';
408
- }
409
- }
410
- if (hint && result.content) {
411
- result.content = [
412
- ...result.content,
413
- { type: 'text', text: hint },
414
- ];
415
- }
416
- return result;
417
- }
418
- /** Call a tool on an MCP server (handles both regular and meta-tools) */
252
+ /** Call a tool on an MCP server */
419
253
  async callTool(toolName, args) {
420
- // Handle meta-tool calls: resolve to the real tool name and args
421
- if ((0, tool_grouping_1.isMetaTool)(toolName)) {
422
- const action = args.action;
423
- // LLMs sometimes put params at the top level instead of inside "params".
424
- // If "params" exists, use it. Otherwise, extract everything except "action"
425
- // as the params object — this handles both { action, params: {...} } and
426
- // { action, localPath, name, ... } formats.
427
- let params;
428
- if (args.params && typeof args.params === 'object') {
429
- params = args.params;
430
- }
431
- else {
432
- const { action: _a, ...rest } = args;
433
- params = Object.keys(rest).length > 0 ? rest : {};
434
- }
435
- if (!action)
436
- throw new Error(`Meta-tool "${toolName}" requires an "action" parameter`);
437
- const { originalToolName, originalArgs } = (0, tool_grouping_1.resolveMetaTool)(toolName, action, params);
438
- // Find the server for this meta-tool
439
- const metaTool = this.allDiscoveredTools.find((t) => t.name === toolName);
440
- if (!metaTool)
441
- throw new Error(`Meta-tool not found: ${toolName}`);
442
- const instance = this.servers.get(metaTool.serverId);
443
- if (!instance || !instance.ready)
444
- throw new Error(`MCP server ${metaTool.serverId} not running`);
445
- // Strip prefix to get the raw MCP tool name
446
- const rawName = originalToolName.replace(`mcp_${metaTool.serverId}_`, '');
447
- // Auto-create parent directories for downloadFile to avoid "Parent directory does not exist"
448
- if (rawName === 'downloadFile' || rawName === 'download_file') {
449
- const localPath = originalArgs.localPath || originalArgs.local_path;
450
- if (typeof localPath === 'string') {
451
- const parentDir = path.dirname(localPath);
452
- if (!fs.existsSync(parentDir)) {
453
- fs.mkdirSync(parentDir, { recursive: true });
454
- }
455
- }
456
- }
457
- try {
458
- const result = await this.sendRequest(instance, 'tools/call', { name: rawName, arguments: originalArgs });
459
- // Bug 4: Enrich meta-tool errors with stderr
460
- if (result?.isError) {
461
- const recentStderr = instance.stderrBuffer?.slice(-3)
462
- .filter((s) => s.includes('Error') || s.includes('error') || s.includes('fail') || s.includes('not exist') || s.includes('Invalid'))
463
- .join(' | ');
464
- if (recentStderr) {
465
- const existingText = result.content?.map((c) => c.text || '').join(' ') || '';
466
- result.content = [{ type: 'text', text: `${existingText || 'Unknown error'}. Server details: ${recentStderr}` }];
467
- }
468
- }
469
- // Bug 1: Validate downloadFile results — detect Google sign-in pages
470
- if ((rawName === 'downloadFile' || rawName === 'download_file') && !result?.isError) {
471
- const localPath = (originalArgs.localPath || originalArgs.local_path);
472
- if (localPath && fs.existsSync(localPath)) {
473
- const header = Buffer.alloc(500);
474
- const fd = fs.openSync(localPath, 'r');
475
- fs.readSync(fd, header, 0, 500, 0);
476
- fs.closeSync(fd);
477
- const headerStr = header.toString('utf8').toLowerCase();
478
- if (headerStr.includes('accounts.google.com') ||
479
- (headerStr.startsWith('<!doctype html>') && !localPath.endsWith('.html'))) {
480
- fs.unlinkSync(localPath);
481
- return {
482
- content: [{ type: 'text', text: 'ERROR: Download failed — received Google sign-in page instead of actual file content. The OAuth token may have expired. The file was NOT saved. Try using run_command to download via curl with a fresh token, or ask the user to re-authenticate Google Workspace.' }],
483
- isError: true,
484
- };
485
- }
486
- }
487
- }
488
- return this.enrichErrorResult(result, originalToolName, originalArgs);
489
- }
490
- catch (err) {
491
- throw new Error(`MCP tool ${rawName} failed: ${err.message}${err.stack ? '\n' + err.stack.split('\n').slice(0, 3).join('\n') : ''}`);
492
- }
493
- }
494
254
  const tool = this.allDiscoveredTools.find((t) => t.name === toolName);
495
255
  if (!tool)
496
256
  throw new Error(`MCP tool not found: ${toolName}`);
@@ -499,45 +259,11 @@ class MCPManager extends events_1.EventEmitter {
499
259
  throw new Error(`MCP server ${tool.serverId} not running`);
500
260
  // Strip the mcp_{serverId}_ prefix to get the original tool name
501
261
  const originalName = toolName.replace(`mcp_${tool.serverId}_`, '');
502
- // Auto-create parent directories for downloadFile to avoid "Parent directory does not exist"
503
- if (originalName === 'downloadFile' || originalName === 'download_file') {
504
- const localPath = args.localPath || args.local_path;
505
- if (typeof localPath === 'string') {
506
- const parentDir = path.dirname(localPath);
507
- if (!fs.existsSync(parentDir)) {
508
- fs.mkdirSync(parentDir, { recursive: true });
509
- }
510
- }
511
- }
512
- try {
513
- const result = await this.sendRequest(instance, 'tools/call', {
514
- name: originalName,
515
- arguments: args,
516
- });
517
- // Bug 1: Validate downloadFile results — detect Google sign-in pages
518
- if ((originalName === 'downloadFile' || originalName === 'download_file') && !result?.isError) {
519
- const localPath = (args.localPath || args.local_path);
520
- if (localPath && fs.existsSync(localPath)) {
521
- const header = Buffer.alloc(500);
522
- const fd = fs.openSync(localPath, 'r');
523
- fs.readSync(fd, header, 0, 500, 0);
524
- fs.closeSync(fd);
525
- const headerStr = header.toString('utf8').toLowerCase();
526
- if (headerStr.includes('accounts.google.com') ||
527
- (headerStr.startsWith('<!doctype html>') && !localPath.endsWith('.html'))) {
528
- fs.unlinkSync(localPath);
529
- return {
530
- content: [{ type: 'text', text: 'ERROR: Download failed — received Google sign-in page instead of actual file content. The OAuth token may have expired. The file was NOT saved. Try using run_command to download via curl with a fresh token, or ask the user to re-authenticate Google Workspace.' }],
531
- isError: true,
532
- };
533
- }
534
- }
535
- }
536
- return this.enrichErrorResult(result, toolName, args);
537
- }
538
- catch (err) {
539
- throw new Error(`MCP tool ${originalName} failed: ${err.message}${err.stack ? '\n' + err.stack.split('\n').slice(0, 3).join('\n') : ''}`);
540
- }
262
+ const result = await this.sendRequest(instance, 'tools/call', {
263
+ name: originalName,
264
+ arguments: args,
265
+ });
266
+ return result;
541
267
  }
542
268
  /** Rebuild the merged list of all discovered tools */
543
269
  rebuildToolList() {
@@ -560,41 +286,22 @@ class MCPManager extends events_1.EventEmitter {
560
286
  const output = result?.content
561
287
  ? result.content.map((c) => c.text || JSON.stringify(c)).join('\n')
562
288
  : JSON.stringify(result);
563
- let errorOutput = output;
564
- if (result?.isError) {
565
- // Enrich error with recent stderr from the MCP server
566
- const srv = this.servers.get(t.serverId);
567
- const recentStderr = srv?.stderrBuffer?.slice(-3)
568
- .filter((s) => s.includes('Error') || s.includes('error') || s.includes('fail') || s.includes('not exist'))
569
- .join('\n');
570
- const parts = ['ERROR:'];
571
- if (output && output !== 'Unknown error')
572
- parts.push(output);
573
- if (recentStderr)
574
- parts.push(`Server details: ${recentStderr}`);
575
- if (parts.length === 1)
576
- parts.push('Unknown error from MCP server');
577
- errorOutput = parts.join(' ');
578
- }
579
289
  return {
580
290
  tool_call_id: '',
581
291
  name: t.name,
582
292
  success: !result?.isError,
583
- output: result?.isError ? errorOutput : output,
584
- error: result?.isError ? errorOutput : undefined,
293
+ output,
585
294
  verified: true,
586
295
  verification_details: 'MCP tool execution',
587
296
  };
588
297
  }
589
298
  catch (err) {
590
- const errorDetail = err.message || 'Unknown error';
591
- const stackHint = err.stack ? '\n' + err.stack.split('\n').slice(1, 3).join('\n') : '';
592
299
  return {
593
300
  tool_call_id: '',
594
301
  name: t.name,
595
302
  success: false,
596
303
  output: '',
597
- error: `${errorDetail}${stackHint}`,
304
+ error: err.message,
598
305
  verified: true,
599
306
  verification_details: 'MCP tool execution failed',
600
307
  };
@@ -617,6 +324,47 @@ class MCPManager extends events_1.EventEmitter {
617
324
  alwaysOn: instance.config.alwaysOn,
618
325
  };
619
326
  }
327
+ async waitForServerExit(id, instance) {
328
+ await new Promise((resolve) => {
329
+ let settled = false;
330
+ const finish = () => {
331
+ if (settled)
332
+ return;
333
+ settled = true;
334
+ clearTimeout(timeout);
335
+ instance.process.removeListener('exit', finish);
336
+ this.removeListener('server-stopped', onStopped);
337
+ resolve();
338
+ };
339
+ const onStopped = (stoppedId) => {
340
+ if (stoppedId === id)
341
+ finish();
342
+ };
343
+ const timeout = setTimeout(finish, 5_000);
344
+ instance.process.once('exit', finish);
345
+ this.on('server-stopped', onStopped);
346
+ instance.process.kill();
347
+ });
348
+ }
349
+ async reloadIntegrationServers(integration) {
350
+ if (integration !== 'google')
351
+ return [];
352
+ const restarted = [];
353
+ const runningGoogleServers = Array.from(this.servers.entries())
354
+ .filter(([id]) => GOOGLE_INTEGRATION_SERVER_IDS.has(id));
355
+ for (const [id, instance] of runningGoogleServers) {
356
+ const config = {
357
+ ...instance.config,
358
+ envVars: { ...instance.config.envVars },
359
+ args: [...instance.config.args],
360
+ };
361
+ const entry = instance.entry;
362
+ await this.waitForServerExit(id, instance);
363
+ await this.launchServer(config, entry);
364
+ restarted.push(id);
365
+ }
366
+ return restarted;
367
+ }
620
368
  /** Stop a running MCP server */
621
369
  async stopServer(id) {
622
370
  const instance = this.servers.get(id);
@@ -654,76 +402,6 @@ class MCPManager extends events_1.EventEmitter {
654
402
  this.saveConfigs();
655
403
  }
656
404
  }
657
- /**
658
- * Pre-warm an npm package by running `npx -y <package> --help` in the background.
659
- * This downloads/caches the package so subsequent launches are fast.
660
- */
661
- static preWarmPackage(npmPackage) {
662
- console.log(`[MCP] Pre-warming package cache: ${npmPackage}`);
663
- const child = (0, child_process_1.spawn)('npx', ['-y', npmPackage, '--help'], {
664
- stdio: 'ignore',
665
- detached: true,
666
- env: { ...process.env },
667
- });
668
- child.unref();
669
- child.on('exit', (code) => {
670
- console.log(`[MCP] Pre-warm of ${npmPackage} finished (exit ${code})`);
671
- });
672
- }
673
- /**
674
- * Reload all running Google MCP servers after token refresh.
675
- * Stops each server, then re-launches with fresh credentials from the token store.
676
- */
677
- async reloadIntegrationServers(integration) {
678
- if (integration !== 'google')
679
- return [];
680
- const GOOGLE_IDS = new Set(['google-workspace', 'google-drive', 'google-calendar', 'gmail']);
681
- const restarted = [];
682
- const runningGoogle = Array.from(this.servers.entries())
683
- .filter(([id]) => GOOGLE_IDS.has(id));
684
- for (const [id, instance] of runningGoogle) {
685
- const config = { ...instance.config };
686
- const entry = instance.entry;
687
- console.log(`[MCP] Reloading ${id} after Google token refresh...`);
688
- try {
689
- // Stop current instance
690
- instance.process.kill();
691
- // Wait briefly for exit
692
- await new Promise((resolve) => {
693
- const timeout = setTimeout(resolve, 5000);
694
- instance.process.once('exit', () => { clearTimeout(timeout); resolve(); });
695
- });
696
- this.servers.delete(id);
697
- this.rebuildToolList();
698
- // Re-launch with fresh tokens from the updated token store
699
- await this.launchServer(config, entry);
700
- restarted.push(id);
701
- console.log(`[MCP] ✓ Reloaded ${id} successfully`);
702
- }
703
- catch (err) {
704
- console.error(`[MCP] ✗ Failed to reload ${id}: ${err.message}`);
705
- }
706
- }
707
- return restarted;
708
- }
709
- /**
710
- * Fix 3: Check which configured (always-on) servers are NOT currently running.
711
- * Returns names of dead servers for injection into system prompt / tool list.
712
- */
713
- getDeadServers() {
714
- const configs = this.loadPersistedConfigs();
715
- const running = new Set(this.servers.keys());
716
- return configs
717
- .filter(c => c.alwaysOn && !running.has(c.id))
718
- .map(c => c.id);
719
- }
720
- /**
721
- * Check if a specific server is alive and responsive.
722
- */
723
- isServerAlive(id) {
724
- const instance = this.servers.get(id);
725
- return !!(instance && instance.ready && instance.process && !instance.process.killed);
726
- }
727
405
  /** Shutdown all MCP servers */
728
406
  async shutdown() {
729
407
  for (const [id, instance] of this.servers) {