@probelabs/probe 0.6.0-rc100

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.
Files changed (115) hide show
  1. package/README.md +583 -0
  2. package/bin/.gitkeep +0 -0
  3. package/bin/probe +158 -0
  4. package/bin/probe-binary +0 -0
  5. package/build/agent/ProbeAgent.d.ts +199 -0
  6. package/build/agent/ProbeAgent.js +1486 -0
  7. package/build/agent/acp/README.md +347 -0
  8. package/build/agent/acp/connection.js +237 -0
  9. package/build/agent/acp/connection.test.js +311 -0
  10. package/build/agent/acp/examples/simple-client.js +212 -0
  11. package/build/agent/acp/examples/tool-lifecycle.js +230 -0
  12. package/build/agent/acp/final-test.js +173 -0
  13. package/build/agent/acp/index.js +5 -0
  14. package/build/agent/acp/integration.test.js +385 -0
  15. package/build/agent/acp/manual-test.js +410 -0
  16. package/build/agent/acp/protocol-test.js +190 -0
  17. package/build/agent/acp/server.js +448 -0
  18. package/build/agent/acp/server.test.js +371 -0
  19. package/build/agent/acp/test-runner.js +216 -0
  20. package/build/agent/acp/test-utils/README.md +315 -0
  21. package/build/agent/acp/test-utils/acp-tester.js +484 -0
  22. package/build/agent/acp/test-utils/mock-acp-client.js +434 -0
  23. package/build/agent/acp/tools.js +368 -0
  24. package/build/agent/acp/tools.test.js +334 -0
  25. package/build/agent/acp/types.js +218 -0
  26. package/build/agent/acp/types.test.js +327 -0
  27. package/build/agent/appTracer.js +360 -0
  28. package/build/agent/fileSpanExporter.js +169 -0
  29. package/build/agent/index.js +7426 -0
  30. package/build/agent/mcp/client.js +338 -0
  31. package/build/agent/mcp/config.js +313 -0
  32. package/build/agent/mcp/index.js +64 -0
  33. package/build/agent/mcp/xmlBridge.js +371 -0
  34. package/build/agent/mockProvider.js +53 -0
  35. package/build/agent/probeTool.js +257 -0
  36. package/build/agent/schemaUtils.js +1726 -0
  37. package/build/agent/simpleTelemetry.js +267 -0
  38. package/build/agent/telemetry.js +225 -0
  39. package/build/agent/tokenCounter.js +395 -0
  40. package/build/agent/tools.js +163 -0
  41. package/build/cli.js +49 -0
  42. package/build/delegate.js +267 -0
  43. package/build/directory-resolver.js +237 -0
  44. package/build/downloader.js +750 -0
  45. package/build/extract.js +149 -0
  46. package/build/index.js +70 -0
  47. package/build/mcp/index.js +514 -0
  48. package/build/mcp/index.ts +608 -0
  49. package/build/query.js +116 -0
  50. package/build/search.js +247 -0
  51. package/build/tools/common.js +410 -0
  52. package/build/tools/index.js +40 -0
  53. package/build/tools/langchain.js +88 -0
  54. package/build/tools/system-message.js +121 -0
  55. package/build/tools/vercel.js +271 -0
  56. package/build/utils/file-lister.js +193 -0
  57. package/build/utils.js +128 -0
  58. package/cjs/agent/ProbeAgent.cjs +5829 -0
  59. package/cjs/index.cjs +6217 -0
  60. package/cjs/package.json +3 -0
  61. package/index.d.ts +401 -0
  62. package/package.json +114 -0
  63. package/scripts/postinstall.js +172 -0
  64. package/src/agent/ProbeAgent.d.ts +199 -0
  65. package/src/agent/ProbeAgent.js +1486 -0
  66. package/src/agent/acp/README.md +347 -0
  67. package/src/agent/acp/connection.js +237 -0
  68. package/src/agent/acp/connection.test.js +311 -0
  69. package/src/agent/acp/examples/simple-client.js +212 -0
  70. package/src/agent/acp/examples/tool-lifecycle.js +230 -0
  71. package/src/agent/acp/final-test.js +173 -0
  72. package/src/agent/acp/index.js +5 -0
  73. package/src/agent/acp/integration.test.js +385 -0
  74. package/src/agent/acp/manual-test.js +410 -0
  75. package/src/agent/acp/protocol-test.js +190 -0
  76. package/src/agent/acp/server.js +448 -0
  77. package/src/agent/acp/server.test.js +371 -0
  78. package/src/agent/acp/test-runner.js +216 -0
  79. package/src/agent/acp/test-utils/README.md +315 -0
  80. package/src/agent/acp/test-utils/acp-tester.js +484 -0
  81. package/src/agent/acp/test-utils/mock-acp-client.js +434 -0
  82. package/src/agent/acp/tools.js +368 -0
  83. package/src/agent/acp/tools.test.js +334 -0
  84. package/src/agent/acp/types.js +218 -0
  85. package/src/agent/acp/types.test.js +327 -0
  86. package/src/agent/appTracer.js +360 -0
  87. package/src/agent/fileSpanExporter.js +169 -0
  88. package/src/agent/index.js +813 -0
  89. package/src/agent/mcp/client.js +338 -0
  90. package/src/agent/mcp/config.js +313 -0
  91. package/src/agent/mcp/index.js +64 -0
  92. package/src/agent/mcp/xmlBridge.js +371 -0
  93. package/src/agent/mockProvider.js +53 -0
  94. package/src/agent/probeTool.js +257 -0
  95. package/src/agent/schemaUtils.js +1726 -0
  96. package/src/agent/simpleTelemetry.js +267 -0
  97. package/src/agent/telemetry.js +225 -0
  98. package/src/agent/tokenCounter.js +395 -0
  99. package/src/agent/tools.js +163 -0
  100. package/src/cli.js +49 -0
  101. package/src/delegate.js +267 -0
  102. package/src/directory-resolver.js +237 -0
  103. package/src/downloader.js +750 -0
  104. package/src/extract.js +149 -0
  105. package/src/index.js +70 -0
  106. package/src/mcp/index.ts +608 -0
  107. package/src/query.js +116 -0
  108. package/src/search.js +247 -0
  109. package/src/tools/common.js +410 -0
  110. package/src/tools/index.js +40 -0
  111. package/src/tools/langchain.js +88 -0
  112. package/src/tools/system-message.js +121 -0
  113. package/src/tools/vercel.js +271 -0
  114. package/src/utils/file-lister.js +193 -0
  115. package/src/utils.js +128 -0
