@probelabs/probe 0.6.0-rc208 → 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.
Files changed (30) hide show
  1. package/bin/binaries/probe-v0.6.0-rc210-aarch64-apple-darwin.tar.gz +0 -0
  2. package/bin/binaries/probe-v0.6.0-rc210-aarch64-unknown-linux-musl.tar.gz +0 -0
  3. package/bin/binaries/probe-v0.6.0-rc210-x86_64-apple-darwin.tar.gz +0 -0
  4. package/bin/binaries/probe-v0.6.0-rc210-x86_64-pc-windows-msvc.zip +0 -0
  5. package/bin/binaries/probe-v0.6.0-rc210-x86_64-unknown-linux-musl.tar.gz +0 -0
  6. package/build/agent/ProbeAgent.d.ts +4 -2
  7. package/build/agent/ProbeAgent.js +87 -5
  8. package/build/agent/bashCommandUtils.js +98 -12
  9. package/build/agent/bashPermissions.js +207 -1
  10. package/build/agent/index.js +283 -23
  11. package/build/agent/mcp/client.js +2 -1
  12. package/build/delegate.js +11 -2
  13. package/build/tools/vercel.js +5 -2
  14. package/cjs/agent/ProbeAgent.cjs +277 -17
  15. package/cjs/index.cjs +277 -17
  16. package/index.d.ts +4 -2
  17. package/package.json +1 -1
  18. package/src/agent/ProbeAgent.d.ts +4 -2
  19. package/src/agent/ProbeAgent.js +87 -5
  20. package/src/agent/bashCommandUtils.js +98 -12
  21. package/src/agent/bashPermissions.js +207 -1
  22. package/src/agent/index.js +5 -5
  23. package/src/agent/mcp/client.js +2 -1
  24. package/src/delegate.js +11 -2
  25. package/src/tools/vercel.js +5 -2
  26. package/bin/binaries/probe-v0.6.0-rc208-aarch64-apple-darwin.tar.gz +0 -0
  27. package/bin/binaries/probe-v0.6.0-rc208-aarch64-unknown-linux-musl.tar.gz +0 -0
  28. package/bin/binaries/probe-v0.6.0-rc208-x86_64-apple-darwin.tar.gz +0 -0
  29. package/bin/binaries/probe-v0.6.0-rc208-x86_64-pc-windows-msvc.zip +0 -0
  30. package/bin/binaries/probe-v0.6.0-rc208-x86_64-unknown-linux-musl.tar.gz +0 -0
