@probelabs/probe 0.6.0-rc117 → 0.6.0-rc119
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 +59 -4
- package/build/agent/index.js +3557 -158
- package/build/agent/probeTool.js +32 -6
- package/build/agent/tokenCounter.js +7 -7
- package/cjs/agent/ProbeAgent.cjs +3575 -176
- package/cjs/index.cjs +3560 -161
- package/package.json +3 -3
- package/src/agent/ProbeAgent.js +59 -4
- package/src/agent/probeTool.js +32 -6
- package/src/agent/tokenCounter.js +7 -7
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-rc119",
|
|
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,13 +80,13 @@
|
|
|
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",
|
|
87
87
|
"glob": "^10.3.10",
|
|
88
|
+
"gpt-tokenizer": "^3.0.1",
|
|
88
89
|
"tar": "^6.2.0",
|
|
89
|
-
"tiktoken": "^1.0.20",
|
|
90
90
|
"zod": "^3.24.2"
|
|
91
91
|
},
|
|
92
92
|
"devDependencies": {
|
package/src/agent/ProbeAgent.js
CHANGED
|
@@ -380,6 +380,9 @@ export class ProbeAgent {
|
|
|
380
380
|
async processImageReferences(content) {
|
|
381
381
|
if (!content) return;
|
|
382
382
|
|
|
383
|
+
// First, try to parse listFiles output format to extract directory context
|
|
384
|
+
const listFilesDirectories = this.extractListFilesDirectories(content);
|
|
385
|
+
|
|
383
386
|
// Enhanced pattern to detect image file mentions in various contexts
|
|
384
387
|
// Looks for: "image", "file", "screenshot", etc. followed by path-like strings with image extensions
|
|
385
388
|
const extensionsPattern = `(?:${SUPPORTED_IMAGE_EXTENSIONS.join('|')})`;
|
|
@@ -414,10 +417,55 @@ export class ProbeAgent {
|
|
|
414
417
|
|
|
415
418
|
// Process each found path
|
|
416
419
|
for (const imagePath of foundPaths) {
|
|
417
|
-
|
|
420
|
+
// Try to resolve the path with directory context from listFiles output
|
|
421
|
+
let resolvedPath = imagePath;
|
|
422
|
+
|
|
423
|
+
// If the path is just a filename (no directory separator), try to find it in listFiles directories
|
|
424
|
+
if (!imagePath.includes('/') && !imagePath.includes('\\')) {
|
|
425
|
+
for (const dir of listFilesDirectories) {
|
|
426
|
+
const potentialPath = resolve(dir, imagePath);
|
|
427
|
+
// Check if this file exists by attempting to load it
|
|
428
|
+
const loaded = await this.loadImageIfValid(potentialPath);
|
|
429
|
+
if (loaded) {
|
|
430
|
+
// Successfully loaded with this directory context
|
|
431
|
+
if (this.debug) {
|
|
432
|
+
console.log(`[DEBUG] Resolved ${imagePath} to ${potentialPath} using listFiles context`);
|
|
433
|
+
}
|
|
434
|
+
break; // Found it, no need to try other directories
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
} else {
|
|
438
|
+
// Path already has directory info, load as-is
|
|
439
|
+
await this.loadImageIfValid(resolvedPath);
|
|
440
|
+
}
|
|
418
441
|
}
|
|
419
442
|
}
|
|
420
443
|
|
|
444
|
+
/**
|
|
445
|
+
* Extract directory paths from listFiles tool output
|
|
446
|
+
* @param {string} content - Tool output content
|
|
447
|
+
* @returns {string[]} - Array of directory paths
|
|
448
|
+
*/
|
|
449
|
+
extractListFilesDirectories(content) {
|
|
450
|
+
const directories = [];
|
|
451
|
+
|
|
452
|
+
// Pattern to match listFiles output format: "/path/to/directory:" at the start of a line
|
|
453
|
+
const dirPattern = /^([^\n:]+):\s*$/gm;
|
|
454
|
+
|
|
455
|
+
let match;
|
|
456
|
+
while ((match = dirPattern.exec(content)) !== null) {
|
|
457
|
+
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}`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return directories;
|
|
467
|
+
}
|
|
468
|
+
|
|
421
469
|
/**
|
|
422
470
|
* Load and cache an image if it's valid and accessible
|
|
423
471
|
* @param {string} imagePath - Path to the image file
|
|
@@ -644,16 +692,23 @@ export class ProbeAgent {
|
|
|
644
692
|
this.mcpBridge = new MCPXmlBridge({ debug: this.debug });
|
|
645
693
|
await this.mcpBridge.initialize(mcpConfig);
|
|
646
694
|
|
|
647
|
-
const
|
|
695
|
+
const mcpToolNames = this.mcpBridge.getToolNames();
|
|
696
|
+
const mcpToolCount = mcpToolNames.length;
|
|
648
697
|
if (mcpToolCount > 0) {
|
|
649
698
|
if (this.debug) {
|
|
650
|
-
console.
|
|
699
|
+
console.error('\n[DEBUG] ========================================');
|
|
700
|
+
console.error(`[DEBUG] MCP Tools Initialized (${mcpToolCount} tools)`);
|
|
701
|
+
console.error('[DEBUG] Available MCP tools:');
|
|
702
|
+
for (const toolName of mcpToolNames) {
|
|
703
|
+
console.error(`[DEBUG] - ${toolName}`);
|
|
704
|
+
}
|
|
705
|
+
console.error('[DEBUG] ========================================\n');
|
|
651
706
|
}
|
|
652
707
|
} else {
|
|
653
708
|
// For backward compatibility: if no tools were loaded, set bridge to null
|
|
654
709
|
// This maintains the behavior expected by existing tests
|
|
655
710
|
if (this.debug) {
|
|
656
|
-
console.
|
|
711
|
+
console.error('[DEBUG] No MCP tools loaded, setting bridge to null');
|
|
657
712
|
}
|
|
658
713
|
this.mcpBridge = null;
|
|
659
714
|
}
|
package/src/agent/probeTool.js
CHANGED
|
@@ -223,9 +223,22 @@ export const listFilesTool = {
|
|
|
223
223
|
|
|
224
224
|
// Security: Validate path to prevent traversal attacks
|
|
225
225
|
const secureBaseDir = path.resolve(baseCwd);
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
226
|
+
|
|
227
|
+
// If directory is absolute, check if it's within the secure base directory
|
|
228
|
+
// If it's relative, resolve it against the secure base directory
|
|
229
|
+
let targetDir;
|
|
230
|
+
if (path.isAbsolute(directory)) {
|
|
231
|
+
targetDir = path.resolve(directory);
|
|
232
|
+
// Check if the absolute path is within the secure base directory
|
|
233
|
+
if (!targetDir.startsWith(secureBaseDir + path.sep) && targetDir !== secureBaseDir) {
|
|
234
|
+
throw new Error(`Path traversal attempt detected. Cannot access directory outside workspace: ${directory}`);
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
targetDir = path.resolve(secureBaseDir, directory);
|
|
238
|
+
// Double-check the resolved path is still within the secure base directory
|
|
239
|
+
if (!targetDir.startsWith(secureBaseDir + path.sep) && targetDir !== secureBaseDir) {
|
|
240
|
+
throw new Error(`Path traversal attempt detected. Access denied: ${directory}`);
|
|
241
|
+
}
|
|
229
242
|
}
|
|
230
243
|
|
|
231
244
|
const debug = process.env.DEBUG === '1';
|
|
@@ -309,9 +322,22 @@ export const searchFilesTool = {
|
|
|
309
322
|
// Security: Validate path to prevent traversal attacks
|
|
310
323
|
const baseCwd = workingDirectory || process.cwd();
|
|
311
324
|
const secureBaseDir = path.resolve(baseCwd);
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
325
|
+
|
|
326
|
+
// If directory is absolute, check if it's within the secure base directory
|
|
327
|
+
// If it's relative, resolve it against the secure base directory
|
|
328
|
+
let targetDir;
|
|
329
|
+
if (path.isAbsolute(directory)) {
|
|
330
|
+
targetDir = path.resolve(directory);
|
|
331
|
+
// Check if the absolute path is within the secure base directory
|
|
332
|
+
if (!targetDir.startsWith(secureBaseDir + path.sep) && targetDir !== secureBaseDir) {
|
|
333
|
+
throw new Error(`Path traversal attempt detected. Cannot access directory outside workspace: ${directory}`);
|
|
334
|
+
}
|
|
335
|
+
} else {
|
|
336
|
+
targetDir = path.resolve(secureBaseDir, directory);
|
|
337
|
+
// Double-check the resolved path is still within the secure base directory
|
|
338
|
+
if (!targetDir.startsWith(secureBaseDir + path.sep) && targetDir !== secureBaseDir) {
|
|
339
|
+
throw new Error(`Path traversal attempt detected. Access denied: ${directory}`);
|
|
340
|
+
}
|
|
315
341
|
}
|
|
316
342
|
|
|
317
343
|
// Validate pattern complexity to prevent DoS
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { encode } from 'gpt-tokenizer';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* TokenCounter class to track token usage in the agent
|
|
5
5
|
*/
|
|
6
6
|
export class TokenCounter {
|
|
7
7
|
constructor() {
|
|
8
|
-
// Initialize the tokenizer with
|
|
8
|
+
// Initialize the tokenizer with gpt-tokenizer (works for both Claude and GPT models)
|
|
9
9
|
try {
|
|
10
|
-
//
|
|
11
|
-
this.tokenizer =
|
|
10
|
+
// gpt-tokenizer uses encode directly, no need to initialize
|
|
11
|
+
this.tokenizer = encode;
|
|
12
12
|
|
|
13
13
|
// Context window tracking
|
|
14
14
|
this.contextSize = 0; // Current size based on history
|
|
@@ -30,7 +30,7 @@ export class TokenCounter {
|
|
|
30
30
|
|
|
31
31
|
} catch (error) {
|
|
32
32
|
console.error('Error initializing tokenizer:', error);
|
|
33
|
-
// Fallback to a simple token counting method if
|
|
33
|
+
// Fallback to a simple token counting method if gpt-tokenizer fails
|
|
34
34
|
this.tokenizer = null;
|
|
35
35
|
this.contextSize = 0;
|
|
36
36
|
this.requestTokens = 0;
|
|
@@ -49,7 +49,7 @@ export class TokenCounter {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
|
-
* Count tokens in a string using
|
|
52
|
+
* Count tokens in a string using gpt-tokenizer or fallback method
|
|
53
53
|
* @param {string} text - The text to count tokens for
|
|
54
54
|
* @returns {number} - The number of tokens
|
|
55
55
|
*/
|
|
@@ -60,7 +60,7 @@ export class TokenCounter {
|
|
|
60
60
|
|
|
61
61
|
if (this.tokenizer) {
|
|
62
62
|
try {
|
|
63
|
-
const tokens = this.tokenizer
|
|
63
|
+
const tokens = this.tokenizer(text);
|
|
64
64
|
return tokens.length;
|
|
65
65
|
} catch (error) {
|
|
66
66
|
// Fallback to a simple approximation (1 token ≈ 4 characters)
|