@probelabs/probe 0.6.0-rc178 → 0.6.0-rc187
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.d.ts +15 -0
- package/build/agent/ProbeAgent.js +50 -23
- package/build/agent/bashCommandUtils.js +63 -0
- package/build/agent/bashExecutor.js +34 -19
- package/build/agent/bashPermissions.js +74 -13
- package/build/agent/index.js +232 -67
- package/build/agent/schemaUtils.js +7 -6
- package/build/delegate.js +10 -4
- package/build/tools/vercel.js +6 -2
- package/cjs/agent/ProbeAgent.cjs +2842 -1870
- package/cjs/index.cjs +2842 -1870
- package/package.json +1 -1
- package/src/agent/ProbeAgent.d.ts +15 -0
- package/src/agent/ProbeAgent.js +50 -23
- package/src/agent/bashCommandUtils.js +63 -0
- package/src/agent/bashExecutor.js +34 -19
- package/src/agent/bashPermissions.js +74 -13
- package/src/agent/index.js +3 -3
- package/src/agent/schemaUtils.js +7 -6
- package/src/delegate.js +10 -4
- package/src/tools/vercel.js +6 -2
|
@@ -19,6 +19,21 @@ export interface ProbeAgentOptions {
|
|
|
19
19
|
allowEdit?: boolean;
|
|
20
20
|
/** Enable the delegate tool for task distribution to subagents */
|
|
21
21
|
enableDelegate?: boolean;
|
|
22
|
+
/** Enable bash tool for command execution */
|
|
23
|
+
enableBash?: boolean;
|
|
24
|
+
/** Bash tool configuration (allow/deny patterns) */
|
|
25
|
+
bashConfig?: {
|
|
26
|
+
/** Additional allowed command patterns */
|
|
27
|
+
allow?: string[];
|
|
28
|
+
/** Additional denied command patterns */
|
|
29
|
+
deny?: string[];
|
|
30
|
+
/** Disable default allow list */
|
|
31
|
+
disableDefaultAllow?: boolean;
|
|
32
|
+
/** Disable default deny list */
|
|
33
|
+
disableDefaultDeny?: boolean;
|
|
34
|
+
/** Enable debug logging for permission checks */
|
|
35
|
+
debug?: boolean;
|
|
36
|
+
};
|
|
22
37
|
/** Search directory path */
|
|
23
38
|
path?: string;
|
|
24
39
|
/** Force specific AI provider */
|
|
@@ -2372,31 +2372,41 @@ When troubleshooting:
|
|
|
2372
2372
|
|
|
2373
2373
|
// Parse tool call from response with valid tools list
|
|
2374
2374
|
// Build validTools based on allowedTools configuration (same pattern as getSystemMessage)
|
|
2375
|
+
// When _disableTools is set, only allow attempt_completion for JSON correction flows
|
|
2375
2376
|
const validTools = [];
|
|
2376
|
-
if (
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2377
|
+
if (options._disableTools) {
|
|
2378
|
+
// Only allow attempt_completion for JSON correction - no search/query/edit tools
|
|
2379
|
+
validTools.push('attempt_completion');
|
|
2380
|
+
if (this.debug) {
|
|
2381
|
+
console.log(`[DEBUG] Tools disabled for this call - only attempt_completion allowed`);
|
|
2382
|
+
}
|
|
2383
|
+
} else {
|
|
2384
|
+
if (this.allowedTools.isEnabled('search')) validTools.push('search');
|
|
2385
|
+
if (this.allowedTools.isEnabled('query')) validTools.push('query');
|
|
2386
|
+
if (this.allowedTools.isEnabled('extract')) validTools.push('extract');
|
|
2387
|
+
if (this.allowedTools.isEnabled('listFiles')) validTools.push('listFiles');
|
|
2388
|
+
if (this.allowedTools.isEnabled('searchFiles')) validTools.push('searchFiles');
|
|
2389
|
+
if (this.allowedTools.isEnabled('readImage')) validTools.push('readImage');
|
|
2390
|
+
if (this.allowedTools.isEnabled('attempt_completion')) validTools.push('attempt_completion');
|
|
2391
|
+
|
|
2392
|
+
// Edit tools (require both allowEdit flag AND allowedTools permission)
|
|
2393
|
+
if (this.allowEdit && this.allowedTools.isEnabled('implement')) {
|
|
2394
|
+
validTools.push('implement', 'edit', 'create');
|
|
2395
|
+
}
|
|
2396
|
+
// Bash tool (require both enableBash flag AND allowedTools permission)
|
|
2397
|
+
if (this.enableBash && this.allowedTools.isEnabled('bash')) {
|
|
2398
|
+
validTools.push('bash');
|
|
2399
|
+
}
|
|
2400
|
+
// Delegate tool (require both enableDelegate flag AND allowedTools permission)
|
|
2401
|
+
if (this.enableDelegate && this.allowedTools.isEnabled('delegate')) {
|
|
2402
|
+
validTools.push('delegate');
|
|
2403
|
+
}
|
|
2395
2404
|
}
|
|
2396
|
-
|
|
2405
|
+
|
|
2397
2406
|
// Try parsing with hybrid parser that supports both native and MCP tools
|
|
2407
|
+
// When _disableTools is set, skip MCP tools entirely
|
|
2398
2408
|
const nativeTools = validTools;
|
|
2399
|
-
const parsedTool = this.mcpBridge
|
|
2409
|
+
const parsedTool = (this.mcpBridge && !options._disableTools)
|
|
2400
2410
|
? parseHybridXmlToolCall(assistantResponseContent, nativeTools, this.mcpBridge)
|
|
2401
2411
|
: parseXmlToolCallWithThinking(assistantResponseContent, validTools);
|
|
2402
2412
|
if (parsedTool) {
|
|
@@ -2501,10 +2511,26 @@ When troubleshooting:
|
|
|
2501
2511
|
// Execute native tool
|
|
2502
2512
|
try {
|
|
2503
2513
|
// Add sessionId and workingDirectory to params for tool execution
|
|
2514
|
+
// Validate and resolve workingDirectory
|
|
2515
|
+
let resolvedWorkingDirectory = (this.allowedFolders && this.allowedFolders[0]) || process.cwd();
|
|
2516
|
+
if (params.workingDirectory) {
|
|
2517
|
+
const requestedDir = resolve(params.workingDirectory);
|
|
2518
|
+
// Check if the requested directory is within allowed folders
|
|
2519
|
+
const isWithinAllowed = !this.allowedFolders || this.allowedFolders.length === 0 ||
|
|
2520
|
+
this.allowedFolders.some(folder => {
|
|
2521
|
+
const resolvedFolder = resolve(folder);
|
|
2522
|
+
return requestedDir === resolvedFolder || requestedDir.startsWith(resolvedFolder + sep);
|
|
2523
|
+
});
|
|
2524
|
+
if (isWithinAllowed) {
|
|
2525
|
+
resolvedWorkingDirectory = requestedDir;
|
|
2526
|
+
} else if (this.debug) {
|
|
2527
|
+
console.error(`[DEBUG] Rejected workingDirectory "${params.workingDirectory}" - not within allowed folders`);
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2504
2530
|
const toolParams = {
|
|
2505
2531
|
...params,
|
|
2506
2532
|
sessionId: this.sessionId,
|
|
2507
|
-
workingDirectory:
|
|
2533
|
+
workingDirectory: resolvedWorkingDirectory
|
|
2508
2534
|
};
|
|
2509
2535
|
|
|
2510
2536
|
// Log tool execution in debug mode
|
|
@@ -3148,7 +3174,8 @@ Convert your previous response content into actual JSON data that follows this s
|
|
|
3148
3174
|
finalResult = await this.answer(correctionPrompt, [], {
|
|
3149
3175
|
...options,
|
|
3150
3176
|
_schemaFormatted: true,
|
|
3151
|
-
_skipValidation: true // Skip validation in recursive correction calls to prevent loops
|
|
3177
|
+
_skipValidation: true, // Skip validation in recursive correction calls to prevent loops
|
|
3178
|
+
_disableTools: true // Only allow attempt_completion - prevent AI from using search/query tools
|
|
3152
3179
|
});
|
|
3153
3180
|
finalResult = cleanSchemaResponse(finalResult);
|
|
3154
3181
|
|
|
@@ -159,6 +159,69 @@ export function isComplexCommand(command) {
|
|
|
159
159
|
return result.isComplex;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
/**
|
|
163
|
+
* Check if a pattern is a complex pattern (contains shell operators)
|
|
164
|
+
* Complex patterns are used to match full command strings including operators
|
|
165
|
+
* @param {string} pattern - Pattern to check
|
|
166
|
+
* @returns {boolean} True if pattern contains shell operators
|
|
167
|
+
*/
|
|
168
|
+
export function isComplexPattern(pattern) {
|
|
169
|
+
if (!pattern || typeof pattern !== 'string') return false;
|
|
170
|
+
|
|
171
|
+
// Check for operators in the pattern (aligned with complexPatterns in parseSimpleCommand)
|
|
172
|
+
const operatorPatterns = [
|
|
173
|
+
/\|/, // Pipes
|
|
174
|
+
/&&/, // Logical AND
|
|
175
|
+
/\|\|/, // Logical OR
|
|
176
|
+
/;/, // Command separator
|
|
177
|
+
/&$/, // Background execution
|
|
178
|
+
/\$\(/, // Command substitution $()
|
|
179
|
+
/`/, // Command substitution ``
|
|
180
|
+
/>/, // Redirection >
|
|
181
|
+
/</, // Redirection <
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
return operatorPatterns.some(p => p.test(pattern));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Convert a glob-style pattern to regex for matching
|
|
189
|
+
* Supports * as wildcard (matches any characters except operators)
|
|
190
|
+
* @param {string} pattern - Glob pattern
|
|
191
|
+
* @returns {RegExp} Compiled regex
|
|
192
|
+
*/
|
|
193
|
+
function globToRegex(pattern) {
|
|
194
|
+
// Escape regex special characters except *
|
|
195
|
+
let escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
|
|
196
|
+
// Convert * to .*? (non-greedy match)
|
|
197
|
+
escaped = escaped.replace(/\*/g, '.*?');
|
|
198
|
+
// Make it match the full string
|
|
199
|
+
return new RegExp('^' + escaped + '$', 'i');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Match a command string against a complex pattern
|
|
204
|
+
* Complex patterns use glob-style wildcards (*) for matching
|
|
205
|
+
* @param {string} command - Full command string
|
|
206
|
+
* @param {string} pattern - Complex pattern with wildcards
|
|
207
|
+
* @returns {boolean} True if command matches the pattern
|
|
208
|
+
*/
|
|
209
|
+
export function matchesComplexPattern(command, pattern) {
|
|
210
|
+
if (!command || !pattern) return false;
|
|
211
|
+
|
|
212
|
+
// Normalize whitespace
|
|
213
|
+
const normalizedCommand = command.trim().replace(/\s+/g, ' ');
|
|
214
|
+
const normalizedPattern = pattern.trim().replace(/\s+/g, ' ');
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const regex = globToRegex(normalizedPattern);
|
|
218
|
+
return regex.test(normalizedCommand);
|
|
219
|
+
} catch (e) {
|
|
220
|
+
// If regex fails, fall back to exact match
|
|
221
|
+
return normalizedCommand === normalizedPattern;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
162
225
|
/**
|
|
163
226
|
* Legacy compatibility function - parses command for permission checking
|
|
164
227
|
* @param {string} command - Command to parse
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { spawn } from 'child_process';
|
|
7
7
|
import { resolve, join } from 'path';
|
|
8
8
|
import { existsSync } from 'fs';
|
|
9
|
-
import { parseCommandForExecution } from './bashCommandUtils.js';
|
|
9
|
+
import { parseCommandForExecution, isComplexCommand } from './bashCommandUtils.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Execute a bash command with security controls
|
|
@@ -63,31 +63,46 @@ export async function executeBashCommand(command, options = {}) {
|
|
|
63
63
|
...env
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
const args = parseCommandForExecution(command);
|
|
69
|
-
if (!args || args.length === 0) {
|
|
70
|
-
resolve({
|
|
71
|
-
success: false,
|
|
72
|
-
error: 'Failed to parse command',
|
|
73
|
-
stdout: '',
|
|
74
|
-
stderr: '',
|
|
75
|
-
exitCode: 1,
|
|
76
|
-
command,
|
|
77
|
-
workingDirectory: cwd,
|
|
78
|
-
duration: Date.now() - startTime
|
|
79
|
-
});
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
66
|
+
// Check if this is a complex command (contains pipes, operators, etc.)
|
|
67
|
+
const isComplex = isComplexCommand(command);
|
|
82
68
|
|
|
83
|
-
|
|
69
|
+
let cmd, cmdArgs, useShell;
|
|
70
|
+
|
|
71
|
+
if (isComplex) {
|
|
72
|
+
// For complex commands, use sh -c to execute through shell
|
|
73
|
+
// This is only reached if the permission checker allowed the complex command
|
|
74
|
+
cmd = 'sh';
|
|
75
|
+
cmdArgs = ['-c', command];
|
|
76
|
+
useShell = false; // We explicitly use sh -c, not spawn's shell option
|
|
77
|
+
if (debug) {
|
|
78
|
+
console.log(`[BashExecutor] Complex command - using sh -c`);
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
// Parse simple command for direct execution
|
|
82
|
+
const args = parseCommandForExecution(command);
|
|
83
|
+
if (!args || args.length === 0) {
|
|
84
|
+
resolve({
|
|
85
|
+
success: false,
|
|
86
|
+
error: 'Failed to parse command',
|
|
87
|
+
stdout: '',
|
|
88
|
+
stderr: '',
|
|
89
|
+
exitCode: 1,
|
|
90
|
+
command,
|
|
91
|
+
workingDirectory: cwd,
|
|
92
|
+
duration: Date.now() - startTime
|
|
93
|
+
});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
[cmd, ...cmdArgs] = args;
|
|
97
|
+
useShell = false;
|
|
98
|
+
}
|
|
84
99
|
|
|
85
100
|
// Spawn the process
|
|
86
101
|
const child = spawn(cmd, cmdArgs, {
|
|
87
102
|
cwd,
|
|
88
103
|
env: processEnv,
|
|
89
104
|
stdio: ['ignore', 'pipe', 'pipe'], // stdin ignored, capture stdout/stderr
|
|
90
|
-
shell:
|
|
105
|
+
shell: useShell, // false for security
|
|
91
106
|
windowsHide: true
|
|
92
107
|
});
|
|
93
108
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { DEFAULT_ALLOW_PATTERNS, DEFAULT_DENY_PATTERNS } from './bashDefaults.js';
|
|
7
|
-
import { parseCommand, isComplexCommand } from './bashCommandUtils.js';
|
|
7
|
+
import { parseCommand, isComplexCommand, isComplexPattern, matchesComplexPattern } from './bashCommandUtils.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Check if a pattern matches a parsed command
|
|
@@ -125,7 +125,7 @@ export class BashPermissionChecker {
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
/**
|
|
128
|
-
* Check if a simple command is allowed (
|
|
128
|
+
* Check if a simple command is allowed (complex commands allowed if they match patterns)
|
|
129
129
|
* @param {string} command - Command to check
|
|
130
130
|
* @returns {Object} Permission result
|
|
131
131
|
*/
|
|
@@ -138,19 +138,17 @@ export class BashPermissionChecker {
|
|
|
138
138
|
};
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
//
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
isComplex: true
|
|
148
|
-
};
|
|
141
|
+
// Check if this is a complex command
|
|
142
|
+
const commandIsComplex = isComplexCommand(command);
|
|
143
|
+
|
|
144
|
+
if (commandIsComplex) {
|
|
145
|
+
// For complex commands, check against complex patterns in allow/deny lists
|
|
146
|
+
return this._checkComplexCommand(command);
|
|
149
147
|
}
|
|
150
148
|
|
|
151
149
|
// Parse the simple command
|
|
152
150
|
const parsed = parseCommand(command);
|
|
153
|
-
|
|
151
|
+
|
|
154
152
|
if (parsed.error) {
|
|
155
153
|
return {
|
|
156
154
|
allowed: false,
|
|
@@ -203,14 +201,77 @@ export class BashPermissionChecker {
|
|
|
203
201
|
parsed: parsed,
|
|
204
202
|
isComplex: false
|
|
205
203
|
};
|
|
206
|
-
|
|
204
|
+
|
|
207
205
|
if (this.debug) {
|
|
208
206
|
console.log(`[BashPermissions] ALLOWED - command passed all checks`);
|
|
209
207
|
}
|
|
210
|
-
|
|
208
|
+
|
|
211
209
|
return result;
|
|
212
210
|
}
|
|
213
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Check a complex command against complex patterns in allow/deny lists
|
|
214
|
+
* @private
|
|
215
|
+
* @param {string} command - Complex command to check
|
|
216
|
+
* @returns {Object} Permission result
|
|
217
|
+
*/
|
|
218
|
+
_checkComplexCommand(command) {
|
|
219
|
+
if (this.debug) {
|
|
220
|
+
console.log(`[BashPermissions] Checking complex command: "${command}"`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Get complex patterns from allow and deny lists
|
|
224
|
+
const complexAllowPatterns = this.allowPatterns.filter(p => isComplexPattern(p));
|
|
225
|
+
const complexDenyPatterns = this.denyPatterns.filter(p => isComplexPattern(p));
|
|
226
|
+
|
|
227
|
+
if (this.debug) {
|
|
228
|
+
console.log(`[BashPermissions] Complex allow patterns: ${complexAllowPatterns.length}`);
|
|
229
|
+
console.log(`[BashPermissions] Complex deny patterns: ${complexDenyPatterns.length}`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Check deny patterns first (deny takes precedence)
|
|
233
|
+
for (const pattern of complexDenyPatterns) {
|
|
234
|
+
if (matchesComplexPattern(command, pattern)) {
|
|
235
|
+
if (this.debug) {
|
|
236
|
+
console.log(`[BashPermissions] DENIED - matches complex deny pattern: ${pattern}`);
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
allowed: false,
|
|
240
|
+
reason: `Command matches deny pattern: ${pattern}`,
|
|
241
|
+
command: command,
|
|
242
|
+
isComplex: true,
|
|
243
|
+
matchedPatterns: [pattern]
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Check allow patterns
|
|
249
|
+
for (const pattern of complexAllowPatterns) {
|
|
250
|
+
if (matchesComplexPattern(command, pattern)) {
|
|
251
|
+
if (this.debug) {
|
|
252
|
+
console.log(`[BashPermissions] ALLOWED - matches complex allow pattern: ${pattern}`);
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
allowed: true,
|
|
256
|
+
command: command,
|
|
257
|
+
isComplex: true,
|
|
258
|
+
matchedPattern: pattern
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// No matching complex pattern found - reject complex command
|
|
264
|
+
if (this.debug) {
|
|
265
|
+
console.log(`[BashPermissions] DENIED - no matching complex pattern found`);
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
allowed: false,
|
|
269
|
+
reason: 'Complex shell commands require explicit allow patterns (e.g., "cd * && git *")',
|
|
270
|
+
command: command,
|
|
271
|
+
isComplex: true
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
214
275
|
/**
|
|
215
276
|
* Get configuration summary
|
|
216
277
|
* @returns {Object} Configuration info
|