@probelabs/probe 0.6.0-rc209 → 0.6.0-rc210

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.
@@ -3764,7 +3764,10 @@ async function delegate({
3764
3764
  disableTools = false,
3765
3765
  searchDelegate = void 0,
3766
3766
  schema = null,
3767
- enableTasks = false
3767
+ enableTasks = false,
3768
+ enableMcp = false,
3769
+ mcpConfig = null,
3770
+ mcpConfigPath = null
3768
3771
  }) {
3769
3772
  if (!task || typeof task !== "string") {
3770
3773
  throw new Error("Task parameter is required and must be a string");
@@ -3820,8 +3823,14 @@ async function delegate({
3820
3823
  allowedTools,
3821
3824
  disableTools,
3822
3825
  searchDelegate,
3823
- enableTasks
3826
+ enableTasks,
3824
3827
  // Inherit from parent (subagent gets isolated TaskManager)
3828
+ enableMcp,
3829
+ // Inherit from parent (subagent creates own MCPXmlBridge)
3830
+ mcpConfig,
3831
+ // Inherit from parent
3832
+ mcpConfigPath
3833
+ // Inherit from parent
3825
3834
  });
3826
3835
  if (debug) {
3827
3836
  console.error(`[DELEGATE] Created subagent with session ${sessionId}`);
@@ -9791,7 +9800,7 @@ var init_vercel = __esm({
9791
9800
  });
9792
9801
  };
9793
9802
  delegateTool = (options = {}) => {
9794
- const { debug = false, timeout = 300, cwd, allowedFolders, enableBash = false, bashConfig, architectureFileName } = options;
9803
+ const { debug = false, timeout = 300, cwd, allowedFolders, enableBash = false, bashConfig, architectureFileName, enableMcp = false, mcpConfig = null, mcpConfigPath = null } = options;
9795
9804
  return tool2({
9796
9805
  name: "delegate",
9797
9806
  description: delegateDescription,
@@ -9850,7 +9859,10 @@ var init_vercel = __esm({
9850
9859
  enableBash,
9851
9860
  bashConfig,
9852
9861
  architectureFileName,
9853
- searchDelegate
9862
+ searchDelegate,
9863
+ enableMcp,
9864
+ mcpConfig,
9865
+ mcpConfigPath
9854
9866
  });
9855
9867
  return result;
9856
9868
  }
@@ -10377,6 +10389,38 @@ function parseSimpleCommand(command) {
10377
10389
  isComplex: false
10378
10390
  };
10379
10391
  }
10392
+ const stripQuotedContent = (str) => {
10393
+ let result = "";
10394
+ let inQuotes2 = false;
10395
+ let quoteChar2 = "";
10396
+ for (let i = 0; i < str.length; i++) {
10397
+ const char = str[i];
10398
+ const nextChar = str[i + 1];
10399
+ if (!inQuotes2 && char === "\\" && nextChar !== void 0) {
10400
+ i++;
10401
+ continue;
10402
+ }
10403
+ if (inQuotes2 && quoteChar2 === '"' && char === "\\" && nextChar !== void 0) {
10404
+ i++;
10405
+ continue;
10406
+ }
10407
+ if (!inQuotes2 && (char === '"' || char === "'")) {
10408
+ inQuotes2 = true;
10409
+ quoteChar2 = char;
10410
+ continue;
10411
+ }
10412
+ if (inQuotes2 && char === quoteChar2) {
10413
+ inQuotes2 = false;
10414
+ quoteChar2 = "";
10415
+ continue;
10416
+ }
10417
+ if (!inQuotes2) {
10418
+ result += char;
10419
+ }
10420
+ }
10421
+ return result;
10422
+ };
10423
+ const strippedForOperators = stripQuotedContent(trimmed);
10380
10424
  const complexPatterns = [
10381
10425
  /\|/,
10382
10426
  // Pipes
@@ -10402,7 +10446,7 @@ function parseSimpleCommand(command) {
10402
10446
  // Brace expansion like {a,b} or {1..10} (but not find {} placeholders)
10403
10447
  ];
10404
10448
  for (const pattern of complexPatterns) {
10405
- if (pattern.test(trimmed)) {
10449
+ if (pattern.test(strippedForOperators)) {
10406
10450
  return {
10407
10451
  success: false,
10408
10452
  error: "Complex shell commands with pipes, operators, or redirections are not supported for security reasons",
@@ -10417,17 +10461,17 @@ function parseSimpleCommand(command) {
10417
10461
  let current = "";
10418
10462
  let inQuotes = false;
10419
10463
  let quoteChar = "";
10420
- let escaped = false;
10421
10464
  for (let i = 0; i < trimmed.length; i++) {
10422
10465
  const char = trimmed[i];
10423
10466
  const nextChar = i + 1 < trimmed.length ? trimmed[i + 1] : "";
10424
- if (escaped) {
10425
- current += char;
10426
- escaped = false;
10467
+ if (!inQuotes && char === "\\" && nextChar) {
10468
+ current += nextChar;
10469
+ i++;
10427
10470
  continue;
10428
10471
  }
10429
- if (char === "\\" && !inQuotes) {
10430
- escaped = true;
10472
+ if (inQuotes && quoteChar === '"' && char === "\\" && nextChar) {
10473
+ current += nextChar;
10474
+ i++;
10431
10475
  continue;
10432
10476
  }
10433
10477
  if (!inQuotes && (char === '"' || char === "'")) {
@@ -10760,8 +10804,97 @@ var init_bashPermissions = __esm({
10760
10804
  });
10761
10805
  return result;
10762
10806
  }
10807
+ /**
10808
+ * Split a complex command into component commands by operators
10809
+ *
10810
+ * ## Escape Handling (Security-Critical)
10811
+ *
10812
+ * This function intentionally PRESERVES escape sequences (both backslash AND
10813
+ * escaped character) in the output. This is step 1 of a 2-step parsing process:
10814
+ *
10815
+ * 1. _splitComplexCommand: Splits by operators, PRESERVES escapes → `echo "test\" && b"`
10816
+ * 2. parseCommand: Interprets escapes in each component → args: ['test" && b']
10817
+ *
10818
+ * This differs from stripQuotedContent() in bashCommandUtils.js which REMOVES
10819
+ * escapes entirely (for operator detection only).
10820
+ *
10821
+ * The security rationale: if we stripped escapes here, `\"` would become `"`,
10822
+ * potentially causing incorrect quote boundary detection and allowing operator
10823
+ * injection. By preserving escapes, parseCommand() can correctly interpret them.
10824
+ *
10825
+ * See bashCommandUtils.js module header for the full escape handling architecture.
10826
+ *
10827
+ * @private
10828
+ * @param {string} command - Complex command to split
10829
+ * @returns {string[]} Array of component commands (with escapes preserved)
10830
+ */
10831
+ _splitComplexCommand(command) {
10832
+ const components = [];
10833
+ let current = "";
10834
+ let inQuotes = false;
10835
+ let quoteChar = "";
10836
+ let i = 0;
10837
+ while (i < command.length) {
10838
+ const char = command[i];
10839
+ const nextChar = command[i + 1] || "";
10840
+ if (!inQuotes && char === "\\") {
10841
+ current += char;
10842
+ if (nextChar) {
10843
+ current += nextChar;
10844
+ i += 2;
10845
+ } else {
10846
+ i++;
10847
+ }
10848
+ continue;
10849
+ }
10850
+ if (inQuotes && quoteChar === '"' && char === "\\" && nextChar) {
10851
+ current += char + nextChar;
10852
+ i += 2;
10853
+ continue;
10854
+ }
10855
+ if (!inQuotes && (char === '"' || char === "'")) {
10856
+ inQuotes = true;
10857
+ quoteChar = char;
10858
+ current += char;
10859
+ i++;
10860
+ continue;
10861
+ }
10862
+ if (inQuotes && char === quoteChar) {
10863
+ inQuotes = false;
10864
+ quoteChar = "";
10865
+ current += char;
10866
+ i++;
10867
+ continue;
10868
+ }
10869
+ if (!inQuotes) {
10870
+ if (char === "&" && nextChar === "&" || char === "|" && nextChar === "|") {
10871
+ if (current.trim()) {
10872
+ components.push(current.trim());
10873
+ }
10874
+ current = "";
10875
+ i += 2;
10876
+ continue;
10877
+ }
10878
+ if (char === "|") {
10879
+ if (current.trim()) {
10880
+ components.push(current.trim());
10881
+ }
10882
+ current = "";
10883
+ i++;
10884
+ continue;
10885
+ }
10886
+ }
10887
+ current += char;
10888
+ i++;
10889
+ }
10890
+ if (current.trim()) {
10891
+ components.push(current.trim());
10892
+ }
10893
+ return components;
10894
+ }
10763
10895
  /**
10764
10896
  * Check a complex command against complex patterns in allow/deny lists
10897
+ * Also supports auto-allowing commands where all components are individually allowed
10765
10898
  * @private
10766
10899
  * @param {string} command - Complex command to check
10767
10900
  * @returns {Object} Permission result
@@ -10816,6 +10949,85 @@ var init_bashPermissions = __esm({
10816
10949
  return result;
10817
10950
  }
10818
10951
  }
10952
+ const components = this._splitComplexCommand(command);
10953
+ if (this.debug) {
10954
+ console.log(`[BashPermissions] Checking ${components.length} command components: ${JSON.stringify(components)}`);
10955
+ }
10956
+ if (components.length > 1) {
10957
+ const componentResults = [];
10958
+ let allAllowed = true;
10959
+ let deniedComponent = null;
10960
+ let deniedReason = null;
10961
+ for (const component of components) {
10962
+ const parsed = parseCommand(component);
10963
+ if (parsed.error || parsed.isComplex) {
10964
+ if (this.debug) {
10965
+ console.log(`[BashPermissions] Component "${component}" is complex or has error: ${parsed.error}`);
10966
+ }
10967
+ allAllowed = false;
10968
+ deniedComponent = component;
10969
+ deniedReason = parsed.error || "Component contains nested complex constructs";
10970
+ break;
10971
+ }
10972
+ if (matchesAnyPattern(parsed, this.denyPatterns)) {
10973
+ if (this.debug) {
10974
+ console.log(`[BashPermissions] Component "${component}" matches deny pattern`);
10975
+ }
10976
+ allAllowed = false;
10977
+ deniedComponent = component;
10978
+ deniedReason = "Component matches deny pattern";
10979
+ break;
10980
+ }
10981
+ if (!matchesAnyPattern(parsed, this.allowPatterns)) {
10982
+ if (this.debug) {
10983
+ console.log(`[BashPermissions] Component "${component}" not in allow list`);
10984
+ }
10985
+ allAllowed = false;
10986
+ deniedComponent = component;
10987
+ deniedReason = "Component not in allow list";
10988
+ break;
10989
+ }
10990
+ componentResults.push({ component, parsed, allowed: true });
10991
+ }
10992
+ if (allAllowed) {
10993
+ if (this.debug) {
10994
+ console.log(`[BashPermissions] ALLOWED - all ${components.length} components passed individual checks`);
10995
+ }
10996
+ const result = {
10997
+ allowed: true,
10998
+ command,
10999
+ isComplex: true,
11000
+ allowedByComponents: true,
11001
+ components: componentResults
11002
+ };
11003
+ this.recordBashEvent("permission.allowed", {
11004
+ command,
11005
+ isComplex: true,
11006
+ allowedByComponents: true,
11007
+ componentCount: components.length
11008
+ });
11009
+ return result;
11010
+ } else {
11011
+ if (this.debug) {
11012
+ console.log(`[BashPermissions] DENIED - component "${deniedComponent}" failed: ${deniedReason}`);
11013
+ }
11014
+ const result = {
11015
+ allowed: false,
11016
+ reason: `Component "${deniedComponent}" not allowed: ${deniedReason}`,
11017
+ command,
11018
+ isComplex: true,
11019
+ failedComponent: deniedComponent
11020
+ };
11021
+ this.recordBashEvent("permission.denied", {
11022
+ command,
11023
+ reason: "component_not_allowed",
11024
+ failedComponent: deniedComponent,
11025
+ componentReason: deniedReason,
11026
+ isComplex: true
11027
+ });
11028
+ return result;
11029
+ }
11030
+ }
10819
11031
  if (this.debug) {
10820
11032
  console.log(`[BashPermissions] DENIED - no matching complex pattern found`);
10821
11033
  }
@@ -71033,6 +71245,9 @@ You are working with a repository located at: ${searchDirectory}
71033
71245
  let lastFormatErrorType = null;
71034
71246
  let sameFormatErrorCount = 0;
71035
71247
  const MAX_REPEATED_FORMAT_ERRORS = 3;
71248
+ let lastNoToolResponse = null;
71249
+ let sameResponseCount = 0;
71250
+ const MAX_REPEATED_IDENTICAL_RESPONSES = 3;
71036
71251
  while (currentIteration < maxIterations && !completionAttempted) {
71037
71252
  currentIteration++;
71038
71253
  if (this.cancelled) throw new Error("Request was cancelled by the user");
@@ -71514,6 +71729,26 @@ ${errorXml}
71514
71729
  }
71515
71730
  break;
71516
71731
  }
71732
+ if (lastNoToolResponse !== null && assistantResponseContent === lastNoToolResponse) {
71733
+ sameResponseCount++;
71734
+ if (sameResponseCount >= MAX_REPEATED_IDENTICAL_RESPONSES) {
71735
+ let cleanedResponse = assistantResponseContent;
71736
+ cleanedResponse = cleanedResponse.replace(/<thinking>[\s\S]*?<\/thinking>/gi, "").trim();
71737
+ cleanedResponse = cleanedResponse.replace(/<thinking>[\s\S]*$/gi, "").trim();
71738
+ const hasSubstantialContent = cleanedResponse.length > 50 && !cleanedResponse.includes("<api_call>") && !cleanedResponse.includes("<tool_name>") && !cleanedResponse.includes("<function>");
71739
+ if (hasSubstantialContent) {
71740
+ if (this.debug) {
71741
+ console.log(`[DEBUG] Same response repeated ${sameResponseCount} times - accepting as final answer (${cleanedResponse.length} chars)`);
71742
+ }
71743
+ finalResult = cleanedResponse;
71744
+ completionAttempted = true;
71745
+ break;
71746
+ }
71747
+ }
71748
+ } else {
71749
+ lastNoToolResponse = assistantResponseContent;
71750
+ sameResponseCount = 1;
71751
+ }
71517
71752
  currentMessages.push({ role: "assistant", content: assistantResponseContent });
71518
71753
  const unrecognizedTool = detectUnrecognizedToolCall(assistantResponseContent, validTools);
71519
71754
  let reminderContent;
@@ -71586,10 +71821,35 @@ Or if your previous response already contains a complete, direct answer (not a t
71586
71821
 
71587
71822
  Note: <attempt_complete></attempt_complete> reuses your PREVIOUS assistant message as the final answer. Only use this if that message was already a valid, complete response to the user's question.`;
71588
71823
  }
71589
- currentMessages.push({
71590
- role: "user",
71591
- content: reminderContent
71592
- });
71824
+ const prevUserMsgIndex = currentMessages.length - 2;
71825
+ const prevUserMsg = currentMessages[prevUserMsgIndex];
71826
+ const isExistingReminder = prevUserMsg && prevUserMsg.role === "user" && (prevUserMsg.content.includes("Please use one of the available tools") || prevUserMsg.content.includes("<tool_result>"));
71827
+ if (isExistingReminder && sameResponseCount > 1) {
71828
+ const prevAssistantIndex = prevUserMsgIndex - 1;
71829
+ const hasSystemMessage2 = currentMessages.length > 0 && currentMessages[0].role === "system";
71830
+ const minValidIndex = hasSystemMessage2 ? 1 : 0;
71831
+ const canSafelyRemove = prevAssistantIndex >= minValidIndex && currentMessages[prevAssistantIndex] && currentMessages[prevAssistantIndex].role === "assistant" && currentMessages.length - 2 >= (hasSystemMessage2 ? 2 : 1);
71832
+ if (canSafelyRemove) {
71833
+ currentMessages.splice(prevAssistantIndex, 2);
71834
+ if (this.debug) {
71835
+ console.log(`[DEBUG] Removed duplicate assistant+reminder pair (iteration ${currentIteration}, same response #${sameResponseCount})`);
71836
+ }
71837
+ } else if (this.debug) {
71838
+ console.log(`[DEBUG] Skipped deduplication: pattern validation failed (prevAssistantIndex=${prevAssistantIndex}, arrayLength=${currentMessages.length})`);
71839
+ }
71840
+ const iterationHint = `
71841
+
71842
+ (Attempt #${sameResponseCount}: Your previous ${sameResponseCount} responses were identical. If you have a complete answer, use <attempt_complete></attempt_complete> to finalize it.)`;
71843
+ currentMessages.push({
71844
+ role: "user",
71845
+ content: reminderContent + iterationHint
71846
+ });
71847
+ } else {
71848
+ currentMessages.push({
71849
+ role: "user",
71850
+ content: reminderContent
71851
+ });
71852
+ }
71593
71853
  if (this.debug) {
71594
71854
  if (unrecognizedTool) {
71595
71855
  console.log(`[DEBUG] Unrecognized tool '${unrecognizedTool}' used. Providing error feedback.`);
package/build/delegate.js CHANGED
@@ -186,6 +186,9 @@ const delegationManager = new DelegationManager();
186
186
  * @param {boolean} [options.searchDelegate] - Use delegated search in the subagent
187
187
  * @param {Object|string} [options.schema] - Optional JSON schema to enforce response format
188
188
  * @param {boolean} [options.enableTasks=false] - Enable task management for the subagent (isolated instance)
189
+ * @param {boolean} [options.enableMcp=false] - Enable MCP tool integration (inherited from parent)
190
+ * @param {Object} [options.mcpConfig] - MCP configuration object (inherited from parent)
191
+ * @param {string} [options.mcpConfigPath] - Path to MCP configuration file (inherited from parent)
189
192
  * @returns {Promise<string>} The response from the delegate agent
190
193
  */
191
194
  export async function delegate({
@@ -208,7 +211,10 @@ export async function delegate({
208
211
  disableTools = false,
209
212
  searchDelegate = undefined,
210
213
  schema = null,
211
- enableTasks = false
214
+ enableTasks = false,
215
+ enableMcp = false,
216
+ mcpConfig = null,
217
+ mcpConfigPath = null
212
218
  }) {
213
219
  if (!task || typeof task !== 'string') {
214
220
  throw new Error('Task parameter is required and must be a string');
@@ -270,7 +276,10 @@ export async function delegate({
270
276
  allowedTools,
271
277
  disableTools,
272
278
  searchDelegate,
273
- enableTasks // Inherit from parent (subagent gets isolated TaskManager)
279
+ enableTasks, // Inherit from parent (subagent gets isolated TaskManager)
280
+ enableMcp, // Inherit from parent (subagent creates own MCPXmlBridge)
281
+ mcpConfig, // Inherit from parent
282
+ mcpConfigPath // Inherit from parent
274
283
  });
275
284
 
276
285
  if (debug) {
@@ -469,7 +469,7 @@ export const extractTool = (options = {}) => {
469
469
  * @returns {Object} Configured delegate tool
470
470
  */
471
471
  export const delegateTool = (options = {}) => {
472
- const { debug = false, timeout = 300, cwd, allowedFolders, enableBash = false, bashConfig, architectureFileName } = options;
472
+ const { debug = false, timeout = 300, cwd, allowedFolders, enableBash = false, bashConfig, architectureFileName, enableMcp = false, mcpConfig = null, mcpConfigPath = null } = options;
473
473
 
474
474
  return tool({
475
475
  name: 'delegate',
@@ -558,7 +558,10 @@ export const delegateTool = (options = {}) => {
558
558
  enableBash,
559
559
  bashConfig,
560
560
  architectureFileName,
561
- searchDelegate
561
+ searchDelegate,
562
+ enableMcp,
563
+ mcpConfig,
564
+ mcpConfigPath
562
565
  });
563
566
 
564
567
  return result;