@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.
@@ -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
- const targetDir = path.resolve(secureBaseDir, directory);
227
- if (!targetDir.startsWith(secureBaseDir + path.sep) && targetDir !== secureBaseDir) {
228
- throw new Error('Path traversal attempt detected. Access denied.');
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
- const files = await listFilesByLevel({
233
- directory: targetDir,
234
- maxFiles: 100,
235
- respectGitignore: !process.env.PROBE_NO_GITIGNORE || process.env.PROBE_NO_GITIGNORE === '',
236
- cwd: secureBaseDir
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
- return files;
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
- const targetDir = path.resolve(secureBaseDir, directory);
259
- if (!targetDir.startsWith(secureBaseDir + path.sep) && targetDir !== secureBaseDir) {
260
- throw new Error('Path traversal attempt detected. Access denied.');
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
- const files = await glob(pattern, options);
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: 2, // Limit mermaid fixing to 2 iterations to prevent long loops
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 { get_encoding } from 'tiktoken';
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 cl100k_base encoding (works for both Claude and GPT models)
8
+ // Initialize the tokenizer with gpt-tokenizer (works for both Claude and GPT models)
9
9
  try {
10
- // Initialize tokenizer
11
- this.tokenizer = get_encoding('cl100k_base');
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 tiktoken fails
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 tiktoken or fallback method
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.encode(text);
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,