@probelabs/probe 0.6.0-rc240 → 0.6.0-rc241

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.
@@ -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
  });
@@ -28836,10 +28894,32 @@ var init_esm5 = __esm({
28836
28894
 
28837
28895
  // src/tools/executePlan.js
28838
28896
  import { tool as tool4 } from "ai";
28897
+ function decodeHtmlEntities(str) {
28898
+ const entities = {
28899
+ "&amp;": "&",
28900
+ "&lt;": "<",
28901
+ "&gt;": ">",
28902
+ "&quot;": '"',
28903
+ "&apos;": "'",
28904
+ "&#39;": "'",
28905
+ "&#x27;": "'"
28906
+ };
28907
+ let result = str.replace(/&(?:amp|lt|gt|quot|apos|#39|#x27);/gi, (match2) => {
28908
+ return entities[match2.toLowerCase()] || match2;
28909
+ });
28910
+ result = result.replace(/&#(\d+);/g, (match2, dec) => {
28911
+ return String.fromCharCode(parseInt(dec, 10));
28912
+ });
28913
+ result = result.replace(/&#x([0-9a-f]+);/gi, (match2, hex) => {
28914
+ return String.fromCharCode(parseInt(hex, 16));
28915
+ });
28916
+ return result;
28917
+ }
28839
28918
  function stripCodeWrapping(code) {
28840
28919
  let s = String(code || "");
28841
28920
  s = s.replace(/^```(?:javascript|js)?\n?/gm, "").replace(/```$/gm, "");
28842
28921
  s = s.replace(/<\/?(?:execute_plan|code)>/g, "");
28922
+ s = decodeHtmlEntities(s);
28843
28923
  return s.trim();
28844
28924
  }
