probe-code 0.6.0

AI-friendly, fully local, semantic code search tool for large codebases
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
// Import tool generators from @buger/probe package
import { searchTool, queryTool, extractTool, DEFAULT_SYSTEM_MESSAGE, listFilesByLevel } from '@buger/probe';
import { exec, spawn } from 'child_process';
import { promisify } from 'util';
import { randomUUID } from 'crypto';
import { EventEmitter } from 'events';
import fs from 'fs';
import { promises as fsPromises } from 'fs';
import path from 'path';
import os from 'os';
import { glob } from 'glob';

// Create an event emitter for tool calls
export const toolCallEmitter = new EventEmitter();

// Map to track active tool executions by session ID
const activeToolExecutions = new Map();

// Function to check if a session has been cancelled
export function isSessionCancelled(sessionId) {
	return activeToolExecutions.get(sessionId)?.cancelled || false;
}

// Function to cancel all tool executions for a session
export function cancelToolExecutions(sessionId) {
	// Only log if not in non-interactive mode or if in debug mode
	if (process.env.PROBE_NON_INTERACTIVE !== '1' || process.env.DEBUG_CHAT === '1') {
		console.log(`Cancelling tool executions for session: ${sessionId}`);
	}
	const sessionData = activeToolExecutions.get(sessionId);
	if (sessionData) {
		sessionData.cancelled = true;
		// Only log if not in non-interactive mode or if in debug mode
		if (process.env.PROBE_NON_INTERACTIVE !== '1' || process.env.DEBUG_CHAT === '1') {
			console.log(`Session ${sessionId} marked as cancelled`);
		}
		return true;
	}
	return false;
}

// Function to register a new tool execution
function registerToolExecution(sessionId) {
	if (!sessionId) return;

	if (!activeToolExecutions.has(sessionId)) {
		activeToolExecutions.set(sessionId, { cancelled: false });
	} else {
		// Reset cancelled flag if session already exists for a new execution
		activeToolExecutions.get(sessionId).cancelled = false;
	}
}

// Function to clear tool execution data for a session
export function clearToolExecutionData(sessionId) {
	if (!sessionId) return;

	if (activeToolExecutions.has(sessionId)) {
		activeToolExecutions.delete(sessionId);
		// Only log if not in non-interactive mode or if in debug mode
		if (process.env.PROBE_NON_INTERACTIVE !== '1' || process.env.DEBUG_CHAT === '1') {
			console.log(`Cleared tool execution data for session: ${sessionId}`);
		}
	}
}

// Generate a default session ID (less relevant now, session is managed per-chat)
const defaultSessionId = randomUUID();
// Only log session ID in debug mode
if (process.env.DEBUG_CHAT === '1') {
	console.log(`Generated default session ID (probeTool.js): ${defaultSessionId}`);
}

// Create configured tools with the session ID
// Note: These configOptions are less critical now as sessionId is passed explicitly
const configOptions = {
	sessionId: defaultSessionId,
	debug: process.env.DEBUG_CHAT === '1'
};

// Create the base tools using the imported generators
const baseSearchTool = searchTool(configOptions);
const baseQueryTool = queryTool(configOptions);
const baseExtractTool = extractTool(configOptions);


