@probelabs/probe 0.6.0-rc186 → 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.
@@ -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 (this.allowedTools.isEnabled('search')) validTools.push('search');
2377
- if (this.allowedTools.isEnabled('query')) validTools.push('query');
2378
- if (this.allowedTools.isEnabled('extract')) validTools.push('extract');
2379
- if (this.allowedTools.isEnabled('listFiles')) validTools.push('listFiles');
2380
- if (this.allowedTools.isEnabled('searchFiles')) validTools.push('searchFiles');
2381
- if (this.allowedTools.isEnabled('readImage')) validTools.push('readImage');
2382
- if (this.allowedTools.isEnabled('attempt_completion')) validTools.push('attempt_completion');
2383
-
2384
- // Edit tools (require both allowEdit flag AND allowedTools permission)
2385
- if (this.allowEdit && this.allowedTools.isEnabled('implement')) {
2386
- validTools.push('implement', 'edit', 'create');
2387
- }
2388
- // Bash tool (require both enableBash flag AND allowedTools permission)
2389
- if (this.enableBash && this.allowedTools.isEnabled('bash')) {
2390
- validTools.push('bash');
2391
- }
2392
- // Delegate tool (require both enableDelegate flag AND allowedTools permission)
2393
- if (this.enableDelegate && this.allowedTools.isEnabled('delegate')) {
2394
- validTools.push('delegate');
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) {
@@ -3164,7 +3174,8 @@ Convert your previous response content into actual JSON data that follows this s
3164
3174
  finalResult = await this.answer(correctionPrompt, [], {
3165
3175
  ...options,
3166
3176
  _schemaFormatted: true,
3167
- _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
3168
3179
  });
3169
3180
  finalResult = cleanSchemaResponse(finalResult);
3170
3181
 
@@ -3513,7 +3513,9 @@ async function delegate({
3513
3513
  parentSessionId = null,
3514
3514
  path: path9 = null,
3515
3515
  provider = null,
3516
- model = null
3516
+ model = null,
3517
+ enableBash = false,
3518
+ bashConfig = null
3517
3519
  }) {
3518
3520
  if (!task || typeof task !== "string") {
3519
3521
  throw new Error("Task parameter is required and must be a string");
@@ -3555,7 +3557,11 @@ async function delegate({
3555
3557
  // Inherit from parent
3556
3558
  provider,
3557
3559
  // Inherit from parent
3558
- model
3560
+ model,
3561
+ // Inherit from parent
3562
+ enableBash,
3563
+ // Inherit from parent
3564
+ bashConfig
3559
3565
  // Inherit from parent
3560
3566
  });
3561
3567
  if (debug) {
@@ -8455,7 +8461,7 @@ var init_vercel = __esm({
8455
8461
  });
8456
8462
  };
8457
8463
  delegateTool = (options = {}) => {
8458
- const { debug = false, timeout = 300, cwd, allowedFolders } = options;
8464
+ const { debug = false, timeout = 300, cwd, allowedFolders, enableBash = false, bashConfig } = options;
8459
8465
  return tool({
8460
8466
  name: "delegate",
8461
8467
  description: delegateDescription,
@@ -8505,7 +8511,9 @@ var init_vercel = __esm({
8505
8511
  path: effectivePath,
8506
8512
  provider,
8507
8513
  model,
8508
- tracer
8514
+ tracer,
8515
+ enableBash,
8516
+ bashConfig
8509
8517
  });
8510
8518
  return result;
8511
8519
  }
@@ -55098,18 +55106,18 @@ function createJsonCorrectionPrompt(invalidResponse, schema, errorOrValidation,
55098
55106
  const strengthLevels = [
55099
55107
  {
55100
55108
  prefix: "CRITICAL JSON ERROR:",
55101
- instruction: "You MUST fix this and return ONLY valid JSON.",
55102
- emphasis: "Return ONLY the corrected JSON, with no additional text or markdown formatting."
55109
+ instruction: "You MUST fix this and respond using attempt_completion with ONLY valid JSON as the result.",
55110
+ emphasis: "Use attempt_completion with ONLY the corrected JSON in the result field. No explanatory text, no markdown, no code blocks."
55103
55111
  },
55104
55112
  {
55105
55113
  prefix: "URGENT - JSON PARSING FAILED:",
55106
- instruction: "This is your second chance. Return ONLY valid JSON that can be parsed by JSON.parse().",
55107
- emphasis: "ABSOLUTELY NO explanatory text, greetings, or formatting. ONLY JSON."
55114
+ instruction: "This is your second chance. Use attempt_completion with valid JSON that can be parsed by JSON.parse().",
55115
+ emphasis: "ABSOLUTELY NO explanatory text or formatting. Use attempt_completion with ONLY raw JSON in the result."
55108
55116
  },
55109
55117
  {
55110
55118
  prefix: "FINAL ATTEMPT - CRITICAL JSON ERROR:",
55111
- instruction: "This is the final retry. You MUST return ONLY raw JSON without any other content.",
55112
- emphasis: 'EXAMPLE: {"key": "value"} NOT: ```json{"key": "value"}``` NOT: Here is the JSON: {"key": "value"}'
55119
+ instruction: "This is the final retry. You MUST use attempt_completion with ONLY raw JSON in the result field.",
55120
+ emphasis: 'CORRECT: <attempt_completion><result>{"key": "value"}</result></attempt_completion>\nWRONG: Here is the JSON: {"key": "value"}\nWRONG: ```json{"key": "value"}```'
55113
55121
  }
55114
55122
  ];
55115
55123
  const level = Math.min(retryCount, strengthLevels.length - 1);
@@ -61123,24 +61131,31 @@ You are working with a repository located at: ${searchDirectory}
61123
61131
  console.log(`[DEBUG] Assistant response (${assistantResponseContent.length} chars): ${assistantPreview}`);
61124
61132
  }
61125
61133
  const validTools = [];
61126
- if (this.allowedTools.isEnabled("search")) validTools.push("search");
61127
- if (this.allowedTools.isEnabled("query")) validTools.push("query");
61128
- if (this.allowedTools.isEnabled("extract")) validTools.push("extract");
61129
- if (this.allowedTools.isEnabled("listFiles")) validTools.push("listFiles");
61130
- if (this.allowedTools.isEnabled("searchFiles")) validTools.push("searchFiles");
61131
- if (this.allowedTools.isEnabled("readImage")) validTools.push("readImage");
61132
- if (this.allowedTools.isEnabled("attempt_completion")) validTools.push("attempt_completion");
61133
- if (this.allowEdit && this.allowedTools.isEnabled("implement")) {
61134
- validTools.push("implement", "edit", "create");
61135
- }
61136
- if (this.enableBash && this.allowedTools.isEnabled("bash")) {
61137
- validTools.push("bash");
61138
- }
61139
- if (this.enableDelegate && this.allowedTools.isEnabled("delegate")) {
61140
- validTools.push("delegate");
61134
+ if (options._disableTools) {
61135
+ validTools.push("attempt_completion");
61136
+ if (this.debug) {
61137
+ console.log(`[DEBUG] Tools disabled for this call - only attempt_completion allowed`);
61138
+ }
61139
+ } else {
61140
+ if (this.allowedTools.isEnabled("search")) validTools.push("search");
61141
+ if (this.allowedTools.isEnabled("query")) validTools.push("query");
61142
+ if (this.allowedTools.isEnabled("extract")) validTools.push("extract");
61143
+ if (this.allowedTools.isEnabled("listFiles")) validTools.push("listFiles");
61144
+ if (this.allowedTools.isEnabled("searchFiles")) validTools.push("searchFiles");
61145
+ if (this.allowedTools.isEnabled("readImage")) validTools.push("readImage");
61146
+ if (this.allowedTools.isEnabled("attempt_completion")) validTools.push("attempt_completion");
61147
+ if (this.allowEdit && this.allowedTools.isEnabled("implement")) {
61148
+ validTools.push("implement", "edit", "create");
61149
+ }
61150
+ if (this.enableBash && this.allowedTools.isEnabled("bash")) {
61151
+ validTools.push("bash");
61152
+ }
61153
+ if (this.enableDelegate && this.allowedTools.isEnabled("delegate")) {
61154
+ validTools.push("delegate");
61155
+ }
61141
61156
  }
61142
61157
  const nativeTools = validTools;
61143
- const parsedTool = this.mcpBridge ? parseHybridXmlToolCall(assistantResponseContent, nativeTools, this.mcpBridge) : parseXmlToolCallWithThinking(assistantResponseContent, validTools);
61158
+ const parsedTool = this.mcpBridge && !options._disableTools ? parseHybridXmlToolCall(assistantResponseContent, nativeTools, this.mcpBridge) : parseXmlToolCallWithThinking(assistantResponseContent, validTools);
61144
61159
  if (parsedTool) {
61145
61160
  const { toolName, params } = parsedTool;
61146
61161
  if (this.debug) console.log(`[DEBUG] Parsed tool call: ${toolName} with params:`, params);
@@ -61752,8 +61767,10 @@ Convert your previous response content into actual JSON data that follows this s
61752
61767
  finalResult = await this.answer(correctionPrompt, [], {
61753
61768
  ...options,
61754
61769
  _schemaFormatted: true,
61755
- _skipValidation: true
61770
+ _skipValidation: true,
61756
61771
  // Skip validation in recursive correction calls to prevent loops
61772
+ _disableTools: true
61773
+ // Only allow attempt_completion - prevent AI from using search/query tools
61757
61774
  });
61758
61775
  finalResult = cleanSchemaResponse(finalResult);
61759
61776
  validation = validateJsonResponse(finalResult, { debug: this.debug });
@@ -63232,7 +63249,7 @@ Please reformat your previous response to match this schema exactly. Only return
63232
63249
  if (!validation.isValid) {
63233
63250
  const correctionPrompt = createJsonCorrectionPrompt(result, schema, validation.error);
63234
63251
  try {
63235
- result = await agent.answer(correctionPrompt, [], { schema, _schemaFormatted: true });
63252
+ result = await agent.answer(correctionPrompt, [], { schema, _schemaFormatted: true, _disableTools: true });
63236
63253
  result = cleanSchemaResponse(result);
63237
63254
  const finalValidation = validateJsonResponse(result);
63238
63255
  if (!finalValidation.isValid && args.debug) {
@@ -63518,11 +63535,11 @@ Please reformat your previous response to match this schema exactly. Only return
63518
63535
  if (appTracer) {
63519
63536
  result = await appTracer.withSpan(
63520
63537
  "agent.json_correction",
63521
- () => agent.answer(correctionPrompt, [], { schema, _schemaFormatted: true }),
63538
+ () => agent.answer(correctionPrompt, [], { schema, _schemaFormatted: true, _disableTools: true }),
63522
63539
  { "original_error": validation.error }
63523
63540
  );
63524
63541
  } else {
63525
- result = await agent.answer(correctionPrompt, [], { schema, _schemaFormatted: true });
63542
+ result = await agent.answer(correctionPrompt, [], { schema, _schemaFormatted: true, _disableTools: true });
63526
63543
  }
63527
63544
  result = cleanSchemaResponse(result);
63528
63545
  const finalValidation = validateJsonResponse(result);
@@ -787,21 +787,22 @@ export function createJsonCorrectionPrompt(invalidResponse, schema, errorOrValid
787
787
  }
788
788
 
789
789
  // Create increasingly stronger prompts based on retry attempt
790
+ // These prompts explicitly instruct the AI to use attempt_completion with the JSON result
790
791
  const strengthLevels = [
791
792
  {
792
793
  prefix: "CRITICAL JSON ERROR:",
793
- instruction: "You MUST fix this and return ONLY valid JSON.",
794
- emphasis: "Return ONLY the corrected JSON, with no additional text or markdown formatting."
794
+ instruction: "You MUST fix this and respond using attempt_completion with ONLY valid JSON as the result.",
795
+ emphasis: "Use attempt_completion with ONLY the corrected JSON in the result field. No explanatory text, no markdown, no code blocks."
795
796
  },
796
797
  {
797
798
  prefix: "URGENT - JSON PARSING FAILED:",
798
- instruction: "This is your second chance. Return ONLY valid JSON that can be parsed by JSON.parse().",
799
- emphasis: "ABSOLUTELY NO explanatory text, greetings, or formatting. ONLY JSON."
799
+ instruction: "This is your second chance. Use attempt_completion with valid JSON that can be parsed by JSON.parse().",
800
+ emphasis: "ABSOLUTELY NO explanatory text or formatting. Use attempt_completion with ONLY raw JSON in the result."
800
801
  },
801
802
  {
802
803
  prefix: "FINAL ATTEMPT - CRITICAL JSON ERROR:",
803
- instruction: "This is the final retry. You MUST return ONLY raw JSON without any other content.",
804
- emphasis: "EXAMPLE: {\"key\": \"value\"} NOT: ```json{\"key\": \"value\"}``` NOT: Here is the JSON: {\"key\": \"value\"}"
804
+ instruction: "This is the final retry. You MUST use attempt_completion with ONLY raw JSON in the result field.",
805
+ emphasis: "CORRECT: <attempt_completion><result>{\"key\": \"value\"}</result></attempt_completion>\nWRONG: Here is the JSON: {\"key\": \"value\"}\nWRONG: ```json{\"key\": \"value\"}```"
805
806
  }
806
807
  ];
807
808
 
package/build/delegate.js CHANGED
@@ -176,6 +176,8 @@ const delegationManager = new DelegationManager();
176
176
  * @param {string} [options.provider] - AI provider (inherited from parent)
177
177
  * @param {string} [options.model] - AI model (inherited from parent)
178
178
  * @param {Object} [options.tracer=null] - Telemetry tracer instance
179
+ * @param {boolean} [options.enableBash=false] - Enable bash tool (inherited from parent)
180
+ * @param {Object} [options.bashConfig] - Bash configuration (inherited from parent)
179
181
  * @returns {Promise<string>} The response from the delegate agent
180
182
  */
181
183
  export async function delegate({
@@ -188,7 +190,9 @@ export async function delegate({
188
190
  parentSessionId = null,
189
191
  path = null,
190
192
  provider = null,
191
- model = null
193
+ model = null,
194
+ enableBash = false,
195
+ bashConfig = null
192
196
  }) {
193
197
  if (!task || typeof task !== 'string') {
194
198
  throw new Error('Task parameter is required and must be a string');
@@ -234,9 +238,11 @@ export async function delegate({
234
238
  maxIterations: remainingIterations,
235
239
  debug,
236
240
  tracer,
237
- path, // Inherit from parent
238
- provider, // Inherit from parent
239
- model // Inherit from parent
241
+ path, // Inherit from parent
242
+ provider, // Inherit from parent
243
+ model, // Inherit from parent
244
+ enableBash, // Inherit from parent
245
+ bashConfig // Inherit from parent
240
246
  });
241
247
 
242
248
  if (debug) {
@@ -239,10 +239,12 @@ export const extractTool = (options = {}) => {
239
239
  * @param {number} [options.timeout=300] - Default timeout in seconds
240
240
  * @param {string} [options.cwd] - Working directory to use if not specified in call
241
241
  * @param {string[]} [options.allowedFolders] - Allowed folders for workspace isolation
242
+ * @param {boolean} [options.enableBash=false] - Enable bash tool for sub-agents
243
+ * @param {Object} [options.bashConfig] - Bash configuration (allow/deny patterns)
242
244
  * @returns {Object} Configured delegate tool
243
245
  */
244
246
  export const delegateTool = (options = {}) => {
245
- const { debug = false, timeout = 300, cwd, allowedFolders } = options;
247
+ const { debug = false, timeout = 300, cwd, allowedFolders, enableBash = false, bashConfig } = options;
246
248
 
247
249
  return tool({
248
250
  name: 'delegate',
@@ -309,7 +311,9 @@ export const delegateTool = (options = {}) => {
309
311
  path: effectivePath,
310
312
  provider,
311
313
  model,
312
- tracer
314
+ tracer,
315
+ enableBash,
316
+ bashConfig
313
317
  });
314
318
 
315
319
  return result;
@@ -30422,7 +30422,9 @@ async function delegate({
30422
30422
  parentSessionId = null,
30423
30423
  path: path9 = null,
30424
30424
  provider = null,
30425
- model = null
30425
+ model = null,
30426
+ enableBash = false,
30427
+ bashConfig = null
30426
30428
  }) {
30427
30429
  if (!task || typeof task !== "string") {
30428
30430
  throw new Error("Task parameter is required and must be a string");
@@ -30464,7 +30466,11 @@ async function delegate({
30464
30466
  // Inherit from parent
30465
30467
  provider,
30466
30468
  // Inherit from parent
30467
- model
30469
+ model,
30470
+ // Inherit from parent
30471
+ enableBash,
30472
+ // Inherit from parent
30473
+ bashConfig
30468
30474
  // Inherit from parent
30469
30475
  });
30470
30476
  if (debug) {
@@ -35365,7 +35371,7 @@ var init_vercel = __esm({
35365
35371
  });
35366
35372
  };
35367
35373
  delegateTool = (options = {}) => {
35368
- const { debug = false, timeout = 300, cwd, allowedFolders } = options;
35374
+ const { debug = false, timeout = 300, cwd, allowedFolders, enableBash = false, bashConfig } = options;
35369
35375
  return (0, import_ai.tool)({
35370
35376
  name: "delegate",
35371
35377
  description: delegateDescription,
@@ -35415,7 +35421,9 @@ var init_vercel = __esm({
35415
35421
  path: effectivePath,
35416
35422
  provider,
35417
35423
  model,
35418
- tracer
35424
+ tracer,
35425
+ enableBash,
35426
+ bashConfig
35419
35427
  });
35420
35428
  return result;
35421
35429
  }
@@ -81784,18 +81792,18 @@ function createJsonCorrectionPrompt(invalidResponse, schema, errorOrValidation,
81784
81792
  const strengthLevels = [
81785
81793
  {
81786
81794
  prefix: "CRITICAL JSON ERROR:",
81787
- instruction: "You MUST fix this and return ONLY valid JSON.",
81788
- emphasis: "Return ONLY the corrected JSON, with no additional text or markdown formatting."
81795
+ instruction: "You MUST fix this and respond using attempt_completion with ONLY valid JSON as the result.",
81796
+ emphasis: "Use attempt_completion with ONLY the corrected JSON in the result field. No explanatory text, no markdown, no code blocks."
81789
81797
  },
81790
81798
  {
81791
81799
  prefix: "URGENT - JSON PARSING FAILED:",
81792
- instruction: "This is your second chance. Return ONLY valid JSON that can be parsed by JSON.parse().",
81793
- emphasis: "ABSOLUTELY NO explanatory text, greetings, or formatting. ONLY JSON."
81800
+ instruction: "This is your second chance. Use attempt_completion with valid JSON that can be parsed by JSON.parse().",
81801
+ emphasis: "ABSOLUTELY NO explanatory text or formatting. Use attempt_completion with ONLY raw JSON in the result."
81794
81802
  },
81795
81803
  {
81796
81804
  prefix: "FINAL ATTEMPT - CRITICAL JSON ERROR:",
81797
- instruction: "This is the final retry. You MUST return ONLY raw JSON without any other content.",
81798
- emphasis: 'EXAMPLE: {"key": "value"} NOT: ```json{"key": "value"}``` NOT: Here is the JSON: {"key": "value"}'
81805
+ instruction: "This is the final retry. You MUST use attempt_completion with ONLY raw JSON in the result field.",
81806
+ emphasis: 'CORRECT: <attempt_completion><result>{"key": "value"}</result></attempt_completion>\nWRONG: Here is the JSON: {"key": "value"}\nWRONG: ```json{"key": "value"}```'
81799
81807
  }
81800
81808
  ];
81801
81809
  const level = Math.min(retryCount, strengthLevels.length - 1);
@@ -87808,24 +87816,31 @@ You are working with a repository located at: ${searchDirectory}
87808
87816
  console.log(`[DEBUG] Assistant response (${assistantResponseContent.length} chars): ${assistantPreview}`);
87809
87817
  }
87810
87818
  const validTools = [];
87811
- if (this.allowedTools.isEnabled("search")) validTools.push("search");
87812
- if (this.allowedTools.isEnabled("query")) validTools.push("query");
87813
- if (this.allowedTools.isEnabled("extract")) validTools.push("extract");
87814
- if (this.allowedTools.isEnabled("listFiles")) validTools.push("listFiles");
87815
- if (this.allowedTools.isEnabled("searchFiles")) validTools.push("searchFiles");
87816
- if (this.allowedTools.isEnabled("readImage")) validTools.push("readImage");
87817
- if (this.allowedTools.isEnabled("attempt_completion")) validTools.push("attempt_completion");
87818
- if (this.allowEdit && this.allowedTools.isEnabled("implement")) {
87819
- validTools.push("implement", "edit", "create");
87820
- }
87821
- if (this.enableBash && this.allowedTools.isEnabled("bash")) {
87822
- validTools.push("bash");
87823
- }
87824
- if (this.enableDelegate && this.allowedTools.isEnabled("delegate")) {
87825
- validTools.push("delegate");
87819
+ if (options._disableTools) {
87820
+ validTools.push("attempt_completion");
87821
+ if (this.debug) {
87822
+ console.log(`[DEBUG] Tools disabled for this call - only attempt_completion allowed`);
87823
+ }
87824
+ } else {
87825
+ if (this.allowedTools.isEnabled("search")) validTools.push("search");
87826
+ if (this.allowedTools.isEnabled("query")) validTools.push("query");
87827
+ if (this.allowedTools.isEnabled("extract")) validTools.push("extract");
87828
+ if (this.allowedTools.isEnabled("listFiles")) validTools.push("listFiles");
87829
+ if (this.allowedTools.isEnabled("searchFiles")) validTools.push("searchFiles");
87830
+ if (this.allowedTools.isEnabled("readImage")) validTools.push("readImage");
87831
+ if (this.allowedTools.isEnabled("attempt_completion")) validTools.push("attempt_completion");
87832
+ if (this.allowEdit && this.allowedTools.isEnabled("implement")) {
87833
+ validTools.push("implement", "edit", "create");
87834
+ }
87835
+ if (this.enableBash && this.allowedTools.isEnabled("bash")) {
87836
+ validTools.push("bash");
87837
+ }
87838
+ if (this.enableDelegate && this.allowedTools.isEnabled("delegate")) {
87839
+ validTools.push("delegate");
87840
+ }
87826
87841
  }
87827
87842
  const nativeTools = validTools;
87828
- const parsedTool = this.mcpBridge ? parseHybridXmlToolCall(assistantResponseContent, nativeTools, this.mcpBridge) : parseXmlToolCallWithThinking(assistantResponseContent, validTools);
87843
+ const parsedTool = this.mcpBridge && !options._disableTools ? parseHybridXmlToolCall(assistantResponseContent, nativeTools, this.mcpBridge) : parseXmlToolCallWithThinking(assistantResponseContent, validTools);
87829
87844
  if (parsedTool) {
87830
87845
  const { toolName, params } = parsedTool;
87831
87846
  if (this.debug) console.log(`[DEBUG] Parsed tool call: ${toolName} with params:`, params);
@@ -88437,8 +88452,10 @@ Convert your previous response content into actual JSON data that follows this s
88437
88452
  finalResult = await this.answer(correctionPrompt, [], {
88438
88453
  ...options,
88439
88454
  _schemaFormatted: true,
88440
- _skipValidation: true
88455
+ _skipValidation: true,
88441
88456
  // Skip validation in recursive correction calls to prevent loops
88457
+ _disableTools: true
88458
+ // Only allow attempt_completion - prevent AI from using search/query tools
88442
88459
  });
88443
88460
  finalResult = cleanSchemaResponse(finalResult);
88444
88461
  validation = validateJsonResponse(finalResult, { debug: this.debug });
package/cjs/index.cjs CHANGED
@@ -79414,18 +79414,18 @@ function createJsonCorrectionPrompt(invalidResponse, schema, errorOrValidation,
79414
79414
  const strengthLevels = [
79415
79415
  {
79416
79416
  prefix: "CRITICAL JSON ERROR:",
79417
- instruction: "You MUST fix this and return ONLY valid JSON.",
79418
- emphasis: "Return ONLY the corrected JSON, with no additional text or markdown formatting."
79417
+ instruction: "You MUST fix this and respond using attempt_completion with ONLY valid JSON as the result.",
79418
+ emphasis: "Use attempt_completion with ONLY the corrected JSON in the result field. No explanatory text, no markdown, no code blocks."
79419
79419
  },
79420
79420
  {
79421
79421
  prefix: "URGENT - JSON PARSING FAILED:",
79422
- instruction: "This is your second chance. Return ONLY valid JSON that can be parsed by JSON.parse().",
79423
- emphasis: "ABSOLUTELY NO explanatory text, greetings, or formatting. ONLY JSON."
79422
+ instruction: "This is your second chance. Use attempt_completion with valid JSON that can be parsed by JSON.parse().",
79423
+ emphasis: "ABSOLUTELY NO explanatory text or formatting. Use attempt_completion with ONLY raw JSON in the result."
79424
79424
  },
79425
79425
  {
79426
79426
  prefix: "FINAL ATTEMPT - CRITICAL JSON ERROR:",
79427
- instruction: "This is the final retry. You MUST return ONLY raw JSON without any other content.",
79428
- emphasis: 'EXAMPLE: {"key": "value"} NOT: ```json{"key": "value"}``` NOT: Here is the JSON: {"key": "value"}'
79427
+ instruction: "This is the final retry. You MUST use attempt_completion with ONLY raw JSON in the result field.",
79428
+ emphasis: 'CORRECT: <attempt_completion><result>{"key": "value"}</result></attempt_completion>\nWRONG: Here is the JSON: {"key": "value"}\nWRONG: ```json{"key": "value"}```'
79429
79429
  }
79430
79430
  ];
79431
79431
  const level = Math.min(retryCount, strengthLevels.length - 1);
@@ -85438,24 +85438,31 @@ You are working with a repository located at: ${searchDirectory}
85438
85438
  console.log(`[DEBUG] Assistant response (${assistantResponseContent.length} chars): ${assistantPreview}`);
85439
85439
  }
85440
85440
  const validTools = [];
85441
- if (this.allowedTools.isEnabled("search")) validTools.push("search");
85442
- if (this.allowedTools.isEnabled("query")) validTools.push("query");
85443
- if (this.allowedTools.isEnabled("extract")) validTools.push("extract");
85444
- if (this.allowedTools.isEnabled("listFiles")) validTools.push("listFiles");
85445
- if (this.allowedTools.isEnabled("searchFiles")) validTools.push("searchFiles");
85446
- if (this.allowedTools.isEnabled("readImage")) validTools.push("readImage");
85447
- if (this.allowedTools.isEnabled("attempt_completion")) validTools.push("attempt_completion");
85448
- if (this.allowEdit && this.allowedTools.isEnabled("implement")) {
85449
- validTools.push("implement", "edit", "create");
85450
- }
85451
- if (this.enableBash && this.allowedTools.isEnabled("bash")) {
85452
- validTools.push("bash");
85453
- }
85454
- if (this.enableDelegate && this.allowedTools.isEnabled("delegate")) {
85455
- validTools.push("delegate");
85441
+ if (options._disableTools) {
85442
+ validTools.push("attempt_completion");
85443
+ if (this.debug) {
85444
+ console.log(`[DEBUG] Tools disabled for this call - only attempt_completion allowed`);
85445
+ }
85446
+ } else {
85447
+ if (this.allowedTools.isEnabled("search")) validTools.push("search");
85448
+ if (this.allowedTools.isEnabled("query")) validTools.push("query");
85449
+ if (this.allowedTools.isEnabled("extract")) validTools.push("extract");
85450
+ if (this.allowedTools.isEnabled("listFiles")) validTools.push("listFiles");
85451
+ if (this.allowedTools.isEnabled("searchFiles")) validTools.push("searchFiles");
85452
+ if (this.allowedTools.isEnabled("readImage")) validTools.push("readImage");
85453
+ if (this.allowedTools.isEnabled("attempt_completion")) validTools.push("attempt_completion");
85454
+ if (this.allowEdit && this.allowedTools.isEnabled("implement")) {
85455
+ validTools.push("implement", "edit", "create");
85456
+ }
85457
+ if (this.enableBash && this.allowedTools.isEnabled("bash")) {
85458
+ validTools.push("bash");
85459
+ }
85460
+ if (this.enableDelegate && this.allowedTools.isEnabled("delegate")) {
85461
+ validTools.push("delegate");
85462
+ }
85456
85463
  }
85457
85464
  const nativeTools = validTools;
85458
- const parsedTool = this.mcpBridge ? parseHybridXmlToolCall(assistantResponseContent, nativeTools, this.mcpBridge) : parseXmlToolCallWithThinking(assistantResponseContent, validTools);
85465
+ const parsedTool = this.mcpBridge && !options._disableTools ? parseHybridXmlToolCall(assistantResponseContent, nativeTools, this.mcpBridge) : parseXmlToolCallWithThinking(assistantResponseContent, validTools);
85459
85466
  if (parsedTool) {
85460
85467
  const { toolName, params } = parsedTool;
85461
85468
  if (this.debug) console.log(`[DEBUG] Parsed tool call: ${toolName} with params:`, params);
@@ -86067,8 +86074,10 @@ Convert your previous response content into actual JSON data that follows this s
86067
86074
  finalResult = await this.answer(correctionPrompt, [], {
86068
86075
  ...options,
86069
86076
  _schemaFormatted: true,
86070
- _skipValidation: true
86077
+ _skipValidation: true,
86071
86078
  // Skip validation in recursive correction calls to prevent loops
86079
+ _disableTools: true
86080
+ // Only allow attempt_completion - prevent AI from using search/query tools
86072
86081
  });
86073
86082
  finalResult = cleanSchemaResponse(finalResult);
86074
86083
  validation = validateJsonResponse(finalResult, { debug: this.debug });
@@ -86448,7 +86457,9 @@ async function delegate({
86448
86457
  parentSessionId = null,
86449
86458
  path: path9 = null,
86450
86459
  provider = null,
86451
- model = null
86460
+ model = null,
86461
+ enableBash = false,
86462
+ bashConfig = null
86452
86463
  }) {
86453
86464
  if (!task || typeof task !== "string") {
86454
86465
  throw new Error("Task parameter is required and must be a string");
@@ -86490,7 +86501,11 @@ async function delegate({
86490
86501
  // Inherit from parent
86491
86502
  provider,
86492
86503
  // Inherit from parent
86493
- model
86504
+ model,
86505
+ // Inherit from parent
86506
+ enableBash,
86507
+ // Inherit from parent
86508
+ bashConfig
86494
86509
  // Inherit from parent
86495
86510
  });
86496
86511
  if (debug) {
@@ -86860,7 +86875,7 @@ var init_vercel = __esm({
86860
86875
  });
86861
86876
  };
86862
86877
  delegateTool = (options = {}) => {
86863
- const { debug = false, timeout = 300, cwd, allowedFolders } = options;
86878
+ const { debug = false, timeout = 300, cwd, allowedFolders, enableBash = false, bashConfig } = options;
86864
86879
  return (0, import_ai3.tool)({
86865
86880
  name: "delegate",
86866
86881
  description: delegateDescription,
@@ -86910,7 +86925,9 @@ var init_vercel = __esm({
86910
86925
  path: effectivePath,
86911
86926
  provider,
86912
86927
  model,
86913
- tracer
86928
+ tracer,
86929
+ enableBash,
86930
+ bashConfig
86914
86931
  });
86915
86932
  return result;
86916
86933
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probelabs/probe",
3
- "version": "0.6.0-rc186",
3
+ "version": "0.6.0-rc187",
4
4
  "description": "Node.js wrapper for the probe code search tool",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -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 (this.allowedTools.isEnabled('search')) validTools.push('search');
2377
- if (this.allowedTools.isEnabled('query')) validTools.push('query');
2378
- if (this.allowedTools.isEnabled('extract')) validTools.push('extract');
2379
- if (this.allowedTools.isEnabled('listFiles')) validTools.push('listFiles');
2380
- if (this.allowedTools.isEnabled('searchFiles')) validTools.push('searchFiles');
2381
- if (this.allowedTools.isEnabled('readImage')) validTools.push('readImage');
2382
- if (this.allowedTools.isEnabled('attempt_completion')) validTools.push('attempt_completion');
2383
-
2384
- // Edit tools (require both allowEdit flag AND allowedTools permission)
2385
- if (this.allowEdit && this.allowedTools.isEnabled('implement')) {
2386
- validTools.push('implement', 'edit', 'create');
2387
- }
2388
- // Bash tool (require both enableBash flag AND allowedTools permission)
2389
- if (this.enableBash && this.allowedTools.isEnabled('bash')) {
2390
- validTools.push('bash');
2391
- }
2392
- // Delegate tool (require both enableDelegate flag AND allowedTools permission)
2393
- if (this.enableDelegate && this.allowedTools.isEnabled('delegate')) {
2394
- validTools.push('delegate');
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) {
@@ -3164,7 +3174,8 @@ Convert your previous response content into actual JSON data that follows this s
3164
3174
  finalResult = await this.answer(correctionPrompt, [], {
3165
3175
  ...options,
3166
3176
  _schemaFormatted: true,
3167
- _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
3168
3179
  });
3169
3180
  finalResult = cleanSchemaResponse(finalResult);
3170
3181
 
@@ -520,7 +520,7 @@ class ProbeAgentMcpServer {
520
520
  // Retry once with correction prompt
521
521
  const correctionPrompt = createJsonCorrectionPrompt(result, schema, validation.error);
522
522
  try {
523
- result = await agent.answer(correctionPrompt, [], { schema, _schemaFormatted: true });
523
+ result = await agent.answer(correctionPrompt, [], { schema, _schemaFormatted: true, _disableTools: true });
524
524
  result = cleanSchemaResponse(result);
525
525
 
526
526
  // Validate again after correction
@@ -863,11 +863,11 @@ async function main() {
863
863
  try {
864
864
  if (appTracer) {
865
865
  result = await appTracer.withSpan('agent.json_correction',
866
- () => agent.answer(correctionPrompt, [], { schema, _schemaFormatted: true }),
866
+ () => agent.answer(correctionPrompt, [], { schema, _schemaFormatted: true, _disableTools: true }),
867
867
  { 'original_error': validation.error }
868
868
  );
869
869
  } else {
870
- result = await agent.answer(correctionPrompt, [], { schema, _schemaFormatted: true });
870
+ result = await agent.answer(correctionPrompt, [], { schema, _schemaFormatted: true, _disableTools: true });
871
871
  }
872
872
  result = cleanSchemaResponse(result);
873
873
 
@@ -787,21 +787,22 @@ export function createJsonCorrectionPrompt(invalidResponse, schema, errorOrValid
787
787
  }
788
788
 
789
789
  // Create increasingly stronger prompts based on retry attempt
790
+ // These prompts explicitly instruct the AI to use attempt_completion with the JSON result
790
791
  const strengthLevels = [
791
792
  {
792
793
  prefix: "CRITICAL JSON ERROR:",
793
- instruction: "You MUST fix this and return ONLY valid JSON.",
794
- emphasis: "Return ONLY the corrected JSON, with no additional text or markdown formatting."
794
+ instruction: "You MUST fix this and respond using attempt_completion with ONLY valid JSON as the result.",
795
+ emphasis: "Use attempt_completion with ONLY the corrected JSON in the result field. No explanatory text, no markdown, no code blocks."
795
796
  },
796
797
  {
797
798
  prefix: "URGENT - JSON PARSING FAILED:",
798
- instruction: "This is your second chance. Return ONLY valid JSON that can be parsed by JSON.parse().",
799
- emphasis: "ABSOLUTELY NO explanatory text, greetings, or formatting. ONLY JSON."
799
+ instruction: "This is your second chance. Use attempt_completion with valid JSON that can be parsed by JSON.parse().",
800
+ emphasis: "ABSOLUTELY NO explanatory text or formatting. Use attempt_completion with ONLY raw JSON in the result."
800
801
  },
801
802
  {
802
803
  prefix: "FINAL ATTEMPT - CRITICAL JSON ERROR:",
803
- instruction: "This is the final retry. You MUST return ONLY raw JSON without any other content.",
804
- emphasis: "EXAMPLE: {\"key\": \"value\"} NOT: ```json{\"key\": \"value\"}``` NOT: Here is the JSON: {\"key\": \"value\"}"
804
+ instruction: "This is the final retry. You MUST use attempt_completion with ONLY raw JSON in the result field.",
805
+ emphasis: "CORRECT: <attempt_completion><result>{\"key\": \"value\"}</result></attempt_completion>\nWRONG: Here is the JSON: {\"key\": \"value\"}\nWRONG: ```json{\"key\": \"value\"}```"
805
806
  }
806
807
  ];
807
808
 
package/src/delegate.js CHANGED
@@ -176,6 +176,8 @@ const delegationManager = new DelegationManager();
176
176
  * @param {string} [options.provider] - AI provider (inherited from parent)
177
177
  * @param {string} [options.model] - AI model (inherited from parent)
178
178
  * @param {Object} [options.tracer=null] - Telemetry tracer instance
179
+ * @param {boolean} [options.enableBash=false] - Enable bash tool (inherited from parent)
180
+ * @param {Object} [options.bashConfig] - Bash configuration (inherited from parent)
179
181
  * @returns {Promise<string>} The response from the delegate agent
180
182
  */
181
183
  export async function delegate({
@@ -188,7 +190,9 @@ export async function delegate({
188
190
  parentSessionId = null,
189
191
  path = null,
190
192
  provider = null,
191
- model = null
193
+ model = null,
194
+ enableBash = false,
195
+ bashConfig = null
192
196
  }) {
193
197
  if (!task || typeof task !== 'string') {
194
198
  throw new Error('Task parameter is required and must be a string');
@@ -234,9 +238,11 @@ export async function delegate({
234
238
  maxIterations: remainingIterations,
235
239
  debug,
236
240
  tracer,
237
- path, // Inherit from parent
238
- provider, // Inherit from parent
239
- model // Inherit from parent
241
+ path, // Inherit from parent
242
+ provider, // Inherit from parent
243
+ model, // Inherit from parent
244
+ enableBash, // Inherit from parent
245
+ bashConfig // Inherit from parent
240
246
  });
241
247
 
242
248
  if (debug) {
@@ -239,10 +239,12 @@ export const extractTool = (options = {}) => {
239
239
  * @param {number} [options.timeout=300] - Default timeout in seconds
240
240
  * @param {string} [options.cwd] - Working directory to use if not specified in call
241
241
  * @param {string[]} [options.allowedFolders] - Allowed folders for workspace isolation
242
+ * @param {boolean} [options.enableBash=false] - Enable bash tool for sub-agents
243
+ * @param {Object} [options.bashConfig] - Bash configuration (allow/deny patterns)
242
244
  * @returns {Object} Configured delegate tool
243
245
  */
244
246
  export const delegateTool = (options = {}) => {
245
- const { debug = false, timeout = 300, cwd, allowedFolders } = options;
247
+ const { debug = false, timeout = 300, cwd, allowedFolders, enableBash = false, bashConfig } = options;
246
248
 
247
249
  return tool({
248
250
  name: 'delegate',
@@ -309,7 +311,9 @@ export const delegateTool = (options = {}) => {
309
311
  path: effectivePath,
310
312
  provider,
311
313
  model,
312
- tracer
314
+ tracer,
315
+ enableBash,
316
+ bashConfig
313
317
  });
314
318
 
315
319
  return result;