@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.
@@ -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);