@probelabs/probe 0.6.0-rc237 → 0.6.0-rc239

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.
@@ -117,6 +117,23 @@ function traceToolCall(toolName, fn, tracer, logFn) {
117
117
  };
118
118
  }
119
119
 
120
+ /**
121
+ * Try to parse a string as JSON if it looks like a JSON object or array.
122
+ * Checks if the first non-whitespace character is '{' or '[' before attempting parse.
123
+ * Returns the original string if it's not JSON.
124
+ *
125
+ * @param {string} text - The text to try parsing
126
+ * @returns {any} Parsed JSON value, or the original string
127
+ */
128
+ function tryParseJSONValue(text) {
129
+ if (typeof text !== 'string') return text;
130
+ const firstChar = text.trimStart()[0];
131
+ if (firstChar === '{' || firstChar === '[') {
132
+ try { return JSON.parse(text); } catch (_) { /* not valid JSON */ }
133
+ }
134
+ return text;
135
+ }
136
+
120
137
  /**
121
138
  * Generate sandbox globals that bridge DSL function calls to real tool implementations.
122
139
  *
@@ -195,7 +212,15 @@ export function generateSandboxGlobals(options) {
195
212
  if (mcpBridge) {
196
213
  for (const [name, tool] of Object.entries(mcpTools)) {
197
214
  const rawMcpFn = async (params = {}) => {
198
- return tool.execute(params);
215
+ const result = await tool.execute(params);
216
+ // Extract text from MCP response envelope: { content: [{ type: 'text', text: '...' }] }
217
+ const text = result?.content?.[0]?.text;
218
+ if (text === undefined) {
219
+ // No envelope — if raw result is a JSON-like string, try parsing it
220
+ if (typeof result === 'string') return tryParseJSONValue(result);
221
+ return result;
222
+ }
223
+ return tryParseJSONValue(text);
199
224
  };
200
225
  globals[name] = traceToolCall(name, rawMcpFn, tracer, logFn);
201
226
  }
@@ -4,7 +4,7 @@
4
4
  * Orchestrates the full pipeline:
5
5
  * 1. Validate (AST whitelist)
6
6
  * 2. Transform (inject await, wrap in async IIFE)
7
- * 3. Execute in SandboxJS with tool globals + timeout
7
+ * 3. Execute in SandboxJS with tool globals
8
8
  *
9
9
  * Returns the result or a structured error.
10
10
  */
@@ -25,7 +25,6 @@ const Sandbox = SandboxModule.default || SandboxModule;
25
25
  * @param {Object} [options.mcpTools={}] - MCP tool metadata
26
26
  * @param {Function} options.llmCall - Function for LLM() calls: (instruction, data, options?) => Promise<any>
27
27
  * @param {number} [options.mapConcurrency=3] - Concurrency limit for map()
28
- * @param {number} [options.timeoutMs=120000] - Execution timeout in milliseconds (default 2 min)
29
28
  * @param {number} [options.maxLoopIterations=5000] - Max iterations for while/for loops
30
29
  * @param {Object} [options.tracer=null] - SimpleAppTracer instance for OTEL telemetry
31
30
  * @returns {Object} Runtime with execute() method
@@ -37,7 +36,6 @@ export function createDSLRuntime(options) {
37
36
  mcpTools = {},
38
37
  llmCall,
39
38
  mapConcurrency = 3,
40
- timeoutMs = 120000,
41
39
  maxLoopIterations = 5000,
42
40
  tracer = null,
43
41
  sessionStore = {},
@@ -108,9 +106,8 @@ export function createDSLRuntime(options) {
108
106
  };
109
107
  }
110
108
 
111
- // Step 3: Execute in SandboxJS with timeout
109
+ // Step 3: Execute in SandboxJS
112
110
  tracer?.addEvent?.('dsl.phase.execute_start', {
113
- 'dsl.timeout_ms': timeoutMs,
114
111
  'dsl.max_loop_iterations': maxLoopIterations,
115
112
  });
116
113
 
@@ -147,20 +144,10 @@ export function createDSLRuntime(options) {
147
144
  };