@@ -0,0 +1,371 @@
1
+ /**
2
+ * XML-to-MCP Bridge
3
+ * Allows using MCP tools with XML-like syntax while maintaining JSON parameters
4
+ */
5
+
6
+ import { MCPClientManager } from './client.js';
7
+ import { loadMCPConfiguration } from './config.js';
8
+
9
+ /**
10
+ * Convert MCP tool to XML definition format
11
+ * @param {string} name - Tool name
12
+ * @param {Object} tool - MCP tool object
13
+ * @returns {string} XML-formatted tool definition
14
+ */
15
+ export function mcpToolToXmlDefinition(name, tool) {
16
+ const description = tool.description || 'MCP tool';
17
+ const inputSchema = tool.inputSchema || tool.parameters || {};
18
+
19
+ // Build parameter documentation
20
+ let paramDocs = '';
21
+ if (inputSchema.properties) {
22
+ paramDocs = '\n\nParameters (provide as JSON object):';
23
+ for (const [paramName, paramSchema] of Object.entries(inputSchema.properties)) {
24
+ const required = inputSchema.required?.includes(paramName) ? ' (required)' : ' (optional)';
25
+ const desc = paramSchema.description || '';
26
+ const type = paramSchema.type || 'any';
27
+ paramDocs += `\n- ${paramName}: ${type}${required} - ${desc}`;
28
+
29
+ if (paramSchema.enum) {
30
+ paramDocs += ` [choices: ${paramSchema.enum.join(', ')}]`;
31
+ }
32
+ }
33
+ }
34
+
35
+ return `## ${name}
36
+ Description: ${description}${paramDocs}
37
+
38
+ Usage:
39
+ <${name}>
40
+ <params>
41
+ {
42
+ "param1": "value1",
43
+ "param2": "value2"
44
+ }
45
+ </params>
46
+ </${name}>
47
+
48
+ Or for simple single parameter:
49
+ <${name}>
50
+ <params>value</params>
51
+ </${name}>`;
52
+ }
53
+
54
+ /**
55
+ * Parse XML tool call with JSON parameters
56
+ * Handles both JSON object parameters and simple string parameters
57
+ * @param {string} xmlString - XML string containing tool call
58
+ * @param {Array<string>} mcpToolNames - List of available MCP tool names
59
+ * @returns {Object|null} Parsed tool call with name and params
60
+ */
61
+ export function parseXmlMcpToolCall(xmlString, mcpToolNames = []) {
62
+ // Clean the XML string
63
+ const cleanedXml = xmlString.replace(/<thinking>[\s\S]*?<\/thinking>/g, '').trim();
64
+
65
+ for (const toolName of mcpToolNames) {
66
+ // Look for the tool in XML format
67
+ const openTag = `<${toolName}>`;
68
+ const closeTag = `</${toolName}>`;
69
+
70
+ const openIndex = cleanedXml.indexOf(openTag);
71
+ if (openIndex === -1) continue;
72
+
73
+ const closeIndex = cleanedXml.indexOf(closeTag, openIndex);
74
+ if (closeIndex === -1) continue;
75
+
76
+ // Extract content between tags
77
+ const contentStart = openIndex + openTag.length;
78
+ const content = cleanedXml.substring(contentStart, closeIndex).trim();
79
+
80
+ // Look for params tag
81
+ const paramsMatch = content.match(/<params>([\s\S]*?)<\/params>/);
82
+
83
+ let params = {};
84
+ if (paramsMatch) {
85
+ let paramsContent = paramsMatch[1].trim();
86
+
87
+ // Handle CDATA sections
88
+ const cdataMatch = paramsContent.match(/^<!\[CDATA\[([\s\S]*?)\]\]>$/);
89
+ if (cdataMatch) {
90
+ paramsContent = cdataMatch[1];
91
+ }
92
+
93
+ // Try to parse as JSON first
94
+ try {
95
+ // Handle JSON object
96
+ if (paramsContent.startsWith('{')) {
97
+ params = JSON.parse(paramsContent);
98
+ } else {
99
+ // Handle simple string parameter
100
+ // For backwards compatibility with simple XML params
101
+ params = { value: paramsContent };
102
+ }
103
+ } catch (e) {
104
+ // If JSON parsing fails, treat as simple string
105
+ params = { value: paramsContent };
106
+ }
107
+ } else {
108
+ // Legacy format: parse individual XML parameters
109
+ const paramPattern = /<(\w+)>([\s\S]*?)<\/\1>/g;
110
+ let match;
111
+ while ((match = paramPattern.exec(content)) !== null) {
112
+ const [, paramName, paramValue] = match;
113
+ params[paramName] = paramValue.trim();
114
+ }
115
+ }
116
+
117
+ return { toolName, params };
118
+ }
119
+
120
+ return null;
121
+ }
122
+
123
+ /**
124
+ * MCP Tool Manager that bridges XML and MCP
125
+ */
126
+ export class MCPXmlBridge {
127
+ constructor(options = {}) {
128
+ this.debug = options.debug || false;
129
+ this.mcpTools = {};
130
+ this.mcpManager = null;
131
+ this.xmlDefinitions = {};
132
+ }
133
+
134
+ /**
135
+ * Initialize MCP connections and load tools
136
+ * @param {Object|Array<Object>} config - MCP configuration object or server configurations (deprecated)
137
+ */
138
+ async initialize(config = null) {
139
+ let mcpConfigs = null;
140
+
141
+ if (!config) {
142
+ // No config provided - fall back to auto-discovery for backward compatibility
143
+ mcpConfigs = loadMCPConfiguration();
144
+ } else if (Array.isArray(config)) {
145
+ // Deprecated: Array of server configs (backward compatibility)
146
+ mcpConfigs = { mcpServers: config };
147
+ } else {
148
+ // New: Full config object provided directly
149
+ mcpConfigs = config;
150
+ }
151
+
152
+ if (!mcpConfigs || !mcpConfigs.mcpServers || Object.keys(mcpConfigs.mcpServers).length === 0) {
153
+ if (this.debug) {
154
+ console.error('[MCP] No MCP servers configured');
155
+ }
156
+ return;
157
+ }
158
+
159
+ try {
160
+ // Initialize the MCP client manager
161
+ this.mcpManager = new MCPClientManager({ debug: this.debug });
162
+ const result = await this.mcpManager.initialize(mcpConfigs);
163
+
164
+ // Get tools from the manager
165
+ const vercelTools = this.mcpManager.getVercelTools();
166
+ this.mcpTools = vercelTools;
167
+
168
+ // Generate XML definitions for all tools
169
+ for (const [name, tool] of Object.entries(vercelTools)) {
170
+ this.xmlDefinitions[name] = mcpToolToXmlDefinition(name, tool);
171
+ }
172
+
173
+ if (this.debug) {
174
+ console.error(`[MCP] Loaded ${Object.keys(vercelTools).length} MCP tools from ${result.connected} server(s)`);
175
+ }
176
+ } catch (error) {
177
+ console.error('[MCP] Failed to initialize MCP connections:', error);
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Get all XML tool definitions for inclusion in system prompt
183
+ * @returns {string} Combined XML tool definitions
184
+ */
185
+ getXmlToolDefinitions() {
186
+ return Object.values(this.xmlDefinitions).join('\n\n');
187
+ }
188
+
189
+ /**
190
+ * Get list of MCP tool names
191
+ * @returns {Array<string>} Tool names
192
+ */
193
+ getToolNames() {
194
+ return Object.keys(this.mcpTools);
195
+ }
196
+
197
+ /**
198
+ * Execute an MCP tool from XML call
199
+ * @param {string} xmlString - XML tool call string
200
+ * @returns {Promise<Object>} Tool execution result
201
+ */
202
+ async executeFromXml(xmlString) {
203
+ const parsed = parseXmlMcpToolCall(xmlString, this.getToolNames());
204
+
205
+ if (!parsed) {
206
+ throw new Error('No valid MCP tool call found in XML');
207
+ }
208
+
209
+ const { toolName, params } = parsed;
210
+
211
+ if (this.debug) {
212
+ console.error(`[MCP] Executing MCP tool: ${toolName} with params:`, params);
213
+ }
214
+
215
+ const tool = this.mcpTools[toolName];
216
+ if (!tool) {
217
+ throw new Error(`Unknown MCP tool: ${toolName}`);
218
+ }
219
+
220
+ try {
221
+ const result = await tool.execute(params);
222
+ return {
223
+ success: true,
224
+ toolName,
225
+ result
226
+ };
227
+ } catch (error) {
228
+ return {
229
+ success: false,
230
+ toolName,
231
+ error: error.message
232
+ };
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Check if a tool call is an MCP tool
238
+ * @param {string} toolName - Tool name to check
239
+ * @returns {boolean} True if it's an MCP tool
240
+ */
241
+ isMcpTool(toolName) {
242
+ return toolName in this.mcpTools;
243
+ }
244
+
245
+ /**
246
+ * Clean up MCP connections
247
+ */
248
+ async cleanup() {
249
+ if (this.mcpManager) {
250
+ await this.mcpManager.disconnect();
251
+ }
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Enhanced XML parser that handles both native and MCP tools
257
+ * @param {string} xmlString - XML string to parse
258
+ * @param {Array<string>} nativeTools - List of native tool names
259
+ * @param {MCPXmlBridge} mcpBridge - MCP bridge instance
260
+ * @returns {Object|null} Parsed tool call
261
+ */
262
+ export function parseHybridXmlToolCall(xmlString, nativeTools = [], mcpBridge = null) {
263
+ // First try native tools with standard XML parsing
264
+ for (const toolName of nativeTools) {
265
+ const nativeResult = parseNativeXmlTool(xmlString, toolName);
266
+ if (nativeResult) {
267
+ return { ...nativeResult, type: 'native' };
268
+ }
269
+ }
270
+
271
+ // Then try MCP tools if bridge is available
272
+ if (mcpBridge) {
273
+ const mcpResult = parseXmlMcpToolCall(xmlString, mcpBridge.getToolNames());
274
+ if (mcpResult) {
275
+ return { ...mcpResult, type: 'mcp' };
276
+ }
277
+ }
278
+
279
+ return null;
280
+ }
281
+
282
+ /**
283
+ * Parse native XML tool (existing format)
284
+ * @param {string} xmlString - XML string
285
+ * @param {string} toolName - Tool name to look for
286
+ * @returns {Object|null} Parsed tool call
287
+ */
288
+ function parseNativeXmlTool(xmlString, toolName) {
289
+ const openTag = `<${toolName}>`;
290
+ const closeTag = `</${toolName}>`;
291
+
292
+ const openIndex = xmlString.indexOf(openTag);
293
+ if (openIndex === -1) return null;
294
+
295
+ const closeIndex = xmlString.indexOf(closeTag, openIndex);
296
+ if (closeIndex === -1) return null;
297
+
298
+ const contentStart = openIndex + openTag.length;
299
+ const content = xmlString.substring(contentStart, closeIndex).trim();
300
+
301
+ // Parse individual XML parameters (native format)
302
+ const params = {};
303
+ const paramPattern = /<(\w+)>([\s\S]*?)<\/\1>/g;
304
+ let match;
305
+
306
+ while ((match = paramPattern.exec(content)) !== null) {
307
+ const [, paramName, paramValue] = match;
308
+ // Skip if this is the params tag itself (MCP format)
309
+ if (paramName !== 'params') {
310
+ params[paramName] = paramValue.trim();
311
+ }
312
+ }
313
+
314
+ // Only return if we found actual parameters (not MCP format)
315
+ if (Object.keys(params).length > 0) {
316
+ return { toolName, params };
317
+ }
318
+
319
+ return null;
320
+ }
321
+
322
+ /**
323
+ * Create a combined system message with both native and MCP tools
324
+ * @param {string} baseSystemMessage - Base system message
325
+ * @param {string} nativeToolDefinitions - Native tool definitions in XML format
326
+ * @param {MCPXmlBridge} mcpBridge - MCP bridge with loaded tools
327
+ * @returns {string} Combined system message
328
+ */
329
+ export function createHybridSystemMessage(baseSystemMessage, nativeToolDefinitions, mcpBridge) {
330
+ let message = baseSystemMessage;
331
+
332
+ // Add native tools section
333
+ if (nativeToolDefinitions) {
334
+ message += '\n\n=== NATIVE TOOLS ===\n';
335
+ message += 'These tools use standard XML parameter format:\n\n';
336
+ message += nativeToolDefinitions;
337
+ }
338
+
339
+ // Add MCP tools section if available
340
+ if (mcpBridge && mcpBridge.getToolNames().length > 0) {
341
+ message += '\n\n=== MCP TOOLS ===\n';
342
+ message += 'These tools use JSON parameters within the params tag:\n\n';
343
+ message += mcpBridge.getXmlToolDefinitions();
344
+ }
345
+
346
+ // Add usage instructions
347
+ message += '\n\n=== TOOL USAGE INSTRUCTIONS ===\n';
348
+ message += `
349
+ For NATIVE tools, use standard XML format:
350
+ <search>
351
+ <query>authentication</query>
352
+ <path>./src</path>
353
+ </search>
354
+
355
+ For MCP tools, use JSON within params tag:
356
+ <mcp_tool_name>
357
+ <params>
358
+ {
359
+ "param1": "value1",
360
+ "param2": 123
361
+ }
362
+ </params>
363
+ </mcp_tool_name>
364
+
365
+ IMPORTANT: Always check the tool definition to determine whether it's a native tool (XML params) or MCP tool (JSON params).
366
+ `;
367
+
368
+ return message;
369
+ }
370
+
371
+ export default MCPXmlBridge;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Mock AI provider for testing purposes
3
+ * This provider simulates AI responses without making actual API calls
4
+ */
5
+
6
+ export function createMockProvider() {
7
+ return {
8
+ languageModel: (modelName) => ({
9
+ modelId: `mock-${modelName}`,
10
+ provider: 'mock',
11
+
12
+ // Mock the doGenerate method used by Vercel AI SDK
13
+ doGenerate: async ({ messages, tools }) => {
14
+ // Simulate processing time
15
+ await new Promise(resolve => setTimeout(resolve, 10));
16
+
17
+ // Return a mock response
18
+ return {
19
+ text: 'This is a mock response for testing',
20
+ toolCalls: [],
21
+ usage: {
22
+ promptTokens: 10,
23
+ completionTokens: 5,
24
+ totalTokens: 15
25
+ }
26
+ };
27
+ },
28
+
29
+ // Mock the doStream method for streaming responses
30
+ doStream: async function* ({ messages, tools }) {
31
+ // Simulate streaming response
32
+ yield {
33
+ type: 'text-delta',
34
+ textDelta: 'Mock streaming response'
35
+ };
36
+
37
+ yield {
38
+ type: 'finish',
39
+ usage: {
40
+ promptTokens: 10,
41
+ completionTokens: 5,
42
+ totalTokens: 15
43
+ }
44
+ };
45
+ }
46
+ })
47
+ };
48
+ }
49
+
50
+ export function createMockModel(modelName = 'mock-model') {
51
+ const provider = createMockProvider();
52
+ return provider.languageModel(modelName);
53
+ }
@@ -0,0 +1,257 @@
1
+ // Simplified tool wrapper for probe agent (based on examples/chat/probeTool.js)
2
+ import { listFilesByLevel } from '../index.js';
3
+ import { exec } from 'child_process';
4
+ import { promisify } from 'util';
5
+ import { randomUUID } from 'crypto';
6
+ import { EventEmitter } from 'events';
7
+ import fs from 'fs';
8
+ import { promises as fsPromises } from 'fs';
9
+ import path from 'path';
10
+ import { glob } from 'glob';
11
+
12
+ // Create an event emitter for tool calls (simplified for single-shot operations)
13
+ export const toolCallEmitter = new EventEmitter();
14
+
15
+ // Map to track active tool executions by session ID
16
+ const activeToolExecutions = new Map();
17
+
18
+ // Function to check if a session has been cancelled
19
+ export function isSessionCancelled(sessionId) {
20
+ return activeToolExecutions.get(sessionId)?.cancelled || false;
21
+ }
22
+
23
+ // Function to cancel all tool executions for a session
24
+ export function cancelToolExecutions(sessionId) {
25
+ if (process.env.DEBUG === '1') {
26
+ console.log(`Cancelling tool executions for session: ${sessionId}`);
27
+ }
28
+ const sessionData = activeToolExecutions.get(sessionId);
29
+ if (sessionData) {
30
+ sessionData.cancelled = true;
31
+ return true;
32
+ }
33
+ return false;
34
+ }
35
+
36
+ // Function to register a new tool execution
37
+ function registerToolExecution(sessionId) {
38
+ if (!sessionId) return;
39
+
40
+ if (!activeToolExecutions.has(sessionId)) {
41
+ activeToolExecutions.set(sessionId, { cancelled: false });
42
+ } else {
43
+ // Reset cancelled flag if session already exists for a new execution
44
+ activeToolExecutions.get(sessionId).cancelled = false;
45
+ }
46
+ }
47
+
48
+ // Function to clear tool execution data for a session
49
+ export function clearToolExecutionData(sessionId) {
50
+ if (!sessionId) return;
51
+
52
+ if (activeToolExecutions.has(sessionId)) {
53
+ activeToolExecutions.delete(sessionId);
54
+ if (process.env.DEBUG === '1') {
55
+ console.log(`Cleared tool execution data for session: ${sessionId}`);
56
+ }
57
+ }
58
+ }
59
+
60
+ // Wrap the tools to emit events and handle cancellation
61
+ const wrapToolWithEmitter = (tool, toolName, baseExecute) => {
62
+ return {
63
+ ...tool, // Spread schema, description etc.
64
+ execute: async (params) => { // The execute function now receives parsed params
65
+ const debug = process.env.DEBUG === '1';
66
+ // Get the session ID from params (passed down from ProbeAgent)
67
+ const toolSessionId = params.sessionId || randomUUID();
68
+
69
+ if (debug) {
70
+ console.log(`[DEBUG] probeTool: Executing ${toolName} for session ${toolSessionId}`);
71
+ }
72
+
73
+ registerToolExecution(toolSessionId);
74
+
75
+ let executionError = null;
76
+ let result = null;
77
+
78
+ try {
79
+ // Emit the tool call start event
80
+ const toolCallStartData = {
81
+ timestamp: new Date().toISOString(),
82
+ name: toolName,
83
+ args: params,
84
+ status: 'started'
85
+ };
86
+
87
+ if (debug) {
88
+ console.log(`[DEBUG] probeTool: Emitting toolCallStart:${toolSessionId}`);
89
+ }
90
+ toolCallEmitter.emit(`toolCall:${toolSessionId}`, toolCallStartData);
91
+
92
+ // Check for cancellation before execution
93
+ if (isSessionCancelled(toolSessionId)) {
94
+ if (debug) {
95
+ console.log(`Tool execution cancelled before start for ${toolSessionId}`);
96
+ }
97
+ throw new Error(`Tool execution cancelled for session ${toolSessionId}`);
98
+ }
99
+
100
+ // Execute the base function
101
+ result = await baseExecute(params);
102
+
103
+ // Check for cancellation after execution
104
+ if (isSessionCancelled(toolSessionId)) {
105
+ if (debug) {
106
+ console.log(`Tool execution cancelled after completion for ${toolSessionId}`);
107
+ }
108
+ throw new Error(`Tool execution cancelled for session ${toolSessionId}`);
109
+ }
110
+
111
+ } catch (error) {
112
+ executionError = error;
113
+ if (debug) {
114
+ console.error(`[DEBUG] probeTool: Error in ${toolName}:`, error);
115
+ }
116
+ }
117
+
118
+ // Handle execution results and emit appropriate events
119
+ if (executionError) {
120
+ const toolCallErrorData = {
121
+ timestamp: new Date().toISOString(),
122
+ name: toolName,
123
+ args: params,
124
+ error: executionError.message || 'Unknown error',
125
+ status: 'error'
126
+ };
127
+ if (debug) {
128
+ console.log(`[DEBUG] probeTool: Emitting toolCall:${toolSessionId} (error)`);
129
+ }
130
+ toolCallEmitter.emit(`toolCall:${toolSessionId}`, toolCallErrorData);
131
+
132
+ throw executionError;
133
+ } else {
134
+ // If loop exited due to cancellation within the loop
135
+ if (isSessionCancelled(toolSessionId)) {
136
+ if (process.env.DEBUG === '1') {
137
+ console.log(`Tool execution finished but session was cancelled for ${toolSessionId}`);
138
+ }
139
+ throw new Error(`Tool execution cancelled for session ${toolSessionId}`);
140
+ }
141
+
142
+ // Emit the tool call completion event
143
+ const toolCallData = {
144
+ timestamp: new Date().toISOString(),
145
+ name: toolName,
146
+ args: params,
147
+ // Safely preview result
148
+ resultPreview: typeof result === 'string'
149
+ ? (result.length > 200 ? result.substring(0, 200) + '...' : result)
150
+ : (result ? JSON.stringify(result).substring(0, 200) + '...' : 'No Result'),
151
+ status: 'completed'
152
+ };
153
+ if (debug) {
154
+ console.log(`[DEBUG] probeTool: Emitting toolCall:${toolSessionId} (completed)`);
155
+ }
156
+ toolCallEmitter.emit(`toolCall:${toolSessionId}`, toolCallData);
157
+
158
+ return result;
159
+ }
160
+ }
161
+ };
162
+ };
163
+
164
+ // Create wrapped tool instances - these will be created by the ProbeAgent
165
+ export function createWrappedTools(baseTools) {
166
+ const wrappedTools = {};
167
+
168
+ // Wrap search tool
169
+ if (baseTools.searchTool) {
170
+ wrappedTools.searchToolInstance = wrapToolWithEmitter(
171
+ baseTools.searchTool,
172
+ 'search',
173
+ baseTools.searchTool.execute
174
+ );
175
+ }
176
+
177
+ // Wrap query tool
178
+ if (baseTools.queryTool) {
179
+ wrappedTools.queryToolInstance = wrapToolWithEmitter(
180
+ baseTools.queryTool,
181
+ 'query',
182
+ baseTools.queryTool.execute
183
+ );
184
+ }
185
+
186
+ // Wrap extract tool
187
+ if (baseTools.extractTool) {
188
+ wrappedTools.extractToolInstance = wrapToolWithEmitter(
189
+ baseTools.extractTool,
190
+ 'extract',
191
+ baseTools.extractTool.execute
192
+ );
193
+ }
194
+
195
+ // Wrap delegate tool
196
+ if (baseTools.delegateTool) {
197
+ wrappedTools.delegateToolInstance = wrapToolWithEmitter(
198
+ baseTools.delegateTool,
199
+ 'delegate',
200
+ baseTools.delegateTool.execute
201
+ );
202
+ }
203
+
204
+ return wrappedTools;
205
+ }
206
+
207
+ // Simple file listing tool
208
+ export const listFilesTool = {
209
+ execute: async (params) => {
210
+ const { directory = '.' } = params;
211
+
212
+ try {
213
+ const files = await listFilesByLevel({
214
+ directory,
215
+ maxFiles: 100,
216
+ respectGitignore: !process.env.PROBE_NO_GITIGNORE || process.env.PROBE_NO_GITIGNORE === '',
217
+ cwd: process.cwd()
218
+ });
219
+
220
+ return files;
221
+ } catch (error) {
222
+ throw new Error(`Failed to list files: ${error.message}`);
223
+ }
224
+ }
225
+ };
226
+
227
+ // Simple file search tool
228
+ export const searchFilesTool = {
229
+ execute: async (params) => {
230
+ const { pattern, directory = '.', recursive = true } = params;
231
+
232
+ if (!pattern) {
233
+ throw new Error('Pattern is required for file search');
234
+ }
235
+
236
+ try {
237
+ const options = {
238
+ cwd: directory,
239
+ ignore: ['node_modules/**', '.git/**'],
240
+ absolute: false
241
+ };
242
+
243
+ if (!recursive) {
244
+ options.deep = 1;
245
+ }
246
+
247
+ const files = await glob(pattern, options);
248
+ return files;
249
+ } catch (error) {
250
+ throw new Error(`Failed to search files: ${error.message}`);
251
+ }
252
+ }
253
+ };
254
+
255
+ // Wrap the additional tools
256
+ export const listFilesToolInstance = wrapToolWithEmitter(listFilesTool, 'listFiles', listFilesTool.execute);
257
+ export const searchFilesToolInstance = wrapToolWithEmitter(searchFilesTool, 'searchFiles', searchFilesTool.execute);