@probelabs/probe 0.6.0-rc173 → 0.6.0-rc175

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/index.d.ts CHANGED
@@ -17,6 +17,8 @@ export interface ProbeAgentOptions {
17
17
  allowEdit?: boolean;
18
18
  /** Search directory path */
19
19
  path?: string;
20
+ /** Working directory for resolving relative paths (independent of allowedFolders security) */
21
+ cwd?: string;
20
22
  /** Force specific AI provider */
21
23
  provider?: 'anthropic' | 'openai' | 'google';
22
24
  /** Override model name */
@@ -41,6 +43,12 @@ export interface ProbeAgentOptions {
41
43
  allowedTools?: string[] | null;
42
44
  /** Convenience flag to disable all tools (equivalent to allowedTools: []). Takes precedence over allowedTools if set. */
43
45
  disableTools?: boolean;
46
+ /** Disable automatic mermaid diagram validation and fixing */
47
+ disableMermaidValidation?: boolean;
48
+ /** Disable automatic JSON validation and fixing (prevents infinite recursion in JsonFixingAgent) */
49
+ disableJsonValidation?: boolean;
50
+ /** Custom prompt to run after attempt_completion for validation/review (runs before mermaid/JSON validation) */
51
+ completionPrompt?: string;
44
52
  }
45
53
 
46
54
  /**
@@ -246,7 +254,10 @@ export declare class ProbeAgent {
246
254
 
247
255
  /** Allowed search folders */
248
256
  readonly allowedFolders: string[];
249
-
257
+
258
+ /** Working directory for resolving relative paths */
259
+ readonly cwd: string | null;
260
+
250
261
  /** Debug mode status */
251
262
  readonly debug: boolean;
252
263
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probelabs/probe",
3
- "version": "0.6.0-rc173",
3
+ "version": "0.6.0-rc175",
4
4
  "description": "Node.js wrapper for the probe code search tool",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -79,7 +79,7 @@
79
79
  "@ai-sdk/openai": "^2.0.10",
80
80
  "@anthropic-ai/claude-agent-sdk": "^0.1.46",
81
81
  "@modelcontextprotocol/sdk": "^1.0.0",
82
- "@probelabs/maid": "^0.0.21",
82
+ "@probelabs/maid": "^0.0.22",
83
83
  "adm-zip": "^0.5.16",
84
84
  "ai": "^5.0.0",
85
85
  "ajv": "^8.17.1",
@@ -45,6 +45,12 @@ export interface ProbeAgentOptions {
45
45
  retry?: RetryOptions;
46
46
  /** Fallback configuration for multi-provider support */
47
47
  fallback?: FallbackOptions | { auto: boolean };
48
+ /** Disable automatic mermaid diagram validation and fixing */
49
+ disableMermaidValidation?: boolean;
50
+ /** Disable automatic JSON validation and fixing (prevents infinite recursion in JsonFixingAgent) */
51
+ disableJsonValidation?: boolean;
52
+ /** Custom prompt to run after attempt_completion for validation/review (runs before mermaid/JSON validation) */
53
+ completionPrompt?: string;
48
54
  }
49
55
 
50
56
  /**
@@ -95,6 +95,7 @@ export class ProbeAgent {
95
95
  * @param {boolean} [options.allowEdit=false] - Allow the use of the 'implement' tool
96
96
  * @param {boolean} [options.enableDelegate=false] - Enable the delegate tool for task distribution to subagents
97
97
  * @param {string} [options.path] - Search directory path
98
+ * @param {string} [options.cwd] - Working directory for resolving relative paths (independent of allowedFolders)
98
99
  * @param {string} [options.provider] - Force specific AI provider
99
100
  * @param {string} [options.model] - Override model name
100
101
  * @param {boolean} [options.debug] - Enable debug mode
@@ -123,6 +124,7 @@ export class ProbeAgent {
123
124
  * @param {Array<Object>} [options.fallback.providers] - List of provider configurations for custom fallback
124
125
  * @param {boolean} [options.fallback.stopOnSuccess=true] - Stop on first success
125
126
  * @param {number} [options.fallback.maxTotalAttempts=10] - Maximum total attempts across all providers
127
+ * @param {string} [options.completionPrompt] - Custom prompt to run after attempt_completion for validation/review (runs before mermaid/JSON validation)
126
128
  */
127
129
  constructor(options = {}) {
128
130
  // Basic configuration
@@ -147,6 +149,9 @@ export class ProbeAgent {
147
149
  this.disableMermaidValidation = !!options.disableMermaidValidation;
148
150
  this.disableJsonValidation = !!options.disableJsonValidation;
149
151
 
152
+ // Completion prompt for post-completion validation/review
153
+ this.completionPrompt = options.completionPrompt || null;
154
+
150
155
  // Tool filtering configuration
151
156
  // Parse allowedTools option: ['*'] = all tools, [] or null = no tools, ['tool1', 'tool2'] = specific tools
152
157
  // Supports exclusion with '!' prefix: ['*', '!bash'] = all tools except bash
@@ -180,6 +185,9 @@ export class ProbeAgent {
180
185
  this.allowedFolders = [process.cwd()];
181
186
  }
182
187
 
188
+ // Working directory for resolving relative paths (separate from allowedFolders security)
189
+ this.cwd = options.cwd || null;
190
+
183
191
  // API configuration
184
192
  this.clientApiProvider = options.provider || null;
185
193
  this.clientApiModel = options.model || null;
@@ -425,7 +433,8 @@ export class ProbeAgent {
425
433
  const configOptions = {
426
434
  sessionId: this.sessionId,
427
435
  debug: this.debug,
428
- defaultPath: this.allowedFolders.length > 0 ? this.allowedFolders[0] : process.cwd(),
436
+ // Use explicit cwd if set, otherwise fall back to first allowed folder
437
+ cwd: this.cwd || (this.allowedFolders.length > 0 ? this.allowedFolders[0] : process.cwd()),
429
438
  allowedFolders: this.allowedFolders,
430
439
  outline: this.outline,
431
440
  allowEdit: this.allowEdit,
@@ -2743,6 +2752,63 @@ IMPORTANT: When using <attempt_complete>, this must be the ONLY content in your
2743
2752
  // Continue even if storage fails
2744
2753
  }
2745
2754
 
2755
+ // Completion prompt handling - run a follow-up prompt after attempt_completion for validation/review
2756
+ // This runs BEFORE mermaid validation and JSON schema validation
2757
+ // Skip if we're already in a completion prompt follow-up call or if no completion prompt is configured
2758
+ if (completionAttempted && this.completionPrompt && !options._completionPromptProcessed) {
2759
+ if (this.debug) {
2760
+ console.log('[DEBUG] Running completion prompt for post-completion validation/review...');
2761
+ }
2762
+
2763
+ try {
2764
+ // Record completion prompt start in telemetry
2765
+ if (this.tracer) {
2766
+ this.tracer.recordEvent('completion_prompt.started', {
2767
+ 'completion_prompt.original_result_length': finalResult?.length || 0
2768
+ });
2769
+ }
2770
+
2771
+ // Create the completion prompt with the current result as context
2772
+ const completionPromptMessage = `${this.completionPrompt}
2773
+
2774
+ Here is the result to review:
2775
+ <result>
2776
+ ${finalResult}
2777
+ </result>
2778
+
2779
+ After reviewing, provide your final answer using attempt_completion.`;
2780
+
2781
+ // Make a follow-up call with the completion prompt
2782
+ // Pass _completionPromptProcessed to prevent infinite loops
2783
+ const completionResult = await this.answer(completionPromptMessage, [], {
2784
+ ...options,
2785
+ _completionPromptProcessed: true
2786
+ });
2787
+
2788
+ // Update finalResult with the result from the completion prompt
2789
+ finalResult = completionResult;
2790
+
2791
+ if (this.debug) {
2792
+ console.log(`[DEBUG] Completion prompt finished. New result length: ${finalResult?.length || 0}`);
2793
+ }
2794
+
2795
+ // Record completion prompt completion in telemetry
2796
+ if (this.tracer) {
2797
+ this.tracer.recordEvent('completion_prompt.completed', {
2798
+ 'completion_prompt.final_result_length': finalResult?.length || 0
2799
+ });
2800
+ }
2801
+ } catch (error) {
2802
+ console.error('[ERROR] Completion prompt failed:', error);
2803
+ // Keep the original result if completion prompt fails
2804
+ if (this.tracer) {
2805
+ this.tracer.recordEvent('completion_prompt.error', {
2806
+ 'completion_prompt.error': error.message
2807
+ });
2808
+ }
2809
+ }
2810
+ }
2811
+
2746
2812
  // Schema handling - format response according to provided schema
2747
2813
  // Skip schema processing if result came from attempt_completion tool
2748
2814
  // Don't apply schema formatting if we failed due to max iterations
@@ -3330,6 +3396,7 @@ Convert your previous response content into actual JSON data that follows this s
3330
3396
  enableDelegate: this.enableDelegate,
3331
3397
  path: this.allowedFolders[0], // Use first allowed folder as primary path
3332
3398
  allowedFolders: [...this.allowedFolders],
3399
+ cwd: this.cwd, // Preserve explicit working directory
3333
3400
  provider: this.clientApiProvider,
3334
3401
  model: this.clientApiModel,
3335
3402
  debug: this.debug,
@@ -3338,6 +3405,7 @@ Convert your previous response content into actual JSON data that follows this s
3338
3405
  maxIterations: this.maxIterations,
3339
3406
  disableMermaidValidation: this.disableMermaidValidation,
3340
3407
  disableJsonValidation: this.disableJsonValidation,
3408
+ completionPrompt: this.completionPrompt,
3341
3409
  allowedTools: allowedToolsArray,
3342
3410
  enableMcp: !!this.mcpBridge,
3343
3411
  mcpConfig: this.mcpConfig,
@@ -8,6 +8,7 @@ import fs from 'fs';
8
8
  import { promises as fsPromises } from 'fs';
9
9
  import path from 'path';
10
10
  import { glob } from 'glob';
11
+ import { getEntryType } from '../utils/symlink-utils.js';
11
12
 
12
13
  // Create an event emitter for tool calls (simplified for single-shot operations)
13
14
  export const toolCallEmitter = new EventEmitter();
@@ -287,23 +288,13 @@ export const listFilesTool = {
287
288
 
288
289
  // Format the results as ls-style output
289
290
  const entries = await Promise.all(files.map(async (file) => {
290
- const isDirectory = file.isDirectory();
291
291
  const fullPath = path.join(targetDir, file.name);
292
-
293
- let size = 0;
294
- try {
295
- const stats = await fsPromises.stat(fullPath);
296
- size = stats.size;
297
- } catch (statError) {
298
- if (debug) {
299
- console.log(`[DEBUG] Could not stat file ${file.name}:`, statError.message);
300
- }
301
- }
292
+ const entryType = await getEntryType(file, fullPath);
302
293
 
303
294
  return {
304
295
  name: file.name,
305
- isDirectory,
306
- size
296
+ isDirectory: entryType.isDirectory,
297
+ size: entryType.size
307
298
  };
308
299
  }));
309
300
 
package/src/downloader.js CHANGED
@@ -14,6 +14,7 @@ import os from 'os';
14
14
  import { fileURLToPath } from 'url';
15
15
  import { ensureBinDirectory } from './utils.js';
16
16
  import { getPackageBinDir } from './directory-resolver.js';
17
+ import { getEntryType } from './utils/symlink-utils.js';
17
18
 
18
19
  const exec = promisify(execCallback);
19
20
 
@@ -770,10 +771,13 @@ async function extractBinary(assetPath, outputDir) {
770
771
  for (const entry of entries) {
771
772
  const fullPath = path.join(dir, entry.name);
772
773
 
773
- if (entry.isDirectory()) {
774
+ // Use shared utility to follow symlinks and get actual target type
775
+ const entryType = await getEntryType(entry, fullPath);
776
+
777
+ if (entryType.isDirectory) {
774
778
  const result = await findBinary(fullPath);
775
779
  if (result) return result;
776
- } else if (entry.isFile()) {
780
+ } else if (entryType.isFile) {
777
781
  // Check if this is the binary we're looking for
778
782
  if (entry.name === binaryName ||
779
783
  entry.name === BINARY_NAME ||
package/src/extract.js CHANGED
@@ -6,6 +6,7 @@
6
6
  import { exec, spawn } from 'child_process';
7
7
  import { promisify } from 'util';
8
8
  import { getBinaryPath, buildCliArgs, escapeString } from './utils.js';
9
+ import { validateCwdPath } from './utils/path-validation.js';
9
10
 
10
11
  const execAsync = promisify(exec);
11
12
 
@@ -27,6 +28,7 @@ const EXTRACT_FLAG_MAP = {
27
28
  * @param {string[]} [options.files] - Files to extract from (can include line numbers with colon, e.g., "/path/to/file.rs:10")
28
29
  * @param {string} [options.inputFile] - Path to a file containing unstructured text to extract file paths from
29
30
  * @param {string|Buffer} [options.content] - Content to pipe to stdin (e.g., git diff output). Alternative to inputFile.
31
+ * @param {string} [options.cwd] - Working directory for resolving relative file paths
30
32
  * @param {boolean} [options.allowTests] - Include test files
31
33
  * @param {number} [options.contextLines] - Number of context lines to include
32
34
  * @param {string} [options.format] - Output format ('markdown', 'plain', 'json', 'xml', 'color', 'outline-xml', 'outline-diff')
@@ -71,6 +73,10 @@ export async function extract(options) {
71
73
  }
72
74
  }
73
75
 
76
+ // Get the working directory (cwd option sets working directory for relative file resolution)
77
+ // Validate and normalize the path to prevent path traversal attacks
78
+ const cwd = await validateCwdPath(options.cwd);
79
+
74
80
  // Create a single log record with all extract parameters (only in debug mode)
75
81
  if (process.env.DEBUG === '1') {
76
82
  let logMessage = `\nExtract:`;
@@ -79,6 +85,7 @@ export async function extract(options) {
79
85
  }
80
86
  if (options.inputFile) logMessage += ` inputFile="${options.inputFile}"`;
81
87
  if (options.content) logMessage += ` content=(${typeof options.content === 'string' ? options.content.length : options.content.byteLength} bytes)`;
88
+ if (options.cwd) logMessage += ` cwd="${cwd}"`;
82
89
  if (options.allowTests) logMessage += " allowTests=true";
83
90
  if (options.contextLines) logMessage += ` contextLines=${options.contextLines}`;
84
91
  if (options.format) logMessage += ` format=${options.format}`;
@@ -88,14 +95,14 @@ export async function extract(options) {
88
95
 
89
96
  // If content is provided, use spawn with stdin piping
90
97
  if (hasContent) {
91
- return extractWithStdin(binaryPath, cliArgs, options.content, options);
98
+ return extractWithStdin(binaryPath, cliArgs, options.content, options, cwd);
92
99
  }
93
100
 
94
101
  // Otherwise use exec for simple command execution
95
102
  const command = `${binaryPath} extract ${cliArgs.join(' ')}`;
96
103
 
97
104
  try {
98
- const { stdout, stderr } = await execAsync(command);
105
+ const { stdout, stderr } = await execAsync(command, { cwd });
99
106
 
100
107
  if (stderr) {
101
108
  console.error(`stderr: ${stderr}`);
@@ -104,7 +111,7 @@ export async function extract(options) {
104
111
  return processExtractOutput(stdout, options);
105
112
  } catch (error) {
106
113
  // Enhance error message with command details
107
- const errorMessage = `Error executing extract command: ${error.message}\nCommand: ${command}`;
114
+ const errorMessage = `Error executing extract command: ${error.message}\nCommand: ${command}\nCwd: ${cwd}`;
108
115
  throw new Error(errorMessage);
109
116
  }
110
117
  }
@@ -113,10 +120,11 @@ export async function extract(options) {
113
120
  * Extract with content piped to stdin
114
121
  * @private
115
122
  */
116
- function extractWithStdin(binaryPath, cliArgs, content, options) {
123
+ function extractWithStdin(binaryPath, cliArgs, content, options, cwd) {
117
124
  return new Promise((resolve, reject) => {
118
125
  const childProcess = spawn(binaryPath, ['extract', ...cliArgs], {
119
- stdio: ['pipe', 'pipe', 'pipe']
126
+ stdio: ['pipe', 'pipe', 'pipe'],
127
+ cwd
120
128
  });
121
129
 
122
130
  let stdout = '';
package/src/extractor.js CHANGED
@@ -9,6 +9,7 @@ import tar from 'tar';
9
9
  import AdmZip from 'adm-zip';
10
10
  import os from 'os';
11
11
  import { fileURLToPath } from 'url';
12
+ import { getEntryType } from './utils/symlink-utils.js';
12
13
 
13
14
  const __filename = fileURLToPath(import.meta.url);
14
15
  const __dirname = path.dirname(__filename);
@@ -148,10 +149,13 @@ async function findBinary(dir, baseDir, binaryName, isWindows) {
148
149
  continue;
149
150
  }
150
151
 
151
- if (entry.isDirectory()) {
152
+ // Use shared utility to follow symlinks and get actual target type
153
+ const entryType = await getEntryType(entry, fullPath);
154
+
155
+ if (entryType.isDirectory) {
152
156
  const result = await findBinary(fullPath, baseDir, binaryName, isWindows);
153
157
  if (result) return result;
154
- } else if (entry.isFile()) {
158
+ } else if (entryType.isFile) {
155
159
  // Check if this is the binary we're looking for
156
160
  if (entry.name === binaryName ||
157
161
  entry.name === BINARY_NAME ||
package/src/query.js CHANGED
@@ -6,6 +6,7 @@
6
6
  import { exec } from 'child_process';
7
7
  import { promisify } from 'util';
8
8
  import { getBinaryPath, buildCliArgs, escapeString } from './utils.js';
9
+ import { validateCwdPath } from './utils/path-validation.js';
9
10
 
10
11
  const execAsync = promisify(exec);
11
12
 
@@ -26,6 +27,7 @@ const QUERY_FLAG_MAP = {
26
27
  *
27
28
  * @param {Object} options - Query options
28
29
  * @param {string} options.path - Path to search in
30
+ * @param {string} [options.cwd] - Working directory for resolving relative paths (defaults to process.cwd())
29
31
  * @param {string} options.pattern - The ast-grep pattern to search for
30
32
  * @param {string} [options.language] - Programming language to search in
31
33
  * @param {string[]} [options.ignore] - Patterns to ignore
@@ -62,9 +64,14 @@ export async function query(options) {
62
64
  // Add pattern and path as positional arguments
63
65
  cliArgs.push(escapeString(options.pattern), escapeString(options.path));
64
66
 
67
+ // Get the working directory (cwd option for resolving relative paths)
68
+ // Validate and normalize the path to prevent path traversal attacks
69
+ const cwd = await validateCwdPath(options.cwd);
70
+
65
71
  // Create a single log record with all query parameters (only in debug mode)
66
72
  if (process.env.DEBUG === '1') {
67
73
  let logMessage = `Query: pattern="${options.pattern}" path="${options.path}"`;
74
+ if (options.cwd) logMessage += ` cwd="${options.cwd}"`;
68
75
  if (options.language) logMessage += ` language=${options.language}`;
69
76
  if (options.maxResults) logMessage += ` maxResults=${options.maxResults}`;
70
77
  if (options.allowTests) logMessage += " allowTests=true";
@@ -75,7 +82,7 @@ export async function query(options) {
75
82
  const command = `${binaryPath} query ${cliArgs.join(' ')}`;
76
83
 
77
84
  try {
78
- const { stdout, stderr } = await execAsync(command);
85
+ const { stdout, stderr } = await execAsync(command, { cwd });
79
86
 
80
87
  if (stderr) {
81
88
  console.error(`stderr: ${stderr}`);
@@ -110,7 +117,7 @@ export async function query(options) {
110
117
  return stdout;
111
118
  } catch (error) {
112
119
  // Enhance error message with command details
113
- const errorMessage = `Error executing query command: ${error.message}\nCommand: ${command}`;
120
+ const errorMessage = `Error executing query command: ${error.message}\nCommand: ${command}\nCwd: ${cwd}`;
114
121
  throw new Error(errorMessage);
115
122
  }
116
123
  }
package/src/search.js CHANGED
@@ -6,6 +6,7 @@
6
6
  import { execFile } from 'child_process';
7
7
  import { promisify } from 'util';
8
8
  import { getBinaryPath, buildCliArgs } from './utils.js';
9
+ import { validateCwdPath } from './utils/path-validation.js';
9
10
 
10
11
  const execFileAsync = promisify(execFile);
11
12
 
@@ -38,6 +39,7 @@ const SEARCH_FLAG_MAP = {
38
39
  *
39
40
  * @param {Object} options - Search options
40
41
  * @param {string} options.path - Path to search in
42
+ * @param {string} [options.cwd] - Working directory for resolving relative paths (defaults to process.cwd())
41
43
  * @param {string|string[]} options.query - Search query or queries
42
44
  * @param {boolean} [options.filesOnly] - Only output file paths
43
45
  * @param {string[]} [options.ignore] - Patterns to ignore
@@ -125,9 +127,14 @@ export async function search(options) {
125
127
  // Add query and path as positional arguments
126
128
  const queries = Array.isArray(options.query) ? options.query : [options.query];
127
129
 
130
+ // Get the working directory (cwd option for resolving relative paths)
131
+ // Validate and normalize the path to prevent path traversal attacks
132
+ const cwd = await validateCwdPath(options.cwd);
133
+
128
134
  // Create a single log record with all search parameters (only in debug mode)
129
135
  if (process.env.DEBUG === '1') {
130
136
  let logMessage = `\nSearch: query="${queries[0]}" path="${options.path}"`;
137
+ if (options.cwd) logMessage += ` cwd="${options.cwd}"`;
131
138
  if (options.maxResults) logMessage += ` maxResults=${options.maxResults}`;
132
139
  logMessage += ` maxTokens=${options.maxTokens}`;
133
140
  logMessage += ` timeout=${options.timeout}`;
@@ -154,6 +161,7 @@ export async function search(options) {
154
161
  try {
155
162
  // Execute with execFile (no shell, prevents command injection)
156
163
  const { stdout, stderr } = await execFileAsync(binaryPath, args, {
164
+ cwd,
157
165
  timeout: options.timeout * 1000, // Convert seconds to milliseconds
158
166
  maxBuffer: 50 * 1024 * 1024 // 50MB buffer for large outputs
159
167
  });
@@ -226,13 +234,13 @@ export async function search(options) {
226
234
  } catch (error) {
227
235
  // Check if the error is a timeout
228
236
  if (error.code === 'ETIMEDOUT' || error.killed) {
229
- const timeoutMessage = `Search operation timed out after ${options.timeout} seconds.\nBinary: ${binaryPath}\nArgs: ${args.join(' ')}`;
237
+ const timeoutMessage = `Search operation timed out after ${options.timeout} seconds.\nBinary: ${binaryPath}\nArgs: ${args.join(' ')}\nCwd: ${cwd}`;
230
238
  console.error(timeoutMessage);
231
239
  throw new Error(timeoutMessage);
232
240
  }
233
241
 
234
242
  // Enhance error message with command details
235
- const errorMessage = `Error executing search command: ${error.message}\nBinary: ${binaryPath}\nArgs: ${args.join(' ')}`;
243
+ const errorMessage = `Error executing search command: ${error.message}\nBinary: ${binaryPath}\nArgs: ${args.join(' ')}\nCwd: ${cwd}`;
236
244
  throw new Error(errorMessage);
237
245
  }
238
246
  }
package/src/tools/bash.js CHANGED
@@ -22,15 +22,15 @@ import { executeBashCommand, formatExecutionResult, validateExecutionOptions } f
22
22
  * @param {Object} [options.bashConfig.env={}] - Default environment variables
23
23
  * @param {number} [options.bashConfig.maxBuffer] - Maximum output buffer size
24
24
  * @param {boolean} [options.debug=false] - Enable debug logging
25
- * @param {string} [options.defaultPath] - Default working directory from probe config
25
+ * @param {string} [options.cwd] - Working directory from probe config
26
26
  * @param {string[]} [options.allowedFolders] - Allowed directories for execution
27
27
  * @returns {Object} Configured bash tool
28
28
  */
29
29
  export const bashTool = (options = {}) => {
30
- const {
30
+ const {
31
31
  bashConfig = {},
32
32
  debug = false,
33
- defaultPath,
33
+ cwd,
34
34
  allowedFolders = []
35
35
  } = options;
36
36
 
@@ -48,8 +48,8 @@ export const bashTool = (options = {}) => {
48
48
  if (bashConfig.workingDirectory) {
49
49
  return bashConfig.workingDirectory;
50
50
  }
51
- if (defaultPath) {
52
- return defaultPath;
51
+ if (cwd) {
52
+ return cwd;
53
53
  }
54
54
  if (allowedFolders && allowedFolders.length > 0) {
55
55
  return allowedFolders[0];
package/src/tools/edit.js CHANGED
@@ -40,7 +40,7 @@ function parseFileToolOptions(options = {}) {
40
40
  return {
41
41
  debug: options.debug || false,
42
42
  allowedFolders: options.allowedFolders || [],
43
- defaultPath: options.defaultPath
43
+ cwd: options.cwd
44
44
  };
45
45
  }
46
46
 
@@ -50,11 +50,11 @@ function parseFileToolOptions(options = {}) {
50
50
  * @param {Object} [options] - Configuration options
51
51
  * @param {boolean} [options.debug=false] - Enable debug logging
52
52
  * @param {string[]} [options.allowedFolders] - Allowed directories for file operations
53
- * @param {string} [options.defaultPath] - Default working directory
53
+ * @param {string} [options.cwd] - Working directory
54
54
  * @returns {Object} Configured edit tool
55
55
  */
56
56
  export const editTool = (options = {}) => {
57
- const { debug, allowedFolders, defaultPath } = parseFileToolOptions(options);
57
+ const { debug, allowedFolders, cwd } = parseFileToolOptions(options);
58
58
 
59
59
  return tool({
60
60
  name: 'edit',
@@ -111,7 +111,7 @@ Important:
111
111
  }
112
112
 
113
113
  // Resolve the file path
114
- const resolvedPath = isAbsolute(file_path) ? file_path : resolve(defaultPath || process.cwd(), file_path);
114
+ const resolvedPath = isAbsolute(file_path) ? file_path : resolve(cwd || process.cwd(), file_path);
115
115
 
116
116
  if (debug) {
117
117
  console.error(`[Edit] Attempting to edit file: ${resolvedPath}`);
@@ -182,11 +182,11 @@ Important:
182
182
  * @param {Object} [options] - Configuration options
183
183
  * @param {boolean} [options.debug=false] - Enable debug logging
184
184
  * @param {string[]} [options.allowedFolders] - Allowed directories for file operations
185
- * @param {string} [options.defaultPath] - Default working directory
185
+ * @param {string} [options.cwd] - Working directory
186
186
  * @returns {Object} Configured create tool
187
187
  */
188
188
  export const createTool = (options = {}) => {
189
- const { debug, allowedFolders, defaultPath } = parseFileToolOptions(options);
189
+ const { debug, allowedFolders, cwd } = parseFileToolOptions(options);
190
190
 
191
191
  return tool({
192
192
  name: 'create',
@@ -235,7 +235,7 @@ Important:
235
235
  }
236
236
 
237
237
  // Resolve the file path
238
- const resolvedPath = isAbsolute(file_path) ? file_path : resolve(defaultPath || process.cwd(), file_path);
238
+ const resolvedPath = isAbsolute(file_path) ? file_path : resolve(cwd || process.cwd(), file_path);
239
239
 
240
240
  if (debug) {
241
241
  console.error(`[Create] Attempting to create file: ${resolvedPath}`);
@@ -9,7 +9,9 @@ import { extract } from '../extract.js';
9
9
  import { searchSchema, querySchema, extractSchema, searchDescription, queryDescription, extractDescription, parseTargets } from './common.js';
10
10
 
11
11
  // LangChain tool for searching code
12
- export function createSearchTool() {
12
+ export function createSearchTool(options = {}) {
13
+ const { cwd } = options;
14
+
13
15
  return {
14
16
  name: 'search',
15
17
  description: searchDescription,
@@ -19,6 +21,7 @@ export function createSearchTool() {
19
21
  const results = await search({
20
22
  query: searchQuery,
21
23
  path,
24
+ cwd, // Working directory for resolving relative paths
22
25
  allow_tests,
23
26
  exact,
24
27
  json: false,
@@ -37,7 +40,9 @@ export function createSearchTool() {
37
40
  }
38
41
 
39
42
  // LangChain tool for querying code
40
- export function createQueryTool() {
43
+ export function createQueryTool(options = {}) {
44
+ const { cwd } = options;
45
+
41
46
  return {
42
47
  name: 'query',
43
48
  description: queryDescription,
@@ -47,6 +52,7 @@ export function createQueryTool() {
47
52
  const results = await query({
48
53
  pattern,
49
54
  path,
55
+ cwd, // Working directory for resolving relative paths
50
56
  language,
51
57
  allow_tests,
52
58
  json: false
@@ -62,7 +68,9 @@ export function createQueryTool() {
62
68
  }
63
69
 
64
70
  // LangChain tool for extracting code
65
- export function createExtractTool() {
71
+ export function createExtractTool(options = {}) {
72
+ const { cwd } = options;
73
+
66
74
  return {
67
75
  name: 'extract',
68
76
  description: extractDescription,
@@ -74,6 +82,7 @@ export function createExtractTool() {
74
82
 
75
83
  const results = await extract({
76
84
  files,
85
+ cwd, // Working directory for resolving relative paths
77
86
  allowTests: allow_tests,
78
87
  contextLines: context_lines,
79
88
  format