// Wrap the tools to emit events and handle cancellation
const wrapToolWithEmitter = (tool, toolName, baseExecute) => {
	return {
		...tool, // Spread schema, description etc.
		execute: async (params) => { // The execute function now receives parsed params
			const debug = process.env.DEBUG_CHAT === '1';
			// Get the session ID from params (passed down from probeChat.js)
			const toolSessionId = params.sessionId || defaultSessionId; // Fallback, but should always have sessionId

			if (debug) {
				console.log(`[DEBUG] probeTool: Executing ${toolName} for session ${toolSessionId}`);
				console.log(`[DEBUG] probeTool: Received params:`, params);
			}

			// Register this tool execution (and reset cancel flag if needed)
			registerToolExecution(toolSessionId);

			// Check if this session has been cancelled *before* execution
			if (isSessionCancelled(toolSessionId)) {
				// Only log if not in non-interactive mode or if in debug mode
				console.error(`Tool execution cancelled BEFORE starting for session ${toolSessionId}`);
				throw new Error(`Tool execution cancelled for session ${toolSessionId}`);
			}
			// Only log if not in non-interactive mode or if in debug mode
			console.error(`Executing ${toolName} for session ${toolSessionId}`); // Simplified log

			// Remove sessionId from params before passing to base tool if it expects only schema params
			const { sessionId, ...toolParams } = params;

			try {
				// Emit a tool call start event
				const toolCallStartData = {
					timestamp: new Date().toISOString(),
					name: toolName,
					args: toolParams, // Log schema params
					status: 'started'
				};
				if (debug) {
					console.log(`[DEBUG] probeTool: Emitting toolCallStart:${toolSessionId}`);
				}
				toolCallEmitter.emit(`toolCall:${toolSessionId}`, toolCallStartData);

				// Execute the original tool's execute function with schema params
				// Use a promise-based approach with cancellation check
				let result = null;
				let executionError = null;

				const executionPromise = baseExecute(toolParams).catch(err => {
					executionError = err; // Capture error
				});

				const checkInterval = 50; // Check every 50ms
				while (result === null && executionError === null) {
					if (isSessionCancelled(toolSessionId)) {
						console.error(`Tool execution cancelled DURING execution for session ${toolSessionId}`);
						// Attempt to signal cancellation if the underlying tool supports it (future enhancement)
						// For now, just throw the cancellation error
						throw new Error(`Tool execution cancelled for session ${toolSessionId}`);
					}
					// Check if promise is resolved or rejected
					const status = await Promise.race([
						executionPromise.then(() => 'resolved').catch(() => 'rejected'),
						new Promise(resolve => setTimeout(() => resolve('pending'), checkInterval))
					]);

					if (status === 'resolved') {
						result = await executionPromise; // Get the result
					} else if (status === 'rejected') {
						// Error already captured by the catch block on executionPromise
						break;
					}
					// If 'pending', continue loop
				}

				// If loop exited due to error
				if (executionError) {
					throw executionError;
				}

				// If loop exited due to cancellation within the loop
				if (isSessionCancelled(toolSessionId)) {
					// Only log if not in non-interactive mode or if in debug mode
					if (process.env.PROBE_NON_INTERACTIVE !== '1' || process.env.DEBUG_CHAT === '1') {
						console.log(`Tool execution finished but session was cancelled for ${toolSessionId}`);
					}
					throw new Error(`Tool execution cancelled for session ${toolSessionId}`);
				}


				// Emit the tool call completion event
				const toolCallData = {
					timestamp: new Date().toISOString(),
					name: toolName,
					args: toolParams,
					// Safely preview result
					resultPreview: typeof result === 'string'
						? (result.length > 200 ? result.substring(0, 200) + '...' : result)
						: (result ? JSON.stringify(result).substring(0, 200) + '...' : 'No Result'),
					status: 'completed'
				};
				if (debug) {
					console.log(`[DEBUG] probeTool: Emitting toolCall:${toolSessionId} (completed)`);
				}
				toolCallEmitter.emit(`toolCall:${toolSessionId}`, toolCallData);

				return result;
			} catch (error) {
				// If it's a cancellation error, re-throw it directly
				if (error.message.includes('cancelled for session')) {
					// Only log if not in non-interactive mode or if in debug mode
					if (process.env.PROBE_NON_INTERACTIVE !== '1' || process.env.DEBUG_CHAT === '1') {
						console.log(`Caught cancellation error for ${toolName} in session ${toolSessionId}`);
					}
					// Emit cancellation event? Or let the caller handle it? Let caller handle.
					throw error;
				}

				// Handle other execution errors
				if (debug) {
					console.error(`[DEBUG] probeTool: Error executing ${toolName}:`, error);
				}

				// Emit a tool call error event
				const toolCallErrorData = {
					timestamp: new Date().toISOString(),
					name: toolName,
					args: toolParams,
					error: error.message || 'Unknown error',
					status: 'error'
				};
				if (debug) {
					console.log(`[DEBUG] probeTool: Emitting toolCall:${toolSessionId} (error)`);
				}
				toolCallEmitter.emit(`toolCall:${toolSessionId}`, toolCallErrorData);

				throw error; // Re-throw the error to be caught by probeChat.js loop
			}
		}
	};
};

