@probelabs/probe 0.6.0-rc116 → 0.6.0-rc118
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 +159 -14
- package/build/agent/index.js +1529 -241
- package/build/agent/probeTool.js +125 -24
- package/build/agent/schemaUtils.js +1 -1
- package/build/agent/tokenCounter.js +7 -7
- package/build/index.js +4 -0
- package/cjs/agent/ProbeAgent.cjs +1546 -258
- package/cjs/index.cjs +1338 -46
- package/package.json +2 -2
- package/src/agent/ProbeAgent.js +159 -14
- package/src/agent/probeTool.js +125 -24
- package/src/agent/schemaUtils.js +1 -1
- package/src/agent/tokenCounter.js +7 -7
- package/src/index.js +4 -0
package/build/agent/probeTool.js
CHANGED
|
@@ -213,65 +213,166 @@ export function createWrappedTools(baseTools) {
|
|
|
213
213
|
return wrappedTools;
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
// Simple file listing tool
|
|
216
|
+
// Simple file listing tool with ls-like formatted output
|
|
217
217
|
export const listFilesTool = {
|
|
218
218
|
execute: async (params) => {
|
|
219
219
|
const { directory = '.', workingDirectory } = params;
|
|
220
|
-
|
|
220
|
+
|
|
221
221
|
// Use the provided working directory, or fall back to process.cwd()
|
|
222
222
|
const baseCwd = workingDirectory || process.cwd();
|
|
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
|
+
|
|
244
|
+
const debug = process.env.DEBUG === '1';
|
|
245
|
+
|
|
246
|
+
if (debug) {
|
|
247
|
+
console.log(`[DEBUG] Listing files in directory: ${targetDir}`);
|
|
248
|
+
}
|
|
249
|
+
|
|
231
250
|
try {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
251
|
+
// Read the directory contents
|
|
252
|
+
const files = await fsPromises.readdir(targetDir, { withFileTypes: true });
|
|
253
|
+
|
|
254
|
+
// Format size for human readability
|
|
255
|
+
const formatSize = (size) => {
|
|
256
|
+
if (size < 1024) return `${size}B`;
|
|
257
|
+
if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)}K`;
|
|
258
|
+
if (size < 1024 * 1024 * 1024) return `${(size / (1024 * 1024)).toFixed(1)}M`;
|
|
259
|
+
return `${(size / (1024 * 1024 * 1024)).toFixed(1)}G`;
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// Format the results as ls-style output
|
|
263
|
+
const entries = await Promise.all(files.map(async (file) => {
|
|
264
|
+
const isDirectory = file.isDirectory();
|
|
265
|
+
const fullPath = path.join(targetDir, file.name);
|
|
266
|
+
|
|
267
|
+
let size = 0;
|
|
268
|
+
try {
|
|
269
|
+
const stats = await fsPromises.stat(fullPath);
|
|
270
|
+
size = stats.size;
|
|
271
|
+
} catch (statError) {
|
|
272
|
+
if (debug) {
|
|
273
|
+
console.log(`[DEBUG] Could not stat file ${file.name}:`, statError.message);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
name: file.name,
|
|
279
|
+
isDirectory,
|
|
280
|
+
size
|
|
281
|
+
};
|
|
282
|
+
}));
|
|
283
|
+
|
|
284
|
+
// Sort: directories first, then files, both alphabetically
|
|
285
|
+
entries.sort((a, b) => {
|
|
286
|
+
if (a.isDirectory && !b.isDirectory) return -1;
|
|
287
|
+
if (!a.isDirectory && b.isDirectory) return 1;
|
|
288
|
+
return a.name.localeCompare(b.name);
|
|
237
289
|
});
|
|
238
|
-
|
|
239
|
-
|
|
290
|
+
|
|
291
|
+
// Format entries
|
|
292
|
+
const formatted = entries.map(entry => {
|
|
293
|
+
const type = entry.isDirectory ? 'dir ' : 'file';
|
|
294
|
+
const sizeStr = formatSize(entry.size).padStart(8);
|
|
295
|
+
return `${type} ${sizeStr} ${entry.name}`;
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
if (debug) {
|
|
299
|
+
console.log(`[DEBUG] Found ${entries.length} files/directories in ${targetDir}`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Return as formatted text output
|
|
303
|
+
const header = `${targetDir}:\n`;
|
|
304
|
+
const output = header + formatted.join('\n');
|
|
305
|
+
|
|
306
|
+
return output;
|
|
240
307
|
} catch (error) {
|
|
241
308
|
throw new Error(`Failed to list files: ${error.message}`);
|
|
242
309
|
}
|
|
243
310
|
}
|
|
244
311
|
};
|
|
245
312
|
|
|
246
|
-
// Simple file search tool
|
|
313
|
+
// Simple file search tool with timeout protection
|
|
247
314
|
export const searchFilesTool = {
|
|
248
315
|
execute: async (params) => {
|
|
249
316
|
const { pattern, directory = '.', recursive = true, workingDirectory } = params;
|
|
250
|
-
|
|
317
|
+
|
|
251
318
|
if (!pattern) {
|
|
252
319
|
throw new Error('Pattern is required for file search');
|
|
253
320
|
}
|
|
254
|
-
|
|
321
|
+
|
|
255
322
|
// Security: Validate path to prevent traversal attacks
|
|
256
323
|
const baseCwd = workingDirectory || process.cwd();
|
|
257
324
|
const secureBaseDir = path.resolve(baseCwd);
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
+
}
|
|
261
341
|
}
|
|
262
|
-
|
|
342
|
+
|
|
343
|
+
// Validate pattern complexity to prevent DoS
|
|
344
|
+
if (pattern.includes('**/**') || pattern.split('*').length > 10) {
|
|
345
|
+
throw new Error('Pattern too complex. Please use a simpler glob pattern.');
|
|
346
|
+
}
|
|
347
|
+
|
|
263
348
|
try {
|
|
264
349
|
const options = {
|
|
265
350
|
cwd: targetDir,
|
|
266
351
|
ignore: ['node_modules/**', '.git/**'],
|
|
267
352
|
absolute: false
|
|
268
353
|
};
|
|
269
|
-
|
|
354
|
+
|
|
270
355
|
if (!recursive) {
|
|
271
356
|
options.deep = 1;
|
|
272
357
|
}
|
|
273
|
-
|
|
274
|
-
|
|
358
|
+
|
|
359
|
+
// Create a timeout promise (10 seconds)
|
|
360
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
361
|
+
setTimeout(() => reject(new Error('Search operation timed out after 10 seconds')), 10000);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// Race glob against timeout
|
|
365
|
+
const files = await Promise.race([
|
|
366
|
+
glob(pattern, options),
|
|
367
|
+
timeoutPromise
|
|
368
|
+
]);
|
|
369
|
+
|
|
370
|
+
// Limit results to prevent memory issues
|
|
371
|
+
const maxResults = 1000;
|
|
372
|
+
if (files.length > maxResults) {
|
|
373
|
+
return files.slice(0, maxResults);
|
|
374
|
+
}
|
|
375
|
+
|
|
275
376
|
return files;
|
|
276
377
|
} catch (error) {
|
|
277
378
|
throw new Error(`Failed to search files: ${error.message}`);
|
|
@@ -813,7 +813,7 @@ When presented with a broken Mermaid diagram, analyze it thoroughly and provide
|
|
|
813
813
|
debug: this.options.debug,
|
|
814
814
|
tracer: this.options.tracer,
|
|
815
815
|
allowEdit: this.options.allowEdit,
|
|
816
|
-
maxIterations:
|
|
816
|
+
maxIterations: 10, // Allow more iterations for mermaid fixing to handle complex diagrams
|
|
817
817
|
disableMermaidValidation: true // CRITICAL: Disable mermaid validation in nested agent to prevent infinite recursion
|
|
818
818
|
});
|
|
819
819
|
}
|
|
@@ -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)
|
package/build/index.js
CHANGED
|
@@ -34,6 +34,7 @@ import { searchTool, queryTool, extractTool, delegateTool } from './tools/vercel
|
|
|
34
34
|
import { bashTool } from './tools/bash.js';
|
|
35
35
|
import { ProbeAgent } from './agent/ProbeAgent.js';
|
|
36
36
|
import { SimpleTelemetry, SimpleAppTracer, initializeSimpleTelemetryFromOptions } from './agent/simpleTelemetry.js';
|
|
37
|
+
import { listFilesToolInstance, searchFilesToolInstance } from './agent/probeTool.js';
|
|
37
38
|
|
|
38
39
|
export {
|
|
39
40
|
search,
|
|
@@ -57,6 +58,9 @@ export {
|
|
|
57
58
|
extractTool,
|
|
58
59
|
delegateTool,
|
|
59
60
|
bashTool,
|
|
61
|
+
// Export tool instances
|
|
62
|
+
listFilesToolInstance,
|
|
63
|
+
searchFilesToolInstance,
|
|
60
64
|
// Export schemas
|
|
61
65
|
searchSchema,
|
|
62
66
|
querySchema,
|