@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.
@@ -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 standard XML parsing
264
- for (const toolName of nativeTools) {
265
- const nativeResult = parseNativeXmlTool(xmlString, toolName);
266
- if (nativeResult) {
267
- return { ...nativeResult, type: 'native' };
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
@@ -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
 
@@ -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
- // Replace internal double quotes with single quotes to avoid nesting
1195
- const safeContent = content.replace(/"/g, "'");
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, '&quot;') // Replace double quotes with HTML entity
1200
+ .replace(/'/g, '&#39;'); // 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
- // Replace internal double quotes with single quotes to avoid nesting
1215
- const safeContent = content.replace(/"/g, "'");
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, '&quot;') // Replace double quotes with HTML entity
1225
+ .replace(/'/g, '&#39;'); // 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
- // Replace internal double quotes with single quotes to avoid nesting
1450
- const safeContent = content.replace(/"/g, "'");
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, '&quot;') // Replace double quotes with HTML entity
1465
+ .replace(/'/g, '&#39;'); // 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
- // Replace internal double quotes with single quotes to avoid nesting
1470
- const safeContent = content.replace(/"/g, "'");
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, '&quot;') // Replace double quotes with HTML entity
1490
+ .replace(/'/g, '&#39;'); // Replace single quotes with HTML entity
1471
1491
  return `{"${safeContent}"}`;
1472
1492
  }
1473
1493
 
@@ -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
- return {
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
- // Extract thinking content if present (for potential logging or analysis)
138
- const thinkingMatch = xmlString.match(/<thinking>([\s\S]*?)<\/thinking>/);
139
- const thinkingContent = thinkingMatch ? thinkingMatch[1].trim() : null;
140
-
141
- // Remove thinking tags and their content from the XML string
142
- let cleanedXmlString = xmlString.replace(/<thinking>[\s\S]*?<\/thinking>/g, '').trim();
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
- return parsedTool;
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/build/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
+ };