// Create the implement tool
const baseImplementTool = {
	name: "implement",
	description: 'Implement a feature or fix a bug using aider. Only available when --allow-edit is enabled.',
	parameters: {
		type: 'object',
		properties: {
			task: {
				type: 'string',
				description: 'The task description to pass to aider for implementation'
			}
		},
		required: ['task']
	},
	execute: async ({ task, autoCommits = false, prompt, sessionId }) => {
		const execPromise = promisify(exec); // Keep this for compatibility
		const debug = process.env.DEBUG_CHAT === '1';
		// Get the current working directory where probe-chat is running
		const currentWorkingDir = process.cwd();

		// Use the modules imported at the top of the file

		if (debug) {
			console.log(`[DEBUG] Executing aider with task: ${task}`);
			console.log(`[DEBUG] Auto-commits: ${autoCommits}`);
			console.log(`[DEBUG] Working directory: ${currentWorkingDir}`);
			if (prompt) console.log(`[DEBUG] Custom prompt: ${prompt}`);
		}

		// Create a temporary file for the task message
		const tempDir = os.tmpdir();
		const tempFilePath = path.join(tempDir, `aider-task-${Date.now()}-${Math.random().toString(36).substring(2, 10)}.txt`);

		try {
			// Write the task to the temporary file
			await fsPromises.writeFile(tempFilePath, task, 'utf8');

			if (debug) {
				console.log(`[DEBUG] Created temporary file for task: ${tempFilePath}`);
			}

			// Build the aider command with the message-file argument
			const autoCommitsFlag = '';
			const aiderCommand = `aider --yes --no-check-update --no-auto-commits --no-analytics ${autoCommitsFlag} --message-file "${tempFilePath}"`;

			console.error("Task:", task.substring(0, 100) + (task.length > 100 ? "..." : ""));
			console.error("Working directory:", currentWorkingDir);
			console.error("Temp file:", tempFilePath);

			// Use a safer approach that won't interfere with other tools
			// We'll use child_process.spawn but in a way that's compatible with the existing code
			return new Promise((resolve, reject) => {
				try {
					// Create a child process with spawn
					const childProcess = spawn('sh', ['-c', aiderCommand], {
						cwd: currentWorkingDir
					});

					let stdoutData = '';
					let stderrData = '';

					// Stream stdout in real-time to stderr
					childProcess.stdout.on('data', (data) => {
						const output = data.toString();
						stdoutData += output;
						// Print to stderr in real-time
						process.stderr.write(output);
					});

					// Stream stderr in real-time to stderr
					childProcess.stderr.on('data', (data) => {
						const output = data.toString();
						stderrData += output;
						// Print to stderr in real-time
						process.stderr.write(output);
					});

					// Handle process completion
					childProcess.on('close', (code) => {
						if (debug) {
							console.log(`[DEBUG] aider process exited with code ${code}`);
							console.log(`[DEBUG] Total stdout: ${stdoutData.length} chars`);
							console.log(`[DEBUG] Total stderr: ${stderrData.length} chars`);
						}

						// Clean up the temporary file
						fsPromises.unlink(tempFilePath)
							.then(() => {
								if (debug) {
									console.log(`[DEBUG] Removed temporary file: ${tempFilePath}`);
								}
							})
							.catch(err => {
								console.error(`Error removing temporary file ${tempFilePath}:`, err);
							})
							.finally(() => {
								// Always resolve, never reject (to match exec behavior)
								resolve({
									success: code === 0,
									output: stdoutData,
									error: stderrData || (code !== 0 ? `Process exited with code ${code}` : null),
									command: aiderCommand,
									timestamp: new Date().toISOString(),
									prompt: prompt || null
								});
							});
					});

					// Handle process errors (like command not found)
					childProcess.on('error', (error) => {
						console.error(`Error executing aider:`, error);

						// Clean up the temporary file
						fsPromises.unlink(tempFilePath)
							.then(() => {
								if (debug) {
									console.log(`[DEBUG] Removed temporary file after error: ${tempFilePath}`);
								}
							})
							.catch(err => {
								console.error(`Error removing temporary file ${tempFilePath}:`, err);
							})
							.finally(() => {
								// Still resolve with error information, don't reject
								resolve({
									success: false,
									output: stdoutData,
									error: error.message || 'Unknown error executing aider',
									command: aiderCommand,
									timestamp: new Date().toISOString(),
									prompt: prompt || null
								});
							});
					});
				} catch (error) {
					// Catch any synchronous errors from spawn
					console.error(`Error spawning aider process:`, error);

					// Clean up the temporary file
					fsPromises.unlink(tempFilePath)
						.then(() => {
							if (debug) {
								console.log(`[DEBUG] Removed temporary file after spawn error: ${tempFilePath}`);
							}
						})
						.catch(err => {
							console.error(`Error removing temporary file ${tempFilePath}:`, err);
						})
						.finally(() => {
							resolve({
								success: false,
								output: null,
								error: error.message || 'Unknown error spawning aider process',
								command: aiderCommand,
								timestamp: new Date().toISOString(),
								prompt: prompt || null
							});
						});
				}
			});
		} catch (error) {
			// Handle errors with creating or writing to the temp file
			console.error(`Error creating temporary file:`, error);
			return {
				success: false,
				output: null,
				error: `Error creating temporary file: ${error.message}`,
				command: null,
				timestamp: new Date().toISOString(),
				prompt: prompt || null
			};
		}
	}
};

