@probelabs/probe 0.6.0-rc240 → 0.6.0-rc242

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.
@@ -102,6 +102,7 @@ import { formatErrorForAI, ParameterError } from '../utils/error-types.js';
102
102
  import { getCommonPrefix, toRelativePath, safeRealpath } from '../utils/path-validation.js';
103
103
  import { truncateIfNeeded, getMaxOutputTokens } from './outputTruncator.js';
104
104
  import { DelegationManager } from '../delegate.js';
105
+ import { extractRawOutputBlocks } from '../tools/executePlan.js';
105
106
  import {
106
107
  TaskManager,
107
108
  createTaskTool,
@@ -3610,6 +3611,18 @@ Follow these instructions carefully:
3610
3611
 
3611
3612
  let toolResultContent = typeof executionResult === 'string' ? executionResult : JSON.stringify(executionResult, null, 2);
3612
3613
 
3614
+ // Extract raw output blocks and pass them through to output buffer (before truncation)
3615
+ // This prevents LLM from processing/hallucinating large structured output from execute_plan
3616
+ if (this._outputBuffer) {
3617
+ const { cleanedContent, extractedBlocks } = extractRawOutputBlocks(toolResultContent, this._outputBuffer);
3618
+ if (extractedBlocks.length > 0) {
3619
+ toolResultContent = cleanedContent;
3620
+ if (this.debug) {
3621
+ console.log(`[DEBUG] Extracted ${extractedBlocks.length} raw output blocks (${extractedBlocks.reduce((sum, b) => sum + b.length, 0)} chars) to output buffer`);
3622
+ }
3623
+ }
3624
+ }
3625
+
3613
3626
  // Truncate if output exceeds token limit
3614
3627
  try {
3615
3628
  const truncateResult = await truncateIfNeeded(toolResultContent, this.tokenCounter, this.sessionId, this.maxOutputTokens);
@@ -3856,6 +3869,18 @@ Follow these instructions carefully:
3856
3869
  toolResultContent = toolResultContent.split(wsPrefix).join('');
3857
3870
  }
3858
3871
 
3872
+ // Extract raw output blocks and pass them through to output buffer (before truncation)
3873
+ // This prevents LLM from processing/hallucinating large structured output from execute_plan
3874
+ if (this._outputBuffer) {
3875
+ const { cleanedContent, extractedBlocks } = extractRawOutputBlocks(toolResultContent, this._outputBuffer);
3876
+ if (extractedBlocks.length > 0) {
3877
+ toolResultContent = cleanedContent;
3878
+ if (this.debug) {
3879
+ console.log(`[DEBUG] Extracted ${extractedBlocks.length} raw output blocks (${extractedBlocks.reduce((sum, b) => sum + b.length, 0)} chars) to output buffer`);
3880
+ }
3881
+ }
3882
+ }
3883
+
3859
3884
  // Truncate if output exceeds token limit
3860
3885
  try {
3861
3886
  const truncateResult = await truncateIfNeeded(toolResultContent, this.tokenCounter, this.sessionId, this.maxOutputTokens);
@@ -227,9 +227,21 @@ export function generateSandboxGlobals(options) {
227
227
  }
228
228
 
229
229
  // LLM() built-in — delegate already has its own OTEL, but we add a DSL-level span
230
+ // When schema is provided, auto-parse the JSON result for easier downstream processing
230
231
  if (llmCall) {
231
232
  const rawLLM = async (instruction, data, opts = {}) => {
232
- return llmCall(instruction, data, opts);
233
+ const result = await llmCall(instruction, data, opts);
234
+ // Auto-parse JSON when schema is provided and result is a string
235
+ if (opts.schema && typeof result === 'string') {
236
+ try {
237
+ return JSON.parse(result);
238
+ } catch (e) {
239
+ // If parsing fails, return the raw string (may have formatting issues)
240
+ logFn?.('[LLM] Warning: schema provided but result is not valid JSON');
241
+ return result;
242
+ }
243
+ }
244
+ return result;
233
245
  };
234
246
  globals.LLM = traceToolCall('LLM', rawLLM, tracer, logFn);
235
247
  }
@@ -306,6 +318,81 @@ export function generateSandboxGlobals(options) {
306
318
  return chunks;
307
319
  };
308
320
 
321
+ // chunkByKey() - chunk data ensuring same-key items stay together
322
+ // - Chunks CAN have multiple keys (customers)
323
+ // - But same key NEVER splits across chunks
324
+ globals.chunkByKey = (data, keyFn, maxTokens = 20000) => {
325
+ const CHARS_PER_TOKEN = 4;
326
+ const maxChars = maxTokens * CHARS_PER_TOKEN;
327
+ const text = typeof data === 'string' ? data : JSON.stringify(data);
328
+
329
+ // Find all File: markers
330
+ const blockRegex = /^File: ([^\n]+)/gm;
331
+ const markers = [];
332
+ let match;
333
+ while ((match = blockRegex.exec(text)) !== null) {
334
+ markers.push({ index: match.index, file: match[1].trim() });
335
+ }
336
+
337
+ // No File: headers - fallback to regular chunk
338
+ if (markers.length === 0) {
339
+ return globals.chunk(data, maxTokens);
340
+ }
341
+
342
+ const chunks = [];
343
+ let currentChunk = '';
344
+ let currentSize = 0;
345
+ let keysInChunk = new Set(); // Track which keys are in current chunk
346
+
347
+ // Process each block
348
+ for (let i = 0; i < markers.length; i++) {
349
+ const start = markers[i].index;
350
+ const end = i + 1 < markers.length ? markers[i + 1].index : text.length;
351
+ const block = text.slice(start, end).trim();
352
+ const file = markers[i].file;
353
+ const key = typeof keyFn === 'function' ? keyFn(file) : file;
354
+
355
+ const blockSize = block.length + 2; // +2 for \n\n separator
356
+ const wouldOverflow = currentSize + blockSize > maxChars;
357
+ const keyAlreadyInChunk = keysInChunk.has(key);
358
+
359
+ // Decision logic:
360
+ // - If key already in chunk: MUST add (never split a key)
361
+ // - If new key and would overflow: flush first, then add
362
+ // - If new key and fits: add to current chunk
363
+
364
+ if (!keyAlreadyInChunk && wouldOverflow && currentChunk) {
365
+ // New key would overflow - flush current chunk first
366
+ chunks.push(currentChunk.trim());
367
+ currentChunk = '';
368
+ currentSize = 0;
369
+ keysInChunk = new Set();
370
+ }
371
+
372
+ // Add block to current chunk
373
+ if (currentChunk) currentChunk += '\n\n';
374
+ currentChunk += block;
375
+ currentSize += blockSize;
376
+ keysInChunk.add(key);
377
+ }
378
+
379
+ // Flush final chunk
380
+ if (currentChunk.trim()) {
381
+ chunks.push(currentChunk.trim());
382
+ }
383
+
384
+ return chunks.length > 0 ? chunks : [''];
385
+ };
386
+
387
+ // extractPaths() - extract unique file paths from search results
388
+ // Parses File: headers and returns deduplicated array of paths
389
+ globals.extractPaths = (searchResults) => {
390
+ const text = typeof searchResults === 'string' ? searchResults : JSON.stringify(searchResults);
391
+ const matches = text.match(/^File: ([^\n]+)/gm) || [];
392
+ const paths = matches.map(m => m.replace('File: ', '').trim());
393
+ return [...new Set(paths)];
394
+ };
395
+
309
396
  // Utility functions (pure, no async)
310
397
  globals.log = (message) => {
311
398
  // Collected by the runtime for the execution log
@@ -9,6 +9,81 @@
9
9
  import * as acorn from 'acorn';
10
10
  import * as walk from 'acorn-walk';
11
11
 
12
+ /**
13
+ * Convert a character offset to line and column numbers.
14
+ * @param {string} code - The source code
15
+ * @param {number} offset - Character offset
16
+ * @returns {{ line: number, column: number }}
17
+ */
18
+ function offsetToLineColumn(code, offset) {
19
+ const lines = code.split('\n');
20
+ let pos = 0;
21
+ for (let i = 0; i < lines.length; i++) {
22
+ const lineLength = lines[i].length + 1; // +1 for newline
23
+ if (pos + lineLength > offset) {
24
+ return { line: i + 1, column: offset - pos + 1 };
25
+ }
26
+ pos += lineLength;
27
+ }
28
+ return { line: lines.length, column: 1 };
29
+ }
30
+
31
+ /**
32
+ * Generate a code snippet with an arrow pointing to the error location.
33
+ * @param {string} code - The source code
34
+ * @param {number} line - Line number (1-based)
35
+ * @param {number} column - Column number (1-based)
36
+ * @param {number} contextLines - Number of lines to show before/after (default: 2)
37
+ * @returns {string}
38
+ */
39
+ function generateErrorSnippet(code, line, column, contextLines = 2) {
40
+ const lines = code.split('\n');
41
+ const startLine = Math.max(0, line - 1 - contextLines);
42
+ const endLine = Math.min(lines.length, line + contextLines);
43
+
44
+ const snippetLines = [];
45
+ const lineNumWidth = String(endLine).length;
46
+
47
+ for (let i = startLine; i < endLine; i++) {
48
+ const lineNum = String(i + 1).padStart(lineNumWidth, ' ');
49
+ const marker = (i + 1 === line) ? '>' : ' ';
50
+ snippetLines.push(`${marker} ${lineNum} | ${lines[i]}`);
51
+
52
+ // Add arrow line for the error line
53
+ if (i + 1 === line) {
54
+ const padding = ' '.repeat(lineNumWidth + 4); // " 123 | " prefix
55
+ const arrow = ' '.repeat(Math.max(0, column - 1)) + '^';
56
+ snippetLines.push(`${padding}${arrow}`);
57
+ }
58
+ }
59
+
60
+ return snippetLines.join('\n');
61
+ }
62
+
63
+ /**
64
+ * Format an error message with code snippet.
65
+ * @param {string} message - The error message
66
+ * @param {string} code - The source code
67
+ * @param {number} offset - Character offset (optional, use -1 if line/column provided)
68
+ * @param {number} line - Line number (optional)
69
+ * @param {number} column - Column number (optional)
70
+ * @returns {string}
71
+ */
72
+ function formatErrorWithSnippet(message, code, offset = -1, line = 0, column = 0) {
73
+ if (offset >= 0) {
74
+ const loc = offsetToLineColumn(code, offset);
75
+ line = loc.line;
76
+ column = loc.column;
77
+ }
78
+
79
+ if (line <= 0) {
80
+ return message;
81
+ }
82
+
83
+ const snippet = generateErrorSnippet(code, line, column);
84
+ return `${message}\n\n${snippet}`;
85
+ }
86
+
12
87
  // Node types the LLM is allowed to generate
13
88
  const ALLOWED_NODE_TYPES = new Set([
14
89
  'Program',
@@ -102,16 +177,32 @@ export function validateDSL(code) {
102
177
  ecmaVersion: 2022,
103
178
  sourceType: 'script',
104
179
  allowReturnOutsideFunction: true,
180
+ locations: true, // Enable location tracking for better error messages
105
181
  });
106
182
  } catch (e) {
107
- return { valid: false, errors: [`Syntax error: ${e.message}`] };
183
+ // Acorn errors have loc property with line/column
184
+ const line = e.loc?.line || 0;
185
+ const column = e.loc?.column ? e.loc.column + 1 : 0; // Acorn column is 0-based
186
+ const formattedError = formatErrorWithSnippet(
187
+ `Syntax error: ${e.message}`,
188
+ code,
189
+ -1,
190
+ line,
191
+ column
192
+ );
193
+ return { valid: false, errors: [formattedError] };
108
194
  }
109
195
 
196
+ // Helper to add error with code snippet
197
+ const addError = (message, position) => {
198
+ errors.push(formatErrorWithSnippet(message, code, position));
199
+ };
200
+
110
201
  // Step 2: Walk every node and validate
111
202
  walk.full(ast, (node) => {
112
203
  // Check node type against whitelist
113
204
  if (!ALLOWED_NODE_TYPES.has(node.type)) {
114
- errors.push(`Blocked node type: ${node.type} at position ${node.start}`);
205
+ addError(`Blocked node type: ${node.type}`, node.start);
115
206
  return;
116
207
  }
117
208
 
@@ -121,7 +212,7 @@ export function validateDSL(code) {
121
212
  node.type === 'FunctionExpression') &&
122
213
  node.async
123
214
  ) {
124
- errors.push(`Async functions are not allowed at position ${node.start}. Write synchronous code — the runtime handles async.`);
215
+ addError(`Async functions are not allowed. Write synchronous code — the runtime handles async.`, node.start);
125
216
  }
126
217
 
127
218
  // Block generator functions
@@ -129,19 +220,19 @@ export function validateDSL(code) {
129
220
  (node.type === 'FunctionExpression') &&
130
221
  node.generator
131
222
  ) {
132
- errors.push(`Generator functions are not allowed at position ${node.start}`);
223
+ addError(`Generator functions are not allowed`, node.start);
133
224
  }
134
225
 
135
226
 
136
227
  // Check identifiers against blocklist
137
228
  if (node.type === 'Identifier' && BLOCKED_IDENTIFIERS.has(node.name)) {
138
- errors.push(`Blocked identifier: '${node.name}' at position ${node.start}`);
229
+ addError(`Blocked identifier: '${node.name}'`, node.start);
139
230
  }
140
231
 
141
232
  // Check member expressions for blocked properties
142
233
  if (node.type === 'MemberExpression' && !node.computed) {
143
234
  if (node.property.type === 'Identifier' && BLOCKED_PROPERTIES.has(node.property.name)) {
144
- errors.push(`Blocked property access: '.${node.property.name}' at position ${node.property.start}`);
235
+ addError(`Blocked property access: '.${node.property.name}'`, node.property.start);
145
236
  }
146
237
  }
147
238
 
@@ -149,7 +240,7 @@ export function validateDSL(code) {
149
240
  if (node.type === 'MemberExpression' && node.computed) {
150
241
  if (node.property.type === 'Literal' && typeof node.property.value === 'string') {
151
242
  if (BLOCKED_PROPERTIES.has(node.property.value) || BLOCKED_IDENTIFIERS.has(node.property.value)) {
152
- errors.push(`Blocked computed property access: '["${node.property.value}"]' at position ${node.property.start}`);
243
+ addError(`Blocked computed property access: '["${node.property.value}"]'`, node.property.start);
153
244
  }
154
245
  }
155
246
  }
@@ -157,7 +248,7 @@ export function validateDSL(code) {
157
248
  // Block variable declarations named with blocked identifiers
158
249
  if (node.type === 'VariableDeclarator' && node.id.type === 'Identifier') {
159
250
  if (BLOCKED_IDENTIFIERS.has(node.id.name)) {
160
- errors.push(`Cannot declare variable with blocked name: '${node.id.name}' at position ${node.id.start}`);
251
+ addError(`Cannot declare variable with blocked name: '${node.id.name}'`, node.id.start);
161
252
  }
162
253
  }
163
254
  });
@@ -21481,6 +21481,50 @@ var init_walk = __esm({
21481
21481
  });
21482
21482
 
21483
21483
  // src/agent/dsl/validator.js
21484
+ function offsetToLineColumn(code, offset2) {
21485
+ const lines = code.split("\n");
21486
+ let pos = 0;
21487
+ for (let i = 0; i < lines.length; i++) {
21488
+ const lineLength = lines[i].length + 1;
21489
+ if (pos + lineLength > offset2) {
21490
+ return { line: i + 1, column: offset2 - pos + 1 };
21491
+ }
21492
+ pos += lineLength;
21493
+ }
21494
+ return { line: lines.length, column: 1 };
21495
+ }
21496
+ function generateErrorSnippet(code, line, column, contextLines = 2) {
21497
+ const lines = code.split("\n");
21498
+ const startLine = Math.max(0, line - 1 - contextLines);
21499
+ const endLine = Math.min(lines.length, line + contextLines);
21500
+ const snippetLines = [];
21501
+ const lineNumWidth = String(endLine).length;
21502
+ for (let i = startLine; i < endLine; i++) {
21503
+ const lineNum = String(i + 1).padStart(lineNumWidth, " ");
21504
+ const marker = i + 1 === line ? ">" : " ";
21505
+ snippetLines.push(`${marker} ${lineNum} | ${lines[i]}`);
21506
+ if (i + 1 === line) {
21507
+ const padding = " ".repeat(lineNumWidth + 4);
21508
+ const arrow = " ".repeat(Math.max(0, column - 1)) + "^";
21509
+ snippetLines.push(`${padding}${arrow}`);
21510
+ }
21511
+ }
21512
+ return snippetLines.join("\n");
21513
+ }
21514
+ function formatErrorWithSnippet(message, code, offset2 = -1, line = 0, column = 0) {
21515
+ if (offset2 >= 0) {
21516
+ const loc = offsetToLineColumn(code, offset2);
21517
+ line = loc.line;
21518
+ column = loc.column;
21519
+ }
21520
+ if (line <= 0) {
21521
+ return message;
21522
+ }
21523
+ const snippet = generateErrorSnippet(code, line, column);
21524
+ return `${message}
21525
+
21526
+ ${snippet}`;
21527
+ }
21484
21528
  function validateDSL(code) {
21485
21529
  const errors = [];
21486
21530
  let ast;
@@ -21488,40 +21532,54 @@ function validateDSL(code) {
21488
21532
  ast = parse3(code, {
21489
21533
  ecmaVersion: 2022,
21490
21534
  sourceType: "script",
21491
- allowReturnOutsideFunction: true
21535
+ allowReturnOutsideFunction: true,
21536
+ locations: true
21537
+ // Enable location tracking for better error messages
21492
21538
  });
21493
21539
  } catch (e) {
21494
- return { valid: false, errors: [`Syntax error: ${e.message}`] };
21540
+ const line = e.loc?.line || 0;
21541
+ const column = e.loc?.column ? e.loc.column + 1 : 0;
21542
+ const formattedError = formatErrorWithSnippet(
21543
+ `Syntax error: ${e.message}`,
21544
+ code,
21545
+ -1,
21546
+ line,
21547
+ column
21548
+ );
21549
+ return { valid: false, errors: [formattedError] };
21495
21550
  }
21551
+ const addError = (message, position) => {
21552
+ errors.push(formatErrorWithSnippet(message, code, position));
21553
+ };
21496
21554
  full(ast, (node) => {
21497
21555
  if (!ALLOWED_NODE_TYPES.has(node.type)) {
21498
- errors.push(`Blocked node type: ${node.type} at position ${node.start}`);
21556
+ addError(`Blocked node type: ${node.type}`, node.start);
21499
21557
  return;
21500
21558
  }
21501
21559
  if ((node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression") && node.async) {
21502
- errors.push(`Async functions are not allowed at position ${node.start}. Write synchronous code \u2014 the runtime handles async.`);
21560
+ addError(`Async functions are not allowed. Write synchronous code \u2014 the runtime handles async.`, node.start);
21503
21561
  }
21504
21562
  if (node.type === "FunctionExpression" && node.generator) {
21505
- errors.push(`Generator functions are not allowed at position ${node.start}`);
21563
+ addError(`Generator functions are not allowed`, node.start);
21506
21564
  }
21507
21565
  if (node.type === "Identifier" && BLOCKED_IDENTIFIERS.has(node.name)) {
21508
- errors.push(`Blocked identifier: '${node.name}' at position ${node.start}`);
21566
+ addError(`Blocked identifier: '${node.name}'`, node.start);
21509
21567
  }
21510
21568
  if (node.type === "MemberExpression" && !node.computed) {
21511
21569
  if (node.property.type === "Identifier" && BLOCKED_PROPERTIES.has(node.property.name)) {
21512
- errors.push(`Blocked property access: '.${node.property.name}' at position ${node.property.start}`);
21570
+ addError(`Blocked property access: '.${node.property.name}'`, node.property.start);
21513
21571
  }
21514
21572
  }
21515
21573
  if (node.type === "MemberExpression" && node.computed) {
21516
21574
  if (node.property.type === "Literal" && typeof node.property.value === "string") {
21517
21575
  if (BLOCKED_PROPERTIES.has(node.property.value) || BLOCKED_IDENTIFIERS.has(node.property.value)) {
21518
- errors.push(`Blocked computed property access: '["${node.property.value}"]' at position ${node.property.start}`);
21576
+ addError(`Blocked computed property access: '["${node.property.value}"]'`, node.property.start);
21519
21577
  }
21520
21578
  }
21521
21579
  }
21522
21580
  if (node.type === "VariableDeclarator" && node.id.type === "Identifier") {
21523
21581
  if (BLOCKED_IDENTIFIERS.has(node.id.name)) {
21524
- errors.push(`Cannot declare variable with blocked name: '${node.id.name}' at position ${node.id.start}`);
21582
+ addError(`Cannot declare variable with blocked name: '${node.id.name}'`, node.id.start);
21525
21583
  }
21526
21584
  }
21527
21585
  });
@@ -21845,7 +21903,16 @@ function generateSandboxGlobals(options) {
21845
21903
  }
21846
21904
  if (llmCall) {
21847
21905
  const rawLLM = async (instruction, data2, opts = {}) => {
21848
- return llmCall(instruction, data2, opts);
21906
+ const result = await llmCall(instruction, data2, opts);
21907
+ if (opts.schema && typeof result === "string") {
21908
+ try {
21909
+ return JSON.parse(result);
21910
+ } catch (e) {
21911
+ logFn?.("[LLM] Warning: schema provided but result is not valid JSON");
21912
+ return result;
21913
+ }
21914
+ }
21915
+ return result;
21849
21916
  };
21850
21917
  globals.LLM = traceToolCall("LLM", rawLLM, tracer, logFn);
21851
21918
  }
@@ -21902,6 +21969,54 @@ function generateSandboxGlobals(options) {
21902
21969
  }
21903
21970
  return chunks;
21904
21971
  };
21972
+ globals.chunkByKey = (data2, keyFn, maxTokens = 2e4) => {
21973
+ const CHARS_PER_TOKEN3 = 4;
21974
+ const maxChars = maxTokens * CHARS_PER_TOKEN3;
21975
+ const text = typeof data2 === "string" ? data2 : JSON.stringify(data2);
21976
+ const blockRegex = /^File: ([^\n]+)/gm;
21977
+ const markers = [];
21978
+ let match2;
21979
+ while ((match2 = blockRegex.exec(text)) !== null) {
21980
+ markers.push({ index: match2.index, file: match2[1].trim() });
21981
+ }
21982
+ if (markers.length === 0) {
21983
+ return globals.chunk(data2, maxTokens);
21984
+ }
21985
+ const chunks = [];
21986
+ let currentChunk = "";
21987
+ let currentSize = 0;
21988
+ let keysInChunk = /* @__PURE__ */ new Set();
21989
+ for (let i = 0; i < markers.length; i++) {
21990
+ const start = markers[i].index;
21991
+ const end = i + 1 < markers.length ? markers[i + 1].index : text.length;
21992
+ const block = text.slice(start, end).trim();
21993
+ const file = markers[i].file;
21994
+ const key = typeof keyFn === "function" ? keyFn(file) : file;
21995
+ const blockSize = block.length + 2;
21996
+ const wouldOverflow = currentSize + blockSize > maxChars;
21997
+ const keyAlreadyInChunk = keysInChunk.has(key);
21998
+ if (!keyAlreadyInChunk && wouldOverflow && currentChunk) {
21999
+ chunks.push(currentChunk.trim());
22000
+ currentChunk = "";
22001
+ currentSize = 0;
22002
+ keysInChunk = /* @__PURE__ */ new Set();
22003
+ }
22004
+ if (currentChunk) currentChunk += "\n\n";
22005
+ currentChunk += block;
22006
+ currentSize += blockSize;
22007
+ keysInChunk.add(key);
22008
+ }
22009
+ if (currentChunk.trim()) {
22010
+ chunks.push(currentChunk.trim());
22011
+ }
22012
+ return chunks.length > 0 ? chunks : [""];
22013
+ };
22014
+ globals.extractPaths = (searchResults) => {
22015
+ const text = typeof searchResults === "string" ? searchResults : JSON.stringify(searchResults);
22016
+ const matches = text.match(/^File: ([^\n]+)/gm) || [];
22017
+ const paths = matches.map((m) => m.replace("File: ", "").trim());
22018
+ return [...new Set(paths)];
22019
+ };
21905
22020
  globals.log = (message) => {
21906
22021
  if (globals._logs) globals._logs.push(String(message));
21907
22022
  };
@@ -28836,10 +28951,32 @@ var init_esm5 = __esm({
28836
28951
 
28837
28952
  // src/tools/executePlan.js
28838
28953
  import { tool as tool4 } from "ai";
28954
+ function decodeHtmlEntities(str) {
28955
+ const entities = {
28956
+ "&amp;": "&",
28957
+ "&lt;": "<",
28958
+ "&gt;": ">",
28959
+ "&quot;": '"',
28960
+ "&apos;": "'",
28961
+ "&#39;": "'",
28962
+ "&#x27;": "'"
28963
+ };
28964
+ let result = str.replace(/&(?:amp|lt|gt|quot|apos|#39|#x27);/gi, (match2) => {
28965
+ return entities[match2.toLowerCase()] || match2;
28966
+ });
28967
+ result = result.replace(/&#(\d+);/g, (match2, dec) => {
28968
+ return String.fromCharCode(parseInt(dec, 10));
28969
+ });
28970
+ result = result.replace(/&#x([0-9a-f]+);/gi, (match2, hex) => {
28971
+ return String.fromCharCode(parseInt(hex, 16));
28972
+ });
28973
+ return result;
28974
+ }
28839
28975
  function stripCodeWrapping(code) {
28840
28976
  let s = String(code || "");
28841
28977
  s = s.replace(/^```(?:javascript|js)?\n?/gm, "").replace(/```$/gm, "");
28842
28978
  s = s.replace(/<\/?(?:execute_plan|code)>/g, "");
28979
+ s = decodeHtmlEntities(s);
28843
28980
  return s.trim();
28844
28981
  }
28845
28982
  function buildToolImplementations(configOptions) {
@@ -29125,6 +29262,26 @@ Last error: ${lastError}`;
29125
29262
  }
29126
29263
  });
29127
29264
  }
29265
+ function extractRawOutputBlocks(content, outputBuffer = null) {
29266
+ if (typeof content !== "string") {
29267
+ return { cleanedContent: content, extractedBlocks: [] };
29268
+ }
29269
+ const extractedBlocks = [];
29270
+ const regex = new RegExp(`${RAW_OUTPUT_START}\\n([\\s\\S]*?)\\n${RAW_OUTPUT_END}`, "g");
29271
+ let cleanedContent = content;
29272
+ let match2;
29273
+ while ((match2 = regex.exec(content)) !== null) {
29274
+ extractedBlocks.push(match2[1]);
29275
+ }
29276
+ cleanedContent = content.replace(new RegExp(`${RAW_OUTPUT_START}\\n[\\s\\S]*?\\n${RAW_OUTPUT_END}`, "g"), "").replace(/\n\n\[The above raw output \(\d+ chars\) will be passed directly to the final response\. Do NOT repeat, summarize, or modify it\.\]/g, "").trim();
29277
+ if (outputBuffer && extractedBlocks.length > 0) {
29278
+ for (const block of extractedBlocks) {
29279
+ outputBuffer.items = outputBuffer.items || [];
29280
+ outputBuffer.items.push(block);
29281
+ }
29282
+ }
29283
+ return { cleanedContent, extractedBlocks };
29284
+ }
29128
29285
  function formatSuccess(result, description, attempt, outputBuffer) {
29129
29286
  let output = "";
29130
29287
  if (description) {
@@ -29161,10 +29318,15 @@ ${JSON.stringify(resultValue, null, 2)}`;
29161
29318
  }
29162
29319
  }
29163
29320
  if (outputBuffer && outputBuffer.items && outputBuffer.items.length > 0) {
29164
- const totalChars = outputBuffer.items.reduce((sum, item) => sum + item.length, 0);
29321
+ const rawContent = outputBuffer.items.join("\n");
29165
29322
  output += `
29166
29323
 
29167
- [Output buffer: ${totalChars} chars written via output(). This content will be appended directly to your response. Do NOT repeat or summarize it.]`;
29324
+ ${RAW_OUTPUT_START}
29325
+ ${rawContent}
29326
+ ${RAW_OUTPUT_END}`;
29327
+ output += `
29328
+
29329
+ [The above raw output (${rawContent.length} chars) will be passed directly to the final response. Do NOT repeat, summarize, or modify it.]`;
29168
29330
  }
29169
29331
  return output;
29170
29332
  }
@@ -29613,6 +29775,7 @@ Example:
29613
29775
  <clearOutputBuffer>true</clearOutputBuffer>
29614
29776
  </cleanup_execute_plan>`;
29615
29777
  }
29778
+ var RAW_OUTPUT_START, RAW_OUTPUT_END;
29616
29779
  var init_executePlan = __esm({
29617
29780
  "src/tools/executePlan.js"() {
29618
29781
  "use strict";
@@ -29623,6 +29786,8 @@ var init_executePlan = __esm({
29623
29786
  init_extract();
29624
29787
  init_delegate();
29625
29788
  init_esm5();
29789
+ RAW_OUTPUT_START = "<<<RAW_OUTPUT>>>";
29790
+ RAW_OUTPUT_END = "<<<END_RAW_OUTPUT>>>";
29626
29791
  }
29627
29792
  });
29628
29793
 
@@ -68204,7 +68369,7 @@ __export(schemaUtils_exports, {
68204
68369
  createJsonCorrectionPrompt: () => createJsonCorrectionPrompt,
68205
68370
  createMermaidCorrectionPrompt: () => createMermaidCorrectionPrompt,
68206
68371
  createSchemaDefinitionCorrectionPrompt: () => createSchemaDefinitionCorrectionPrompt,
68207
- decodeHtmlEntities: () => decodeHtmlEntities,
68372
+ decodeHtmlEntities: () => decodeHtmlEntities2,
68208
68373
  extractMermaidFromJson: () => extractMermaidFromJson,
68209
68374
  extractMermaidFromMarkdown: () => extractMermaidFromMarkdown,
68210
68375
  generateExampleFromSchema: () => generateExampleFromSchema,
@@ -68309,7 +68474,7 @@ function enforceNoAdditionalProperties(schema) {
68309
68474
  applyRecursively(cloned);
68310
68475
  return cloned;
68311
68476
  }
68312
- function decodeHtmlEntities(text) {
68477
+ function decodeHtmlEntities2(text) {
68313
68478
  if (!text || typeof text !== "string") {
68314
68479
  return text;
68315
68480
  }
@@ -69752,7 +69917,7 @@ When presented with a broken Mermaid diagram, analyze it thoroughly and provide
69752
69917
  * @returns {Promise<string>} - The corrected Mermaid diagram
69753
69918
  */
69754
69919
  async fixMermaidDiagram(diagramContent, originalErrors = [], diagramInfo = {}) {
69755
- const decodedContent = decodeHtmlEntities(diagramContent);
69920
+ const decodedContent = decodeHtmlEntities2(diagramContent);
69756
69921
  if (decodedContent !== diagramContent) {
69757
69922
  try {
69758
69923
  const quickValidation = await validateMermaidDiagram(decodedContent);
@@ -81068,6 +81233,7 @@ var init_ProbeAgent = __esm({
81068
81233
  init_path_validation();
81069
81234
  init_outputTruncator();
81070
81235
  init_delegate();
81236
+ init_executePlan();
81071
81237
  init_tasks();
81072
81238
  dotenv2.config();
81073
81239
  ENGINE_ACTIVITY_TIMEOUT_DEFAULT = 18e4;
@@ -83799,6 +83965,15 @@ You are working with a workspace. Available paths: ${workspaceDesc}
83799
83965
  }
83800
83966
  const executionResult = await this.mcpBridge.mcpTools[toolName].execute(params);
83801
83967
  let toolResultContent = typeof executionResult === "string" ? executionResult : JSON.stringify(executionResult, null, 2);
83968
+ if (this._outputBuffer) {
83969
+ const { cleanedContent, extractedBlocks } = extractRawOutputBlocks(toolResultContent, this._outputBuffer);
83970
+ if (extractedBlocks.length > 0) {
83971
+ toolResultContent = cleanedContent;
83972
+ if (this.debug) {
83973
+ console.log(`[DEBUG] Extracted ${extractedBlocks.length} raw output blocks (${extractedBlocks.reduce((sum, b) => sum + b.length, 0)} chars) to output buffer`);
83974
+ }
83975
+ }
83976
+ }
83802
83977
  try {
83803
83978
  const truncateResult = await truncateIfNeeded(toolResultContent, this.tokenCounter, this.sessionId, this.maxOutputTokens);
83804
83979
  if (truncateResult.truncated) {
@@ -84005,6 +84180,15 @@ ${errorXml}
84005
84180
  const wsPrefix = this.workspaceRoot.endsWith(sep5) ? this.workspaceRoot : this.workspaceRoot + sep5;
84006
84181
  toolResultContent = toolResultContent.split(wsPrefix).join("");
84007
84182
  }
84183
+ if (this._outputBuffer) {
84184
+ const { cleanedContent, extractedBlocks } = extractRawOutputBlocks(toolResultContent, this._outputBuffer);
84185
+ if (extractedBlocks.length > 0) {
84186
+ toolResultContent = cleanedContent;
84187
+ if (this.debug) {
84188
+ console.log(`[DEBUG] Extracted ${extractedBlocks.length} raw output blocks (${extractedBlocks.reduce((sum, b) => sum + b.length, 0)} chars) to output buffer`);
84189
+ }
84190
+ }
84191
+ }
84008
84192
  try {
84009
84193
  const truncateResult = await truncateIfNeeded(toolResultContent, this.tokenCounter, this.sessionId, this.maxOutputTokens);
84010
84194
  if (truncateResult.truncated) {