28845
28925
  function buildToolImplementations(configOptions) {
@@ -68204,7 +68284,7 @@ __export(schemaUtils_exports, {
68204
68284
  createJsonCorrectionPrompt: () => createJsonCorrectionPrompt,
68205
68285
  createMermaidCorrectionPrompt: () => createMermaidCorrectionPrompt,
68206
68286
  createSchemaDefinitionCorrectionPrompt: () => createSchemaDefinitionCorrectionPrompt,
68207
- decodeHtmlEntities: () => decodeHtmlEntities,
68287
+ decodeHtmlEntities: () => decodeHtmlEntities2,
68208
68288
  extractMermaidFromJson: () => extractMermaidFromJson,
68209
68289
  extractMermaidFromMarkdown: () => extractMermaidFromMarkdown,
68210
68290
  generateExampleFromSchema: () => generateExampleFromSchema,
@@ -68309,7 +68389,7 @@ function enforceNoAdditionalProperties(schema) {
68309
68389
  applyRecursively(cloned);
68310
68390
  return cloned;
68311
68391
  }
68312
- function decodeHtmlEntities(text) {
68392
+ function decodeHtmlEntities2(text) {
68313
68393
  if (!text || typeof text !== "string") {
68314
68394
  return text;
68315
68395
  }
@@ -69752,7 +69832,7 @@ When presented with a broken Mermaid diagram, analyze it thoroughly and provide
69752
69832
  * @returns {Promise<string>} - The corrected Mermaid diagram
69753
69833
  */
69754
69834
  async fixMermaidDiagram(diagramContent, originalErrors = [], diagramInfo = {}) {
69755
- const decodedContent = decodeHtmlEntities(diagramContent);
69835
+ const decodedContent = decodeHtmlEntities2(diagramContent);
69756
69836
  if (decodedContent !== diagramContent) {
69757
69837
  try {
69758
69838
  const quickValidation = await validateMermaidDiagram(decodedContent);
@@ -16,8 +16,42 @@ import { glob } from 'glob';
16
16
 
17
17
  export { executePlanSchema };
18
18
 
19
+ /**
20
+ * Decode common HTML entities that LLMs sometimes produce when generating code.
21
+ * This handles entities like &amp;&amp; → &&, &lt;= → <=, etc.
22
+ */
23
+ function decodeHtmlEntities(str) {
24
+ const entities = {
25
+ '&amp;': '&',
26
+ '&lt;': '<',
27
+ '&gt;': '>',
28
+ '&quot;': '"',
29
+ '&apos;': "'",
30
+ '&#39;': "'",
31
+ '&#x27;': "'",
32
+ };
33
+
34
+ // Replace named/common entities
35
+ let result = str.replace(/&(?:amp|lt|gt|quot|apos|#39|#x27);/gi, (match) => {
36
+ return entities[match.toLowerCase()] || match;
37
+ });
38
+
39
+ // Handle numeric entities (decimal): &#60; → <
40
+ result = result.replace(/&#(\d+);/g, (match, dec) => {
41
+ return String.fromCharCode(parseInt(dec, 10));
42
+ });
43
+
44
+ // Handle numeric entities (hex): &#x3C; → <
45
+ result = result.replace(/&#x([0-9a-f]+);/gi, (match, hex) => {
46
+ return String.fromCharCode(parseInt(hex, 16));
47
+ });
48
+
49
+ return result;
50
+ }
51
+
19
52
  /**
20
53
  * Strip markdown fences and XML tags that LLMs sometimes wrap code in.
54
+ * Also decodes HTML entities that may appear in XML-extracted code.
21
55
  */
22
56
  function stripCodeWrapping(code) {
23
57
  let s = String(code || '');
@@ -25,6 +59,8 @@ function stripCodeWrapping(code) {
25
59
  s = s.replace(/^```(?:javascript|js)?\n?/gm, '').replace(/```$/gm, '');
26
60
  // Strip XML-style tags: <execute_plan>, </execute_plan>, <code>, </code>
27
61
  s = s.replace(/<\/?(?:execute_plan|code)>/g, '');
62
+ // Decode HTML entities (e.g., &amp;&amp; → &&, &lt;= → <=)
63
+ s = decodeHtmlEntities(s);
28
64
  return s.trim();
29
65
  }
30
66
 
@@ -50930,6 +50930,50 @@ var init_walk = __esm({
50930
50930
  });
50931
50931
 
50932
50932
  // src/agent/dsl/validator.js
50933
+ function offsetToLineColumn(code, offset2) {
50934
+ const lines = code.split("\n");
50935
+ let pos = 0;
50936
+ for (let i5 = 0; i5 < lines.length; i5++) {
50937
+ const lineLength = lines[i5].length + 1;
50938
+ if (pos + lineLength > offset2) {
50939
+ return { line: i5 + 1, column: offset2 - pos + 1 };
50940
+ }
50941
+ pos += lineLength;
50942
+ }
50943
+ return { line: lines.length, column: 1 };
50944
+ }
50945
+ function generateErrorSnippet(code, line, column, contextLines = 2) {
50946
+ const lines = code.split("\n");
50947
+ const startLine = Math.max(0, line - 1 - contextLines);
50948
+ const endLine = Math.min(lines.length, line + contextLines);
50949
+ const snippetLines = [];
50950
+ const lineNumWidth = String(endLine).length;
50951
+ for (let i5 = startLine; i5 < endLine; i5++) {
50952
+ const lineNum = String(i5 + 1).padStart(lineNumWidth, " ");
50953
+ const marker15 = i5 + 1 === line ? ">" : " ";
50954
+ snippetLines.push(`${marker15} ${lineNum} | ${lines[i5]}`);
50955
+ if (i5 + 1 === line) {
50956
+ const padding = " ".repeat(lineNumWidth + 4);
50957
+ const arrow = " ".repeat(Math.max(0, column - 1)) + "^";
50958
+ snippetLines.push(`${padding}${arrow}`);
50959
+ }
50960
+ }
50961
+ return snippetLines.join("\n");
50962
+ }
50963
+ function formatErrorWithSnippet(message, code, offset2 = -1, line = 0, column = 0) {
50964
+ if (offset2 >= 0) {
50965
+ const loc = offsetToLineColumn(code, offset2);
50966
+ line = loc.line;
50967
+ column = loc.column;
50968
+ }
50969
+ if (line <= 0) {
50970
+ return message;
50971
+ }
50972
+ const snippet = generateErrorSnippet(code, line, column);
50973
+ return `${message}
50974
+
50975
+ ${snippet}`;
50976
+ }
50933
50977
  function validateDSL(code) {
50934
50978
  const errors = [];
50935
50979
  let ast;
@@ -50937,40 +50981,54 @@ function validateDSL(code) {
50937
50981
  ast = parse3(code, {
50938
50982
  ecmaVersion: 2022,
50939
50983
  sourceType: "script",
50940
- allowReturnOutsideFunction: true
50984
+ allowReturnOutsideFunction: true,
50985
+ locations: true
50986
+ // Enable location tracking for better error messages
50941
50987
  });
50942
50988
  } catch (e5) {
50943
- return { valid: false, errors: [`Syntax error: ${e5.message}`] };
50989
+ const line = e5.loc?.line || 0;
50990
+ const column = e5.loc?.column ? e5.loc.column + 1 : 0;
50991
+ const formattedError = formatErrorWithSnippet(
50992
+ `Syntax error: ${e5.message}`,
50993
+ code,
50994
+ -1,
50995
+ line,
50996
+ column
50997
+ );
50998
+ return { valid: false, errors: [formattedError] };
50944
50999
  }
51000
+ const addError = (message, position) => {
51001
+ errors.push(formatErrorWithSnippet(message, code, position));
51002
+ };
50945
51003
  full(ast, (node) => {
50946
51004
  if (!ALLOWED_NODE_TYPES.has(node.type)) {
50947
- errors.push(`Blocked node type: ${node.type} at position ${node.start}`);
51005
+ addError(`Blocked node type: ${node.type}`, node.start);
50948
51006
  return;
50949
51007
  }
50950
51008
  if ((node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression") && node.async) {
50951
- errors.push(`Async functions are not allowed at position ${node.start}. Write synchronous code \u2014 the runtime handles async.`);
51009
+ addError(`Async functions are not allowed. Write synchronous code \u2014 the runtime handles async.`, node.start);
50952
51010
  }
50953
51011
  if (node.type === "FunctionExpression" && node.generator) {
50954
- errors.push(`Generator functions are not allowed at position ${node.start}`);
51012
+ addError(`Generator functions are not allowed`, node.start);
50955
51013
  }
50956
51014
  if (node.type === "Identifier" && BLOCKED_IDENTIFIERS.has(node.name)) {
50957
- errors.push(`Blocked identifier: '${node.name}' at position ${node.start}`);
51015
+ addError(`Blocked identifier: '${node.name}'`, node.start);
50958
51016
  }
50959
51017
  if (node.type === "MemberExpression" && !node.computed) {
50960
51018
  if (node.property.type === "Identifier" && BLOCKED_PROPERTIES.has(node.property.name)) {
50961
- errors.push(`Blocked property access: '.${node.property.name}' at position ${node.property.start}`);
51019
+ addError(`Blocked property access: '.${node.property.name}'`, node.property.start);
50962
51020
  }
50963
51021
  }
50964
51022
  if (node.type === "MemberExpression" && node.computed) {
50965
51023
  if (node.property.type === "Literal" && typeof node.property.value === "string") {
50966
51024
  if (BLOCKED_PROPERTIES.has(node.property.value) || BLOCKED_IDENTIFIERS.has(node.property.value)) {
50967
- errors.push(`Blocked computed property access: '["${node.property.value}"]' at position ${node.property.start}`);
51025
+ addError(`Blocked computed property access: '["${node.property.value}"]'`, node.property.start);
50968
51026
  }
50969
51027
  }
50970
51028
  }
50971
51029
  if (node.type === "VariableDeclarator" && node.id.type === "Identifier") {
50972
51030
  if (BLOCKED_IDENTIFIERS.has(node.id.name)) {
50973
- errors.push(`Cannot declare variable with blocked name: '${node.id.name}' at position ${node.id.start}`);
51031
+ addError(`Cannot declare variable with blocked name: '${node.id.name}'`, node.id.start);
50974
51032
  }
50975
51033
  }
50976
51034
  });
@@ -58284,10 +58342,32 @@ var init_esm5 = __esm({
58284
58342
  });
58285
58343
 
58286
58344
  // src/tools/executePlan.js
58345
+ function decodeHtmlEntities(str) {
58346
+ const entities = {
58347
+ "&amp;": "&",
58348
+ "&lt;": "<",
58349
+ "&gt;": ">",
58350
+ "&quot;": '"',
58351
+ "&apos;": "'",
58352
+ "&#39;": "'",
58353
+ "&#x27;": "'"
58354
+ };
58355
+ let result = str.replace(/&(?:amp|lt|gt|quot|apos|#39|#x27);/gi, (match2) => {
58356
+ return entities[match2.toLowerCase()] || match2;
58357
+ });
58358
+ result = result.replace(/&#(\d+);/g, (match2, dec) => {
58359
+ return String.fromCharCode(parseInt(dec, 10));
58360
+ });
58361
+ result = result.replace(/&#x([0-9a-f]+);/gi, (match2, hex) => {
58362
+ return String.fromCharCode(parseInt(hex, 16));
58363
+ });
58364
+ return result;
58365
+ }
58287
58366
  function stripCodeWrapping(code) {
58288
58367
  let s5 = String(code || "");
58289
58368
  s5 = s5.replace(/^```(?:javascript|js)?\n?/gm, "").replace(/```$/gm, "");
58290
58369
  s5 = s5.replace(/<\/?(?:execute_plan|code)>/g, "");
58370
+ s5 = decodeHtmlEntities(s5);
58291
58371
  return s5.trim();
58292
58372
  }
58293
58373
  function buildToolImplementations(configOptions) {
@@ -97220,7 +97300,7 @@ __export(schemaUtils_exports, {
97220
97300
  createJsonCorrectionPrompt: () => createJsonCorrectionPrompt,
97221
97301
  createMermaidCorrectionPrompt: () => createMermaidCorrectionPrompt,
97222
97302
  createSchemaDefinitionCorrectionPrompt: () => createSchemaDefinitionCorrectionPrompt,
97223
- decodeHtmlEntities: () => decodeHtmlEntities,
97303
+ decodeHtmlEntities: () => decodeHtmlEntities2,
97224
97304
  extractMermaidFromJson: () => extractMermaidFromJson,
97225
97305
  extractMermaidFromMarkdown: () => extractMermaidFromMarkdown,
97226
97306
  generateExampleFromSchema: () => generateExampleFromSchema,
@@ -97325,7 +97405,7 @@ function enforceNoAdditionalProperties(schema) {
97325
97405
  applyRecursively(cloned);
97326
97406
  return cloned;
97327
97407
  }
97328
- function decodeHtmlEntities(text) {
97408
+ function decodeHtmlEntities2(text) {
97329
97409
  if (!text || typeof text !== "string") {
97330
97410
  return text;
97331
97411
  }
@@ -98768,7 +98848,7 @@ When presented with a broken Mermaid diagram, analyze it thoroughly and provide
98768
98848
  * @returns {Promise<string>} - The corrected Mermaid diagram
98769
98849
  */
98770
98850
  async fixMermaidDiagram(diagramContent, originalErrors = [], diagramInfo = {}) {
98771
- const decodedContent = decodeHtmlEntities(diagramContent);
98851
+ const decodedContent = decodeHtmlEntities2(diagramContent);
98772
98852
  if (decodedContent !== diagramContent) {
98773
98853
  try {
98774
98854
  const quickValidation = await validateMermaidDiagram(decodedContent);
package/cjs/index.cjs CHANGED
@@ -112384,6 +112384,50 @@ var init_walk = __esm({
112384
112384
  });
112385
112385
 
112386
112386
  // src/agent/dsl/validator.js
112387
+ function offsetToLineColumn(code, offset2) {
112388
+ const lines = code.split("\n");
112389
+ let pos = 0;
112390
+ for (let i5 = 0; i5 < lines.length; i5++) {
112391
+ const lineLength = lines[i5].length + 1;
112392
+ if (pos + lineLength > offset2) {
112393
+ return { line: i5 + 1, column: offset2 - pos + 1 };
112394
+ }
112395
+ pos += lineLength;
112396
+ }
112397
+ return { line: lines.length, column: 1 };
112398
+ }
112399
+ function generateErrorSnippet(code, line, column, contextLines = 2) {
112400
+ const lines = code.split("\n");
112401
+ const startLine = Math.max(0, line - 1 - contextLines);
112402
+ const endLine = Math.min(lines.length, line + contextLines);
112403
+ const snippetLines = [];
112404
+ const lineNumWidth = String(endLine).length;
112405
+ for (let i5 = startLine; i5 < endLine; i5++) {
112406
+ const lineNum = String(i5 + 1).padStart(lineNumWidth, " ");
112407
+ const marker15 = i5 + 1 === line ? ">" : " ";
112408
+ snippetLines.push(`${marker15} ${lineNum} | ${lines[i5]}`);
112409
+ if (i5 + 1 === line) {
112410
+ const padding = " ".repeat(lineNumWidth + 4);
112411
+ const arrow = " ".repeat(Math.max(0, column - 1)) + "^";
112412
+ snippetLines.push(`${padding}${arrow}`);
112413
+ }
112414
+ }
112415
+ return snippetLines.join("\n");
112416
+ }
112417
+ function formatErrorWithSnippet(message, code, offset2 = -1, line = 0, column = 0) {
112418
+ if (offset2 >= 0) {
112419
+ const loc = offsetToLineColumn(code, offset2);
112420
+ line = loc.line;
112421
+ column = loc.column;
112422
+ }
112423
+ if (line <= 0) {
112424
+ return message;
112425
+ }
112426
+ const snippet = generateErrorSnippet(code, line, column);
112427
+ return `${message}
112428
+
112429
+ ${snippet}`;
112430
+ }
112387
112431
  function validateDSL(code) {
112388
112432
  const errors = [];
112389
112433
  let ast;
@@ -112391,40 +112435,54 @@ function validateDSL(code) {
112391
112435
  ast = parse8(code, {
112392
112436
  ecmaVersion: 2022,
112393
112437
  sourceType: "script",
112394
- allowReturnOutsideFunction: true
112438
+ allowReturnOutsideFunction: true,
112439
+ locations: true
112440
+ // Enable location tracking for better error messages
112395
112441
  });
112396
112442
  } catch (e5) {
112397
- return { valid: false, errors: [`Syntax error: ${e5.message}`] };
112443
+ const line = e5.loc?.line || 0;
112444
+ const column = e5.loc?.column ? e5.loc.column + 1 : 0;
112445
+ const formattedError = formatErrorWithSnippet(
112446
+ `Syntax error: ${e5.message}`,
112447
+ code,
112448
+ -1,
112449
+ line,
112450
+ column
112451
+ );
112452
+ return { valid: false, errors: [formattedError] };
112398
112453
  }
112454
+ const addError = (message, position) => {
112455
+ errors.push(formatErrorWithSnippet(message, code, position));
112456
+ };
112399
112457
  full(ast, (node) => {
112400
112458
  if (!ALLOWED_NODE_TYPES.has(node.type)) {
112401
- errors.push(`Blocked node type: ${node.type} at position ${node.start}`);
112459
+ addError(`Blocked node type: ${node.type}`, node.start);
112402
112460
  return;
112403
112461
  }
112404
112462
  if ((node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression") && node.async) {
112405
- errors.push(`Async functions are not allowed at position ${node.start}. Write synchronous code \u2014 the runtime handles async.`);
112463
+ addError(`Async functions are not allowed. Write synchronous code \u2014 the runtime handles async.`, node.start);
112406
112464
  }
112407
112465
  if (node.type === "FunctionExpression" && node.generator) {
112408
- errors.push(`Generator functions are not allowed at position ${node.start}`);
112466
+ addError(`Generator functions are not allowed`, node.start);
112409
112467
  }
112410
112468
  if (node.type === "Identifier" && BLOCKED_IDENTIFIERS.has(node.name)) {
112411
- errors.push(`Blocked identifier: '${node.name}' at position ${node.start}`);
112469
+ addError(`Blocked identifier: '${node.name}'`, node.start);
112412
112470
  }
112413
112471
  if (node.type === "MemberExpression" && !node.computed) {
112414
112472
  if (node.property.type === "Identifier" && BLOCKED_PROPERTIES.has(node.property.name)) {
112415
- errors.push(`Blocked property access: '.${node.property.name}' at position ${node.property.start}`);
112473
+ addError(`Blocked property access: '.${node.property.name}'`, node.property.start);
112416
112474
  }
112417
112475
  }
112418
112476
  if (node.type === "MemberExpression" && node.computed) {
112419
112477
  if (node.property.type === "Literal" && typeof node.property.value === "string") {
112420
112478
  if (BLOCKED_PROPERTIES.has(node.property.value) || BLOCKED_IDENTIFIERS.has(node.property.value)) {
112421
- errors.push(`Blocked computed property access: '["${node.property.value}"]' at position ${node.property.start}`);
112479
+ addError(`Blocked computed property access: '["${node.property.value}"]'`, node.property.start);
112422
112480
  }
112423
112481
  }
112424
112482
  }
112425
112483
  if (node.type === "VariableDeclarator" && node.id.type === "Identifier") {
112426
112484
  if (BLOCKED_IDENTIFIERS.has(node.id.name)) {
112427
- errors.push(`Cannot declare variable with blocked name: '${node.id.name}' at position ${node.id.start}`);
112485
+ addError(`Cannot declare variable with blocked name: '${node.id.name}'`, node.id.start);
112428
112486
  }
112429
112487
  }
112430
112488
  });
@@ -113055,10 +113113,32 @@ var init_runtime = __esm({
113055
113113
  });
113056
113114
 
113057
113115
  // src/tools/executePlan.js
113116
+ function decodeHtmlEntities2(str) {
113117
+ const entities = {
113118
+ "&amp;": "&",
113119
+ "&lt;": "<",
113120
+ "&gt;": ">",
113121
+ "&quot;": '"',
113122
+ "&apos;": "'",
113123
+ "&#39;": "'",
113124
+ "&#x27;": "'"
113125
+ };
113126
+ let result = str.replace(/&(?:amp|lt|gt|quot|apos|#39|#x27);/gi, (match2) => {
113127
+ return entities[match2.toLowerCase()] || match2;
113128
+ });
113129
+ result = result.replace(/&#(\d+);/g, (match2, dec) => {
113130
+ return String.fromCharCode(parseInt(dec, 10));
113131
+ });
113132
+ result = result.replace(/&#x([0-9a-f]+);/gi, (match2, hex) => {
113133
+ return String.fromCharCode(parseInt(hex, 16));
113134
+ });
113135
+ return result;
113136
+ }
113058
113137
  function stripCodeWrapping(code) {
113059
113138
  let s5 = String(code || "");
113060
113139
  s5 = s5.replace(/^```(?:javascript|js)?\n?/gm, "").replace(/```$/gm, "");
113061
113140
  s5 = s5.replace(/<\/?(?:execute_plan|code)>/g, "");
113141
+ s5 = decodeHtmlEntities2(s5);
113062
113142
  return s5.trim();
113063
113143
  }
113064
113144
  function buildToolImplementations(configOptions) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probelabs/probe",
3
- "version": "0.6.0-rc240",
3
+ "version": "0.6.0-rc241",
4
4
  "description": "Node.js wrapper for the probe code search tool",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -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
  });
@@ -16,8 +16,42 @@ import { glob } from 'glob';
16
16
 
17
17
  export { executePlanSchema };
18
18
 
19
+ /**
20
+ * Decode common HTML entities that LLMs sometimes produce when generating code.
21
+ * This handles entities like &amp;&amp; → &&, &lt;= → <=, etc.
22
+ */
23
+ function decodeHtmlEntities(str) {
24
+ const entities = {
25
+ '&amp;': '&',
26
+ '&lt;': '<',
27
+ '&gt;': '>',
28
+ '&quot;': '"',
29
+ '&apos;': "'",
30
+ '&#39;': "'",
31
+ '&#x27;': "'",
32
+ };
33
+
34
+ // Replace named/common entities
35
+ let result = str.replace(/&(?:amp|lt|gt|quot|apos|#39|#x27);/gi, (match) => {
36
+ return entities[match.toLowerCase()] || match;
37
+ });
38
+
39
+ // Handle numeric entities (decimal): &#60; → <
40
+ result = result.replace(/&#(\d+);/g, (match, dec) => {
41
+ return String.fromCharCode(parseInt(dec, 10));
42
+ });
43
+
44
+ // Handle numeric entities (hex): &#x3C; → <
45
+ result = result.replace(/&#x([0-9a-f]+);/gi, (match, hex) => {
46
+ return String.fromCharCode(parseInt(hex, 16));
47
+ });
48
+
49
+ return result;
50
+ }
51
+
19
52
  /**
20
53
  * Strip markdown fences and XML tags that LLMs sometimes wrap code in.
54
+ * Also decodes HTML entities that may appear in XML-extracted code.
21
55
  */
22
56
  function stripCodeWrapping(code) {
23
57
  let s = String(code || '');
@@ -25,6 +59,8 @@ function stripCodeWrapping(code) {
25
59
  s = s.replace(/^```(?:javascript|js)?\n?/gm, '').replace(/```$/gm, '');
26
60
  // Strip XML-style tags: <execute_plan>, </execute_plan>, <code>, </code>
27
61
  s = s.replace(/<\/?(?:execute_plan|code)>/g, '');
62
+ // Decode HTML entities (e.g., &amp;&amp; → &&, &lt;= → <=)
63
+ s = decodeHtmlEntities(s);
28
64
  return s.trim();
29
65
  }
30
66