// Create the listFiles tool
const baseListFilesTool = {
	name: "listFiles",
	description: 'List files in a specified directory',
	parameters: {
		type: 'object',
		properties: {
			directory: {
				type: 'string',
				description: 'The directory path to list files from. Defaults to current directory if not specified.'
			}
		},
		required: []
	},
	execute: async ({ directory = '.', sessionId }) => {
		const debug = process.env.DEBUG_CHAT === '1';
		const currentWorkingDir = process.cwd();
		const targetDir = path.resolve(currentWorkingDir, directory);

		if (debug) {
			console.log(`[DEBUG] Listing files in directory: ${targetDir}`);
		}

		try {
			// Read the directory contents
			const files = await fs.promises.readdir(targetDir, { withFileTypes: true });

			// Format the results
			const result = files.map(file => {
				const isDirectory = file.isDirectory();
				return {
					name: file.name,
					type: isDirectory ? 'directory' : 'file',
					path: path.join(directory, file.name)
				};
			});

			if (debug) {
				console.log(`[DEBUG] Found ${result.length} files/directories in ${targetDir}`);
			}

			return {
				success: true,
				directory: targetDir,
				files: result,
				timestamp: new Date().toISOString()
			};
		} catch (error) {
			console.error(`Error listing files in ${targetDir}:`, error);
			return {
				success: false,
				directory: targetDir,
				error: error.message || 'Unknown error listing files',
				timestamp: new Date().toISOString()
			};
		}
	}
};

