@probelabs/probe 0.6.0-rc104 → 0.6.0-rc106
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 +12 -1
- package/build/agent/bashCommandUtils.js +200 -0
- package/build/agent/bashDefaults.js +202 -0
- package/build/agent/bashExecutor.js +319 -0
- package/build/agent/bashPermissions.js +228 -0
- package/build/agent/index.js +1551 -167
- package/build/agent/mcp/xmlBridge.js +34 -6
- package/build/agent/probeTool.js +9 -0
- package/build/agent/schemaUtils.js +28 -8
- package/build/agent/tools.js +22 -76
- package/build/agent/xmlParsingUtils.js +118 -0
- package/build/index.js +6 -0
- package/build/tools/bash.js +216 -0
- package/build/tools/common.js +64 -0
- package/build/tools/index.js +6 -0
- package/cjs/agent/ProbeAgent.cjs +1506 -188
- package/cjs/index.cjs +1581 -197
- package/package.json +2 -2
- package/src/agent/ProbeAgent.js +12 -1
- package/src/agent/bashCommandUtils.js +200 -0
- package/src/agent/bashDefaults.js +202 -0
- package/src/agent/bashExecutor.js +319 -0
- package/src/agent/bashPermissions.js +228 -0
- package/src/agent/index.js +83 -2
- package/src/agent/mcp/xmlBridge.js +34 -6
- package/src/agent/probeTool.js +9 -0
- package/src/agent/schemaUtils.js +28 -8
- package/src/agent/tools.js +22 -76
- package/src/agent/xmlParsingUtils.js +118 -0
- package/src/index.js +6 -0
- package/src/tools/bash.js +216 -0
- package/src/tools/common.js +64 -0
- package/src/tools/index.js +6 -0
|
@@ -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/src/agent/probeTool.js
CHANGED
|
@@ -201,6 +201,15 @@ export function createWrappedTools(baseTools) {
|
|
|
201
201
|
);
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
+
// Wrap bash tool
|
|
205
|
+
if (baseTools.bashTool) {
|
|
206
|
+
wrappedTools.bashToolInstance = wrapToolWithEmitter(
|
|
207
|
+
baseTools.bashTool,
|
|
208
|
+
'bash',
|
|
209
|
+
baseTools.bashTool.execute
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
204
213
|
return wrappedTools;
|
|
205
214
|
}
|
|
206
215
|
|
package/src/agent/schemaUtils.js
CHANGED
|
@@ -1191,8 +1191,13 @@ export async function validateAndFixMermaidResponse(response, options = {}) {
|
|
|
1191
1191
|
// Check if content needs quoting (contains problematic patterns)
|
|
1192
1192
|
if (needsQuoting(content)) {
|
|
1193
1193
|
wasFixed = true;
|
|
1194
|
-
//
|
|
1195
|
-
|
|
1194
|
+
// Use HTML entities for quotes as per Mermaid best practices:
|
|
1195
|
+
// - GitHub doesn't support single quotes in node labels (causes 'got PS' error)
|
|
1196
|
+
// - HTML entities are the official way to escape quotes in Mermaid
|
|
1197
|
+
// - Always use double quotes with square brackets ["..."] for node labels
|
|
1198
|
+
const safeContent = content
|
|
1199
|
+
.replace(/"/g, '"') // Replace double quotes with HTML entity
|
|
1200
|
+
.replace(/'/g, '''); // Replace single quotes with HTML entity
|
|
1196
1201
|
return `["${safeContent}"]`;
|
|
1197
1202
|
}
|
|
1198
1203
|
|
|
@@ -1211,8 +1216,13 @@ export async function validateAndFixMermaidResponse(response, options = {}) {
|
|
|
1211
1216
|
// Check if content needs quoting (contains problematic patterns)
|
|
1212
1217
|
if (needsQuoting(content)) {
|
|
1213
1218
|
wasFixed = true;
|
|
1214
|
-
//
|
|
1215
|
-
|
|
1219
|
+
// Use HTML entities for quotes as per Mermaid best practices:
|
|
1220
|
+
// - GitHub doesn't support single quotes in node labels (causes 'got PS' error)
|
|
1221
|
+
// - HTML entities are the official way to escape quotes in Mermaid
|
|
1222
|
+
// - Always use double quotes with curly brackets {"..."} for diamond nodes
|
|
1223
|
+
const safeContent = content
|
|
1224
|
+
.replace(/"/g, '"') // Replace double quotes with HTML entity
|
|
1225
|
+
.replace(/'/g, '''); // Replace single quotes with HTML entity
|
|
1216
1226
|
return `{"${safeContent}"}`;
|
|
1217
1227
|
}
|
|
1218
1228
|
|
|
@@ -1446,8 +1456,13 @@ export async function validateAndFixMermaidResponse(response, options = {}) {
|
|
|
1446
1456
|
// Check if content needs quoting (contains problematic patterns)
|
|
1447
1457
|
if (needsQuoting(content)) {
|
|
1448
1458
|
wasFixed = true;
|
|
1449
|
-
//
|
|
1450
|
-
|
|
1459
|
+
// Use HTML entities for quotes as per Mermaid best practices:
|
|
1460
|
+
// - GitHub doesn't support single quotes in node labels (causes 'got PS' error)
|
|
1461
|
+
// - HTML entities are the official way to escape quotes in Mermaid
|
|
1462
|
+
// - Always use double quotes with square brackets ["..."] for node labels
|
|
1463
|
+
const safeContent = content
|
|
1464
|
+
.replace(/"/g, '"') // Replace double quotes with HTML entity
|
|
1465
|
+
.replace(/'/g, '''); // Replace single quotes with HTML entity
|
|
1451
1466
|
return `["${safeContent}"]`;
|
|
1452
1467
|
}
|
|
1453
1468
|
|
|
@@ -1466,8 +1481,13 @@ export async function validateAndFixMermaidResponse(response, options = {}) {
|
|
|
1466
1481
|
// Check if content needs quoting (contains problematic patterns)
|
|
1467
1482
|
if (needsQuoting(content)) {
|
|
1468
1483
|
wasFixed = true;
|
|
1469
|
-
//
|
|
1470
|
-
|
|
1484
|
+
// Use HTML entities for quotes as per Mermaid best practices:
|
|
1485
|
+
// - GitHub doesn't support single quotes in node labels (causes 'got PS' error)
|
|
1486
|
+
// - HTML entities are the official way to escape quotes in Mermaid
|
|
1487
|
+
// - Always use double quotes with curly brackets {"..."} for diamond nodes
|
|
1488
|
+
const safeContent = content
|
|
1489
|
+
.replace(/"/g, '"') // Replace double quotes with HTML entity
|
|
1490
|
+
.replace(/'/g, '''); // Replace single quotes with HTML entity
|
|
1471
1491
|
return `{"${safeContent}"}`;
|
|
1472
1492
|
}
|
|
1473
1493
|
|
package/src/agent/tools.js
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
queryTool,
|
|
5
5
|
extractTool,
|
|
6
6
|
delegateTool,
|
|
7
|
+
bashTool,
|
|
7
8
|
DEFAULT_SYSTEM_MESSAGE,
|
|
8
9
|
attemptCompletionSchema,
|
|
9
10
|
attemptCompletionToolDefinition,
|
|
@@ -11,22 +12,32 @@ import {
|
|
|
11
12
|
querySchema,
|
|
12
13
|
extractSchema,
|
|
13
14
|
delegateSchema,
|
|
15
|
+
bashSchema,
|
|
14
16
|
searchToolDefinition,
|
|
15
17
|
queryToolDefinition,
|
|
16
18
|
extractToolDefinition,
|
|
17
19
|
delegateToolDefinition,
|
|
20
|
+
bashToolDefinition,
|
|
18
21
|
parseXmlToolCall
|
|
19
22
|
} from '../index.js';
|
|
20
23
|
import { randomUUID } from 'crypto';
|
|
24
|
+
import { processXmlWithThinkingAndRecovery } from './xmlParsingUtils.js';
|
|
21
25
|
|
|
22
26
|
// Create configured tool instances
|
|
23
27
|
export function createTools(configOptions) {
|
|
24
|
-
|
|
28
|
+
const tools = {
|
|
25
29
|
searchTool: searchTool(configOptions),
|
|
26
30
|
queryTool: queryTool(configOptions),
|
|
27
31
|
extractTool: extractTool(configOptions),
|
|
28
32
|
delegateTool: delegateTool(configOptions)
|
|
29
33
|
};
|
|
34
|
+
|
|
35
|
+
// Add bash tool if enabled
|
|
36
|
+
if (configOptions.enableBash) {
|
|
37
|
+
tools.bashTool = bashTool(configOptions);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return tools;
|
|
30
41
|
}
|
|
31
42
|
|
|
32
43
|
// Export tool definitions and schemas
|
|
@@ -36,11 +47,13 @@ export {
|
|
|
36
47
|
querySchema,
|
|
37
48
|
extractSchema,
|
|
38
49
|
delegateSchema,
|
|
50
|
+
bashSchema,
|
|
39
51
|
attemptCompletionSchema,
|
|
40
52
|
searchToolDefinition,
|
|
41
53
|
queryToolDefinition,
|
|
42
54
|
extractToolDefinition,
|
|
43
55
|
delegateToolDefinition,
|
|
56
|
+
bashToolDefinition,
|
|
44
57
|
attemptCompletionToolDefinition,
|
|
45
58
|
parseXmlToolCall
|
|
46
59
|
};
|
|
@@ -134,82 +147,15 @@ User: Find all markdown files in the docs directory, but only at the top level.
|
|
|
134
147
|
* @returns {Object|null} - The parsed tool call or null if no valid tool call found
|
|
135
148
|
*/
|
|
136
149
|
export function parseXmlToolCallWithThinking(xmlString, validTools) {
|
|
137
|
-
//
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
|
181
|
-
return {
|
|
182
|
-
toolName: 'attempt_completion',
|
|
183
|
-
params: { result: '__PREVIOUS_RESPONSE__' }
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Use the original parseXmlToolCall function to parse the cleaned XML string
|
|
188
|
-
const parsedTool = parseXmlToolCall(cleanedXmlString, validTools);
|
|
189
|
-
|
|
190
|
-
// If debugging is enabled, log the thinking content
|
|
191
|
-
if (process.env.DEBUG === '1' && thinkingContent) {
|
|
192
|
-
console.log(`[DEBUG] AI Thinking Process:\n${thinkingContent}`);
|
|
150
|
+
// Use the shared processing logic
|
|
151
|
+
const { cleanedXmlString, recoveryResult } = processXmlWithThinkingAndRecovery(xmlString, validTools);
|
|
152
|
+
|
|
153
|
+
// If recovery found an attempt_complete pattern, return it
|
|
154
|
+
if (recoveryResult) {
|
|
155
|
+
return recoveryResult;
|
|
193
156
|
}
|
|
194
157
|
|
|
195
|
-
|
|
158
|
+
// Otherwise, use the original parseXmlToolCall function to parse the cleaned XML string
|
|
159
|
+
return parseXmlToolCall(cleanedXmlString, validTools);
|
|
196
160
|
}
|
|
197
161
|
|
|
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;
|
|
215
|
-
}
|
|
@@ -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/src/index.js
CHANGED
|
@@ -21,14 +21,17 @@ import {
|
|
|
21
21
|
extractSchema,
|
|
22
22
|
delegateSchema,
|
|
23
23
|
attemptCompletionSchema,
|
|
24
|
+
bashSchema,
|
|
24
25
|
searchToolDefinition,
|
|
25
26
|
queryToolDefinition,
|
|
26
27
|
extractToolDefinition,
|
|
27
28
|
delegateToolDefinition,
|
|
28
29
|
attemptCompletionToolDefinition,
|
|
30
|
+
bashToolDefinition,
|
|
29
31
|
parseXmlToolCall
|
|
30
32
|
} from './tools/common.js';
|
|
31
33
|
import { searchTool, queryTool, extractTool, delegateTool } from './tools/vercel.js';
|
|
34
|
+
import { bashTool } from './tools/bash.js';
|
|
32
35
|
import { ProbeAgent } from './agent/ProbeAgent.js';
|
|
33
36
|
import { SimpleTelemetry, SimpleAppTracer, initializeSimpleTelemetryFromOptions } from './agent/simpleTelemetry.js';
|
|
34
37
|
|
|
@@ -53,18 +56,21 @@ export {
|
|
|
53
56
|
queryTool,
|
|
54
57
|
extractTool,
|
|
55
58
|
delegateTool,
|
|
59
|
+
bashTool,
|
|
56
60
|
// Export schemas
|
|
57
61
|
searchSchema,
|
|
58
62
|
querySchema,
|
|
59
63
|
extractSchema,
|
|
60
64
|
delegateSchema,
|
|
61
65
|
attemptCompletionSchema,
|
|
66
|
+
bashSchema,
|
|
62
67
|
// Export tool definitions
|
|
63
68
|
searchToolDefinition,
|
|
64
69
|
queryToolDefinition,
|
|
65
70
|
extractToolDefinition,
|
|
66
71
|
delegateToolDefinition,
|
|
67
72
|
attemptCompletionToolDefinition,
|
|
73
|
+
bashToolDefinition,
|
|
68
74
|
// Export parser function
|
|
69
75
|
parseXmlToolCall
|
|
70
76
|
};
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bash command execution tool for Vercel AI SDK
|
|
3
|
+
* @module tools/bash
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { tool } from 'ai';
|
|
7
|
+
import { resolve } from 'path';
|
|
8
|
+
import { BashPermissionChecker } from '../agent/bashPermissions.js';
|
|
9
|
+
import { executeBashCommand, formatExecutionResult, validateExecutionOptions } from '../agent/bashExecutor.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Bash tool generator
|
|
13
|
+
*
|
|
14
|
+
* @param {Object} [options] - Configuration options
|
|
15
|
+
* @param {Object} [options.bashConfig] - Bash-specific configuration
|
|
16
|
+
* @param {string[]} [options.bashConfig.allow] - Additional allow patterns
|
|
17
|
+
* @param {string[]} [options.bashConfig.deny] - Additional deny patterns
|
|
18
|
+
* @param {boolean} [options.bashConfig.disableDefaultAllow] - Disable default allow list
|
|
19
|
+
* @param {boolean} [options.bashConfig.disableDefaultDeny] - Disable default deny list
|
|
20
|
+
* @param {number} [options.bashConfig.timeout=120000] - Command timeout in milliseconds
|
|
21
|
+
* @param {string} [options.bashConfig.workingDirectory] - Default working directory
|
|
22
|
+
* @param {Object} [options.bashConfig.env={}] - Default environment variables
|
|
23
|
+
* @param {number} [options.bashConfig.maxBuffer] - Maximum output buffer size
|
|
24
|
+
* @param {boolean} [options.debug=false] - Enable debug logging
|
|
25
|
+
* @param {string} [options.defaultPath] - Default working directory from probe config
|
|
26
|
+
* @param {string[]} [options.allowedFolders] - Allowed directories for execution
|
|
27
|
+
* @returns {Object} Configured bash tool
|
|
28
|
+
*/
|
|
29
|
+
export const bashTool = (options = {}) => {
|
|
30
|
+
const {
|
|
31
|
+
bashConfig = {},
|
|
32
|
+
debug = false,
|
|
33
|
+
defaultPath,
|
|
34
|
+
allowedFolders = []
|
|
35
|
+
} = options;
|
|
36
|
+
|
|
37
|
+
// Create permission checker
|
|
38
|
+
const permissionChecker = new BashPermissionChecker({
|
|
39
|
+
allow: bashConfig.allow,
|
|
40
|
+
deny: bashConfig.deny,
|
|
41
|
+
disableDefaultAllow: bashConfig.disableDefaultAllow,
|
|
42
|
+
disableDefaultDeny: bashConfig.disableDefaultDeny,
|
|
43
|
+
debug
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Determine default working directory
|
|
47
|
+
const getDefaultWorkingDirectory = () => {
|
|
48
|
+
if (bashConfig.workingDirectory) {
|
|
49
|
+
return bashConfig.workingDirectory;
|
|
50
|
+
}
|
|
51
|
+
if (defaultPath) {
|
|
52
|
+
return defaultPath;
|
|
53
|
+
}
|
|
54
|
+
if (allowedFolders && allowedFolders.length > 0) {
|
|
55
|
+
return allowedFolders[0];
|
|
56
|
+
}
|
|
57
|
+
return process.cwd();
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return tool({
|
|
61
|
+
name: 'bash',
|
|
62
|
+
description: `Execute bash commands for system exploration and development tasks.
|
|
63
|
+
|
|
64
|
+
Security: This tool has built-in security with allow/deny lists. By default, only safe read-only commands are allowed for code exploration.
|
|
65
|
+
|
|
66
|
+
Parameters:
|
|
67
|
+
- command: (required) The bash command to execute
|
|
68
|
+
- workingDirectory: (optional) Directory to execute command in
|
|
69
|
+
- timeout: (optional) Command timeout in milliseconds
|
|
70
|
+
- env: (optional) Additional environment variables
|
|
71
|
+
|
|
72
|
+
Examples of allowed commands by default:
|
|
73
|
+
- File exploration: ls, cat, head, tail, find, grep
|
|
74
|
+
- Git operations: git status, git log, git diff, git show
|
|
75
|
+
- Package info: npm list, pip list, cargo --version
|
|
76
|
+
- System info: whoami, pwd, uname, date
|
|
77
|
+
|
|
78
|
+
Dangerous commands are blocked by default (rm -rf, sudo, npm install, etc.)`,
|
|
79
|
+
|
|
80
|
+
inputSchema: {
|
|
81
|
+
type: 'object',
|
|
82
|
+
properties: {
|
|
83
|
+
command: {
|
|
84
|
+
type: 'string',
|
|
85
|
+
description: 'The bash command to execute'
|
|
86
|
+
},
|
|
87
|
+
workingDirectory: {
|
|
88
|
+
type: 'string',
|
|
89
|
+
description: 'Directory to execute the command in (optional)'
|
|
90
|
+
},
|
|
91
|
+
timeout: {
|
|
92
|
+
type: 'number',
|
|
93
|
+
description: 'Command timeout in milliseconds (optional)',
|
|
94
|
+
minimum: 1000,
|
|
95
|
+
maximum: 600000
|
|
96
|
+
},
|
|
97
|
+
env: {
|
|
98
|
+
type: 'object',
|
|
99
|
+
description: 'Additional environment variables (optional)',
|
|
100
|
+
additionalProperties: {
|
|
101
|
+
type: 'string'
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
required: ['command'],
|
|
106
|
+
additionalProperties: false
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
execute: async ({ command, workingDirectory, timeout, env }) => {
|
|
110
|
+
try {
|
|
111
|
+
// Validate command
|
|
112
|
+
if (command === null || command === undefined || typeof command !== 'string') {
|
|
113
|
+
return 'Error: Command is required and must be a string';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (command.trim().length === 0) {
|
|
117
|
+
return 'Error: Command cannot be empty';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Check permissions
|
|
121
|
+
const permissionResult = permissionChecker.check(command.trim());
|
|
122
|
+
if (!permissionResult.allowed) {
|
|
123
|
+
if (debug) {
|
|
124
|
+
console.log(`[BashTool] Permission denied for command: "${command}"`);
|
|
125
|
+
console.log(`[BashTool] Reason: ${permissionResult.reason}`);
|
|
126
|
+
}
|
|
127
|
+
return `Permission denied: ${permissionResult.reason}
|
|
128
|
+
|
|
129
|
+
This command is not allowed by the current security policy.
|
|
130
|
+
|
|
131
|
+
Common reasons:
|
|
132
|
+
1. The command is in the deny list (potentially dangerous)
|
|
133
|
+
2. The command is not in the allow list (not a recognized safe command)
|
|
134
|
+
|
|
135
|
+
If you believe this command should be allowed, you can:
|
|
136
|
+
- Use the --bash-allow option to add specific patterns
|
|
137
|
+
- Use the --no-default-bash-deny flag to remove default restrictions (not recommended)
|
|
138
|
+
|
|
139
|
+
For code exploration, try these safe alternatives:
|
|
140
|
+
- ls, cat, head, tail for file operations
|
|
141
|
+
- find, grep, rg for searching
|
|
142
|
+
- git status, git log, git show for git operations
|
|
143
|
+
- npm list, pip list for package information`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Determine working directory
|
|
147
|
+
const workingDir = workingDirectory || getDefaultWorkingDirectory();
|
|
148
|
+
|
|
149
|
+
// Validate working directory is within allowed folders if specified
|
|
150
|
+
if (allowedFolders && allowedFolders.length > 0) {
|
|
151
|
+
const resolvedWorkingDir = resolve(workingDir);
|
|
152
|
+
const isAllowed = allowedFolders.some(folder => {
|
|
153
|
+
const resolvedFolder = resolve(folder);
|
|
154
|
+
return resolvedWorkingDir.startsWith(resolvedFolder);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
if (!isAllowed) {
|
|
158
|
+
return `Error: Working directory "${workingDir}" is not within allowed folders: ${allowedFolders.join(', ')}`;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Prepare execution options
|
|
163
|
+
const executionOptions = {
|
|
164
|
+
workingDirectory: workingDir,
|
|
165
|
+
timeout: timeout || bashConfig.timeout || 120000,
|
|
166
|
+
env: { ...bashConfig.env, ...env },
|
|
167
|
+
maxBuffer: bashConfig.maxBuffer,
|
|
168
|
+
debug
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// Validate execution options
|
|
172
|
+
const validation = validateExecutionOptions(executionOptions);
|
|
173
|
+
if (!validation.valid) {
|
|
174
|
+
return `Error: Invalid execution options: ${validation.errors.join(', ')}`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (validation.warnings.length > 0 && debug) {
|
|
178
|
+
console.log('[BashTool] Warnings:', validation.warnings);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (debug) {
|
|
182
|
+
console.log(`[BashTool] Executing command: "${command}"`);
|
|
183
|
+
console.log(`[BashTool] Working directory: "${workingDir}"`);
|
|
184
|
+
console.log(`[BashTool] Timeout: ${executionOptions.timeout}ms`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Execute command
|
|
188
|
+
const result = await executeBashCommand(command.trim(), executionOptions);
|
|
189
|
+
|
|
190
|
+
if (debug) {
|
|
191
|
+
console.log(`[BashTool] Command completed - Success: ${result.success}, Duration: ${result.duration}ms`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Format and return result
|
|
195
|
+
const formattedResult = formatExecutionResult(result, debug);
|
|
196
|
+
|
|
197
|
+
// Add metadata for failed commands
|
|
198
|
+
if (!result.success) {
|
|
199
|
+
let errorInfo = `\n\nCommand failed with exit code ${result.exitCode}`;
|
|
200
|
+
if (result.killed) {
|
|
201
|
+
errorInfo += ` (${result.error})`;
|
|
202
|
+
}
|
|
203
|
+
return formattedResult + errorInfo;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return formattedResult;
|
|
207
|
+
|
|
208
|
+
} catch (error) {
|
|
209
|
+
if (debug) {
|
|
210
|
+
console.error('[BashTool] Execution error:', error);
|
|
211
|
+
}
|
|
212
|
+
return `Error executing bash command: ${error.message}`;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
};
|