@probelabs/probe 0.6.0-rc103 → 0.6.0-rc104

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.
@@ -207,14 +207,24 @@ export function createWrappedTools(baseTools) {
207
207
  // Simple file listing tool
208
208
  export const listFilesTool = {
209
209
  execute: async (params) => {
210
- const { directory = '.' } = params;
210
+ const { directory = '.', workingDirectory } = params;
211
+
212
+ // Use the provided working directory, or fall back to process.cwd()
213
+ const baseCwd = workingDirectory || process.cwd();
214
+
215
+ // Security: Validate path to prevent traversal attacks
216
+ const secureBaseDir = path.resolve(baseCwd);
217
+ const targetDir = path.resolve(secureBaseDir, directory);
218
+ if (!targetDir.startsWith(secureBaseDir + path.sep) && targetDir !== secureBaseDir) {
219
+ throw new Error('Path traversal attempt detected. Access denied.');
220
+ }
211
221
 
212
222
  try {
213
223
  const files = await listFilesByLevel({
214
- directory,
224
+ directory: targetDir,
215
225
  maxFiles: 100,
216
226
  respectGitignore: !process.env.PROBE_NO_GITIGNORE || process.env.PROBE_NO_GITIGNORE === '',
217
- cwd: process.cwd()
227
+ cwd: secureBaseDir
218
228
  });
219
229
 
220
230
  return files;
@@ -227,15 +237,23 @@ export const listFilesTool = {
227
237
  // Simple file search tool
228
238
  export const searchFilesTool = {
229
239
  execute: async (params) => {
230
- const { pattern, directory = '.', recursive = true } = params;
240
+ const { pattern, directory = '.', recursive = true, workingDirectory } = params;
231
241
 
232
242
  if (!pattern) {
233
243
  throw new Error('Pattern is required for file search');
234
244
  }
235
245
 
246
+ // Security: Validate path to prevent traversal attacks
247
+ const baseCwd = workingDirectory || process.cwd();
248
+ const secureBaseDir = path.resolve(baseCwd);
249
+ const targetDir = path.resolve(secureBaseDir, directory);
250
+ if (!targetDir.startsWith(secureBaseDir + path.sep) && targetDir !== secureBaseDir) {
251
+ throw new Error('Path traversal attempt detected. Access denied.');
252
+ }
253
+
236
254
  try {
237
255
  const options = {
238
- cwd: directory,
256
+ cwd: targetDir,
239
257
  ignore: ['node_modules/**', '.git/**'],
240
258
  absolute: false
241
259
  };
@@ -141,12 +141,45 @@ export function parseXmlToolCallWithThinking(xmlString, validTools) {
141
141
  // Remove thinking tags and their content from the XML string
142
142
  let cleanedXmlString = xmlString.replace(/<thinking>[\s\S]*?<\/thinking>/g, '').trim();
143
143
 
144
- // Check for attempt_complete shorthand (single tag with no closing tag and no parameters)
145
- const attemptCompleteMatch = cleanedXmlString.match(/^<attempt_complete>\s*$/);
146
- if (attemptCompleteMatch) {
147
- // Convert shorthand to full attempt_completion format with special marker
144
+ // Enhanced recovery logic for attempt_complete shorthand
145
+ // Check for various forms of attempt_complete tags:
146
+ // 1. Perfect shorthand: <attempt_complete>
147
+ // 2. Self-closing: <attempt_complete/>
148
+ // 3. Empty with closing tag: <attempt_complete></attempt_complete>
149
+ // 4. Incomplete: <attempt_complete (missing closing bracket)
150
+ // 5. With trailing content that should be ignored
151
+ const attemptCompletePatterns = [
152
+ // Standard shorthand with optional whitespace
153
+ /^<attempt_complete>\s*$/,
154
+ // Empty with proper closing tag (common case from the logs)
155
+ /^<attempt_complete>\s*<\/attempt_complete>\s*$/,
156
+ // Self-closing variant
157
+ /^<attempt_complete\s*\/>\s*$/,
158
+ // Incomplete opening tag (missing closing bracket)
159
+ /^<attempt_complete\s*$/,
160
+ // With trailing content (extract just the tag part) - must come after empty tag pattern
161
+ /^<attempt_complete>(.*)$/s,
162
+ // Self-closing with trailing content
163
+ /^<attempt_complete\s*\/>(.*)$/s
164
+ ];
165
+
166
+ for (const pattern of attemptCompletePatterns) {
167
+ const match = cleanedXmlString.match(pattern);
168
+ if (match) {
169
+ // Convert any form of attempt_complete to the standard format
170
+ return {
171
+ toolName: 'attempt_completion',
172
+ params: { result: '__PREVIOUS_RESPONSE__' }
173
+ };
174
+ }
175
+ }
176
+
177
+ // Additional recovery: check if the string contains attempt_complete anywhere
178
+ // and treat the entire response as a completion signal if no other tool tags are found
179
+ if (cleanedXmlString.includes('<attempt_complete') && !hasOtherToolTags(cleanedXmlString, validTools)) {
180
+ // This handles malformed cases where attempt_complete appears but is broken
148
181
  return {
149
- toolName: 'attempt_completion',
182
+ toolName: 'attempt_completion',
150
183
  params: { result: '__PREVIOUS_RESPONSE__' }
151
184
  };
152
185
  }
@@ -160,4 +193,23 @@ export function parseXmlToolCallWithThinking(xmlString, validTools) {
160
193
  }
161
194
 
162
195
  return parsedTool;
196
+ }
197
+
198
+ /**
199
+ * Helper function to check if the XML string contains other tool tags
200
+ * @param {string} xmlString - The XML string to check
201
+ * @param {string[]} validTools - List of valid tool names
202
+ * @returns {boolean} - True if other tool tags are found
203
+ */
204
+ function hasOtherToolTags(xmlString, validTools = []) {
205
+ const defaultTools = ['search', 'query', 'extract', 'listFiles', 'searchFiles', 'implement', 'attempt_completion'];
206
+ const toolsToCheck = validTools.length > 0 ? validTools : defaultTools;
207
+
208
+ // Check for any tool tags other than attempt_complete variants
209
+ for (const tool of toolsToCheck) {
210
+ if (tool !== 'attempt_completion' && xmlString.includes(`<${tool}`)) {
211
+ return true;
212
+ }
213
+ }
214
+ return false;
163
215
  }
package/build/utils.js CHANGED
@@ -43,7 +43,7 @@ export async function getBinaryPath(options = {}) {
43
43
 
44
44
  // Check bin directory
45
45
  const isWindows = process.platform === 'win32';
46
- const binaryName = isWindows ? 'probe.exe' : 'probe';
46
+ const binaryName = isWindows ? 'probe.exe' : 'probe-binary';
47
47
  const binaryPath = path.join(binDir, binaryName);
48
48
 
49
49
  if (fs.existsSync(binaryPath) && !forceDownload) {