// Create the searchFiles tool
const baseSearchFilesTool = {
	name: "searchFiles",
	description: 'Search for files using a glob pattern, recursively by default',
	parameters: {
		type: 'object',
		properties: {
			pattern: {
				type: 'string',
				description: 'The glob pattern to search for (e.g., "**/*.js", "*.md")'
			},
			directory: {
				type: 'string',
				description: 'The directory to search in. Defaults to current directory if not specified.'
			},
			recursive: {
				type: 'boolean',
				description: 'Whether to search recursively. Defaults to true.'
			}
		},
		required: ['pattern']
	},
	execute: async ({ pattern, directory, recursive = true, sessionId }) => {
		// Ensure directory defaults to current directory
		directory = directory || '.';

		const debug = process.env.DEBUG_CHAT === '1';
		const currentWorkingDir = process.cwd();
		const targetDir = path.resolve(currentWorkingDir, directory);

		// Log execution parameters to stderr for visibility
		console.error(`Executing searchFiles with params: pattern="${pattern}", directory="${directory}", recursive=${recursive}`);
		console.error(`Resolved target directory: ${targetDir}`);
		console.error(`Current working directory: ${currentWorkingDir}`);

		if (debug) {
			console.log(`[DEBUG] Searching for files with pattern: ${pattern}`);
			console.log(`[DEBUG] In directory: ${targetDir}`);
			console.log(`[DEBUG] Recursive: ${recursive}`);
		}

		// Validate pattern to prevent overly complex patterns
		if (pattern.includes('**/**') || pattern.split('*').length > 10) {
			console.error(`Pattern too complex: ${pattern}`);
			return {
				success: false,
				directory: targetDir,
				pattern: pattern,
				error: 'Pattern too complex. Please use a simpler glob pattern.',
				timestamp: new Date().toISOString()
			};
		}

		try {
			// Set glob options with timeout and limits
			const options = {
				cwd: targetDir,
				dot: true, // Include dotfiles
				nodir: true, // Only return files, not directories
				absolute: false, // Return paths relative to the search directory
				timeout: 10000, // 10 second timeout
				maxDepth: recursive ? 10 : 1, // Limit recursion depth
			};

			// If not recursive, modify the pattern to only search the top level
			const searchPattern = recursive ? pattern : pattern.replace(/^\*\*\//, '');

			console.error(`Starting glob search with pattern: ${searchPattern} in ${targetDir}`);
			console.error(`Glob options: ${JSON.stringify(options)}`);

			// Use a safer approach with manual file searching if the pattern is simple enough
			let files = [];

			// For simple patterns like "*.js" or "bin/*.js", use a more direct approach
			if (pattern.includes('*') && !pattern.includes('**') && pattern.split('/').length <= 2) {
				console.error(`Using direct file search for simple pattern: ${pattern}`);

				try {
					// Handle patterns like "dir/*.ext" or "*.ext"
					const parts = pattern.split('/');
					let searchDir = targetDir;
					let filePattern;

					if (parts.length === 2) {
						// Pattern like "dir/*.ext"
						searchDir = path.join(targetDir, parts[0]);
						filePattern = parts[1];
					} else {
						// Pattern like "*.ext"
						filePattern = parts[0];
					}

					console.error(`Searching in directory: ${searchDir} for files matching: ${filePattern}`);

					// Check if directory exists
					try {
						await fsPromises.access(searchDir);
					} catch (err) {
						console.error(`Directory does not exist: ${searchDir}`);
						return {
							success: true,
							directory: targetDir,
							pattern: pattern,
							recursive: recursive,
							files: [],
							count: 0,
							timestamp: new Date().toISOString()
						};
					}

					// Read directory contents
					const dirEntries = await fsPromises.readdir(searchDir, { withFileTypes: true });

					// Convert glob pattern to regex
					const regexPattern = filePattern
						.replace(/\./g, '\\.')
						.replace(/\*/g, '.*');
					const regex = new RegExp(`^${regexPattern}$`);

					// Filter files based on pattern
					files = dirEntries
						.filter(entry => entry.isFile() && regex.test(entry.name))
						.map(entry => {
							const relativePath = parts.length === 2
								? path.join(parts[0], entry.name)
								: entry.name;
							return relativePath;
						});

					console.error(`Direct search found ${files.length} files matching ${filePattern}`);
				} catch (err) {
					console.error(`Error in direct file search: ${err.message}`);
					// Fall back to glob if direct search fails
					console.error(`Falling back to glob search`);

					// Create a promise that rejects after a timeout
					const timeoutPromise = new Promise((_, reject) => {
						setTimeout(() => reject(new Error('Search operation timed out after 10 seconds')), 10000);
					});

					// Use glob without promisify since it might already return a Promise
					files = await Promise.race([
						glob(searchPattern, options),
						timeoutPromise
					]);
				}
			} else {
				console.error(`Using glob for complex pattern: ${pattern}`);

				// Create a promise that rejects after a timeout
				const timeoutPromise = new Promise((_, reject) => {
					setTimeout(() => reject(new Error('Search operation timed out after 10 seconds')), 10000);
				});

				// Use glob without promisify since it might already return a Promise
				files = await Promise.race([
					glob(searchPattern, options),
					timeoutPromise
				]);
			}

			console.error(`Search completed, found ${files.length} files in ${targetDir}`);
			console.error(`Pattern: ${pattern}, Recursive: ${recursive}`);

			if (debug) {
				console.log(`[DEBUG] Found ${files.length} files matching pattern ${pattern}`);
			}

			// Limit the number of results to prevent memory issues
			const maxResults = 1000;
			const limitedFiles = files.length > maxResults ? files.slice(0, maxResults) : files;

			if (files.length > maxResults) {
				console.warn(`Warning: Limited results to ${maxResults} files out of ${files.length} total matches`);
			}

			return {
				success: true,
				directory: targetDir,
				pattern: pattern,
				recursive: recursive,
				files: limitedFiles.map(file => path.join(directory, file)),
				count: limitedFiles.length,
				totalMatches: files.length,
				limited: files.length > maxResults,
				timestamp: new Date().toISOString()
			};
		} catch (error) {
			console.error(`Error searching files with pattern "${pattern}" in ${targetDir}:`, error);
			console.error(`Search parameters: directory="${directory}", recursive=${recursive}, sessionId=${sessionId}`);
			return {
				success: false,
				directory: targetDir,
				pattern: pattern,
				error: error.message || 'Unknown error searching files',
				timestamp: new Date().toISOString()
			};
		}
	}
};

// Export the wrapped tool instances
export const searchToolInstance = wrapToolWithEmitter(baseSearchTool, 'search', baseSearchTool.execute);
export const queryToolInstance = wrapToolWithEmitter(baseQueryTool, 'query', baseQueryTool.execute);
export const extractToolInstance = wrapToolWithEmitter(baseExtractTool, 'extract', baseExtractTool.execute);
export const implementToolInstance = wrapToolWithEmitter(baseImplementTool, 'implement', baseImplementTool.execute);
export const listFilesToolInstance = wrapToolWithEmitter(baseListFilesTool, 'listFiles', baseListFilesTool.execute);
export const searchFilesToolInstance = wrapToolWithEmitter(baseSearchFilesTool, 'searchFiles', baseSearchFilesTool.execute);

// --- Backward Compatibility Layer (probeTool mapping to searchToolInstance) ---
// This might be less relevant if the AI is strictly using the new XML format,
// but keep it for potential direct API calls or older UI elements.
export const probeTool = {
	...searchToolInstance, // Inherit schema description etc. from the wrapped search tool
	name: "search", // Explicitly set name
	description: 'DEPRECATED: Use <search> tool instead. Search code using keywords.',
	// parameters: searchSchema, // Use the imported schema
	execute: async (params) => { // Expects { keywords, folder, ..., sessionId }
		const debug = process.env.DEBUG_CHAT === '1';
		if (debug) {
			console.log(`[DEBUG] probeTool (Compatibility Layer) executing for session ${params.sessionId}`);
		}

		// Map old params ('keywords', 'folder') to new ones ('query', 'path')
		const { keywords, folder, sessionId, ...rest } = params;
		const mappedParams = {
			query: keywords,
			path: folder || '.', // Default path if folder is missing
			sessionId: sessionId, // Pass session ID through
			...rest // Pass other params like allow_tests, maxResults etc.
		};

		if (debug) {
			console.log("[DEBUG] probeTool mapped params: ", mappedParams);
		}

		// Call the *wrapped* searchToolInstance execute function
		// It will handle cancellation checks and event emitting internally
		try {
			// Note: The name emitted by searchToolInstance will be 'search', not 'probeTool' or 'searchCode'
			const result = await searchToolInstance.execute(mappedParams);

			// Format the result for backward compatibility if needed by caller
			// The raw result from searchToolInstance is likely just the search results array/string
			const formattedResult = {
				results: result, // Assuming result is the direct data
				command: `probe search --query "${keywords}" --path "${folder || '.'}"`, // Reconstruct approx command
				timestamp: new Date().toISOString()
			};
			if (debug) {
				console.log("[DEBUG] probeTool compatibility layer returning formatted result.");
			}
			return formattedResult;

		} catch (error) {
			if (debug) {
				console.error(`[DEBUG] Error in probeTool compatibility layer:`, error);
			}
			// Error is already emitted by the wrapped searchToolInstance, just re-throw
			throw error;
		}
	}
};
// Export necessary items
export { DEFAULT_SYSTEM_MESSAGE, listFilesByLevel };
// Export the tool generator functions if needed elsewhere
export { searchTool, queryTool, extractTool };

// Export capabilities information for the new tools
export const toolCapabilities = {
	search: "Search code using keywords and patterns",
	query: "Query code with structured parameters for more precise results",
	extract: "Extract code blocks and context from files",
	implement: "Implement features or fix bugs using aider (requires --allow-edit)",
	listFiles: "List files and directories in a specified location",
	searchFiles: "Find files matching a glob pattern with recursive search capability"
};