@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/build/agent/ProbeAgent.js +85 -10
- package/build/agent/acp/server.js +11 -6
- package/build/agent/index.js +2447 -147
- package/build/agent/mcp/client.js +17 -3
- package/build/agent/schemaUtils.js +7 -4
- package/cjs/agent/ProbeAgent.cjs +2437 -143
- package/cjs/index.cjs +2437 -143
- package/package.json +2 -2
- package/src/agent/ProbeAgent.js +85 -10
- package/src/agent/acp/server.js +11 -6
- package/src/agent/index.js +5 -1
- package/src/agent/mcp/client.js +17 -3
- package/src/agent/schemaUtils.js +7 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@probelabs/probe",
|
|
3
|
-
"version": "0.6.0-
|
|
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.
|
|
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",
|
package/src/agent/ProbeAgent.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
453
|
-
|
|
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
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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
|
}
|
package/src/agent/acp/server.js
CHANGED
|
@@ -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);
|
package/src/agent/index.js
CHANGED
|
@@ -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) {
|
package/src/agent/mcp/client.js
CHANGED
|
@@ -242,11 +242,25 @@ export class MCPClientManager {
|
|
|
242
242
|
console.error(`[MCP] Calling ${toolName} with args:`, args);
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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);
|
package/src/agent/schemaUtils.js
CHANGED
|
@@ -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
|
-
//
|
|
566
|
-
|
|
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 =
|
|
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:
|
|
586
|
+
errors: actualErrors // Include only actual errors for AI fixing
|
|
584
587
|
};
|
|
585
588
|
}
|
|
586
589
|
|