148
145
  process.on('unhandledRejection', rejectionHandler);
149
146
 
150
- // Race execution against timeout
151
- let timeoutHandle;
152
- const executionPromise = exec().run();
153
- const timeoutPromise = new Promise((_, reject) => {
154
- timeoutHandle = setTimeout(() => {
155
- reject(new Error(`Execution timed out after ${Math.round(timeoutMs / 1000)}s. Script took too long — reduce the amount of work (fewer items, smaller data) or increase timeout.`));
156
- }, timeoutMs);
157
- });
158
-
159
147
  let result;
160
148
  try {
161
- result = await Promise.race([executionPromise, timeoutPromise]);
149
+ result = await exec().run();
162
150
  } finally {
163
- clearTimeout(timeoutHandle);
164
151
  // Delay handler removal — SandboxJS can throw async errors after execution completes
165
152
  setTimeout(() => {
166
153
  process.removeListener('unhandledRejection', rejectionHandler);
@@ -16,18 +16,23 @@ const ALLOWED_NODE_TYPES = new Set([
16
16
  'BlockStatement',
17
17
  'VariableDeclaration',
18
18
  'VariableDeclarator',
19
+ 'FunctionDeclaration',
19
20
  'ArrowFunctionExpression',
20
21
  'FunctionExpression',
21
22
  'CallExpression',
23
+ 'NewExpression',
22
24
  'MemberExpression',
23
25
  'Identifier',
24
26
  'Literal',
25
27
  'TemplateLiteral',
26
28
  'TemplateElement',
29
+ 'TaggedTemplateExpression',
27
30
  'ArrayExpression',
28
31
  'ObjectExpression',
29
32
  'SpreadElement',
30
33
  'IfStatement',
34
+ 'SwitchStatement',
35
+ 'SwitchCase',
31
36
  'ConditionalExpression',
32
37
  'ForOfStatement',
33
38
  'ForInStatement',
@@ -127,10 +132,6 @@ export function validateDSL(code) {
127
132
  errors.push(`Generator functions are not allowed at position ${node.start}`);
128
133
  }
129
134
 
130
- // Block regex literals — SandboxJS doesn't support them
131
- if (node.type === 'Literal' && node.regex) {
132
- errors.push(`Regex literals are not supported at position ${node.start}. Use String methods like indexOf(), includes(), startsWith() instead.`);
133
- }
134
135
 
135
136
  // Check identifiers against blocklist
136
137
  if (node.type === 'Identifier' && BLOCKED_IDENTIFIERS.has(node.name)) {
@@ -21499,9 +21499,6 @@ function validateDSL(code) {
21499
21499
  if (node.type === "FunctionExpression" && node.generator) {
21500
21500
  errors.push(`Generator functions are not allowed at position ${node.start}`);
21501
21501
  }
21502
- if (node.type === "Literal" && node.regex) {
21503
- errors.push(`Regex literals are not supported at position ${node.start}. Use String methods like indexOf(), includes(), startsWith() instead.`);
21504
- }
21505
21502
  if (node.type === "Identifier" && BLOCKED_IDENTIFIERS.has(node.name)) {
21506
21503
  errors.push(`Blocked identifier: '${node.name}' at position ${node.start}`);
21507
21504
  }
@@ -21540,18 +21537,23 @@ var init_validator = __esm({
21540
21537
  "BlockStatement",
21541
21538
  "VariableDeclaration",
21542
21539
  "VariableDeclarator",
21540
+ "FunctionDeclaration",
21543
21541
  "ArrowFunctionExpression",
21544
21542
  "FunctionExpression",
21545
21543
  "CallExpression",
21544
+ "NewExpression",
21546
21545
  "MemberExpression",
21547
21546
  "Identifier",
21548
21547
  "Literal",
21549
21548
  "TemplateLiteral",
21550
21549
  "TemplateElement",
21550
+ "TaggedTemplateExpression",
21551
21551
  "ArrayExpression",
21552
21552
  "ObjectExpression",
21553
21553
  "SpreadElement",
21554
21554
  "IfStatement",
21555
+ "SwitchStatement",
21556
+ "SwitchCase",
21555
21557
  "ConditionalExpression",
21556
21558
  "ForOfStatement",
21557
21559
  "ForInStatement",
@@ -21763,6 +21765,17 @@ function traceToolCall(toolName, fn, tracer, logFn) {
21763
21765
  }
21764
21766
  };
21765
21767
  }
21768
+ function tryParseJSONValue(text) {
21769
+ if (typeof text !== "string") return text;
21770
+ const firstChar = text.trimStart()[0];
21771
+ if (firstChar === "{" || firstChar === "[") {
21772
+ try {
21773
+ return JSON.parse(text);
21774
+ } catch (_) {
21775
+ }
21776
+ }
21777
+ return text;
21778
+ }
21766
21779
  function generateSandboxGlobals(options) {
21767
21780
  const {
21768
21781
  toolImplementations = {},
@@ -21814,7 +21827,13 @@ function generateSandboxGlobals(options) {
21814
21827
  if (mcpBridge) {
21815
21828
  for (const [name, tool5] of Object.entries(mcpTools)) {
21816
21829
  const rawMcpFn = async (params = {}) => {
21817
- return tool5.execute(params);
21830
+ const result = await tool5.execute(params);
21831
+ const text = result?.content?.[0]?.text;
21832
+ if (text === void 0) {
21833
+ if (typeof result === "string") return tryParseJSONValue(result);
21834
+ return result;
21835
+ }
21836
+ return tryParseJSONValue(text);
21818
21837
  };
21819
21838
  globals[name] = traceToolCall(name, rawMcpFn, tracer, logFn);
21820
21839
  }
@@ -21996,7 +22015,6 @@ function createDSLRuntime(options) {
21996
22015
  mcpTools = {},
21997
22016
  llmCall,
21998
22017
  mapConcurrency = 3,
21999
- timeoutMs = 12e4,
22000
22018
  maxLoopIterations = 5e3,
22001
22019
  tracer = null,
22002
22020
  sessionStore = {},
@@ -22051,7 +22069,6 @@ ${validation.errors.join("\n")}`,
22051
22069
  };
22052
22070
  }
22053
22071
  tracer?.addEvent?.("dsl.phase.execute_start", {
22054
- "dsl.timeout_ms": timeoutMs,
22055
22072
  "dsl.max_loop_iterations": maxLoopIterations
22056
22073
  });
22057
22074
  try {
@@ -22079,18 +22096,10 @@ ${validation.errors.join("\n")}`,
22079
22096
  escapedError = reason;
22080
22097
  };
22081
22098
  process.on("unhandledRejection", rejectionHandler);
22082
- let timeoutHandle;
22083
- const executionPromise = exec6().run();
22084
- const timeoutPromise = new Promise((_, reject2) => {
22085
- timeoutHandle = setTimeout(() => {
22086
- reject2(new Error(`Execution timed out after ${Math.round(timeoutMs / 1e3)}s. Script took too long \u2014 reduce the amount of work (fewer items, smaller data) or increase timeout.`));
22087
- }, timeoutMs);
22088
- });
22089
22099
  let result;
22090
22100
  try {
22091
- result = await Promise.race([executionPromise, timeoutPromise]);
22101
+ result = await exec6().run();
22092
22102
  } finally {
22093
- clearTimeout(timeoutHandle);
22094
22103
  setTimeout(() => {
22095
22104
  process.removeListener("unhandledRejection", rejectionHandler);
22096
22105
  }, 500);
@@ -50948,9 +50948,6 @@ function validateDSL(code) {
50948
50948
  if (node.type === "FunctionExpression" && node.generator) {
50949
50949
  errors.push(`Generator functions are not allowed at position ${node.start}`);
50950
50950
  }
50951
- if (node.type === "Literal" && node.regex) {
50952
- errors.push(`Regex literals are not supported at position ${node.start}. Use String methods like indexOf(), includes(), startsWith() instead.`);
50953
- }
50954
50951
  if (node.type === "Identifier" && BLOCKED_IDENTIFIERS.has(node.name)) {
50955
50952
  errors.push(`Blocked identifier: '${node.name}' at position ${node.start}`);
50956
50953
  }
@@ -50989,18 +50986,23 @@ var init_validator = __esm({
50989
50986
  "BlockStatement",
50990
50987
  "VariableDeclaration",
50991
50988
  "VariableDeclarator",
50989
+ "FunctionDeclaration",
50992
50990
  "ArrowFunctionExpression",
50993
50991
  "FunctionExpression",
50994
50992
  "CallExpression",
50993
+ "NewExpression",
50995
50994
  "MemberExpression",
50996
50995
  "Identifier",
50997
50996
  "Literal",
50998
50997
  "TemplateLiteral",
50999
50998
  "TemplateElement",
50999
+ "TaggedTemplateExpression",
51000
51000
  "ArrayExpression",
51001
51001
  "ObjectExpression",
51002
51002
  "SpreadElement",
51003
51003
  "IfStatement",
51004
+ "SwitchStatement",
51005
+ "SwitchCase",
51004
51006
  "ConditionalExpression",
51005
51007
  "ForOfStatement",
51006
51008
  "ForInStatement",
@@ -51212,6 +51214,17 @@ function traceToolCall(toolName, fn, tracer, logFn) {
51212
51214
  }
51213
51215
  };
51214
51216
  }
51217
+ function tryParseJSONValue(text) {
51218
+ if (typeof text !== "string") return text;
51219
+ const firstChar = text.trimStart()[0];
51220
+ if (firstChar === "{" || firstChar === "[") {
51221
+ try {
51222
+ return JSON.parse(text);
51223
+ } catch (_) {
51224
+ }
51225
+ }
51226
+ return text;
51227
+ }
51215
51228
  function generateSandboxGlobals(options) {
51216
51229
  const {
51217
51230
  toolImplementations = {},
@@ -51263,7 +51276,13 @@ function generateSandboxGlobals(options) {
51263
51276
  if (mcpBridge) {
51264
51277
  for (const [name14, tool5] of Object.entries(mcpTools)) {
51265
51278
  const rawMcpFn = async (params = {}) => {
51266
- return tool5.execute(params);
51279
+ const result = await tool5.execute(params);
51280
+ const text = result?.content?.[0]?.text;
51281
+ if (text === void 0) {
51282
+ if (typeof result === "string") return tryParseJSONValue(result);
51283
+ return result;
51284
+ }
51285
+ return tryParseJSONValue(text);
51267
51286
  };
51268
51287
  globals[name14] = traceToolCall(name14, rawMcpFn, tracer, logFn);
51269
51288
  }
@@ -51445,7 +51464,6 @@ function createDSLRuntime(options) {
51445
51464
  mcpTools = {},
51446
51465
  llmCall,
51447
51466
  mapConcurrency = 3,
51448
- timeoutMs = 12e4,
51449
51467
  maxLoopIterations = 5e3,
51450
51468
  tracer = null,
51451
51469
  sessionStore = {},
@@ -51500,7 +51518,6 @@ ${validation.errors.join("\n")}`,
51500
51518
  };
51501
51519
  }
51502
51520
  tracer?.addEvent?.("dsl.phase.execute_start", {
51503
- "dsl.timeout_ms": timeoutMs,
51504
51521
  "dsl.max_loop_iterations": maxLoopIterations
51505
51522
  });
51506
51523
  try {
@@ -51528,18 +51545,10 @@ ${validation.errors.join("\n")}`,
51528
51545
  escapedError = reason;
51529
51546
  };
51530
51547
  process.on("unhandledRejection", rejectionHandler);
51531
- let timeoutHandle;
51532
- const executionPromise = exec6().run();
51533
- const timeoutPromise = new Promise((_, reject2) => {
51534
- timeoutHandle = setTimeout(() => {
51535
- reject2(new Error(`Execution timed out after ${Math.round(timeoutMs / 1e3)}s. Script took too long \u2014 reduce the amount of work (fewer items, smaller data) or increase timeout.`));
51536
- }, timeoutMs);
51537
- });
51538
51548
  let result;
51539
51549
  try {
51540
- result = await Promise.race([executionPromise, timeoutPromise]);
51550
+ result = await exec6().run();
51541
51551
  } finally {
51542
- clearTimeout(timeoutHandle);
51543
51552
  setTimeout(() => {
51544
51553
  process.removeListener("unhandledRejection", rejectionHandler);
51545
51554
  }, 500);
package/cjs/index.cjs CHANGED
@@ -112363,9 +112363,6 @@ function validateDSL(code) {
112363
112363
  if (node.type === "FunctionExpression" && node.generator) {
112364
112364
  errors.push(`Generator functions are not allowed at position ${node.start}`);
112365
112365
  }
112366
- if (node.type === "Literal" && node.regex) {
112367
- errors.push(`Regex literals are not supported at position ${node.start}. Use String methods like indexOf(), includes(), startsWith() instead.`);
112368
- }
112369
112366
  if (node.type === "Identifier" && BLOCKED_IDENTIFIERS.has(node.name)) {
112370
112367
  errors.push(`Blocked identifier: '${node.name}' at position ${node.start}`);
112371
112368
  }
@@ -112404,18 +112401,23 @@ var init_validator = __esm({
112404
112401
  "BlockStatement",
112405
112402
  "VariableDeclaration",
112406
112403
  "VariableDeclarator",
112404
+ "FunctionDeclaration",
112407
112405
  "ArrowFunctionExpression",
112408
112406
  "FunctionExpression",
112409
112407
  "CallExpression",
112408
+ "NewExpression",
112410
112409
  "MemberExpression",
112411
112410
  "Identifier",
112412
112411
  "Literal",
112413
112412
  "TemplateLiteral",
112414
112413
  "TemplateElement",
112414
+ "TaggedTemplateExpression",
112415
112415
  "ArrayExpression",
112416
112416
  "ObjectExpression",
112417
112417
  "SpreadElement",
112418
112418
  "IfStatement",
112419
+ "SwitchStatement",
112420
+ "SwitchCase",
112419
112421
  "ConditionalExpression",
112420
112422
  "ForOfStatement",
112421
112423
  "ForInStatement",
@@ -112627,6 +112629,17 @@ function traceToolCall(toolName, fn, tracer, logFn) {
112627
112629
  }
112628
112630
  };
112629
112631
  }
112632
+ function tryParseJSONValue(text) {
112633
+ if (typeof text !== "string") return text;
112634
+ const firstChar = text.trimStart()[0];
112635
+ if (firstChar === "{" || firstChar === "[") {
112636
+ try {
112637
+ return JSON.parse(text);
112638
+ } catch (_) {
112639
+ }
112640
+ }
112641
+ return text;
112642
+ }
112630
112643
  function generateSandboxGlobals(options) {
112631
112644
  const {
112632
112645
  toolImplementations = {},
@@ -112678,7 +112691,13 @@ function generateSandboxGlobals(options) {
112678
112691
  if (mcpBridge) {
112679
112692
  for (const [name14, tool5] of Object.entries(mcpTools)) {
112680
112693
  const rawMcpFn = async (params = {}) => {
112681
- return tool5.execute(params);
112694
+ const result = await tool5.execute(params);
112695
+ const text = result?.content?.[0]?.text;
112696
+ if (text === void 0) {
112697
+ if (typeof result === "string") return tryParseJSONValue(result);
112698
+ return result;
112699
+ }
112700
+ return tryParseJSONValue(text);
112682
112701
  };
112683
112702
  globals[name14] = traceToolCall(name14, rawMcpFn, tracer, logFn);
112684
112703
  }
@@ -112860,7 +112879,6 @@ function createDSLRuntime(options) {
112860
112879
  mcpTools = {},
112861
112880
  llmCall,
112862
112881
  mapConcurrency = 3,
112863
- timeoutMs = 12e4,
112864
112882
  maxLoopIterations = 5e3,
112865
112883
  tracer = null,
112866
112884
  sessionStore = {},
@@ -112915,7 +112933,6 @@ ${validation.errors.join("\n")}`,
112915
112933
  };
112916
112934
  }
112917
112935
  tracer?.addEvent?.("dsl.phase.execute_start", {
112918
- "dsl.timeout_ms": timeoutMs,
112919
112936
  "dsl.max_loop_iterations": maxLoopIterations
112920
112937
  });
112921
112938
  try {
@@ -112943,18 +112960,10 @@ ${validation.errors.join("\n")}`,
112943
112960
  escapedError = reason;
112944
112961
  };
112945
112962
  process.on("unhandledRejection", rejectionHandler);
112946
- let timeoutHandle;
112947
- const executionPromise = exec6().run();
112948
- const timeoutPromise = new Promise((_, reject2) => {
112949
- timeoutHandle = setTimeout(() => {
112950
- reject2(new Error(`Execution timed out after ${Math.round(timeoutMs / 1e3)}s. Script took too long \u2014 reduce the amount of work (fewer items, smaller data) or increase timeout.`));
112951
- }, timeoutMs);
112952
- });
112953
112963
  let result;
112954
112964
  try {
112955
- result = await Promise.race([executionPromise, timeoutPromise]);
112965
+ result = await exec6().run();
112956
112966
  } finally {
112957
- clearTimeout(timeoutHandle);
112958
112967
  setTimeout(() => {
112959
112968
  process.removeListener("unhandledRejection", rejectionHandler);
112960
112969
  }, 500);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probelabs/probe",
3
- "version": "0.6.0-rc237",
3
+ "version": "0.6.0-rc239",
4
4
  "description": "Node.js wrapper for the probe code search tool",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -117,6 +117,23 @@ function traceToolCall(toolName, fn, tracer, logFn) {
117
117
  };
118
118
  }
119
119
 
120
+ /**
121
+ * Try to parse a string as JSON if it looks like a JSON object or array.
122
+ * Checks if the first non-whitespace character is '{' or '[' before attempting parse.
123
+ * Returns the original string if it's not JSON.
124
+ *
125
+ * @param {string} text - The text to try parsing
126
+ * @returns {any} Parsed JSON value, or the original string
127
+ */
128
+ function tryParseJSONValue(text) {
129
+ if (typeof text !== 'string') return text;
130
+ const firstChar = text.trimStart()[0];
131
+ if (firstChar === '{' || firstChar === '[') {
132
+ try { return JSON.parse(text); } catch (_) { /* not valid JSON */ }
133
+ }
134
+ return text;
135
+ }
136
+
120
137
  /**
121
138
  * Generate sandbox globals that bridge DSL function calls to real tool implementations.
122
139
  *
@@ -195,7 +212,15 @@ export function generateSandboxGlobals(options) {
195
212
  if (mcpBridge) {
196
213
  for (const [name, tool] of Object.entries(mcpTools)) {
197
214
  const rawMcpFn = async (params = {}) => {
198
- return tool.execute(params);
215
+ const result = await tool.execute(params);
216
+ // Extract text from MCP response envelope: { content: [{ type: 'text', text: '...' }] }
217
+ const text = result?.content?.[0]?.text;
218
+ if (text === undefined) {
219
+ // No envelope — if raw result is a JSON-like string, try parsing it
220
+ if (typeof result === 'string') return tryParseJSONValue(result);
221
+ return result;
222
+ }
223
+ return tryParseJSONValue(text);
199
224
  };
200
225
  globals[name] = traceToolCall(name, rawMcpFn, tracer, logFn);
201
226
  }
@@ -4,7 +4,7 @@
4
4
  * Orchestrates the full pipeline:
5
5
  * 1. Validate (AST whitelist)
6
6
  * 2. Transform (inject await, wrap in async IIFE)
7
- * 3. Execute in SandboxJS with tool globals + timeout
7
+ * 3. Execute in SandboxJS with tool globals
8
8
  *
9
9
  * Returns the result or a structured error.
10
10
  */
@@ -25,7 +25,6 @@ const Sandbox = SandboxModule.default || SandboxModule;
25
25
  * @param {Object} [options.mcpTools={}] - MCP tool metadata
26
26
  * @param {Function} options.llmCall - Function for LLM() calls: (instruction, data, options?) => Promise<any>
27
27
  * @param {number} [options.mapConcurrency=3] - Concurrency limit for map()
28
- * @param {number} [options.timeoutMs=120000] - Execution timeout in milliseconds (default 2 min)
29
28
  * @param {number} [options.maxLoopIterations=5000] - Max iterations for while/for loops
30
29
  * @param {Object} [options.tracer=null] - SimpleAppTracer instance for OTEL telemetry
31
30
  * @returns {Object} Runtime with execute() method
@@ -37,7 +36,6 @@ export function createDSLRuntime(options) {
37
36
  mcpTools = {},
38
37
  llmCall,
39
38
  mapConcurrency = 3,
40
- timeoutMs = 120000,
41
39
  maxLoopIterations = 5000,
42
40
  tracer = null,
43
41
  sessionStore = {},
@@ -108,9 +106,8 @@ export function createDSLRuntime(options) {
108
106
  };
109
107
  }
110
108
 
111
- // Step 3: Execute in SandboxJS with timeout
109
+ // Step 3: Execute in SandboxJS
112
110
  tracer?.addEvent?.('dsl.phase.execute_start', {
113
- 'dsl.timeout_ms': timeoutMs,
114
111
  'dsl.max_loop_iterations': maxLoopIterations,
115
112
  });
116
113
 
@@ -147,20 +144,10 @@ export function createDSLRuntime(options) {
147
144
  };
148
145
  process.on('unhandledRejection', rejectionHandler);
149
146
 
150
- // Race execution against timeout
151
- let timeoutHandle;
152
- const executionPromise = exec().run();
153
- const timeoutPromise = new Promise((_, reject) => {
154
- timeoutHandle = setTimeout(() => {
155
- reject(new Error(`Execution timed out after ${Math.round(timeoutMs / 1000)}s. Script took too long — reduce the amount of work (fewer items, smaller data) or increase timeout.`));
156
- }, timeoutMs);
157
- });
158
-
159
147
  let result;
160
148
  try {
161
- result = await Promise.race([executionPromise, timeoutPromise]);
149
+ result = await exec().run();
162
150
  } finally {
163
- clearTimeout(timeoutHandle);
164
151
  // Delay handler removal — SandboxJS can throw async errors after execution completes
165
152
  setTimeout(() => {
166
153
  process.removeListener('unhandledRejection', rejectionHandler);
@@ -16,18 +16,23 @@ const ALLOWED_NODE_TYPES = new Set([
16
16
  'BlockStatement',
17
17
  'VariableDeclaration',
18
18
  'VariableDeclarator',
19
+ 'FunctionDeclaration',
19
20
  'ArrowFunctionExpression',
20
21
  'FunctionExpression',
21
22
  'CallExpression',
23
+ 'NewExpression',
22
24
  'MemberExpression',
23
25
  'Identifier',
24
26
  'Literal',
25
27
  'TemplateLiteral',
26
28
  'TemplateElement',
29
+ 'TaggedTemplateExpression',
27
30
  'ArrayExpression',
28
31
  'ObjectExpression',
29
32
  'SpreadElement',
30
33
  'IfStatement',
34
+ 'SwitchStatement',
35
+ 'SwitchCase',
31
36
  'ConditionalExpression',
32
37
  'ForOfStatement',
33
38
  'ForInStatement',
@@ -127,10 +132,6 @@ export function validateDSL(code) {
127
132
  errors.push(`Generator functions are not allowed at position ${node.start}`);
128
133
  }
129
134
 
130
- // Block regex literals — SandboxJS doesn't support them
131
- if (node.type === 'Literal' && node.regex) {
132
- errors.push(`Regex literals are not supported at position ${node.start}. Use String methods like indexOf(), includes(), startsWith() instead.`);
133
- }
134
135
 
135
136
  // Check identifiers against blocklist
136
137
  if (node.type === 'Identifier' && BLOCKED_IDENTIFIERS.has(node.name)) {