@probelabs/probe 0.6.0-rc119 → 0.6.0-rc121

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.
@@ -8,7 +8,7 @@ import { randomUUID } from 'crypto';
8
8
  import { EventEmitter } from 'events';
9
9
  import { existsSync } from 'fs';
10
10
  import { readFile, stat } from 'fs/promises';
11
- import { resolve, isAbsolute } from 'path';
11
+ import { resolve, isAbsolute, dirname } from 'path';
12
12
  import { TokenCounter } from './tokenCounter.js';
13
13
  import {
14
14
  createTools,
@@ -145,12 +145,48 @@ export class ProbeAgent {
145
145
  // Initialize the AI model
146
146
  this.initializeModel();
147
147
 
148
+ // Note: MCP initialization is now done in initialize() method
149
+ // Constructor must remain synchronous for backward compatibility
150
+ }
151
+
152
+ /**
153
+ * Initialize the agent asynchronously (must be called after constructor)
154
+ * This method initializes MCP and merges MCP tools into the tool list
155
+ */
156
+ async initialize() {
148
157
  // Initialize MCP if enabled
149
158
  if (this.enableMcp) {
150
- this.initializeMCP().catch(error => {
159
+ try {
160
+ await this.initializeMCP();
161
+
162
+ // Merge MCP tools into toolImplementations for unified access
163
+ if (this.mcpBridge) {
164
+ const mcpTools = this.mcpBridge.mcpTools || {};
165
+ for (const [toolName, toolImpl] of Object.entries(mcpTools)) {
166
+ this.toolImplementations[toolName] = toolImpl;
167
+ }
168
+ }
169
+
170
+ // Log all available tools after MCP initialization
171
+ if (this.debug) {
172
+ const allToolNames = Object.keys(this.toolImplementations);
173
+ const nativeToolCount = allToolNames.filter(name => !this.mcpBridge?.mcpTools?.[name]).length;
174
+ const mcpToolCount = allToolNames.length - nativeToolCount;
175
+
176
+ console.error('\n[DEBUG] ========================================');
177
+ console.error('[DEBUG] All Tools Initialized');
178
+ console.error(`[DEBUG] Native tools: ${nativeToolCount}, MCP tools: ${mcpToolCount}`);
179
+ console.error('[DEBUG] Available tools:');
180
+ for (const toolName of allToolNames) {
181
+ const isMCP = this.mcpBridge?.mcpTools?.[toolName] ? ' (MCP)' : '';
182
+ console.error(`[DEBUG] - ${toolName}${isMCP}`);
183
+ }
184
+ console.error('[DEBUG] ========================================\n');
185
+ }
186
+ } catch (error) {
151
187
  console.error('[MCP] Failed to initialize MCP:', error);
152
188
  this.mcpBridge = null;
153
- });
189
+ }
154
190
  }
155
191
  }
156
192
 
@@ -442,23 +478,62 @@ export class ProbeAgent {
442
478
  }
443
479
 
444
480
  /**
445
- * Extract directory paths from listFiles tool output
481
+ * Extract directory paths from tool output (both listFiles and extract tool)
446
482
  * @param {string} content - Tool output content
447
483
  * @returns {string[]} - Array of directory paths
448
484
  */
449
485
  extractListFilesDirectories(content) {
450
486
  const directories = [];
451
487
 
452
- // Pattern to match listFiles output format: "/path/to/directory:" at the start of a line
453
- const dirPattern = /^([^\n:]+):\s*$/gm;
488
+ // Pattern 1: Extract directory from extract tool "File:" header
489
+ // Format: "File: /path/to/file.md" or "File: ./relative/path/file.md"
490
+ const fileHeaderPattern = /^File:\s+(.+)$/gm;
454
491
 
455
492
  let match;
493
+ while ((match = fileHeaderPattern.exec(content)) !== null) {
494
+ const filePath = match[1].trim();
495
+ // Get directory from file path
496
+ const dir = dirname(filePath);
497
+ if (dir && dir !== '.') {
498
+ directories.push(dir);
499
+ if (this.debug) {
500
+ console.log(`[DEBUG] Extracted directory context from File header: ${dir}`);
501
+ }
502
+ }
503
+ }
504
+
505
+ // Pattern 2: Extract directory from listFiles output format: "/path/to/directory:"
506
+ // Matches absolute paths (/path/to/dir:) or current directory markers (.:) or Windows paths (C:\path:) at start of line
507
+ // Very strict to avoid matching random text like ".Something:" or "./Some text:"
508
+ const dirPattern = /^(\/[^\n:]+|[A-Z]:\\[^\n:]+|\.\.?(?:\/[^\n:]+)?):\s*$/gm;
509
+
456
510
  while ((match = dirPattern.exec(content)) !== null) {
457
511
  const dirPath = match[1].trim();
458
- if (dirPath && dirPath.length > 0) {
459
- directories.push(dirPath);
460
- if (this.debug) {
461
- console.log(`[DEBUG] Extracted directory context from listFiles: ${dirPath}`);
512
+
513
+ // Strict validation: must look like an actual filesystem path
514
+ // Reject if contains spaces or other characters that wouldn't be in listFiles output
515
+ const hasInvalidChars = /\s/.test(dirPath); // Contains whitespace
516
+
517
+ // Validate this looks like an actual path, not random text
518
+ // Must be either: absolute path (Unix or Windows), or ./ or ../ followed by valid path chars
519
+ const isValidPath = (
520
+ !hasInvalidChars && (
521
+ dirPath.startsWith('/') || // Unix absolute path
522
+ /^[A-Z]:\\/.test(dirPath) || // Windows absolute path (C:\)
523
+ dirPath === '.' || // Current directory
524
+ dirPath === '..' || // Parent directory
525
+ (dirPath.startsWith('./') && dirPath.length > 2 && !dirPath.includes(' ')) || // ./something (no spaces)
526
+ (dirPath.startsWith('../') && dirPath.length > 3 && !dirPath.includes(' ')) // ../something (no spaces)
527
+ )
528
+ );
529
+
530
+ if (isValidPath) {
531
+ // Avoid duplicates
532
+ if (!directories.includes(dirPath)) {
533
+ directories.push(dirPath);
534
+ if (this.debug) {
535
+ console.log(`[DEBUG] Extracted directory context from listFiles: ${dirPath}`);
536
+ }
462
537
  }
463
538
  }
464
539
  }
@@ -31,12 +31,14 @@ class ACPSession {
31
31
  /**
32
32
  * Get or create ProbeAgent for this session
33
33
  */
34
- getAgent(config = {}) {
34
+ async getAgent(config = {}) {
35
35
  if (!this.agent) {
36
36
  this.agent = new ProbeAgent({
37
37
  sessionId: this.id,
38
38
  ...config
39
39
  });
40
+ // Initialize MCP if enabled
41
+ await this.agent.initialize();
40
42
  }
41
43
  return this.agent;
42
44
  }
@@ -320,20 +322,23 @@ export class ACPServer {
320
322
  }
321
323
 
322
324
  session.touch();
323
-
325
+
324
326
  // Get or create ProbeAgent for this session
325
- const agent = session.getAgent({
327
+ const agent = await session.getAgent({
326
328
  path: this.options.path,
327
329
  provider: this.options.provider,
328
330
  model: this.options.model,
329
331
  allowEdit: this.options.allowEdit,
330
- debug: this.options.debug
332
+ debug: this.options.debug,
333
+ enableMcp: this.options.enableMcp,
334
+ mcpConfig: this.options.mcpConfig,
335
+ mcpConfigPath: this.options.mcpConfigPath
331
336
  });
332
-
337
+
333
338
  if (this.options.debug) {
334
339
  console.error(`[ACP] Processing prompt for session ${params.sessionId}:`, params.message.substring(0, 100));
335
340
  }
336
-
341
+
337
342
  try {
338
343
  // Process the message with the ProbeAgent
339
344
  const response = await agent.answer(params.message);
@@ -60866,13 +60866,14 @@ async function validateMermaidDiagram(diagram) {
60866
60866
  };
60867
60867
  }
60868
60868
  const result = validate(diagram);
60869
- if (result.errors && result.errors.length === 0) {
60869
+ const actualErrors = (result.errors || []).filter((err) => err.severity === "error");
60870
+ if (actualErrors.length === 0) {
60870
60871
  return {
60871
60872
  isValid: true,
60872
60873
  diagramType: result.type || "unknown"
60873
60874
  };
60874
60875
  } else {
60875
- const errorMessages = (result.errors || []).map((err) => {
60876
+ const errorMessages = actualErrors.map((err) => {
60876
60877
  const location = err.line ? `line ${err.line}${err.column ? `:${err.column}` : ""}` : "";
60877
60878
  return location ? `${location} - ${err.message}` : err.message;
60878
60879
  });
@@ -60881,8 +60882,8 @@ async function validateMermaidDiagram(diagram) {
60881
60882
  diagramType: result.type || "unknown",
60882
60883
  error: errorMessages[0] || "Validation failed",
60883
60884
  detailedError: errorMessages.join("\n"),
60884
- errors: result.errors || []
60885
- // Include raw maid errors for AI fixing
60885
+ errors: actualErrors
60886
+ // Include only actual errors for AI fixing
60886
60887
  };
60887
60888
  }
60888
60889
  } catch (error2) {
@@ -61770,10 +61771,19 @@ var init_client2 = __esm({
61770
61771
  if (this.debug) {
61771
61772
  console.error(`[MCP] Calling ${toolName} with args:`, args);
61772
61773
  }
61773
- const result = await clientInfo.client.callTool({
61774
- name: tool3.originalName,
61775
- arguments: args
61774
+ const timeout = this.config?.settings?.timeout || 3e4;
61775
+ const timeoutPromise = new Promise((_2, reject2) => {
61776
+ setTimeout(() => {
61777
+ reject2(new Error(`MCP tool call timeout after ${timeout}ms`));
61778
+ }, timeout);
61776
61779
  });
61780
+ const result = await Promise.race([
61781
+ clientInfo.client.callTool({
61782
+ name: tool3.originalName,
61783
+ arguments: args
61784
+ }),
61785
+ timeoutPromise
61786
+ ]);
61777
61787
  return result;
61778
61788
  } catch (error2) {
61779
61789
  console.error(`[MCP] Error calling tool ${toolName}:`, error2);
@@ -62106,7 +62116,7 @@ import { randomUUID as randomUUID4 } from "crypto";
62106
62116
  import { EventEmitter as EventEmitter2 } from "events";
62107
62117
  import { existsSync as existsSync4 } from "fs";
62108
62118
  import { readFile, stat } from "fs/promises";
62109
- import { resolve as resolve3, isAbsolute } from "path";
62119
+ import { resolve as resolve3, isAbsolute, dirname as dirname3 } from "path";
62110
62120
  var MAX_TOOL_ITERATIONS, MAX_HISTORY_MESSAGES, SUPPORTED_IMAGE_EXTENSIONS, MAX_IMAGE_FILE_SIZE, ProbeAgent;
62111
62121
  var init_ProbeAgent = __esm({
62112
62122
  "src/agent/ProbeAgent.js"() {
@@ -62187,11 +62197,39 @@ var init_ProbeAgent = __esm({
62187
62197
  this.mcpServers = options.mcpServers || null;
62188
62198
  this.mcpBridge = null;
62189
62199
  this.initializeModel();
62200
+ }
62201
+ /**
62202
+ * Initialize the agent asynchronously (must be called after constructor)
62203
+ * This method initializes MCP and merges MCP tools into the tool list
62204
+ */
62205
+ async initialize() {
62190
62206
  if (this.enableMcp) {
62191
- this.initializeMCP().catch((error2) => {
62207
+ try {
62208
+ await this.initializeMCP();
62209
+ if (this.mcpBridge) {
62210
+ const mcpTools = this.mcpBridge.mcpTools || {};
62211
+ for (const [toolName, toolImpl] of Object.entries(mcpTools)) {
62212
+ this.toolImplementations[toolName] = toolImpl;
62213
+ }
62214
+ }
62215
+ if (this.debug) {
62216
+ const allToolNames = Object.keys(this.toolImplementations);
62217
+ const nativeToolCount = allToolNames.filter((name14) => !this.mcpBridge?.mcpTools?.[name14]).length;
62218
+ const mcpToolCount = allToolNames.length - nativeToolCount;
62219
+ console.error("\n[DEBUG] ========================================");
62220
+ console.error("[DEBUG] All Tools Initialized");
62221
+ console.error(`[DEBUG] Native tools: ${nativeToolCount}, MCP tools: ${mcpToolCount}`);
62222
+ console.error("[DEBUG] Available tools:");
62223
+ for (const toolName of allToolNames) {
62224
+ const isMCP = this.mcpBridge?.mcpTools?.[toolName] ? " (MCP)" : "";
62225
+ console.error(`[DEBUG] - ${toolName}${isMCP}`);
62226
+ }
62227
+ console.error("[DEBUG] ========================================\n");
62228
+ }
62229
+ } catch (error2) {
62192
62230
  console.error("[MCP] Failed to initialize MCP:", error2);
62193
62231
  this.mcpBridge = null;
62194
- });
62232
+ }
62195
62233
  }
62196
62234
  }
62197
62235
  /**
@@ -62418,20 +62456,40 @@ var init_ProbeAgent = __esm({
62418
62456
  }
62419
62457
  }
62420
62458
  /**
62421
- * Extract directory paths from listFiles tool output
62459
+ * Extract directory paths from tool output (both listFiles and extract tool)
62422
62460
  * @param {string} content - Tool output content
62423
62461
  * @returns {string[]} - Array of directory paths
62424
62462
  */
62425
62463
  extractListFilesDirectories(content) {
62426
62464
  const directories = [];
62427
- const dirPattern = /^([^\n:]+):\s*$/gm;
62465
+ const fileHeaderPattern = /^File:\s+(.+)$/gm;
62428
62466
  let match;
62467
+ while ((match = fileHeaderPattern.exec(content)) !== null) {
62468
+ const filePath = match[1].trim();
62469
+ const dir = dirname3(filePath);
62470
+ if (dir && dir !== ".") {
62471
+ directories.push(dir);
62472
+ if (this.debug) {
62473
+ console.log(`[DEBUG] Extracted directory context from File header: ${dir}`);
62474
+ }
62475
+ }
62476
+ }
62477
+ const dirPattern = /^(\/[^\n:]+|[A-Z]:\\[^\n:]+|\.\.?(?:\/[^\n:]+)?):\s*$/gm;
62429
62478
  while ((match = dirPattern.exec(content)) !== null) {
62430
62479
  const dirPath = match[1].trim();
62431
- if (dirPath && dirPath.length > 0) {
62432
- directories.push(dirPath);
62433
- if (this.debug) {
62434
- console.log(`[DEBUG] Extracted directory context from listFiles: ${dirPath}`);
62480
+ const hasInvalidChars = /\s/.test(dirPath);
62481
+ const isValidPath = !hasInvalidChars && (dirPath.startsWith("/") || // Unix absolute path
62482
+ /^[A-Z]:\\/.test(dirPath) || // Windows absolute path (C:\)
62483
+ dirPath === "." || // Current directory
62484
+ dirPath === ".." || // Parent directory
62485
+ dirPath.startsWith("./") && dirPath.length > 2 && !dirPath.includes(" ") || // ./something (no spaces)
62486
+ dirPath.startsWith("../") && dirPath.length > 3 && !dirPath.includes(" "));
62487
+ if (isValidPath) {
62488
+ if (!directories.includes(dirPath)) {
62489
+ directories.push(dirPath);
62490
+ if (this.debug) {
62491
+ console.log(`[DEBUG] Extracted directory context from listFiles: ${dirPath}`);
62492
+ }
62435
62493
  }
62436
62494
  }
62437
62495
  }
@@ -64033,12 +64091,13 @@ var ACPSession = class {
64033
64091
  /**
64034
64092
  * Get or create ProbeAgent for this session
64035
64093
  */
64036
- getAgent(config = {}) {
64094
+ async getAgent(config = {}) {
64037
64095
  if (!this.agent) {
64038
64096
  this.agent = new ProbeAgent({
64039
64097
  sessionId: this.id,
64040
64098
  ...config
64041
64099
  });
64100
+ await this.agent.initialize();
64042
64101
  }
64043
64102
  return this.agent;
64044
64103
  }
@@ -64266,12 +64325,15 @@ var ACPServer = class {
64266
64325
  throw new Error(`Session not found: ${params.sessionId}`);
64267
64326
  }
64268
64327
  session.touch();
64269
- const agent = session.getAgent({
64328
+ const agent = await session.getAgent({
64270
64329
  path: this.options.path,
64271
64330
  provider: this.options.provider,
64272
64331
  model: this.options.model,
64273
64332
  allowEdit: this.options.allowEdit,
64274
- debug: this.options.debug
64333
+ debug: this.options.debug,
64334
+ enableMcp: this.options.enableMcp,
64335
+ mcpConfig: this.options.mcpConfig,
64336
+ mcpConfigPath: this.options.mcpConfigPath
64275
64337
  });
64276
64338
  if (this.options.debug) {
64277
64339
  console.error(`[ACP] Processing prompt for session ${params.sessionId}:`, params.message.substring(0, 100));
@@ -64716,6 +64778,7 @@ var ProbeAgentMcpServer = class {
64716
64778
  disableMermaidValidation: !!args.no_mermaid_validation
64717
64779
  };
64718
64780
  this.agent = new ProbeAgent(agentConfig2);
64781
+ await this.agent.initialize();
64719
64782
  }
64720
64783
  const agent = this.agent;
64721
64784
  let result = await agent.answer(query2, [], { schema });
@@ -64952,6 +65015,7 @@ async function main() {
64952
65015
  bashConfig
64953
65016
  };
64954
65017
  const agent = new ProbeAgent(agentConfig2);
65018
+ await agent.initialize();
64955
65019
  let result;
64956
65020
  if (appTracer) {
64957
65021
  const sessionSpan = appTracer.createSessionSpan({
@@ -242,11 +242,25 @@ export class MCPClientManager {
242
242
  console.error(`[MCP] Calling ${toolName} with args:`, args);
243
243
  }
244
244
 
245
- const result = await clientInfo.client.callTool({
246
- name: tool.originalName,
247
- arguments: args
245
+ // Get timeout from config (default 30 seconds)
246
+ const timeout = this.config?.settings?.timeout || 30000;
247
+
248
+ // Create a timeout promise
249
+ const timeoutPromise = new Promise((_, reject) => {
250
+ setTimeout(() => {
251
+ reject(new Error(`MCP tool call timeout after ${timeout}ms`));
252
+ }, timeout);
248
253
  });
249
254
 
255
+ // Race between the actual call and timeout
256
+ const result = await Promise.race([
257
+ clientInfo.client.callTool({
258
+ name: tool.originalName,
259
+ arguments: args
260
+ }),
261
+ timeoutPromise
262
+ ]);
263
+
250
264
  return result;
251
265
  } catch (error) {
252
266
  console.error(`[MCP] Error calling tool ${toolName}:`, error);
@@ -562,15 +562,18 @@ export async function validateMermaidDiagram(diagram) {
562
562
  const result = validate(diagram);
563
563
 
564
564
  // Maid returns { type: string, errors: array }
565
- // Valid if errors array is empty
566
- if (result.errors && result.errors.length === 0) {
565
+ // Only count actual errors (severity: 'error'), not warnings
566
+ const actualErrors = (result.errors || []).filter(err => err.severity === 'error');
567
+
568
+ // Valid if no actual errors (warnings are OK)
569
+ if (actualErrors.length === 0) {
567
570
  return {
568
571
  isValid: true,
569
572
  diagramType: result.type || 'unknown'
570
573
  };
571
574
  } else {
572
575
  // Format maid errors into a readable error message
573
- const errorMessages = (result.errors || []).map(err => {
576
+ const errorMessages = actualErrors.map(err => {
574
577
  const location = err.line ? `line ${err.line}${err.column ? `:${err.column}` : ''}` : '';
575
578
  return location ? `${location} - ${err.message}` : err.message;
576
579
  });
@@ -580,7 +583,7 @@ export async function validateMermaidDiagram(diagram) {
580
583
  diagramType: result.type || 'unknown',
581
584
  error: errorMessages[0] || 'Validation failed',
582
585
  detailedError: errorMessages.join('\n'),
583
- errors: result.errors || [] // Include raw maid errors for AI fixing
586
+ errors: actualErrors // Include only actual errors for AI fixing
584
587
  };
585
588
  }
586
589
 
@@ -60588,13 +60588,14 @@ async function validateMermaidDiagram(diagram) {
60588
60588
  };
60589
60589
  }
60590
60590
  const result = validate(diagram);
60591
- if (result.errors && result.errors.length === 0) {
60591
+ const actualErrors = (result.errors || []).filter((err) => err.severity === "error");
60592
+ if (actualErrors.length === 0) {
60592
60593
  return {
60593
60594
  isValid: true,
60594
60595
  diagramType: result.type || "unknown"
60595
60596
  };
60596
60597
  } else {
60597
- const errorMessages = (result.errors || []).map((err) => {
60598
+ const errorMessages = actualErrors.map((err) => {
60598
60599
  const location = err.line ? `line ${err.line}${err.column ? `:${err.column}` : ""}` : "";
60599
60600
  return location ? `${location} - ${err.message}` : err.message;
60600
60601
  });
@@ -60603,8 +60604,8 @@ async function validateMermaidDiagram(diagram) {
60603
60604
  diagramType: result.type || "unknown",
60604
60605
  error: errorMessages[0] || "Validation failed",
60605
60606
  detailedError: errorMessages.join("\n"),
60606
- errors: result.errors || []
60607
- // Include raw maid errors for AI fixing
60607
+ errors: actualErrors
60608
+ // Include only actual errors for AI fixing
60608
60609
  };
60609
60610
  }
60610
60611
  } catch (error2) {
@@ -61492,10 +61493,19 @@ var init_client2 = __esm({
61492
61493
  if (this.debug) {
61493
61494
  console.error(`[MCP] Calling ${toolName} with args:`, args);
61494
61495
  }
61495
- const result = await clientInfo.client.callTool({
61496
- name: tool3.originalName,
61497
- arguments: args
61496
+ const timeout = this.config?.settings?.timeout || 3e4;
61497
+ const timeoutPromise = new Promise((_2, reject2) => {
61498
+ setTimeout(() => {
61499
+ reject2(new Error(`MCP tool call timeout after ${timeout}ms`));
61500
+ }, timeout);
61498
61501
  });
61502
+ const result = await Promise.race([
61503
+ clientInfo.client.callTool({
61504
+ name: tool3.originalName,
61505
+ arguments: args
61506
+ }),
61507
+ timeoutPromise
61508
+ ]);
61499
61509
  return result;
61500
61510
  } catch (error2) {
61501
61511
  console.error(`[MCP] Error calling tool ${toolName}:`, error2);
@@ -61909,11 +61919,39 @@ var init_ProbeAgent = __esm({
61909
61919
  this.mcpServers = options.mcpServers || null;
61910
61920
  this.mcpBridge = null;
61911
61921
  this.initializeModel();
61922
+ }
61923
+ /**
61924
+ * Initialize the agent asynchronously (must be called after constructor)
61925
+ * This method initializes MCP and merges MCP tools into the tool list
61926
+ */
61927
+ async initialize() {
61912
61928
  if (this.enableMcp) {
61913
- this.initializeMCP().catch((error2) => {
61929
+ try {
61930
+ await this.initializeMCP();
61931
+ if (this.mcpBridge) {
61932
+ const mcpTools = this.mcpBridge.mcpTools || {};
61933
+ for (const [toolName, toolImpl] of Object.entries(mcpTools)) {
61934
+ this.toolImplementations[toolName] = toolImpl;
61935
+ }
61936
+ }
61937
+ if (this.debug) {
61938
+ const allToolNames = Object.keys(this.toolImplementations);
61939
+ const nativeToolCount = allToolNames.filter((name14) => !this.mcpBridge?.mcpTools?.[name14]).length;
61940
+ const mcpToolCount = allToolNames.length - nativeToolCount;
61941
+ console.error("\n[DEBUG] ========================================");
61942
+ console.error("[DEBUG] All Tools Initialized");
61943
+ console.error(`[DEBUG] Native tools: ${nativeToolCount}, MCP tools: ${mcpToolCount}`);
61944
+ console.error("[DEBUG] Available tools:");
61945
+ for (const toolName of allToolNames) {
61946
+ const isMCP = this.mcpBridge?.mcpTools?.[toolName] ? " (MCP)" : "";
61947
+ console.error(`[DEBUG] - ${toolName}${isMCP}`);
61948
+ }
61949
+ console.error("[DEBUG] ========================================\n");
61950
+ }
61951
+ } catch (error2) {
61914
61952
  console.error("[MCP] Failed to initialize MCP:", error2);
61915
61953
  this.mcpBridge = null;
61916
- });
61954
+ }
61917
61955
  }
61918
61956
  }
61919
61957
  /**
@@ -62140,20 +62178,40 @@ var init_ProbeAgent = __esm({
62140
62178
  }
62141
62179
  }
62142
62180
  /**
62143
- * Extract directory paths from listFiles tool output
62181
+ * Extract directory paths from tool output (both listFiles and extract tool)
62144
62182
  * @param {string} content - Tool output content
62145
62183
  * @returns {string[]} - Array of directory paths
62146
62184
  */
62147
62185
  extractListFilesDirectories(content) {
62148
62186
  const directories = [];
62149
- const dirPattern = /^([^\n:]+):\s*$/gm;
62187
+ const fileHeaderPattern = /^File:\s+(.+)$/gm;
62150
62188
  let match;
62189
+ while ((match = fileHeaderPattern.exec(content)) !== null) {
62190
+ const filePath = match[1].trim();
62191
+ const dir = (0, import_path10.dirname)(filePath);
62192
+ if (dir && dir !== ".") {
62193
+ directories.push(dir);
62194
+ if (this.debug) {
62195
+ console.log(`[DEBUG] Extracted directory context from File header: ${dir}`);
62196
+ }
62197
+ }
62198
+ }
62199
+ const dirPattern = /^(\/[^\n:]+|[A-Z]:\\[^\n:]+|\.\.?(?:\/[^\n:]+)?):\s*$/gm;
62151
62200
  while ((match = dirPattern.exec(content)) !== null) {
62152
62201
  const dirPath = match[1].trim();
62153
- if (dirPath && dirPath.length > 0) {
62154
- directories.push(dirPath);
62155
- if (this.debug) {
62156
- console.log(`[DEBUG] Extracted directory context from listFiles: ${dirPath}`);
62202
+ const hasInvalidChars = /\s/.test(dirPath);
62203
+ const isValidPath = !hasInvalidChars && (dirPath.startsWith("/") || // Unix absolute path
62204
+ /^[A-Z]:\\/.test(dirPath) || // Windows absolute path (C:\)
62205
+ dirPath === "." || // Current directory
62206
+ dirPath === ".." || // Parent directory
62207
+ dirPath.startsWith("./") && dirPath.length > 2 && !dirPath.includes(" ") || // ./something (no spaces)
62208
+ dirPath.startsWith("../") && dirPath.length > 3 && !dirPath.includes(" "));
62209
+ if (isValidPath) {
62210
+ if (!directories.includes(dirPath)) {
62211
+ directories.push(dirPath);
62212
+ if (this.debug) {
62213
+ console.log(`[DEBUG] Extracted directory context from listFiles: ${dirPath}`);
62214
+ }
62157
62215
  }
62158
62216
  }
62159
62217
  }
package/cjs/index.cjs CHANGED
@@ -60732,13 +60732,14 @@ async function validateMermaidDiagram(diagram) {
60732
60732
  };
60733
60733
  }
60734
60734
  const result = validate(diagram);
60735
- if (result.errors && result.errors.length === 0) {
60735
+ const actualErrors = (result.errors || []).filter((err) => err.severity === "error");
60736
+ if (actualErrors.length === 0) {
60736
60737
  return {
60737
60738
  isValid: true,
60738
60739
  diagramType: result.type || "unknown"
60739
60740
  };
60740
60741
  } else {
60741
- const errorMessages = (result.errors || []).map((err) => {
60742
+ const errorMessages = actualErrors.map((err) => {
60742
60743
  const location = err.line ? `line ${err.line}${err.column ? `:${err.column}` : ""}` : "";
60743
60744
  return location ? `${location} - ${err.message}` : err.message;
60744
60745
  });
@@ -60747,8 +60748,8 @@ async function validateMermaidDiagram(diagram) {
60747
60748
  diagramType: result.type || "unknown",
60748
60749
  error: errorMessages[0] || "Validation failed",
60749
60750
  detailedError: errorMessages.join("\n"),
60750
- errors: result.errors || []
60751
- // Include raw maid errors for AI fixing
60751
+ errors: actualErrors
60752
+ // Include only actual errors for AI fixing
60752
60753
  };
60753
60754
  }
60754
60755
  } catch (error2) {
@@ -61636,10 +61637,19 @@ var init_client2 = __esm({
61636
61637
  if (this.debug) {
61637
61638
  console.error(`[MCP] Calling ${toolName} with args:`, args);
61638
61639
  }
61639
- const result = await clientInfo.client.callTool({
61640
- name: tool3.originalName,
61641
- arguments: args
61640
+ const timeout = this.config?.settings?.timeout || 3e4;
61641
+ const timeoutPromise = new Promise((_2, reject2) => {
61642
+ setTimeout(() => {
61643
+ reject2(new Error(`MCP tool call timeout after ${timeout}ms`));
61644
+ }, timeout);
61642
61645
  });
61646
+ const result = await Promise.race([
61647
+ clientInfo.client.callTool({
61648
+ name: tool3.originalName,
61649
+ arguments: args
61650
+ }),
61651
+ timeoutPromise
61652
+ ]);
61643
61653
  return result;
61644
61654
  } catch (error2) {
61645
61655
  console.error(`[MCP] Error calling tool ${toolName}:`, error2);
@@ -62053,11 +62063,39 @@ var init_ProbeAgent = __esm({
62053
62063
  this.mcpServers = options.mcpServers || null;
62054
62064
  this.mcpBridge = null;
62055
62065
  this.initializeModel();
62066
+ }
62067
+ /**
62068
+ * Initialize the agent asynchronously (must be called after constructor)
62069
+ * This method initializes MCP and merges MCP tools into the tool list
62070
+ */
62071
+ async initialize() {
62056
62072
  if (this.enableMcp) {
62057
- this.initializeMCP().catch((error2) => {
62073
+ try {
62074
+ await this.initializeMCP();
62075
+ if (this.mcpBridge) {
62076
+ const mcpTools = this.mcpBridge.mcpTools || {};
62077
+ for (const [toolName, toolImpl] of Object.entries(mcpTools)) {
62078
+ this.toolImplementations[toolName] = toolImpl;
62079
+ }
62080
+ }
62081
+ if (this.debug) {
62082
+ const allToolNames = Object.keys(this.toolImplementations);
62083
+ const nativeToolCount = allToolNames.filter((name14) => !this.mcpBridge?.mcpTools?.[name14]).length;
62084
+ const mcpToolCount = allToolNames.length - nativeToolCount;
62085
+ console.error("\n[DEBUG] ========================================");
62086
+ console.error("[DEBUG] All Tools Initialized");
62087
+ console.error(`[DEBUG] Native tools: ${nativeToolCount}, MCP tools: ${mcpToolCount}`);
62088
+ console.error("[DEBUG] Available tools:");
62089
+ for (const toolName of allToolNames) {
62090
+ const isMCP = this.mcpBridge?.mcpTools?.[toolName] ? " (MCP)" : "";
62091
+ console.error(`[DEBUG] - ${toolName}${isMCP}`);
62092
+ }
62093
+ console.error("[DEBUG] ========================================\n");
62094
+ }
62095
+ } catch (error2) {
62058
62096
  console.error("[MCP] Failed to initialize MCP:", error2);
62059
62097
  this.mcpBridge = null;
62060
- });
62098
+ }
62061
62099
  }
62062
62100
  }
62063
62101
  /**
@@ -62284,20 +62322,40 @@ var init_ProbeAgent = __esm({
62284
62322
  }
62285
62323
  }
62286
62324
  /**
62287
- * Extract directory paths from listFiles tool output
62325
+ * Extract directory paths from tool output (both listFiles and extract tool)
62288
62326
  * @param {string} content - Tool output content
62289
62327
  * @returns {string[]} - Array of directory paths
62290
62328
  */
62291
62329
  extractListFilesDirectories(content) {
62292
62330
  const directories = [];
62293
- const dirPattern = /^([^\n:]+):\s*$/gm;
62331
+ const fileHeaderPattern = /^File:\s+(.+)$/gm;
62294
62332
  let match;
62333
+ while ((match = fileHeaderPattern.exec(content)) !== null) {
62334
+ const filePath = match[1].trim();
62335
+ const dir = (0, import_path9.dirname)(filePath);
62336
+ if (dir && dir !== ".") {
62337
+ directories.push(dir);
62338
+ if (this.debug) {
62339
+ console.log(`[DEBUG] Extracted directory context from File header: ${dir}`);
62340
+ }
62341
+ }
62342
+ }
62343
+ const dirPattern = /^(\/[^\n:]+|[A-Z]:\\[^\n:]+|\.\.?(?:\/[^\n:]+)?):\s*$/gm;
62295
62344
  while ((match = dirPattern.exec(content)) !== null) {
62296
62345
  const dirPath = match[1].trim();
62297
- if (dirPath && dirPath.length > 0) {
62298
- directories.push(dirPath);
62299
- if (this.debug) {
62300
- console.log(`[DEBUG] Extracted directory context from listFiles: ${dirPath}`);
62346
+ const hasInvalidChars = /\s/.test(dirPath);
62347
+ const isValidPath = !hasInvalidChars && (dirPath.startsWith("/") || // Unix absolute path
62348
+ /^[A-Z]:\\/.test(dirPath) || // Windows absolute path (C:\)
62349
+ dirPath === "." || // Current directory
62350
+ dirPath === ".." || // Parent directory
62351
+ dirPath.startsWith("./") && dirPath.length > 2 && !dirPath.includes(" ") || // ./something (no spaces)
62352
+ dirPath.startsWith("../") && dirPath.length > 3 && !dirPath.includes(" "));
62353
+ if (isValidPath) {
62354
+ if (!directories.includes(dirPath)) {
62355
+ directories.push(dirPath);
62356
+ if (this.debug) {
62357
+ console.log(`[DEBUG] Extracted directory context from listFiles: ${dirPath}`);
62358
+ }
62301
62359
  }
62302
62360
  }
62303
62361
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probelabs/probe",
3
- "version": "0.6.0-rc119",
3
+ "version": "0.6.0-rc121",
4
4
  "description": "Node.js wrapper for the probe code search tool",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -8,7 +8,7 @@ import { randomUUID } from 'crypto';
8
8
  import { EventEmitter } from 'events';
9
9
  import { existsSync } from 'fs';
10
10
  import { readFile, stat } from 'fs/promises';
11
- import { resolve, isAbsolute } from 'path';
11
+ import { resolve, isAbsolute, dirname } from 'path';
12
12
  import { TokenCounter } from './tokenCounter.js';
13
13
  import {
14
14
  createTools,
@@ -145,12 +145,48 @@ export class ProbeAgent {
145
145
  // Initialize the AI model
146
146
  this.initializeModel();
147
147
 
148
+ // Note: MCP initialization is now done in initialize() method
149
+ // Constructor must remain synchronous for backward compatibility
150
+ }
151
+
152
+ /**
153
+ * Initialize the agent asynchronously (must be called after constructor)
154
+ * This method initializes MCP and merges MCP tools into the tool list
155
+ */
156
+ async initialize() {
148
157
  // Initialize MCP if enabled
149
158
  if (this.enableMcp) {
150
- this.initializeMCP().catch(error => {
159
+ try {
160
+ await this.initializeMCP();
161
+
162
+ // Merge MCP tools into toolImplementations for unified access
163
+ if (this.mcpBridge) {
164
+ const mcpTools = this.mcpBridge.mcpTools || {};
165
+ for (const [toolName, toolImpl] of Object.entries(mcpTools)) {
166
+ this.toolImplementations[toolName] = toolImpl;
167
+ }
168
+ }
169
+
170
+ // Log all available tools after MCP initialization
171
+ if (this.debug) {
172
+ const allToolNames = Object.keys(this.toolImplementations);
173
+ const nativeToolCount = allToolNames.filter(name => !this.mcpBridge?.mcpTools?.[name]).length;
174
+ const mcpToolCount = allToolNames.length - nativeToolCount;
175
+
176
+ console.error('\n[DEBUG] ========================================');
177
+ console.error('[DEBUG] All Tools Initialized');
178
+ console.error(`[DEBUG] Native tools: ${nativeToolCount}, MCP tools: ${mcpToolCount}`);
179
+ console.error('[DEBUG] Available tools:');
180
+ for (const toolName of allToolNames) {
181
+ const isMCP = this.mcpBridge?.mcpTools?.[toolName] ? ' (MCP)' : '';
182
+ console.error(`[DEBUG] - ${toolName}${isMCP}`);
183
+ }
184
+ console.error('[DEBUG] ========================================\n');
185
+ }
186
+ } catch (error) {
151
187
  console.error('[MCP] Failed to initialize MCP:', error);
152
188
  this.mcpBridge = null;
153
- });
189
+ }
154
190
  }
155
191
  }
156
192
 
@@ -442,23 +478,62 @@ export class ProbeAgent {
442
478
  }
443
479
 
444
480
  /**
445
- * Extract directory paths from listFiles tool output
481
+ * Extract directory paths from tool output (both listFiles and extract tool)
446
482
  * @param {string} content - Tool output content
447
483
  * @returns {string[]} - Array of directory paths
448
484
  */
449
485
  extractListFilesDirectories(content) {
450
486
  const directories = [];
451
487
 
452
- // Pattern to match listFiles output format: "/path/to/directory:" at the start of a line
453
- const dirPattern = /^([^\n:]+):\s*$/gm;
488
+ // Pattern 1: Extract directory from extract tool "File:" header
489
+ // Format: "File: /path/to/file.md" or "File: ./relative/path/file.md"
490
+ const fileHeaderPattern = /^File:\s+(.+)$/gm;
454
491
 
455
492
  let match;
493
+ while ((match = fileHeaderPattern.exec(content)) !== null) {
494
+ const filePath = match[1].trim();
495
+ // Get directory from file path
496
+ const dir = dirname(filePath);
497
+ if (dir && dir !== '.') {
498
+ directories.push(dir);
499
+ if (this.debug) {
500
+ console.log(`[DEBUG] Extracted directory context from File header: ${dir}`);
501
+ }
502
+ }
503
+ }
504
+
505
+ // Pattern 2: Extract directory from listFiles output format: "/path/to/directory:"
506
+ // Matches absolute paths (/path/to/dir:) or current directory markers (.:) or Windows paths (C:\path:) at start of line
507
+ // Very strict to avoid matching random text like ".Something:" or "./Some text:"
508
+ const dirPattern = /^(\/[^\n:]+|[A-Z]:\\[^\n:]+|\.\.?(?:\/[^\n:]+)?):\s*$/gm;
509
+
456
510
  while ((match = dirPattern.exec(content)) !== null) {
457
511
  const dirPath = match[1].trim();
458
- if (dirPath && dirPath.length > 0) {
459
- directories.push(dirPath);
460
- if (this.debug) {
461
- console.log(`[DEBUG] Extracted directory context from listFiles: ${dirPath}`);
512
+
513
+ // Strict validation: must look like an actual filesystem path
514
+ // Reject if contains spaces or other characters that wouldn't be in listFiles output
515
+ const hasInvalidChars = /\s/.test(dirPath); // Contains whitespace
516
+
517
+ // Validate this looks like an actual path, not random text
518
+ // Must be either: absolute path (Unix or Windows), or ./ or ../ followed by valid path chars
519
+ const isValidPath = (
520
+ !hasInvalidChars && (
521
+ dirPath.startsWith('/') || // Unix absolute path
522
+ /^[A-Z]:\\/.test(dirPath) || // Windows absolute path (C:\)
523
+ dirPath === '.' || // Current directory
524
+ dirPath === '..' || // Parent directory
525
+ (dirPath.startsWith('./') && dirPath.length > 2 && !dirPath.includes(' ')) || // ./something (no spaces)
526
+ (dirPath.startsWith('../') && dirPath.length > 3 && !dirPath.includes(' ')) // ../something (no spaces)
527
+ )
528
+ );
529
+
530
+ if (isValidPath) {
531
+ // Avoid duplicates
532
+ if (!directories.includes(dirPath)) {
533
+ directories.push(dirPath);
534
+ if (this.debug) {
535
+ console.log(`[DEBUG] Extracted directory context from listFiles: ${dirPath}`);
536
+ }
462
537
  }
463
538
  }
464
539
  }
@@ -31,12 +31,14 @@ class ACPSession {
31
31
  /**
32
32
  * Get or create ProbeAgent for this session
33
33
  */
34
- getAgent(config = {}) {
34
+ async getAgent(config = {}) {
35
35
  if (!this.agent) {
36
36
  this.agent = new ProbeAgent({
37
37
  sessionId: this.id,
38
38
  ...config
39
39
  });
40
+ // Initialize MCP if enabled
41
+ await this.agent.initialize();
40
42
  }
41
43
  return this.agent;
42
44
  }
@@ -320,20 +322,23 @@ export class ACPServer {
320
322
  }
321
323
 
322
324
  session.touch();
323
-
325
+
324
326
  // Get or create ProbeAgent for this session
325
- const agent = session.getAgent({
327
+ const agent = await session.getAgent({
326
328
  path: this.options.path,
327
329
  provider: this.options.provider,
328
330
  model: this.options.model,
329
331
  allowEdit: this.options.allowEdit,
330
- debug: this.options.debug
332
+ debug: this.options.debug,
333
+ enableMcp: this.options.enableMcp,
334
+ mcpConfig: this.options.mcpConfig,
335
+ mcpConfigPath: this.options.mcpConfigPath
331
336
  });
332
-
337
+
333
338
  if (this.options.debug) {
334
339
  console.error(`[ACP] Processing prompt for session ${params.sessionId}:`, params.message.substring(0, 100));
335
340
  }
336
-
341
+
337
342
  try {
338
343
  // Process the message with the ProbeAgent
339
344
  const response = await agent.answer(params.message);
@@ -428,6 +428,8 @@ class ProbeAgentMcpServer {
428
428
  };
429
429
 
430
430
  this.agent = new ProbeAgent(agentConfig);
431
+ // Initialize MCP if enabled
432
+ await this.agent.initialize();
431
433
  }
432
434
 
433
435
  const agent = this.agent;
@@ -717,7 +719,9 @@ async function main() {
717
719
  };
718
720
 
719
721
  const agent = new ProbeAgent(agentConfig);
720
-
722
+ // Initialize MCP if enabled
723
+ await agent.initialize();
724
+
721
725
  // Execute with tracing if available
722
726
  let result;
723
727
  if (appTracer) {
@@ -242,11 +242,25 @@ export class MCPClientManager {
242
242
  console.error(`[MCP] Calling ${toolName} with args:`, args);
243
243
  }
244
244
 
245
- const result = await clientInfo.client.callTool({
246
- name: tool.originalName,
247
- arguments: args
245
+ // Get timeout from config (default 30 seconds)
246
+ const timeout = this.config?.settings?.timeout || 30000;
247
+
248
+ // Create a timeout promise
249
+ const timeoutPromise = new Promise((_, reject) => {
250
+ setTimeout(() => {
251
+ reject(new Error(`MCP tool call timeout after ${timeout}ms`));
252
+ }, timeout);
248
253
  });
249
254
 
255
+ // Race between the actual call and timeout
256
+ const result = await Promise.race([
257
+ clientInfo.client.callTool({
258
+ name: tool.originalName,
259
+ arguments: args
260
+ }),
261
+ timeoutPromise
262
+ ]);
263
+
250
264
  return result;
251
265
  } catch (error) {
252
266
  console.error(`[MCP] Error calling tool ${toolName}:`, error);
@@ -562,15 +562,18 @@ export async function validateMermaidDiagram(diagram) {
562
562
  const result = validate(diagram);
563
563
 
564
564
  // Maid returns { type: string, errors: array }
565
- // Valid if errors array is empty
566
- if (result.errors && result.errors.length === 0) {
565
+ // Only count actual errors (severity: 'error'), not warnings
566
+ const actualErrors = (result.errors || []).filter(err => err.severity === 'error');
567
+
568
+ // Valid if no actual errors (warnings are OK)
569
+ if (actualErrors.length === 0) {
567
570
  return {
568
571
  isValid: true,
569
572
  diagramType: result.type || 'unknown'
570
573
  };
571
574
  } else {
572
575
  // Format maid errors into a readable error message
573
- const errorMessages = (result.errors || []).map(err => {
576
+ const errorMessages = actualErrors.map(err => {
574
577
  const location = err.line ? `line ${err.line}${err.column ? `:${err.column}` : ''}` : '';
575
578
  return location ? `${location} - ${err.message}` : err.message;
576
579
  });
@@ -580,7 +583,7 @@ export async function validateMermaidDiagram(diagram) {
580
583
  diagramType: result.type || 'unknown',
581
584
  error: errorMessages[0] || 'Validation failed',
582
585
  detailedError: errorMessages.join('\n'),
583
- errors: result.errors || [] // Include raw maid errors for AI fixing
586
+ errors: actualErrors // Include only actual errors for AI fixing
584
587
  };
585
588
  }
586
589