@@ -30711,7 +30711,10 @@ async function delegate({
30711
30711
  disableTools = false,
30712
30712
  searchDelegate = void 0,
30713
30713
  schema = null,
30714
- enableTasks = false
30714
+ enableTasks = false,
30715
+ enableMcp = false,
30716
+ mcpConfig = null,
30717
+ mcpConfigPath = null
30715
30718
  }) {
30716
30719
  if (!task || typeof task !== "string") {
30717
30720
  throw new Error("Task parameter is required and must be a string");
@@ -30767,8 +30770,14 @@ async function delegate({
30767
30770
  allowedTools,
30768
30771
  disableTools,
30769
30772
  searchDelegate,
30770
- enableTasks
30773
+ enableTasks,
30771
30774
  // Inherit from parent (subagent gets isolated TaskManager)
30775
+ enableMcp,
30776
+ // Inherit from parent (subagent creates own MCPXmlBridge)
30777
+ mcpConfig,
30778
+ // Inherit from parent
30779
+ mcpConfigPath
30780
+ // Inherit from parent
30772
30781
  });
30773
30782
  if (debug) {
30774
30783
  console.error(`[DELEGATE] Created subagent with session ${sessionId}`);
@@ -36739,7 +36748,7 @@ var init_vercel = __esm({
36739
36748
  });
36740
36749
  };
36741
36750
  delegateTool = (options = {}) => {
36742
- const { debug = false, timeout = 300, cwd, allowedFolders, enableBash = false, bashConfig, architectureFileName } = options;
36751
+ const { debug = false, timeout = 300, cwd, allowedFolders, enableBash = false, bashConfig, architectureFileName, enableMcp = false, mcpConfig = null, mcpConfigPath = null } = options;
36743
36752
  return (0, import_ai2.tool)({
36744
36753
  name: "delegate",
36745
36754
  description: delegateDescription,
@@ -36798,7 +36807,10 @@ var init_vercel = __esm({
36798
36807
  enableBash,
36799
36808
  bashConfig,
36800
36809
  architectureFileName,
36801
- searchDelegate
36810
+ searchDelegate,
36811
+ enableMcp,
36812
+ mcpConfig,
36813
+ mcpConfigPath
36802
36814
  });
36803
36815
  return result;
36804
36816
  }
@@ -37325,6 +37337,38 @@ function parseSimpleCommand(command) {
37325
37337
  isComplex: false
37326
37338
  };
37327
37339
  }
37340
+ const stripQuotedContent = (str) => {
37341
+ let result = "";
37342
+ let inQuotes2 = false;
37343
+ let quoteChar2 = "";
37344
+ for (let i4 = 0; i4 < str.length; i4++) {
37345
+ const char = str[i4];
37346
+ const nextChar = str[i4 + 1];
37347
+ if (!inQuotes2 && char === "\\" && nextChar !== void 0) {
37348
+ i4++;
37349
+ continue;
37350
+ }
37351
+ if (inQuotes2 && quoteChar2 === '"' && char === "\\" && nextChar !== void 0) {
37352
+ i4++;
37353
+ continue;
37354
+ }
37355
+ if (!inQuotes2 && (char === '"' || char === "'")) {
37356
+ inQuotes2 = true;
37357
+ quoteChar2 = char;
37358
+ continue;
37359
+ }
37360
+ if (inQuotes2 && char === quoteChar2) {
37361
+ inQuotes2 = false;
37362
+ quoteChar2 = "";
37363
+ continue;
37364
+ }
37365
+ if (!inQuotes2) {
37366
+ result += char;
37367
+ }
37368
+ }
37369
+ return result;
37370
+ };
37371
+ const strippedForOperators = stripQuotedContent(trimmed);
37328
37372
  const complexPatterns = [
37329
37373
  /\|/,
37330
37374
  // Pipes
@@ -37350,7 +37394,7 @@ function parseSimpleCommand(command) {
37350
37394
  // Brace expansion like {a,b} or {1..10} (but not find {} placeholders)
37351
37395
  ];
37352
37396
  for (const pattern of complexPatterns) {
37353
- if (pattern.test(trimmed)) {
37397
+ if (pattern.test(strippedForOperators)) {
37354
37398
  return {
37355
37399
  success: false,
37356
37400
  error: "Complex shell commands with pipes, operators, or redirections are not supported for security reasons",
@@ -37365,17 +37409,17 @@ function parseSimpleCommand(command) {
37365
37409
  let current = "";
37366
37410
  let inQuotes = false;
37367
37411
  let quoteChar = "";
37368
- let escaped = false;
37369
37412
  for (let i4 = 0; i4 < trimmed.length; i4++) {
37370
37413
  const char = trimmed[i4];
37371
37414
  const nextChar = i4 + 1 < trimmed.length ? trimmed[i4 + 1] : "";
37372
- if (escaped) {
37373
- current += char;
37374
- escaped = false;
37415
+ if (!inQuotes && char === "\\" && nextChar) {
37416
+ current += nextChar;
37417
+ i4++;
37375
37418
  continue;
37376
37419
  }
37377
- if (char === "\\" && !inQuotes) {
37378
- escaped = true;
37420
+ if (inQuotes && quoteChar === '"' && char === "\\" && nextChar) {
37421
+ current += nextChar;
37422
+ i4++;
37379
37423
  continue;
37380
37424
  }
37381
37425
  if (!inQuotes && (char === '"' || char === "'")) {
@@ -37708,8 +37752,97 @@ var init_bashPermissions = __esm({
37708
37752
  });
37709
37753
  return result;
37710
37754
  }
37755
+ /**
37756
+ * Split a complex command into component commands by operators
37757
+ *
37758
+ * ## Escape Handling (Security-Critical)
37759
+ *
37760
+ * This function intentionally PRESERVES escape sequences (both backslash AND
37761
+ * escaped character) in the output. This is step 1 of a 2-step parsing process:
37762
+ *
37763
+ * 1. _splitComplexCommand: Splits by operators, PRESERVES escapes → `echo "test\" && b"`
37764
+ * 2. parseCommand: Interprets escapes in each component → args: ['test" && b']
37765
+ *
37766
+ * This differs from stripQuotedContent() in bashCommandUtils.js which REMOVES
37767
+ * escapes entirely (for operator detection only).
37768
+ *
37769
+ * The security rationale: if we stripped escapes here, `\"` would become `"`,
37770
+ * potentially causing incorrect quote boundary detection and allowing operator
37771
+ * injection. By preserving escapes, parseCommand() can correctly interpret them.
37772
+ *
37773
+ * See bashCommandUtils.js module header for the full escape handling architecture.
37774
+ *
37775
+ * @private
37776
+ * @param {string} command - Complex command to split
37777
+ * @returns {string[]} Array of component commands (with escapes preserved)
37778
+ */
37779
+ _splitComplexCommand(command) {
37780
+ const components = [];
37781
+ let current = "";
37782
+ let inQuotes = false;
37783
+ let quoteChar = "";
37784
+ let i4 = 0;
37785
+ while (i4 < command.length) {
37786
+ const char = command[i4];
37787
+ const nextChar = command[i4 + 1] || "";
37788
+ if (!inQuotes && char === "\\") {
37789
+ current += char;
37790
+ if (nextChar) {
37791
+ current += nextChar;
37792
+ i4 += 2;
37793
+ } else {
37794
+ i4++;
37795
+ }
37796
+ continue;
37797
+ }
37798
+ if (inQuotes && quoteChar === '"' && char === "\\" && nextChar) {
37799
+ current += char + nextChar;
37800
+ i4 += 2;
37801
+ continue;
37802
+ }
37803
+ if (!inQuotes && (char === '"' || char === "'")) {
37804
+ inQuotes = true;
37805
+ quoteChar = char;
37806
+ current += char;
37807
+ i4++;
37808
+ continue;
37809
+ }
37810
+ if (inQuotes && char === quoteChar) {
37811
+ inQuotes = false;
37812
+ quoteChar = "";
37813
+ current += char;
37814
+ i4++;
37815
+ continue;
37816
+ }
37817
+ if (!inQuotes) {
37818
+ if (char === "&" && nextChar === "&" || char === "|" && nextChar === "|") {
37819
+ if (current.trim()) {
37820
+ components.push(current.trim());
37821
+ }
37822
+ current = "";
37823
+ i4 += 2;
37824
+ continue;
37825
+ }
37826
+ if (char === "|") {
37827
+ if (current.trim()) {
37828
+ components.push(current.trim());
37829
+ }
37830
+ current = "";
37831
+ i4++;
37832
+ continue;
37833
+ }
37834
+ }
37835
+ current += char;
37836
+ i4++;
37837
+ }
37838
+ if (current.trim()) {
37839
+ components.push(current.trim());
37840
+ }
37841
+ return components;
37842
+ }
37711
37843
  /**
37712
37844
  * Check a complex command against complex patterns in allow/deny lists
37845
+ * Also supports auto-allowing commands where all components are individually allowed
37713
37846
  * @private
37714
37847
  * @param {string} command - Complex command to check
37715
37848
  * @returns {Object} Permission result
@@ -37764,6 +37897,85 @@ var init_bashPermissions = __esm({
37764
37897
  return result;
37765
37898
  }
37766
37899
  }
37900
+ const components = this._splitComplexCommand(command);
37901
+ if (this.debug) {
37902
+ console.log(`[BashPermissions] Checking ${components.length} command components: ${JSON.stringify(components)}`);
37903
+ }
37904
+ if (components.length > 1) {
37905
+ const componentResults = [];
37906
+ let allAllowed = true;
37907
+ let deniedComponent = null;
37908
+ let deniedReason = null;
37909
+ for (const component of components) {
37910
+ const parsed = parseCommand(component);
37911
+ if (parsed.error || parsed.isComplex) {
37912
+ if (this.debug) {
37913
+ console.log(`[BashPermissions] Component "${component}" is complex or has error: ${parsed.error}`);
37914
+ }
37915
+ allAllowed = false;
37916
+ deniedComponent = component;
37917
+ deniedReason = parsed.error || "Component contains nested complex constructs";
37918
+ break;
37919
+ }
37920
+ if (matchesAnyPattern(parsed, this.denyPatterns)) {
37921
+ if (this.debug) {
37922
+ console.log(`[BashPermissions] Component "${component}" matches deny pattern`);
37923
+ }
37924
+ allAllowed = false;
37925
+ deniedComponent = component;
37926
+ deniedReason = "Component matches deny pattern";
37927
+ break;
37928
+ }
37929
+ if (!matchesAnyPattern(parsed, this.allowPatterns)) {
37930
+ if (this.debug) {
37931
+ console.log(`[BashPermissions] Component "${component}" not in allow list`);
37932
+ }
37933
+ allAllowed = false;
37934
+ deniedComponent = component;
37935
+ deniedReason = "Component not in allow list";
37936
+ break;
37937
+ }
37938
+ componentResults.push({ component, parsed, allowed: true });
37939
+ }
37940
+ if (allAllowed) {
37941
+ if (this.debug) {
37942
+ console.log(`[BashPermissions] ALLOWED - all ${components.length} components passed individual checks`);
37943
+ }
37944
+ const result = {
37945
+ allowed: true,
37946
+ command,
37947
+ isComplex: true,
37948
+ allowedByComponents: true,
37949
+ components: componentResults
37950
+ };
37951
+ this.recordBashEvent("permission.allowed", {
37952
+ command,
37953
+ isComplex: true,
37954
+ allowedByComponents: true,
37955
+ componentCount: components.length
37956
+ });
37957
+ return result;
37958
+ } else {
37959
+ if (this.debug) {
37960
+ console.log(`[BashPermissions] DENIED - component "${deniedComponent}" failed: ${deniedReason}`);
37961
+ }
37962
+ const result = {
37963
+ allowed: false,
37964
+ reason: `Component "${deniedComponent}" not allowed: ${deniedReason}`,
37965
+ command,
37966
+ isComplex: true,
37967
+ failedComponent: deniedComponent
37968
+ };
37969
+ this.recordBashEvent("permission.denied", {
37970
+ command,
37971
+ reason: "component_not_allowed",
37972
+ failedComponent: deniedComponent,
37973
+ componentReason: deniedReason,
37974
+ isComplex: true
37975
+ });
37976
+ return result;
37977
+ }
37978
+ }
37767
37979
  if (this.debug) {
37768
37980
  console.log(`[BashPermissions] DENIED - no matching complex pattern found`);
37769
37981
  }
@@ -85275,7 +85487,7 @@ var init_client2 = __esm({
85275
85487
  clientInfo.client.callTool({
85276
85488
  name: tool4.originalName,
85277
85489
  arguments: args
85278
- }),
85490
+ }, void 0, { timeout }),
85279
85491
  timeoutPromise
85280
85492
  ]);
85281
85493
  const durationMs = Date.now() - startTime;
@@ -95829,7 +96041,7 @@ var init_ProbeAgent = __esm({
95829
96041
  this.maxIterations = options.maxIterations || null;
95830
96042
  this.disableMermaidValidation = !!options.disableMermaidValidation;
95831
96043
  this.disableJsonValidation = !!options.disableJsonValidation;
95832
- this.enableSkills = options.disableSkills ? false : options.enableSkills !== void 0 ? !!options.enableSkills : true;
96044
+ this.enableSkills = options.disableSkills ? false : !!(options.allowSkills || options.enableSkills);
95833
96045
  if (Array.isArray(options.skillDirs)) {
95834
96046
  this.skillDirs = options.skillDirs;
95835
96047
  } else if (typeof options.skillDirs === "string") {
@@ -97724,6 +97936,9 @@ You are working with a repository located at: ${searchDirectory}
97724
97936
  let lastFormatErrorType = null;
97725
97937
  let sameFormatErrorCount = 0;
97726
97938
  const MAX_REPEATED_FORMAT_ERRORS = 3;
97939
+ let lastNoToolResponse = null;
97940
+ let sameResponseCount = 0;
97941
+ const MAX_REPEATED_IDENTICAL_RESPONSES = 3;
97727
97942
  while (currentIteration < maxIterations && !completionAttempted) {
97728
97943
  currentIteration++;
97729
97944
  if (this.cancelled) throw new Error("Request was cancelled by the user");
@@ -98205,6 +98420,26 @@ ${errorXml}
98205
98420
  }
98206
98421
  break;
98207
98422
  }
98423
+ if (lastNoToolResponse !== null && assistantResponseContent === lastNoToolResponse) {
98424
+ sameResponseCount++;
98425
+ if (sameResponseCount >= MAX_REPEATED_IDENTICAL_RESPONSES) {
98426
+ let cleanedResponse = assistantResponseContent;
98427
+ cleanedResponse = cleanedResponse.replace(/<thinking>[\s\S]*?<\/thinking>/gi, "").trim();
98428
+ cleanedResponse = cleanedResponse.replace(/<thinking>[\s\S]*$/gi, "").trim();
98429
+ const hasSubstantialContent = cleanedResponse.length > 50 && !cleanedResponse.includes("<api_call>") && !cleanedResponse.includes("<tool_name>") && !cleanedResponse.includes("<function>");
98430
+ if (hasSubstantialContent) {
98431
+ if (this.debug) {
98432
+ console.log(`[DEBUG] Same response repeated ${sameResponseCount} times - accepting as final answer (${cleanedResponse.length} chars)`);
98433
+ }
98434
+ finalResult = cleanedResponse;
98435
+ completionAttempted = true;
98436
+ break;
98437
+ }
98438
+ }
98439
+ } else {
98440
+ lastNoToolResponse = assistantResponseContent;
98441
+ sameResponseCount = 1;
98442
+ }
98208
98443
  currentMessages.push({ role: "assistant", content: assistantResponseContent });
98209
98444
  const unrecognizedTool = detectUnrecognizedToolCall(assistantResponseContent, validTools);
98210
98445
  let reminderContent;
@@ -98277,10 +98512,35 @@ Or if your previous response already contains a complete, direct answer (not a t
98277
98512
 
98278
98513
  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.`;
98279
98514
  }
98280
- currentMessages.push({
98281
- role: "user",
98282
- content: reminderContent
98283
- });
98515
+ const prevUserMsgIndex = currentMessages.length - 2;
98516
+ const prevUserMsg = currentMessages[prevUserMsgIndex];
98517
+ const isExistingReminder = prevUserMsg && prevUserMsg.role === "user" && (prevUserMsg.content.includes("Please use one of the available tools") || prevUserMsg.content.includes("<tool_result>"));
98518
+ if (isExistingReminder && sameResponseCount > 1) {
98519
+ const prevAssistantIndex = prevUserMsgIndex - 1;
98520
+ const hasSystemMessage2 = currentMessages.length > 0 && currentMessages[0].role === "system";
98521
+ const minValidIndex = hasSystemMessage2 ? 1 : 0;
98522
+ const canSafelyRemove = prevAssistantIndex >= minValidIndex && currentMessages[prevAssistantIndex] && currentMessages[prevAssistantIndex].role === "assistant" && currentMessages.length - 2 >= (hasSystemMessage2 ? 2 : 1);
98523
+ if (canSafelyRemove) {
98524
+ currentMessages.splice(prevAssistantIndex, 2);
98525
+ if (this.debug) {
98526
+ console.log(`[DEBUG] Removed duplicate assistant+reminder pair (iteration ${currentIteration}, same response #${sameResponseCount})`);
98527
+ }
98528
+ } else if (this.debug) {
98529
+ console.log(`[DEBUG] Skipped deduplication: pattern validation failed (prevAssistantIndex=${prevAssistantIndex}, arrayLength=${currentMessages.length})`);
98530
+ }
98531
+ const iterationHint = `
98532
+
98533
+ (Attempt #${sameResponseCount}: Your previous ${sameResponseCount} responses were identical. If you have a complete answer, use <attempt_complete></attempt_complete> to finalize it.)`;
98534
+ currentMessages.push({
98535
+ role: "user",
98536
+ content: reminderContent + iterationHint
98537
+ });
98538
+ } else {
98539
+ currentMessages.push({
98540
+ role: "user",
98541
+ content: reminderContent
98542
+ });
98543
+ }
98284
98544
  if (this.debug) {
98285
98545
  if (unrecognizedTool) {
98286
98546
  console.log(`[DEBUG] Unrecognized tool '${unrecognizedTool}' used. Providing error feedback.`);