@probelabs/probe 0.6.0-rc118 → 0.6.0-rc120

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probelabs/probe",
3
- "version": "0.6.0-rc118",
3
+ "version": "0.6.0-rc120",
4
4
  "description": "Node.js wrapper for the probe code search tool",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -80,7 +80,7 @@
80
80
  "@opentelemetry/sdk-node": "^0.203.0",
81
81
  "@opentelemetry/sdk-trace-base": "^1.30.0",
82
82
  "@opentelemetry/semantic-conventions": "^1.36.0",
83
- "@probelabs/maid": "^0.0.7",
83
+ "@probelabs/maid": "^0.0.8",
84
84
  "ai": "^5.0.0",
85
85
  "axios": "^1.8.3",
86
86
  "fs-extra": "^11.1.1",
@@ -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