@probelabs/probe 0.6.0-rc259 → 0.6.0-rc261

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probelabs/probe",
3
- "version": "0.6.0-rc259",
3
+ "version": "0.6.0-rc261",
4
4
  "description": "Node.js wrapper for the probe code search tool",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -127,6 +127,8 @@ export function parseSimpleCommand(command) {
127
127
  /&&/, // Logical AND
128
128
  /\|\|/, // Logical OR
129
129
  /(?<!\\);/, // Command separator (but not escaped \;)
130
+ /\n/, // Newline command separator (multi-line commands)
131
+ /\r/, // Carriage return (CRLF line endings)
130
132
  /&$/, // Background execution
131
133
  /\$\(/, // Command substitution $()
132
134
  /`/, // Command substitution ``
@@ -260,6 +262,7 @@ export function isComplexPattern(pattern) {
260
262
  /&&/, // Logical AND
261
263
  /\|\|/, // Logical OR
262
264
  /;/, // Command separator
265
+ /\n/, // Newline command separator
263
266
  /&$/, // Background execution
264
267
  /\$\(/, // Command substitution $()
265
268
  /`/, // Command substitution ``
@@ -402,6 +402,29 @@ export class BashPermissionChecker {
402
402
  i++;
403
403
  continue;
404
404
  }
405
+ // Check for newline (command separator in multi-line scripts)
406
+ // Also handle \r\n (CRLF) line endings
407
+ if (char === '\n' || (char === '\r' && nextChar === '\n')) {
408
+ if (current.trim()) {
409
+ components.push(current.trim());
410
+ }
411
+ current = '';
412
+ if (char === '\r') {
413
+ i += 2; // Skip \r\n
414
+ } else {
415
+ i++;
416
+ }
417
+ continue;
418
+ }
419
+ if (char === '\r') {
420
+ // Bare \r without \n
421
+ if (current.trim()) {
422
+ components.push(current.trim());
423
+ }
424
+ current = '';
425
+ i++;
426
+ continue;
427
+ }
405
428
  }
406
429
 
407
430
  current += char;
@@ -266,6 +266,36 @@ function normalizeJsonQuotes(str) {
266
266
  return result;
267
267
  }
268
268
 
269
+ /**
270
+ * Check if a code block is embedded within a structured markdown document.
271
+ * Used to avoid extracting embedded JSON examples from markdown text.
272
+ * Issue #456: attempt_completion results containing markdown with JSON code block
273
+ * documentation examples were having the examples extracted as structured output.
274
+ *
275
+ * Normal AI behavior: AI wraps JSON answer in a code block with brief explanation text.
276
+ * Issue #456: AI returns a markdown document that contains JSON code blocks as examples.
277
+ *
278
+ * We distinguish these by checking if the surrounding text contains markdown structural
279
+ * elements (headings) which indicate a document rather than a brief explanation.
280
+ *
281
+ * @param {string} text - The full trimmed text
282
+ * @param {RegExpMatchArray} match - The regex match for the code block
283
+ * @returns {boolean} - true if the code block is embedded in a markdown document
284
+ */
285
+ function isCodeBlockEmbeddedInDocument(text, match) {
286
+ const beforeBlock = text.substring(0, match.index).trim();
287
+ const afterBlock = text.substring(match.index + match[0].length).trim();
288
+
289
+ // Check if surrounding text contains markdown headings (lines starting with #)
290
+ // This is a strong signal that the content is a structured document, not just explanation text
291
+ const hasMarkdownHeadings = /^#{1,6}\s/m.test(beforeBlock) || /^#{1,6}\s/m.test(afterBlock);
292
+ if (hasMarkdownHeadings) {
293
+ return true;
294
+ }
295
+
296
+ return false;
297
+ }
298
+
269
299
  /**
270
300
  * Clean AI response by extracting JSON content when response contains JSON
271
301
  * Only processes responses that contain JSON structures { or [
@@ -311,15 +341,20 @@ export function cleanSchemaResponse(response) {
311
341
  }
312
342
 
313
343
  // First, look for JSON after code block markers - similar to mermaid extraction
344
+ // Only extract from code blocks when they are the primary content (not embedded examples in markdown).
345
+ // Issue #456: When attempt_completion result contains markdown with embedded JSON code blocks
346
+ // as documentation examples, extracting those blocks produces wrong structured output.
347
+ // We check that there's no significant text content outside the code block.
348
+
314
349
  // Try with json language specifier
315
350
  const jsonBlockMatch = trimmed.match(/```json\s*\n([\s\S]*?)\n```/);
316
- if (jsonBlockMatch) {
351
+ if (jsonBlockMatch && !isCodeBlockEmbeddedInDocument(trimmed, jsonBlockMatch)) {
317
352
  return normalizeJsonQuotes(jsonBlockMatch[1].trim());
318
353
  }
319
354
 
320
355
  // Try any code block with JSON content
321
356
  const anyBlockMatch = trimmed.match(/```\s*\n([{\[][\s\S]*?[}\]])\s*```/);
322
- if (anyBlockMatch) {
357
+ if (anyBlockMatch && !isCodeBlockEmbeddedInDocument(trimmed, anyBlockMatch)) {
323
358
  return normalizeJsonQuotes(anyBlockMatch[1].trim());
324
359
  }
325
360
 
@@ -331,7 +366,7 @@ export function cleanSchemaResponse(response) {
331
366
 
332
367
  for (const pattern of codeBlockPatterns) {
333
368
  const match = trimmed.match(pattern);
334
- if (match) {
369
+ if (match && !isCodeBlockEmbeddedInDocument(trimmed, match)) {
335
370
  return normalizeJsonQuotes(match[1].trim());
336
371
  }
337
372
  }
@@ -345,10 +380,11 @@ export function cleanSchemaResponse(response) {
345
380
  }
346
381
 
347
382
  // Look for code block start followed immediately by JSON
383
+ // Only extract if the code block is not embedded in a structured markdown document
348
384
  const codeBlockStartPattern = /```(?:json)?\s*\n?\s*([{\[])/;
349
385
  const codeBlockMatch = trimmed.match(codeBlockStartPattern);
350
386
 
351
- if (codeBlockMatch) {
387
+ if (codeBlockMatch && !isCodeBlockEmbeddedInDocument(trimmed, codeBlockMatch)) {
352
388
  const startIndex = codeBlockMatch.index + codeBlockMatch[0].length - 1; // Position of the bracket
353
389
 
354
390
  // Find the matching closing bracket
@@ -11,7 +11,7 @@ import { DEFAULT_VALID_TOOLS, buildToolTagPattern } from '../tools/common.js';
11
11
  * @param {string} xmlString - The XML string to clean
12
12
  * @returns {string} - Cleaned XML string without thinking tags
13
13
  */
14
- export function removeThinkingTags(xmlString) {
14
+ export function removeThinkingTags(xmlString, validTools = DEFAULT_VALID_TOOLS) {
15
15
  let result = xmlString;
16
16
 
17
17
  // Remove all properly closed thinking tags first
@@ -26,8 +26,8 @@ export function removeThinkingTags(xmlString) {
26
26
  const afterThinking = result.substring(thinkingIndex + '<thinking>'.length);
27
27
 
28
28
  // Look for any tool tags in the remaining content
29
- // Use the shared tool list to build the pattern dynamically
30
- const toolPattern = buildToolTagPattern(DEFAULT_VALID_TOOLS);
29
+ // Use the provided valid tools list to build the pattern dynamically
30
+ const toolPattern = buildToolTagPattern(validTools);
31
31
  const toolMatch = afterThinking.match(toolPattern);
32
32
 
33
33
  if (toolMatch) {
@@ -201,7 +201,9 @@ export function processXmlWithThinkingAndRecovery(xmlString, validTools = []) {
201
201
  const thinkingContent = extractThinkingContent(xmlString);
202
202
 
203
203
  // Remove thinking tags and their content from the XML string
204
- const cleanedXmlString = removeThinkingTags(xmlString);
204
+ // Forward validTools so that tool tags (e.g. edit, create) inside unclosed
205
+ // thinking blocks are preserved when they are in the valid tools list
206
+ const cleanedXmlString = removeThinkingTags(xmlString, validTools.length > 0 ? validTools : undefined);
205
207
 
206
208
  // Check for attempt_complete recovery patterns
207
209
  const recoveryResult = checkAttemptCompleteRecovery(cleanedXmlString, validTools);