@probelabs/probe 0.6.0-rc115 → 0.6.0-rc117

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probelabs/probe",
3
- "version": "0.6.0-rc115",
3
+ "version": "0.6.0-rc117",
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.6",
83
+ "@probelabs/maid": "^0.0.7",
84
84
  "ai": "^5.0.0",
85
85
  "axios": "^1.8.3",
86
86
  "fs-extra": "^11.1.1",
@@ -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
  /**
@@ -1117,34 +1131,74 @@ When troubleshooting:
1117
1131
  if (type === 'mcp' && this.mcpBridge && this.mcpBridge.isMcpTool(toolName)) {
1118
1132
  // Execute MCP tool
1119
1133
  try {
1120
- if (this.debug) console.log(`[DEBUG] Executing MCP tool '${toolName}' with params:`, params);
1134
+ // Log MCP tool execution in debug mode
1135
+ if (this.debug) {
1136
+ console.error(`\n[DEBUG] ========================================`);
1137
+ console.error(`[DEBUG] Executing MCP tool: ${toolName}`);
1138
+ console.error(`[DEBUG] Arguments:`);
1139
+ for (const [key, value] of Object.entries(params)) {
1140
+ const displayValue = typeof value === 'string' && value.length > 100
1141
+ ? value.substring(0, 100) + '...'
1142
+ : value;
1143
+ console.error(`[DEBUG] ${key}: ${JSON.stringify(displayValue)}`);
1144
+ }
1145
+ console.error(`[DEBUG] ========================================\n`);
1146
+ }
1121
1147
 
1122
1148
  // Execute MCP tool through the bridge
1123
1149
  const executionResult = await this.mcpBridge.mcpTools[toolName].execute(params);
1124
1150
 
1125
1151
  const toolResultContent = typeof executionResult === 'string' ? executionResult : JSON.stringify(executionResult, null, 2);
1126
- const preview = createMessagePreview(toolResultContent);
1152
+
1153
+ // Log MCP tool result in debug mode
1127
1154
  if (this.debug) {
1128
- console.log(`[DEBUG] MCP tool '${toolName}' executed successfully. Result preview: ${preview}`);
1155
+ const preview = toolResultContent.length > 500 ? toolResultContent.substring(0, 500) + '...' : toolResultContent;
1156
+ console.error(`[DEBUG] ========================================`);
1157
+ console.error(`[DEBUG] MCP tool '${toolName}' completed successfully`);
1158
+ console.error(`[DEBUG] Result preview:`);
1159
+ console.error(preview);
1160
+ console.error(`[DEBUG] ========================================\n`);
1129
1161
  }
1130
1162
 
1131
1163
  currentMessages.push({ role: 'user', content: `<tool_result>\n${toolResultContent}\n</tool_result>` });
1132
1164
  } catch (error) {
1133
1165
  console.error(`Error executing MCP tool ${toolName}:`, error);
1134
1166
  const toolResultContent = `Error executing MCP tool ${toolName}: ${error.message}`;
1135
- if (this.debug) console.log(`[DEBUG] MCP tool '${toolName}' execution FAILED.`);
1167
+
1168
+ // Log MCP tool error in debug mode
1169
+ if (this.debug) {
1170
+ console.error(`[DEBUG] ========================================`);
1171
+ console.error(`[DEBUG] MCP tool '${toolName}' failed with error:`);
1172
+ console.error(`[DEBUG] ${error.message}`);
1173
+ console.error(`[DEBUG] ========================================\n`);
1174
+ }
1175
+
1136
1176
  currentMessages.push({ role: 'user', content: `<tool_result>\n${toolResultContent}\n</tool_result>` });
1137
1177
  }
1138
1178
  } else if (this.toolImplementations[toolName]) {
1139
1179
  // Execute native tool
1140
1180
  try {
1141
1181
  // Add sessionId and workingDirectory to params for tool execution
1142
- const toolParams = {
1143
- ...params,
1182
+ const toolParams = {
1183
+ ...params,
1144
1184
  sessionId: this.sessionId,
1145
1185
  workingDirectory: (this.allowedFolders && this.allowedFolders[0]) || process.cwd()
1146
1186
  };
1147
-
1187
+
1188
+ // Log tool execution in debug mode
1189
+ if (this.debug) {
1190
+ console.error(`\n[DEBUG] ========================================`);
1191
+ console.error(`[DEBUG] Executing tool: ${toolName}`);
1192
+ console.error(`[DEBUG] Arguments:`);
1193
+ for (const [key, value] of Object.entries(params)) {
1194
+ const displayValue = typeof value === 'string' && value.length > 100
1195
+ ? value.substring(0, 100) + '...'
1196
+ : value;
1197
+ console.error(`[DEBUG] ${key}: ${JSON.stringify(displayValue)}`);
1198
+ }
1199
+ console.error(`[DEBUG] ========================================\n`);
1200
+ }
1201
+
1148
1202
  // Emit tool start event
1149
1203
  this.events.emit('toolCall', {
1150
1204
  timestamp: new Date().toISOString(),
@@ -1196,6 +1250,18 @@ When troubleshooting:
1196
1250
  toolResult = await executeToolCall();
1197
1251
  }
1198
1252
 
1253
+ // Log tool result in debug mode
1254
+ if (this.debug) {
1255
+ const resultPreview = typeof toolResult === 'string'
1256
+ ? (toolResult.length > 500 ? toolResult.substring(0, 500) + '...' : toolResult)
1257
+ : (toolResult ? JSON.stringify(toolResult, null, 2).substring(0, 500) + '...' : 'No Result');
1258
+ console.error(`[DEBUG] ========================================`);
1259
+ console.error(`[DEBUG] Tool '${toolName}' completed successfully`);
1260
+ console.error(`[DEBUG] Result preview:`);
1261
+ console.error(resultPreview);
1262
+ console.error(`[DEBUG] ========================================\n`);
1263
+ }
1264
+
1199
1265
  // Emit tool success event
1200
1266
  this.events.emit('toolCall', {
1201
1267
  timestamp: new Date().toISOString(),
@@ -1206,8 +1272,16 @@ When troubleshooting:
1206
1272
  : (toolResult ? JSON.stringify(toolResult).substring(0, 200) + '...' : 'No Result'),
1207
1273
  status: 'completed'
1208
1274
  });
1209
-
1275
+
1210
1276
  } catch (toolError) {
1277
+ // Log tool error in debug mode
1278
+ if (this.debug) {
1279
+ console.error(`[DEBUG] ========================================`);
1280
+ console.error(`[DEBUG] Tool '${toolName}' failed with error:`);
1281
+ console.error(`[DEBUG] ${toolError.message}`);
1282
+ console.error(`[DEBUG] ========================================\n`);
1283
+ }
1284
+
1211
1285
  // Emit tool error event
1212
1286
  this.events.emit('toolCall', {
1213
1287
  timestamp: new Date().toISOString(),
@@ -1262,7 +1336,23 @@ When troubleshooting:
1262
1336
  }
1263
1337
  }
1264
1338
  } else {
1265
- // No tool call found, add assistant response and ask for tool usage
1339
+ // No tool call found
1340
+ // Special case: If response contains a mermaid code block and no schema was provided,
1341
+ // treat it as a valid completion (for mermaid diagram fixing workflow)
1342
+ const hasMermaidCodeBlock = /```mermaid\s*\n[\s\S]*?\n```/.test(assistantResponseContent);
1343
+ const hasNoSchemaOrTools = !options.schema && validTools.length === 0;
1344
+
1345
+ if (hasMermaidCodeBlock && hasNoSchemaOrTools) {
1346
+ // Accept mermaid code block as final answer for diagram fixing
1347
+ finalResult = assistantResponseContent;
1348
+ completionAttempted = true;
1349
+ if (this.debug) {
1350
+ console.error(`[DEBUG] Accepting mermaid code block as valid completion (no schema, no tools)`);
1351
+ }
1352
+ break;
1353
+ }
1354
+
1355
+ // Add assistant response and ask for tool usage
1266
1356
  currentMessages.push({ role: 'assistant', content: assistantResponseContent });
1267
1357
 
1268
1358
  // Build appropriate reminder message based on whether schema is provided
@@ -213,45 +213,99 @@ 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
226
  const targetDir = path.resolve(secureBaseDir, directory);
227
227
  if (!targetDir.startsWith(secureBaseDir + path.sep) && targetDir !== secureBaseDir) {
228
228
  throw new Error('Path traversal attempt detected. Access denied.');
229
229
  }
230
-
230
+
231
+ const debug = process.env.DEBUG === '1';
232
+
233
+ if (debug) {
234
+ console.log(`[DEBUG] Listing files in directory: ${targetDir}`);
235
+ }
236
+
231
237
  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
238
+ // Read the directory contents
239
+ const files = await fsPromises.readdir(targetDir, { withFileTypes: true });
240
+
241
+ // Format size for human readability
242
+ const formatSize = (size) => {
243
+ if (size < 1024) return `${size}B`;
244
+ if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)}K`;
245
+ if (size < 1024 * 1024 * 1024) return `${(size / (1024 * 1024)).toFixed(1)}M`;
246
+ return `${(size / (1024 * 1024 * 1024)).toFixed(1)}G`;
247
+ };
248
+
249
+ // Format the results as ls-style output
250
+ const entries = await Promise.all(files.map(async (file) => {
251
+ const isDirectory = file.isDirectory();
252
+ const fullPath = path.join(targetDir, file.name);
253
+
254
+ let size = 0;
255
+ try {
256
+ const stats = await fsPromises.stat(fullPath);
257
+ size = stats.size;
258
+ } catch (statError) {
259
+ if (debug) {
260
+ console.log(`[DEBUG] Could not stat file ${file.name}:`, statError.message);
261
+ }
262
+ }
263
+
264
+ return {
265
+ name: file.name,
266
+ isDirectory,
267
+ size
268
+ };
269
+ }));
270
+
271
+ // Sort: directories first, then files, both alphabetically
272
+ entries.sort((a, b) => {
273
+ if (a.isDirectory && !b.isDirectory) return -1;
274
+ if (!a.isDirectory && b.isDirectory) return 1;
275
+ return a.name.localeCompare(b.name);
237
276
  });
238
-
239
- return files;
277
+
278
+ // Format entries
279
+ const formatted = entries.map(entry => {
280
+ const type = entry.isDirectory ? 'dir ' : 'file';
281
+ const sizeStr = formatSize(entry.size).padStart(8);
282
+ return `${type} ${sizeStr} ${entry.name}`;
283
+ });
284
+
285
+ if (debug) {
286
+ console.log(`[DEBUG] Found ${entries.length} files/directories in ${targetDir}`);
287
+ }
288
+
289
+ // Return as formatted text output
290
+ const header = `${targetDir}:\n`;
291
+ const output = header + formatted.join('\n');
292
+
293
+ return output;
240
294
  } catch (error) {
241
295
  throw new Error(`Failed to list files: ${error.message}`);
242
296
  }
243
297
  }
244
298
  };
245
299
 
246
- // Simple file search tool
300
+ // Simple file search tool with timeout protection
247
301
  export const searchFilesTool = {
248
302
  execute: async (params) => {
249
303
  const { pattern, directory = '.', recursive = true, workingDirectory } = params;
250
-
304
+
251
305
  if (!pattern) {
252
306
  throw new Error('Pattern is required for file search');
253
307
  }
254
-
308
+
255
309
  // Security: Validate path to prevent traversal attacks
256
310
  const baseCwd = workingDirectory || process.cwd();
257
311
  const secureBaseDir = path.resolve(baseCwd);
@@ -259,19 +313,40 @@ export const searchFilesTool = {
259
313
  if (!targetDir.startsWith(secureBaseDir + path.sep) && targetDir !== secureBaseDir) {
260
314
  throw new Error('Path traversal attempt detected. Access denied.');
261
315
  }
262
-
316
+
317
+ // Validate pattern complexity to prevent DoS
318
+ if (pattern.includes('**/**') || pattern.split('*').length > 10) {
319
+ throw new Error('Pattern too complex. Please use a simpler glob pattern.');
320
+ }
321
+
263
322
  try {
264
323
  const options = {
265
324
  cwd: targetDir,
266
325
  ignore: ['node_modules/**', '.git/**'],
267
326
  absolute: false
268
327
  };
269
-
328
+
270
329
  if (!recursive) {
271
330
  options.deep = 1;
272
331
  }
273
-
274
- const files = await glob(pattern, options);
332
+
333
+ // Create a timeout promise (10 seconds)
334
+ const timeoutPromise = new Promise((_, reject) => {
335
+ setTimeout(() => reject(new Error('Search operation timed out after 10 seconds')), 10000);
336
+ });
337
+
338
+ // Race glob against timeout
339
+ const files = await Promise.race([
340
+ glob(pattern, options),
341
+ timeoutPromise
342
+ ]);
343
+
344
+ // Limit results to prevent memory issues
345
+ const maxResults = 1000;
346
+ if (files.length > maxResults) {
347
+ return files.slice(0, maxResults);
348
+ }
349
+
275
350
  return files;
276
351
  } catch (error) {
277
352
  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
  }
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,