@probelabs/probe 0.6.0-rc103 → 0.6.0-rc105
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/build/agent/ProbeAgent.js +78 -9
- package/build/agent/index.js +27982 -440
- package/build/agent/mcp/xmlBridge.js +34 -6
- package/build/agent/probeTool.js +23 -5
- package/build/agent/tools.js +10 -24
- package/build/agent/xmlParsingUtils.js +118 -0
- package/build/utils.js +1 -1
- package/cjs/agent/ProbeAgent.cjs +27820 -287
- package/cjs/index.cjs +27883 -350
- package/package.json +2 -1
- package/src/agent/ProbeAgent.js +78 -9
- package/src/agent/index.js +14 -32
- package/src/agent/mcp/xmlBridge.js +34 -6
- package/src/agent/probeTool.js +23 -5
- package/src/agent/tools.js +10 -24
- package/src/agent/xmlParsingUtils.js +118 -0
- package/src/utils.js +1 -1
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { MCPClientManager } from './client.js';
|
|
7
7
|
import { loadMCPConfiguration } from './config.js';
|
|
8
|
+
import { processXmlWithThinkingAndRecovery } from '../xmlParsingUtils.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Convert MCP tool to XML definition format
|
|
@@ -254,18 +255,18 @@ export class MCPXmlBridge {
|
|
|
254
255
|
|
|
255
256
|
/**
|
|
256
257
|
* Enhanced XML parser that handles both native and MCP tools
|
|
258
|
+
* Uses the exact same logic as CLI/SDK mode to ensure consistency
|
|
257
259
|
* @param {string} xmlString - XML string to parse
|
|
258
260
|
* @param {Array<string>} nativeTools - List of native tool names
|
|
259
261
|
* @param {MCPXmlBridge} mcpBridge - MCP bridge instance
|
|
260
262
|
* @returns {Object|null} Parsed tool call
|
|
261
263
|
*/
|
|
262
264
|
export function parseHybridXmlToolCall(xmlString, nativeTools = [], mcpBridge = null) {
|
|
263
|
-
// First try native tools with
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
265
|
+
// First try native tools with the same logic as CLI/SDK mode
|
|
266
|
+
// This includes thinking tag removal and attempt_complete recovery logic
|
|
267
|
+
const nativeResult = parseNativeXmlToolWithThinking(xmlString, nativeTools);
|
|
268
|
+
if (nativeResult) {
|
|
269
|
+
return { ...nativeResult, type: 'native' };
|
|
269
270
|
}
|
|
270
271
|
|
|
271
272
|
// Then try MCP tools if bridge is available
|
|
@@ -279,6 +280,33 @@ export function parseHybridXmlToolCall(xmlString, nativeTools = [], mcpBridge =
|
|
|
279
280
|
return null;
|
|
280
281
|
}
|
|
281
282
|
|
|
283
|
+
/**
|
|
284
|
+
* Parse native XML tools using the same logic as CLI/SDK mode
|
|
285
|
+
* Now uses shared utilities instead of duplicating code
|
|
286
|
+
* @param {string} xmlString - XML string to parse
|
|
287
|
+
* @param {Array<string>} validTools - List of valid tool names
|
|
288
|
+
* @returns {Object|null} Parsed tool call
|
|
289
|
+
*/
|
|
290
|
+
function parseNativeXmlToolWithThinking(xmlString, validTools) {
|
|
291
|
+
// Use the shared processing logic
|
|
292
|
+
const { cleanedXmlString, recoveryResult } = processXmlWithThinkingAndRecovery(xmlString, validTools);
|
|
293
|
+
|
|
294
|
+
// If recovery found an attempt_complete pattern, return it
|
|
295
|
+
if (recoveryResult) {
|
|
296
|
+
return recoveryResult;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Use the original parseNativeXmlTool function to parse the cleaned XML string
|
|
300
|
+
for (const toolName of validTools) {
|
|
301
|
+
const result = parseNativeXmlTool(cleanedXmlString, toolName);
|
|
302
|
+
if (result) {
|
|
303
|
+
return result;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
|
|
282
310
|
/**
|
|
283
311
|
* Parse native XML tool (existing format)
|
|
284
312
|
* @param {string} xmlString - XML string
|
package/build/agent/probeTool.js
CHANGED
|
@@ -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:
|
|
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:
|
|
256
|
+
cwd: targetDir,
|
|
239
257
|
ignore: ['node_modules/**', '.git/**'],
|
|
240
258
|
absolute: false
|
|
241
259
|
};
|
package/build/agent/tools.js
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
parseXmlToolCall
|
|
19
19
|
} from '../index.js';
|
|
20
20
|
import { randomUUID } from 'crypto';
|
|
21
|
+
import { processXmlWithThinkingAndRecovery } from './xmlParsingUtils.js';
|
|
21
22
|
|
|
22
23
|
// Create configured tool instances
|
|
23
24
|
export function createTools(configOptions) {
|
|
@@ -134,30 +135,15 @@ User: Find all markdown files in the docs directory, but only at the top level.
|
|
|
134
135
|
* @returns {Object|null} - The parsed tool call or null if no valid tool call found
|
|
135
136
|
*/
|
|
136
137
|
export function parseXmlToolCallWithThinking(xmlString, validTools) {
|
|
137
|
-
//
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
|
148
|
-
return {
|
|
149
|
-
toolName: 'attempt_completion',
|
|
150
|
-
params: { result: '__PREVIOUS_RESPONSE__' }
|
|
151
|
-
};
|
|
138
|
+
// Use the shared processing logic
|
|
139
|
+
const { cleanedXmlString, recoveryResult } = processXmlWithThinkingAndRecovery(xmlString, validTools);
|
|
140
|
+
|
|
141
|
+
// If recovery found an attempt_complete pattern, return it
|
|
142
|
+
if (recoveryResult) {
|
|
143
|
+
return recoveryResult;
|
|
152
144
|
}
|
|
153
145
|
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
// If debugging is enabled, log the thinking content
|
|
158
|
-
if (process.env.DEBUG === '1' && thinkingContent) {
|
|
159
|
-
console.log(`[DEBUG] AI Thinking Process:\n${thinkingContent}`);
|
|
160
|
-
}
|
|
146
|
+
// Otherwise, use the original parseXmlToolCall function to parse the cleaned XML string
|
|
147
|
+
return parseXmlToolCall(cleanedXmlString, validTools);
|
|
148
|
+
}
|
|
161
149
|
|
|
162
|
-
return parsedTool;
|
|
163
|
-
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared XML parsing utilities used by both CLI/SDK and MCP modes
|
|
3
|
+
* This module contains the core logic for thinking tag removal and attempt_complete recovery
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Remove thinking tags and their content from XML string
|
|
8
|
+
* @param {string} xmlString - The XML string to clean
|
|
9
|
+
* @returns {string} - Cleaned XML string without thinking tags
|
|
10
|
+
*/
|
|
11
|
+
export function removeThinkingTags(xmlString) {
|
|
12
|
+
return xmlString.replace(/<thinking>[\s\S]*?<\/thinking>/g, '').trim();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Extract thinking content for potential logging
|
|
17
|
+
* @param {string} xmlString - The XML string to extract from
|
|
18
|
+
* @returns {string|null} - Thinking content or null if not found
|
|
19
|
+
*/
|
|
20
|
+
export function extractThinkingContent(xmlString) {
|
|
21
|
+
const thinkingMatch = xmlString.match(/<thinking>([\s\S]*?)<\/thinking>/);
|
|
22
|
+
return thinkingMatch ? thinkingMatch[1].trim() : null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Check for attempt_complete recovery patterns and return standardized result
|
|
27
|
+
* @param {string} cleanedXmlString - XML string with thinking tags already removed
|
|
28
|
+
* @param {Array<string>} validTools - List of valid tool names
|
|
29
|
+
* @returns {Object|null} - Standardized attempt_completion result or null
|
|
30
|
+
*/
|
|
31
|
+
export function checkAttemptCompleteRecovery(cleanedXmlString, validTools = []) {
|
|
32
|
+
// Enhanced recovery logic for attempt_complete shorthand
|
|
33
|
+
const attemptCompletePatterns = [
|
|
34
|
+
// Standard shorthand with optional whitespace
|
|
35
|
+
/^<attempt_complete>\s*$/,
|
|
36
|
+
// Empty with proper closing tag (common case from the logs)
|
|
37
|
+
/^<attempt_complete>\s*<\/attempt_complete>\s*$/,
|
|
38
|
+
// Self-closing variant
|
|
39
|
+
/^<attempt_complete\s*\/>\s*$/,
|
|
40
|
+
// Incomplete opening tag (missing closing bracket)
|
|
41
|
+
/^<attempt_complete\s*$/,
|
|
42
|
+
// With trailing content (extract just the tag part) - must come after empty tag pattern
|
|
43
|
+
/^<attempt_complete>(.*)$/s,
|
|
44
|
+
// Self-closing with trailing content
|
|
45
|
+
/^<attempt_complete\s*\/>(.*)$/s
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
for (const pattern of attemptCompletePatterns) {
|
|
49
|
+
const match = cleanedXmlString.match(pattern);
|
|
50
|
+
if (match) {
|
|
51
|
+
// Convert any form of attempt_complete to the standard format
|
|
52
|
+
return {
|
|
53
|
+
toolName: 'attempt_completion',
|
|
54
|
+
params: { result: '__PREVIOUS_RESPONSE__' }
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Additional recovery: check if the string contains attempt_complete anywhere
|
|
60
|
+
// and treat the entire response as a completion signal if no other tool tags are found
|
|
61
|
+
if (cleanedXmlString.includes('<attempt_complete') && !hasOtherToolTags(cleanedXmlString, validTools)) {
|
|
62
|
+
// This handles malformed cases where attempt_complete appears but is broken
|
|
63
|
+
return {
|
|
64
|
+
toolName: 'attempt_completion',
|
|
65
|
+
params: { result: '__PREVIOUS_RESPONSE__' }
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Helper function to check if the XML string contains other tool tags
|
|
74
|
+
* @param {string} xmlString - The XML string to check
|
|
75
|
+
* @param {string[]} validTools - List of valid tool names
|
|
76
|
+
* @returns {boolean} - True if other tool tags are found
|
|
77
|
+
*/
|
|
78
|
+
function hasOtherToolTags(xmlString, validTools = []) {
|
|
79
|
+
const defaultTools = ['search', 'query', 'extract', 'listFiles', 'searchFiles', 'implement', 'attempt_completion'];
|
|
80
|
+
const toolsToCheck = validTools.length > 0 ? validTools : defaultTools;
|
|
81
|
+
|
|
82
|
+
// Check for any tool tags other than attempt_complete variants
|
|
83
|
+
for (const tool of toolsToCheck) {
|
|
84
|
+
if (tool !== 'attempt_completion' && xmlString.includes(`<${tool}`)) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Apply the full thinking tag removal and attempt_complete recovery logic
|
|
93
|
+
* This replicates the core logic from parseXmlToolCallWithThinking
|
|
94
|
+
* @param {string} xmlString - The XML string to process
|
|
95
|
+
* @param {Array<string>} validTools - List of valid tool names
|
|
96
|
+
* @returns {Object} - Processing result with cleanedXml and potentialRecovery
|
|
97
|
+
*/
|
|
98
|
+
export function processXmlWithThinkingAndRecovery(xmlString, validTools = []) {
|
|
99
|
+
// Extract thinking content if present (for potential logging or analysis)
|
|
100
|
+
const thinkingContent = extractThinkingContent(xmlString);
|
|
101
|
+
|
|
102
|
+
// Remove thinking tags and their content from the XML string
|
|
103
|
+
const cleanedXmlString = removeThinkingTags(xmlString);
|
|
104
|
+
|
|
105
|
+
// Check for attempt_complete recovery patterns
|
|
106
|
+
const recoveryResult = checkAttemptCompleteRecovery(cleanedXmlString, validTools);
|
|
107
|
+
|
|
108
|
+
// If debugging is enabled, log the thinking content
|
|
109
|
+
if (process.env.DEBUG === '1' && thinkingContent) {
|
|
110
|
+
console.log(`[DEBUG] AI Thinking Process:\n${thinkingContent}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
cleanedXmlString,
|
|
115
|
+
thinkingContent,
|
|
116
|
+
recoveryResult
|
|
117
|
+
};
|
|
118
|
+
}
|
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) {
|