@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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@probelabs/probe",
|
|
3
|
-
"version": "0.6.0-
|
|
3
|
+
"version": "0.6.0-rc118",
|
|
4
4
|
"description": "Node.js wrapper for the probe code search tool",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"module": "src/index.js",
|
|
@@ -85,8 +85,8 @@
|
|
|
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
|
@@ -188,9 +188,23 @@ export class ProbeAgent {
|
|
|
188
188
|
if (this.enableBash && wrappedTools.bashToolInstance) {
|
|
189
189
|
this.toolImplementations.bash = wrappedTools.bashToolInstance;
|
|
190
190
|
}
|
|
191
|
-
|
|
191
|
+
|
|
192
192
|
// Store wrapped tools for ACP system
|
|
193
193
|
this.wrappedTools = wrappedTools;
|
|
194
|
+
|
|
195
|
+
// Log available tools in debug mode
|
|
196
|
+
if (this.debug) {
|
|
197
|
+
console.error('\n[DEBUG] ========================================');
|
|
198
|
+
console.error('[DEBUG] ProbeAgent Tools Initialized');
|
|
199
|
+
console.error('[DEBUG] Session ID:', this.sessionId);
|
|
200
|
+
console.error('[DEBUG] Available tools:');
|
|
201
|
+
for (const toolName of Object.keys(this.toolImplementations)) {
|
|
202
|
+
console.error(`[DEBUG] - ${toolName}`);
|
|
203
|
+
}
|
|
204
|
+
console.error('[DEBUG] Allowed folders:', this.allowedFolders);
|
|
205
|
+
console.error('[DEBUG] Outline mode:', this.outline);
|
|
206
|
+
console.error('[DEBUG] ========================================\n');
|
|
207
|
+
}
|
|
194
208
|
}
|
|
195
209
|
|
|
196
210
|
/**
|
|
@@ -366,6 +380,9 @@ export class ProbeAgent {
|
|
|
366
380
|
async processImageReferences(content) {
|
|
367
381
|
if (!content) return;
|
|
368
382
|
|
|
383
|
+
// First, try to parse listFiles output format to extract directory context
|
|
384
|
+
const listFilesDirectories = this.extractListFilesDirectories(content);
|
|
385
|
+
|
|
369
386
|
// Enhanced pattern to detect image file mentions in various contexts
|
|
370
387
|
// Looks for: "image", "file", "screenshot", etc. followed by path-like strings with image extensions
|
|
371
388
|
const extensionsPattern = `(?:${SUPPORTED_IMAGE_EXTENSIONS.join('|')})`;
|
|
@@ -400,10 +417,55 @@ export class ProbeAgent {
|
|
|
400
417
|
|
|
401
418
|
// Process each found path
|
|
402
419
|
for (const imagePath of foundPaths) {
|
|
403
|
-
|
|
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
|
+
}
|
|
404
441
|
}
|
|
405
442
|
}
|
|
406
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
|
+
|
|
407
469
|
/**
|
|
408
470
|
* Load and cache an image if it's valid and accessible
|
|
409
471
|
* @param {string} imagePath - Path to the image file
|
|
@@ -630,16 +692,23 @@ export class ProbeAgent {
|
|
|
630
692
|
this.mcpBridge = new MCPXmlBridge({ debug: this.debug });
|
|
631
693
|
await this.mcpBridge.initialize(mcpConfig);
|
|
632
694
|
|
|
633
|
-
const
|
|
695
|
+
const mcpToolNames = this.mcpBridge.getToolNames();
|
|
696
|
+
const mcpToolCount = mcpToolNames.length;
|
|
634
697
|
if (mcpToolCount > 0) {
|
|
635
698
|
if (this.debug) {
|
|
636
|
-
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');
|
|
637
706
|
}
|
|
638
707
|
} else {
|
|
639
708
|
// For backward compatibility: if no tools were loaded, set bridge to null
|
|
640
709
|
// This maintains the behavior expected by existing tests
|
|
641
710
|
if (this.debug) {
|
|
642
|
-
console.
|
|
711
|
+
console.error('[DEBUG] No MCP tools loaded, setting bridge to null');
|
|
643
712
|
}
|
|
644
713
|
this.mcpBridge = null;
|
|
645
714
|
}
|
|
@@ -1117,34 +1186,74 @@ When troubleshooting:
|
|
|
1117
1186
|
if (type === 'mcp' && this.mcpBridge && this.mcpBridge.isMcpTool(toolName)) {
|
|
1118
1187
|
// Execute MCP tool
|
|
1119
1188
|
try {
|
|
1120
|
-
|
|
1189
|
+
// Log MCP tool execution in debug mode
|
|
1190
|
+
if (this.debug) {
|
|
1191
|
+
console.error(`\n[DEBUG] ========================================`);
|
|
1192
|
+
console.error(`[DEBUG] Executing MCP tool: ${toolName}`);
|
|
1193
|
+
console.error(`[DEBUG] Arguments:`);
|
|
1194
|
+
for (const [key, value] of Object.entries(params)) {
|
|
1195
|
+
const displayValue = typeof value === 'string' && value.length > 100
|
|
1196
|
+
? value.substring(0, 100) + '...'
|
|
1197
|
+
: value;
|
|
1198
|
+
console.error(`[DEBUG] ${key}: ${JSON.stringify(displayValue)}`);
|
|
1199
|
+
}
|
|
1200
|
+
console.error(`[DEBUG] ========================================\n`);
|
|
1201
|
+
}
|
|
1121
1202
|
|
|
1122
1203
|
// Execute MCP tool through the bridge
|
|
1123
1204
|
const executionResult = await this.mcpBridge.mcpTools[toolName].execute(params);
|
|
1124
1205
|
|
|
1125
1206
|
const toolResultContent = typeof executionResult === 'string' ? executionResult : JSON.stringify(executionResult, null, 2);
|
|
1126
|
-
|
|
1207
|
+
|
|
1208
|
+
// Log MCP tool result in debug mode
|
|
1127
1209
|
if (this.debug) {
|
|
1128
|
-
|
|
1210
|
+
const preview = toolResultContent.length > 500 ? toolResultContent.substring(0, 500) + '...' : toolResultContent;
|
|
1211
|
+
console.error(`[DEBUG] ========================================`);
|
|
1212
|
+
console.error(`[DEBUG] MCP tool '${toolName}' completed successfully`);
|
|
1213
|
+
console.error(`[DEBUG] Result preview:`);
|
|
1214
|
+
console.error(preview);
|
|
1215
|
+
console.error(`[DEBUG] ========================================\n`);
|
|
1129
1216
|
}
|
|
1130
1217
|
|
|
1131
1218
|
currentMessages.push({ role: 'user', content: `<tool_result>\n${toolResultContent}\n</tool_result>` });
|
|
1132
1219
|
} catch (error) {
|
|
1133
1220
|
console.error(`Error executing MCP tool ${toolName}:`, error);
|
|
1134
1221
|
const toolResultContent = `Error executing MCP tool ${toolName}: ${error.message}`;
|
|
1135
|
-
|
|
1222
|
+
|
|
1223
|
+
// Log MCP tool error in debug mode
|
|
1224
|
+
if (this.debug) {
|
|
1225
|
+
console.error(`[DEBUG] ========================================`);
|
|
1226
|
+
console.error(`[DEBUG] MCP tool '${toolName}' failed with error:`);
|
|
1227
|
+
console.error(`[DEBUG] ${error.message}`);
|
|
1228
|
+
console.error(`[DEBUG] ========================================\n`);
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1136
1231
|
currentMessages.push({ role: 'user', content: `<tool_result>\n${toolResultContent}\n</tool_result>` });
|
|
1137
1232
|
}
|
|
1138
1233
|
} else if (this.toolImplementations[toolName]) {
|
|
1139
1234
|
// Execute native tool
|
|
1140
1235
|
try {
|
|
1141
1236
|
// Add sessionId and workingDirectory to params for tool execution
|
|
1142
|
-
const toolParams = {
|
|
1143
|
-
...params,
|
|
1237
|
+
const toolParams = {
|
|
1238
|
+
...params,
|
|
1144
1239
|
sessionId: this.sessionId,
|
|
1145
1240
|
workingDirectory: (this.allowedFolders && this.allowedFolders[0]) || process.cwd()
|
|
1146
1241
|
};
|
|
1147
|
-
|
|
1242
|
+
|
|
1243
|
+
// Log tool execution in debug mode
|
|
1244
|
+
if (this.debug) {
|
|
1245
|
+
console.error(`\n[DEBUG] ========================================`);
|
|
1246
|
+
console.error(`[DEBUG] Executing tool: ${toolName}`);
|
|
1247
|
+
console.error(`[DEBUG] Arguments:`);
|
|
1248
|
+
for (const [key, value] of Object.entries(params)) {
|
|
1249
|
+
const displayValue = typeof value === 'string' && value.length > 100
|
|
1250
|
+
? value.substring(0, 100) + '...'
|
|
1251
|
+
: value;
|
|
1252
|
+
console.error(`[DEBUG] ${key}: ${JSON.stringify(displayValue)}`);
|
|
1253
|
+
}
|
|
1254
|
+
console.error(`[DEBUG] ========================================\n`);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1148
1257
|
// Emit tool start event
|
|
1149
1258
|
this.events.emit('toolCall', {
|
|
1150
1259
|
timestamp: new Date().toISOString(),
|
|
@@ -1196,6 +1305,18 @@ When troubleshooting:
|
|
|
1196
1305
|
toolResult = await executeToolCall();
|
|
1197
1306
|
}
|
|
1198
1307
|
|
|
1308
|
+
// Log tool result in debug mode
|
|
1309
|
+
if (this.debug) {
|
|
1310
|
+
const resultPreview = typeof toolResult === 'string'
|
|
1311
|
+
? (toolResult.length > 500 ? toolResult.substring(0, 500) + '...' : toolResult)
|
|
1312
|
+
: (toolResult ? JSON.stringify(toolResult, null, 2).substring(0, 500) + '...' : 'No Result');
|
|
1313
|
+
console.error(`[DEBUG] ========================================`);
|
|
1314
|
+
console.error(`[DEBUG] Tool '${toolName}' completed successfully`);
|
|
1315
|
+
console.error(`[DEBUG] Result preview:`);
|
|
1316
|
+
console.error(resultPreview);
|
|
1317
|
+
console.error(`[DEBUG] ========================================\n`);
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1199
1320
|
// Emit tool success event
|
|
1200
1321
|
this.events.emit('toolCall', {
|
|
1201
1322
|
timestamp: new Date().toISOString(),
|
|
@@ -1206,8 +1327,16 @@ When troubleshooting:
|
|
|
1206
1327
|
: (toolResult ? JSON.stringify(toolResult).substring(0, 200) + '...' : 'No Result'),
|
|
1207
1328
|
status: 'completed'
|
|
1208
1329
|
});
|
|
1209
|
-
|
|
1330
|
+
|
|
1210
1331
|
} catch (toolError) {
|
|
1332
|
+
// Log tool error in debug mode
|
|
1333
|
+
if (this.debug) {
|
|
1334
|
+
console.error(`[DEBUG] ========================================`);
|
|
1335
|
+
console.error(`[DEBUG] Tool '${toolName}' failed with error:`);
|
|
1336
|
+
console.error(`[DEBUG] ${toolError.message}`);
|
|
1337
|
+
console.error(`[DEBUG] ========================================\n`);
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1211
1340
|
// Emit tool error event
|
|
1212
1341
|
this.events.emit('toolCall', {
|
|
1213
1342
|
timestamp: new Date().toISOString(),
|
|
@@ -1262,7 +1391,23 @@ When troubleshooting:
|
|
|
1262
1391
|
}
|
|
1263
1392
|
}
|
|
1264
1393
|
} else {
|
|
1265
|
-
// No tool call found
|
|
1394
|
+
// No tool call found
|
|
1395
|
+
// Special case: If response contains a mermaid code block and no schema was provided,
|
|
1396
|
+
// treat it as a valid completion (for mermaid diagram fixing workflow)
|
|
1397
|
+
const hasMermaidCodeBlock = /```mermaid\s*\n[\s\S]*?\n```/.test(assistantResponseContent);
|
|
1398
|
+
const hasNoSchemaOrTools = !options.schema && validTools.length === 0;
|
|
1399
|
+
|
|
1400
|
+
if (hasMermaidCodeBlock && hasNoSchemaOrTools) {
|
|
1401
|
+
// Accept mermaid code block as final answer for diagram fixing
|
|
1402
|
+
finalResult = assistantResponseContent;
|
|
1403
|
+
completionAttempted = true;
|
|
1404
|
+
if (this.debug) {
|
|
1405
|
+
console.error(`[DEBUG] Accepting mermaid code block as valid completion (no schema, no tools)`);
|
|
1406
|
+
}
|
|
1407
|
+
break;
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
// Add assistant response and ask for tool usage
|
|
1266
1411
|
currentMessages.push({ role: 'assistant', content: assistantResponseContent });
|
|
1267
1412
|
|
|
1268
1413
|
// Build appropriate reminder message based on whether schema is provided
|
package/src/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}`);
|
package/src/agent/schemaUtils.js
CHANGED
|
@@ -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/src/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,
|