@langchain/quickjs 0.2.5 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,79 +1,17 @@
1
1
  import { createMiddleware, tool } from "langchain";
2
2
  import { z } from "zod/v4";
3
- import { StateBackend, adaptBackendProtocol, resolveBackend } from "deepagents";
4
3
  import dedent from "dedent";
4
+ import { getCurrentTaskInput } from "@langchain/langgraph";
5
+ import { adaptBackendProtocol, resolveBackend } from "deepagents";
5
6
  import { shouldInterruptAfterDeadline } from "quickjs-emscripten";
6
7
  import { newQuickJSAsyncWASMModuleFromVariant } from "quickjs-emscripten-core";
7
- import { compile } from "json-schema-to-typescript";
8
- import { toJsonSchema } from "@langchain/core/utils/json_schema";
8
+ import * as posix from "node:path/posix";
9
9
  import { Parser } from "acorn";
10
10
  import { tsPlugin } from "@sveltejs/acorn-typescript";
11
11
  import { walk } from "estree-walker";
12
12
  import MagicString from "magic-string";
13
- import { getCurrentTaskInput } from "@langchain/langgraph";
14
- //#region src/utils.ts
15
- /**
16
- * Convert a snake_case or kebab-case string to camelCase.
17
- */
18
- function toCamelCase(name) {
19
- return name.replace(/[-_]([a-z])/g, (_, c) => c.toUpperCase());
20
- }
21
- /**
22
- * Format the result of a REPL evaluation for the agent.
23
- */
24
- function formatReplResult(result) {
25
- const parts = [];
26
- if (result.logs.length > 0) parts.push(result.logs.join("\n"));
27
- if (result.ok) {
28
- if (result.value !== void 0) {
29
- const formatted = typeof result.value === "string" ? result.value : JSON.stringify(result.value, null, 2);
30
- parts.push(`→ ${formatted}`);
31
- }
32
- } else if (result.error) {
33
- const errName = result.error.name || "Error";
34
- const errMsg = result.error.message || "Unknown error";
35
- parts.push(`${errName}: ${errMsg}`);
36
- if (result.error.stack) parts.push(result.error.stack);
37
- }
38
- return parts.join("\n") || "(no output)";
39
- }
40
- function safeToJsonSchema(schema) {
41
- try {
42
- return toJsonSchema(schema);
43
- } catch {
44
- return;
45
- }
46
- }
47
- async function schemaToInterface(jsonSchema, interfaceName) {
48
- return (await compile({
49
- ...jsonSchema,
50
- additionalProperties: false
51
- }, interfaceName, {
52
- bannerComment: "",
53
- additionalProperties: false
54
- })).replace(/^export /, "").trimEnd();
55
- }
56
- function capitalize(s) {
57
- return s.charAt(0).toUpperCase() + s.slice(1);
58
- }
59
- async function toolToTypeSignature(name, description, jsonSchema) {
60
- const inputType = `${capitalize(name)}Input`;
61
- if (!jsonSchema || !jsonSchema.properties) return dedent`
62
- /**
63
- * ${description}
64
- */
65
- async tools.${name}(input: Record<string, unknown>): Promise<string>
66
- `;
67
- return dedent`
68
- ${await schemaToInterface(jsonSchema, inputType)}
69
-
70
- /**
71
- * ${description}
72
- */
73
- async tools.${name}(input: ${inputType}): Promise<string>
74
- `;
75
- }
76
- //#endregion
13
+ import { compile } from "json-schema-to-typescript";
14
+ import { toJsonSchema } from "@langchain/core/utils/json_schema";
77
15
  //#region src/transform.ts
78
16
  /**
79
17
  * AST-based code transform pipeline for the REPL.
@@ -234,6 +172,260 @@ function findLastNonEmptyNode(nodes, s) {
234
172
  function isExpression(node) {
235
173
  return node.type === "ExpressionStatement";
236
174
  }
175
+ /**
176
+ * Strip TypeScript type syntax from an ES-module source so QuickJS can
177
+ * evaluate it as a standard JS module.
178
+ *
179
+ * Unlike `transformForEval`, this keeps `import`/`export` declarations,
180
+ * does not hoist to `globalThis`, and does not wrap in an IIFE.
181
+ * On parse failure the original source is returned unchanged.
182
+ */
183
+ function stripTypeSyntax(code) {
184
+ let ast;
185
+ try {
186
+ ast = TSParser.parse(code, {
187
+ ecmaVersion: "latest",
188
+ sourceType: "module",
189
+ locations: true
190
+ });
191
+ } catch {
192
+ return code;
193
+ }
194
+ const magicString = new MagicString(code);
195
+ const program = ast;
196
+ for (const node of program.body) {
197
+ if (isTSOnlyNode(node)) {
198
+ magicString.remove(node.start, node.end);
199
+ continue;
200
+ }
201
+ walk(node, { enter(n) {
202
+ stripTypeAnnotationFromNode(magicString, n);
203
+ } });
204
+ }
205
+ return magicString.toString();
206
+ }
207
+ //#endregion
208
+ //#region src/skills.ts
209
+ /**
210
+ * File extensions the loader will enumerate from a skill directory.
211
+ */
212
+ const SKILL_MODULE_EXTENSIONS = [
213
+ ".js",
214
+ ".mjs",
215
+ ".cjs",
216
+ ".ts",
217
+ ".mts",
218
+ ".cts",
219
+ ".jsx",
220
+ ".tsx"
221
+ ];
222
+ /**
223
+ * Hard cap on total bytes pulled for one skill's bundle (1 MiB).
224
+ */
225
+ const MAX_SKILL_BUNDLE_BYTES = 1 * 1024 * 1024;
226
+ /**
227
+ * Validates a skill name against the spec's kebab-case rule.
228
+ */
229
+ const SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
230
+ /**
231
+ * Matches `"@/skills/<name>"` or `'@/skills/<name>'` references in source.
232
+ * Template literals and computed specifiers are not caught.
233
+ */
234
+ const SKILL_SPECIFIER_RE = /["']@\/skills\/([a-z0-9]+(?:-[a-z0-9]+)*)["']/g;
235
+ /**
236
+ * List every code-extension file under `skillDir` (recursive).
237
+ */
238
+ async function enumerateCodeFiles(backend, skillDir, skillName) {
239
+ const seen = /* @__PURE__ */ new Set();
240
+ for (const ext of SKILL_MODULE_EXTENSIONS) {
241
+ const result = await backend.glob(`**/*${ext}`, skillDir);
242
+ if (result.error !== void 0) throw new Error(`Skill '${skillName}': failed to list '${skillDir}': ${result.error}`);
243
+ const matches = result.files ?? [];
244
+ for (const match of matches) seen.add(match.path);
245
+ }
246
+ return [...seen].sort();
247
+ }
248
+ /**
249
+ * Decode download responses into [path, source] pairs.
250
+ */
251
+ function decodeFiles(responses, skillName) {
252
+ const decoder = new TextDecoder("utf-8", { fatal: true });
253
+ const pairs = [];
254
+ for (const response of responses) {
255
+ if (response.error !== null || response.content === null) throw new Error(`Skill '${skillName}': failed to download '${response.path}': ${response.error ?? "no content"}`);
256
+ let source;
257
+ try {
258
+ source = decoder.decode(response.content);
259
+ } catch {
260
+ throw new Error(`Skill '${skillName}': file '${response.path}' is not valid UTF-8`);
261
+ }
262
+ pairs.push([response.path, source]);
263
+ }
264
+ return pairs;
265
+ }
266
+ /**
267
+ * Throws an Error when the total decoded size of all files exceeds
268
+ * `MAX_SKILL_BUNDLE_BYTES`. Counts characters rather than bytes, which
269
+ * over-counts multi-byte UTF-8. Intentionally errs toward rejection.
270
+ */
271
+ function validateBundleSize(pairs, skillName) {
272
+ let total = 0;
273
+ for (const [, source] of pairs) total += source.length;
274
+ if (total > 1048576) throw new Error(`Skill '${skillName}': bundle exceeds ${MAX_SKILL_BUNDLE_BYTES} bytes (total ${total})`);
275
+ }
276
+ /**
277
+ * Express `absolutePath` as a POSIX-relative path under `skillDir`.
278
+ * Throws an Error if the path escapes the skill directory which indicates
279
+ * a backend bug, not a user error.
280
+ */
281
+ function relativeUnder(skillDir, absolutePath, skillName) {
282
+ const rel = posix.relative(skillDir, absolutePath);
283
+ if (rel === "" || rel.startsWith("..")) throw new Error(`Skill '${skillName}': file ${absolutePath} is not under '${skillDir}'`);
284
+ return rel;
285
+ }
286
+ /**
287
+ * Build the relative-path → source map, applying `stripTypeSyntax` to each file.
288
+ */
289
+ function buildFilesMap(skillDir, entryRel, pairs, skillName) {
290
+ const files = /* @__PURE__ */ new Map();
291
+ let entryPresent = false;
292
+ for (const [absPath, source] of pairs) {
293
+ const rel = relativeUnder(skillDir, absPath, skillName);
294
+ files.set(rel, stripTypeSyntax(source));
295
+ if (rel === entryRel) entryPresent = true;
296
+ }
297
+ if (!entryPresent) throw new Error(`Skill '${skillName}': module path '${entryRel}' did not match any file in the skill directory`);
298
+ return files;
299
+ }
300
+ /**
301
+ * Build a `LoadedSkill` from a skill's metadata and a backend handle.
302
+ *
303
+ * Enumerates code files under the skill directory, downloads them,
304
+ * strips TypeScript syntax, and validates the entrypoint is present.
305
+ */
306
+ async function loadSkill(metadata, backend) {
307
+ const name = metadata.name;
308
+ if (!SKILL_NAME_RE.test(name)) throw new Error(`Skill name '${name}' is not a valid kebab-case identifier`);
309
+ const entryRel = metadata.module;
310
+ if (entryRel === void 0 || entryRel === "") throw new Error(`Skill '${name}' has no 'module' frontmatter key - only skills with a declared entrypoint are installable`);
311
+ const adapted = adaptBackendProtocol(backend);
312
+ if (adapted.downloadFiles === void 0) throw new Error(`Skill '${name}': backend does not implement downloadFiles`);
313
+ const skillDir = posix.dirname(metadata.path);
314
+ const codeFiles = await enumerateCodeFiles(adapted, skillDir, name);
315
+ if (codeFiles.length === 0) throw new Error(`Skill '${name}': no JS/TS files under '${skillDir}'`);
316
+ const filePairs = decodeFiles(await adapted.downloadFiles(codeFiles), name);
317
+ validateBundleSize(filePairs, name);
318
+ const files = buildFilesMap(skillDir, entryRel, filePairs, name);
319
+ return {
320
+ name,
321
+ specifier: `@/skills/${name}`,
322
+ entryRel,
323
+ files
324
+ };
325
+ }
326
+ /**
327
+ * Extract skill names referenced by `"@/skills/<name>"` literals in source.
328
+ *
329
+ * Used as a pre-eval scan so the middleware can surface `SkillNotAvailable`
330
+ * before evaluation starts. Dynamic imports with computed specifiers are
331
+ * not detected.
332
+ */
333
+ function scanSkillReferences(source) {
334
+ const names = /* @__PURE__ */ new Set();
335
+ const matches = source.matchAll(SKILL_SPECIFIER_RE);
336
+ for (const match of matches) names.add(match[1]);
337
+ return names;
338
+ }
339
+ //#endregion
340
+ //#region src/errors.ts
341
+ /**
342
+ * Thrown when a single eval exhausts its configured PTC call budget.
343
+ */
344
+ var PTCCallBudgetExceededError = class extends Error {
345
+ limit;
346
+ attempted;
347
+ functionName;
348
+ constructor(options) {
349
+ super(`PTC call budget exceeded (limit=${options.limit}, attempted=${options.attempted}, function=${options.functionName})`);
350
+ this.name = "PTCCallBudgetExceededError";
351
+ this.limit = options.limit;
352
+ this.attempted = options.attempted;
353
+ this.functionName = options.functionName;
354
+ }
355
+ };
356
+ //#endregion
357
+ //#region src/utils.ts
358
+ /**
359
+ * Convert a snake_case or kebab-case string to camelCase.
360
+ */
361
+ function toCamelCase(name) {
362
+ return name.replace(/[-_]([a-z])/g, (_, c) => c.toUpperCase());
363
+ }
364
+ /**
365
+ * Format the result of a REPL evaluation for the agent.
366
+ */
367
+ function formatReplResult(result) {
368
+ const parts = [];
369
+ if (result.logs.length > 0) {
370
+ let logsText = result.logs.join("\n");
371
+ if (result.logsDroppedChars > 0) logsText += `\n[truncated ${result.logsDroppedChars} chars]`;
372
+ parts.push(logsText);
373
+ }
374
+ if (result.ok) {
375
+ if (result.value !== void 0) {
376
+ const formatted = typeof result.value === "string" ? result.value : JSON.stringify(result.value, null, 2);
377
+ parts.push(`→ ${formatted}`);
378
+ }
379
+ } else if (result.error) {
380
+ const errName = result.error.name || "Error";
381
+ const errMsg = result.error.message || "Unknown error";
382
+ parts.push(`${errName}: ${errMsg}`);
383
+ if (result.error.stack) parts.push(result.error.stack);
384
+ }
385
+ return parts.join("\n") || "(no output)";
386
+ }
387
+ function safeToJsonSchema(schema) {
388
+ try {
389
+ return toJsonSchema(schema);
390
+ } catch {
391
+ return;
392
+ }
393
+ }
394
+ async function schemaToInterface(jsonSchema, interfaceName) {
395
+ return (await compile({
396
+ ...jsonSchema,
397
+ additionalProperties: false
398
+ }, interfaceName, {
399
+ bannerComment: "",
400
+ additionalProperties: false
401
+ })).replace(/^export /, "").trimEnd();
402
+ }
403
+ function capitalize(s) {
404
+ return s.charAt(0).toUpperCase() + s.slice(1);
405
+ }
406
+ async function toolToTypeSignature(name, description, jsonSchema) {
407
+ const inputType = `${capitalize(name)}Input`;
408
+ if (!jsonSchema || !jsonSchema.properties) return dedent`
409
+ /**
410
+ * ${description}
411
+ */
412
+ async tools.${name}(input: Record<string, unknown>): Promise<string>
413
+ `;
414
+ return dedent`
415
+ ${await schemaToInterface(jsonSchema, inputType)}
416
+
417
+ /**
418
+ * ${description}
419
+ */
420
+ async tools.${name}(input: ${inputType}): Promise<string>
421
+ `;
422
+ }
423
+ /**
424
+ * Render a pre-eval error when referenced skills are not available on the agent.
425
+ */
426
+ function formatSkillNotAvailable(missing) {
427
+ return `Skills unavailable: ${[...missing].sort().join(", ")}`;
428
+ }
237
429
  //#endregion
238
430
  //#region src/session.ts
239
431
  /**
@@ -253,65 +445,237 @@ function isExpression(node) {
253
445
  * It holds an `id` that keys into a static session map. The heavy QuickJS
254
446
  * runtime is lazily started on the first `.eval()` call, making the session
255
447
  * safe across graph interrupts and checkpointing.
256
- *
257
- * File writes inside the REPL are buffered (`pendingWrites`) and only
258
- * flushed to the backend after a script finishes executing. Call
259
- * `session.flushWrites(backend)` after eval to persist them.
260
448
  */
261
449
  const DEFAULT_MEMORY_LIMIT = 50 * 1024 * 1024;
262
450
  const DEFAULT_MAX_STACK_SIZE = 320 * 1024;
263
451
  const DEFAULT_EXECUTION_TIMEOUT = 3e4;
264
- let asyncModulePromise;
265
- async function getAsyncModule() {
266
- if (!asyncModulePromise) asyncModulePromise = (async () => {
267
- const variant = await import("@jitl/quickjs-ng-wasmfile-release-asyncify");
268
- return newQuickJSAsyncWASMModuleFromVariant(variant.default ?? variant);
269
- })();
270
- return asyncModulePromise;
452
+ const DEFAULT_MAX_PTC_CALLS = 256;
453
+ const DEFAULT_MAX_RESULTS_CHARS = 4e3;
454
+ const variantImport = import("@jitl/quickjs-ng-wasmfile-release-asyncify");
455
+ async function newAsyncModule() {
456
+ const variant = await variantImport;
457
+ return newQuickJSAsyncWASMModuleFromVariant(variant.default ?? variant);
458
+ }
459
+ function makeErrorSource(message) {
460
+ return `throw { name: "Error", message: ${JSON.stringify(message)} };`;
461
+ }
462
+ /**
463
+ * Parse a canonicalized skill specifier into `{ name, rel }`.
464
+ * Returns `undefined` for anything that isn't a valid `@/skills/<name>` or
465
+ * `@/skills/<name>/<rel>` shape. `rel` is absent for the bare form.
466
+ */
467
+ function parseSkillSpecifier(specifier) {
468
+ if (!specifier.startsWith("@/skills/")) return;
469
+ const tail = specifier.slice(9);
470
+ const slashIdx = tail.indexOf("/");
471
+ const name = slashIdx === -1 ? tail : tail.slice(0, slashIdx);
472
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) return;
473
+ const rel = slashIdx === -1 ? void 0 : tail.slice(slashIdx + 1);
474
+ if (rel !== void 0 && rel === "") return;
475
+ return {
476
+ name,
477
+ rel
478
+ };
479
+ }
480
+ /**
481
+ * Return the `@/skills/<name>` prefix for the skill that owns `base`, or `undefined`.
482
+ */
483
+ function matchSkillPrefix(base) {
484
+ const parsed = parseSkillSpecifier(base);
485
+ if (parsed === void 0) return;
486
+ return `@/skills/${parsed.name}`;
487
+ }
488
+ /**
489
+ * Return the directory portion of a slash-separated specifier path.
490
+ */
491
+ function posixDirname(p) {
492
+ const idx = p.lastIndexOf("/");
493
+ if (idx === -1) return "";
494
+ return p.slice(0, idx);
271
495
  }
272
496
  /**
497
+ * POSIX join for slash-separated specifiers. Avoids `node:path/posix`
498
+ * since session.ts is consumed in browser bundles.
499
+ */
500
+ function posixJoin(base, rel) {
501
+ const out = [];
502
+ const segments = `${base}/${rel}`.split("/");
503
+ for (const segment of segments) {
504
+ if (segment === "" || segment === ".") continue;
505
+ if (segment === "..") {
506
+ out.pop();
507
+ continue;
508
+ }
509
+ out.push(segment);
510
+ }
511
+ return out.join("/");
512
+ }
513
+ /**
514
+ * Fixed-size character buffer for capturing console output from the QuickJS VM.
515
+ *
516
+ * Lines are accumulated up to `maxChars`. Once the cap is reached, excess
517
+ * characters are counted as dropped rather than silently discarded without
518
+ * attribution, so callers can surface a truncation notice to the user.
519
+ */
520
+ var ConsoleBuffer = class {
521
+ maxChars;
522
+ buffer = "";
523
+ droppedChars = 0;
524
+ constructor(maxChars) {
525
+ this.maxChars = Math.max(maxChars, 0);
526
+ }
527
+ /**
528
+ * Append `line` to the buffer.
529
+ *
530
+ * If the buffer is already full the entire line is counted as dropped.
531
+ * If `line` partially fits, the fitting prefix is stored and the remainder
532
+ * is counted as dropped.
533
+ */
534
+ append(line) {
535
+ const remaining = this.maxChars - this.buffer.length;
536
+ if (remaining <= 0) {
537
+ this.droppedChars += line.length;
538
+ return;
539
+ }
540
+ if (line.length <= remaining) this.buffer += line;
541
+ else {
542
+ this.buffer += line.slice(0, remaining);
543
+ this.droppedChars += line.length - remaining;
544
+ }
545
+ }
546
+ /**
547
+ * Return the buffered output and dropped-character count as `[buffered,
548
+ * droppedChars]`, then reset both to zero.
549
+ */
550
+ drain() {
551
+ const out = this.buffer;
552
+ const dropped = this.droppedChars;
553
+ this.buffer = "";
554
+ this.droppedChars = 0;
555
+ return [out, dropped];
556
+ }
557
+ };
558
+ /**
273
559
  * Sandboxed JavaScript REPL session backed by QuickJS WASM.
274
560
  *
275
561
  * Serializable — holds an `id` that keys into a static session map.
276
562
  * The QuickJS runtime is lazily started on the first `.eval()` call
277
563
  * and reconnected if a session with the same id already exists.
278
564
  * This makes it safe to store in LangGraph state across interrupts.
279
- *
280
- * File writes are buffered during execution and flushed via
281
- * `flushWrites(backend)` after eval completes.
282
565
  */
283
566
  var ReplSession = class ReplSession {
284
567
  static sessions = /* @__PURE__ */ new Map();
285
568
  id;
286
- pendingWrites = [];
287
569
  runtime = null;
288
570
  context = null;
289
- logs = [];
290
- _options;
291
- _backend = null;
571
+ consoleBuffer = new ConsoleBuffer(DEFAULT_MAX_RESULTS_CHARS);
572
+ options;
573
+ skillsContext;
574
+ skillsLoaded = /* @__PURE__ */ new Map();
575
+ skillsFailed = /* @__PURE__ */ new Map();
576
+ maxPtcCalls;
577
+ ptcCallsRemaining = null;
292
578
  constructor(id, options = {}) {
293
579
  this.id = id;
294
- this._options = options;
295
- }
296
- get backend() {
297
- return this._backend;
298
- }
299
- set backend(b) {
300
- this._backend = b ? adaptBackendProtocol(b) : null;
580
+ this.options = options;
581
+ this.maxPtcCalls = options.maxPtcCalls !== void 0 ? options.maxPtcCalls : 256;
301
582
  }
302
583
  async ensureStarted() {
303
584
  if (this.runtime) return;
304
- const { memoryLimitBytes = DEFAULT_MEMORY_LIMIT, maxStackSizeBytes = DEFAULT_MAX_STACK_SIZE, backend, tools } = this._options;
305
- const runtime = (await getAsyncModule()).newRuntime();
585
+ const { memoryLimitBytes = DEFAULT_MEMORY_LIMIT, maxStackSizeBytes = DEFAULT_MAX_STACK_SIZE, tools, skillsEnabled = false, maxResultChars = DEFAULT_MAX_RESULTS_CHARS } = this.options;
586
+ const runtime = (await newAsyncModule()).newRuntime();
306
587
  runtime.setMemoryLimit(memoryLimitBytes);
307
588
  runtime.setMaxStackSize(maxStackSizeBytes);
308
589
  const context = runtime.newContext();
309
590
  this.runtime = runtime;
310
591
  this.context = context;
592
+ this.consoleBuffer = new ConsoleBuffer(maxResultChars);
311
593
  this.setupConsole();
312
- if (backend) this._backend = adaptBackendProtocol(backend);
313
- this.injectVfs();
314
- if (tools && tools.length > 0) this.injectTools(tools);
594
+ if (tools !== void 0 && tools.length > 0) this.injectTools(tools);
595
+ if (skillsEnabled) this.installModuleLoader();
596
+ }
597
+ /**
598
+ * Load the skill into cache on first access and replay cached errors.
599
+ */
600
+ async ensureSkillLoaded(name) {
601
+ const cached = this.skillsLoaded.get(name);
602
+ if (cached !== void 0) return cached;
603
+ const cachedError = this.skillsFailed.get(name);
604
+ if (cachedError !== void 0) throw cachedError;
605
+ const ctx = this.skillsContext;
606
+ if (ctx === void 0) throw new Error(`Skill '${name}' referenced but skills are not configured for this session`);
607
+ const metadata = ctx.metadata.find((m) => m.name === name);
608
+ if (metadata === void 0) throw new Error(`Skill '${name}' referenced but not available on this agent`);
609
+ try {
610
+ const loaded = await loadSkill(metadata, ctx.backend);
611
+ this.skillsLoaded.set(name, loaded);
612
+ return loaded;
613
+ } catch (err) {
614
+ this.skillsFailed.set(name, err);
615
+ throw err;
616
+ }
617
+ }
618
+ async resolveSpecifier(specifier) {
619
+ const parsed = parseSkillSpecifier(specifier);
620
+ if (parsed === void 0) return makeErrorSource(`Module not found: ${specifier}`);
621
+ let loaded;
622
+ try {
623
+ loaded = await this.ensureSkillLoaded(parsed.name);
624
+ } catch (err) {
625
+ return makeErrorSource(err.message ?? String(err));
626
+ }
627
+ if (parsed.rel === void 0) {
628
+ const source = loaded.files.get(loaded.entryRel);
629
+ if (source === void 0) return makeErrorSource(`Skill '${parsed.name}': entrypoint '${loaded.entryRel}' missing from bundle`);
630
+ return source;
631
+ }
632
+ const source = loaded.files.get(parsed.rel);
633
+ if (source === void 0) return makeErrorSource(`Skill '${parsed.name}': '${parsed.rel}' not found in bundle`);
634
+ return source;
635
+ }
636
+ /**
637
+ * Canonicalize an `import` specifier. Bare specifiers pass through;
638
+ * relative specifiers are resolved against the importing module's path.
639
+ * Traversal out of a skill's `@/skills/<name>/` namespace is rejected.
640
+ */
641
+ normalizeSpecifier(base, requested) {
642
+ if (!(requested.startsWith("./") || requested.startsWith("../"))) return requested;
643
+ const parsed = parseSkillSpecifier(base);
644
+ const resolved = posixJoin(parsed !== void 0 && parsed.rel === void 0 ? base : posixDirname(base), requested);
645
+ const skillPrefix = matchSkillPrefix(base);
646
+ if (skillPrefix === void 0) return resolved;
647
+ if (!resolved.startsWith(`${skillPrefix}/`)) return `__resolve_error__:${requested} escapes ${skillPrefix}`;
648
+ return resolved;
649
+ }
650
+ /**
651
+ * Wire the QuickJS module loader and normalizer on this session's runtime.
652
+ */
653
+ installModuleLoader() {
654
+ if (this.runtime === null) return;
655
+ this.runtime.setModuleLoader(async (specifier) => this.resolveSpecifier(specifier), (base, requested) => this.normalizeSpecifier(base, requested));
656
+ }
657
+ /**
658
+ * Initialise the per-eval PTC counter. Called at the top of every `eval()`.
659
+ */
660
+ resetPtcBudget() {
661
+ this.ptcCallsRemaining = this.maxPtcCalls === null ? null : this.maxPtcCalls;
662
+ }
663
+ /**
664
+ * Decrement the PTC call counter and throw if the budget is exhausted.
665
+ * `null` budget means unlimited — returns immediately without decrementing.
666
+ */
667
+ consumePtcBudget(functionName) {
668
+ if (this.ptcCallsRemaining === null) return;
669
+ if (this.ptcCallsRemaining > 0) {
670
+ this.ptcCallsRemaining--;
671
+ return;
672
+ }
673
+ const limit = this.maxPtcCalls ?? 0;
674
+ throw new PTCCallBudgetExceededError({
675
+ limit,
676
+ attempted: limit + 1,
677
+ functionName
678
+ });
315
679
  }
316
680
  /**
317
681
  * Get or create a session for the given id.
@@ -322,10 +686,7 @@ var ReplSession = class ReplSession {
322
686
  */
323
687
  static getOrCreate(id, options = {}) {
324
688
  const existing = ReplSession.sessions.get(id);
325
- if (existing) {
326
- if (options.backend) existing._backend = adaptBackendProtocol(options.backend);
327
- return existing;
328
- }
689
+ if (existing) return existing;
329
690
  const session = new ReplSession(id, options);
330
691
  ReplSession.sessions.set(id, session);
331
692
  return session;
@@ -337,6 +698,31 @@ var ReplSession = class ReplSession {
337
698
  return ReplSession.sessions.get(id) ?? null;
338
699
  }
339
700
  /**
701
+ * Returns true if any session exists whose key equals `threadId` or starts
702
+ * with `threadId:`. Useful for tests that need to confirm a session was
703
+ * created without knowing the full `threadId:middlewareId` key.
704
+ */
705
+ static hasAnyForThread(threadId) {
706
+ const prefix = `${threadId}:`;
707
+ for (const key of ReplSession.sessions.keys()) if (key === threadId || key.startsWith(prefix)) return true;
708
+ return false;
709
+ }
710
+ /**
711
+ * Dispose and remove the session with the given key, if it exists.
712
+ */
713
+ static deleteSession(key) {
714
+ const session = ReplSession.sessions.get(key);
715
+ if (session) session.dispose();
716
+ }
717
+ /**
718
+ * Push the current skills metadata + backend into the session.
719
+ * Called by the middleware once per `js_eval` invocation, before eval runs.
720
+ * Pass `undefined` to clear the context (no skill imports will resolve).
721
+ */
722
+ setSkillsContext(ctx) {
723
+ this.skillsContext = ctx;
724
+ }
725
+ /**
340
726
  * Evaluate code in this session.
341
727
  *
342
728
  * Lazily starts the QuickJS runtime on the first call. Code is
@@ -349,88 +735,94 @@ var ReplSession = class ReplSession {
349
735
  await this.ensureStarted();
350
736
  const runtime = this.runtime;
351
737
  const context = this.context;
352
- this.logs.length = 0;
353
- if (timeoutMs >= 0) runtime.setInterruptHandler(shouldInterruptAfterDeadline(Date.now() + timeoutMs));
354
- else runtime.setInterruptHandler(() => false);
355
- const transformed = transformForEval(code);
356
- const result = await context.evalCodeAsync(transformed);
357
- if (result.error) {
358
- const error = context.dump(result.error);
359
- result.error.dispose();
738
+ const drainLogs = () => {
739
+ const [raw, dropped] = this.consoleBuffer.drain();
360
740
  return {
361
- ok: false,
362
- error,
363
- logs: [...this.logs]
741
+ logs: raw.length > 0 ? raw.split("\n").filter((l) => l.length > 0) : [],
742
+ logsDroppedChars: dropped
364
743
  };
365
- }
366
- const promiseState = context.getPromiseState(result.value);
367
- if (promiseState.type === "fulfilled") {
368
- if (promiseState.notAPromise) {
369
- const value = context.dump(result.value);
370
- result.value.dispose();
744
+ };
745
+ this.resetPtcBudget();
746
+ try {
747
+ if (timeoutMs >= 0) runtime.setInterruptHandler(shouldInterruptAfterDeadline(Date.now() + timeoutMs));
748
+ else runtime.setInterruptHandler(() => false);
749
+ const transformed = transformForEval(code);
750
+ const result = await context.evalCodeAsync(transformed);
751
+ if (result.error) {
752
+ const error = context.dump(result.error);
753
+ result.error.dispose();
371
754
  return {
372
- ok: true,
373
- value,
374
- logs: [...this.logs]
755
+ ok: false,
756
+ error,
757
+ ...drainLogs()
375
758
  };
376
759
  }
377
- const value = context.dump(promiseState.value);
378
- promiseState.value.dispose();
379
- result.value.dispose();
380
- return {
381
- ok: true,
382
- value,
383
- logs: [...this.logs]
384
- };
385
- }
386
- if (promiseState.type === "rejected") {
387
- const error = context.dump(promiseState.error);
388
- promiseState.error.dispose();
389
- result.value.dispose();
390
- return {
391
- ok: false,
392
- error,
393
- logs: [...this.logs]
394
- };
395
- }
396
- const noTimeout = timeoutMs < 0;
397
- const deadline = noTimeout ? Infinity : Date.now() + timeoutMs;
398
- while (noTimeout || Date.now() < deadline) {
399
- context.runtime.executePendingJobs();
400
- const state = context.getPromiseState(result.value);
401
- if (state.type === "fulfilled") {
402
- const value = context.dump(state.value);
403
- state.value.dispose();
760
+ const promiseState = context.getPromiseState(result.value);
761
+ if (promiseState.type === "fulfilled") {
762
+ if (promiseState.notAPromise) {
763
+ const value = context.dump(result.value);
764
+ result.value.dispose();
765
+ return {
766
+ ok: true,
767
+ value,
768
+ ...drainLogs()
769
+ };
770
+ }
771
+ const value = context.dump(promiseState.value);
772
+ promiseState.value.dispose();
404
773
  result.value.dispose();
405
774
  return {
406
775
  ok: true,
407
776
  value,
408
- logs: [...this.logs]
777
+ ...drainLogs()
409
778
  };
410
779
  }
411
- if (state.type === "rejected") {
412
- const error = context.dump(state.error);
413
- state.error.dispose();
780
+ if (promiseState.type === "rejected") {
781
+ const error = context.dump(promiseState.error);
782
+ promiseState.error.dispose();
414
783
  result.value.dispose();
415
784
  return {
416
785
  ok: false,
417
786
  error,
418
- logs: [...this.logs]
787
+ ...drainLogs()
419
788
  };
420
789
  }
421
- await new Promise((r) => setTimeout(r, 1));
790
+ const noTimeout = timeoutMs < 0;
791
+ const deadline = noTimeout ? Infinity : Date.now() + timeoutMs;
792
+ while (noTimeout || Date.now() < deadline) {
793
+ context.runtime.executePendingJobs();
794
+ const state = context.getPromiseState(result.value);
795
+ if (state.type === "fulfilled") {
796
+ const value = context.dump(state.value);
797
+ state.value.dispose();
798
+ result.value.dispose();
799
+ return {
800
+ ok: true,
801
+ value,
802
+ ...drainLogs()
803
+ };
804
+ }
805
+ if (state.type === "rejected") {
806
+ const error = context.dump(state.error);
807
+ state.error.dispose();
808
+ result.value.dispose();
809
+ return {
810
+ ok: false,
811
+ error,
812
+ ...drainLogs()
813
+ };
814
+ }
815
+ await new Promise((r) => setTimeout(r, 1));
816
+ }
817
+ result.value.dispose();
818
+ return {
819
+ ok: false,
820
+ error: { message: "Promise timed out — execution interrupted" },
821
+ ...drainLogs()
822
+ };
823
+ } finally {
824
+ this.ptcCallsRemaining = null;
422
825
  }
423
- result.value.dispose();
424
- return {
425
- ok: false,
426
- error: { message: "Promise timed out — execution interrupted" },
427
- logs: [...this.logs]
428
- };
429
- }
430
- async flushWrites(backend) {
431
- const adapted = adaptBackendProtocol(backend);
432
- const writes = this.pendingWrites.splice(0);
433
- for (const { path, content } of writes) await adapted.write(path, content);
434
826
  }
435
827
  dispose() {
436
828
  try {
@@ -459,7 +851,6 @@ var ReplSession = class ReplSession {
459
851
  }
460
852
  setupConsole() {
461
853
  const context = this.context;
462
- const logs = this.logs;
463
854
  const consoleHandle = context.newObject();
464
855
  for (const method of [
465
856
  "log",
@@ -470,7 +861,8 @@ var ReplSession = class ReplSession {
470
861
  ]) {
471
862
  const fnHandle = context.newFunction(method, (...args) => {
472
863
  const formatted = args.map((a) => context.dump(a)).map((a) => typeof a === "object" && a !== null ? JSON.stringify(a) : String(a)).join(" ");
473
- logs.push(method === "log" || method === "info" || method === "debug" ? formatted : `[${method}] ${formatted}`);
864
+ const line = method === "log" || method === "info" || method === "debug" ? formatted : `[${method}] ${formatted}`;
865
+ this.consoleBuffer.append(line + "\n");
474
866
  });
475
867
  context.setProp(consoleHandle, method, fnHandle);
476
868
  fnHandle.dispose();
@@ -478,67 +870,6 @@ var ReplSession = class ReplSession {
478
870
  context.setProp(context.global, "console", consoleHandle);
479
871
  consoleHandle.dispose();
480
872
  }
481
- injectVfs() {
482
- const context = this.context;
483
- const getBackend = () => this._backend;
484
- const { pendingWrites } = this;
485
- const readFileHandle = context.newFunction("readFile", (pathHandle) => {
486
- const backend = getBackend();
487
- if (!backend) {
488
- const promise = context.newPromise();
489
- const err = context.newError("Backend not available");
490
- promise.reject(err);
491
- err.dispose();
492
- promise.settled.then(context.runtime.executePendingJobs);
493
- return promise.handle;
494
- }
495
- const path = context.getString(pathHandle);
496
- const promise = context.newPromise();
497
- (async () => {
498
- try {
499
- const result = await backend.readRaw(path);
500
- if (result.error || !result.data) {
501
- const err = context.newError(`ENOENT: no such file or directory '${path}'.`);
502
- promise.reject(err);
503
- err.dispose();
504
- } else {
505
- const content = Array.isArray(result.data.content) ? result.data.content.join("\n") : typeof result.data.content === "string" ? result.data.content : null;
506
- if (content === null) {
507
- const err = context.newError(`Cannot read binary file '${path}' as text.`);
508
- promise.reject(err);
509
- err.dispose();
510
- return;
511
- }
512
- const val = context.newString(content);
513
- promise.resolve(val);
514
- val.dispose();
515
- }
516
- } catch {
517
- const err = context.newError(`ENOENT: no such file or directory '${path}'.`);
518
- promise.reject(err);
519
- err.dispose();
520
- }
521
- promise.settled.then(context.runtime.executePendingJobs);
522
- })();
523
- return promise.handle;
524
- });
525
- context.setProp(context.global, "readFile", readFileHandle);
526
- readFileHandle.dispose();
527
- const writeFileHandle = context.newFunction("writeFile", (pathHandle, contentHandle) => {
528
- const path = context.getString(pathHandle);
529
- const content = context.getString(contentHandle);
530
- const promise = context.newPromise();
531
- pendingWrites.push({
532
- path,
533
- content
534
- });
535
- promise.resolve(context.undefined);
536
- promise.settled.then(context.runtime.executePendingJobs);
537
- return promise.handle;
538
- });
539
- context.setProp(context.global, "writeFile", writeFileHandle);
540
- writeFileHandle.dispose();
541
- }
542
873
  injectTools(tools) {
543
874
  const context = this.context;
544
875
  const toolsNs = context.newObject();
@@ -549,6 +880,7 @@ var ReplSession = class ReplSession {
549
880
  const promise = context.newPromise();
550
881
  (async () => {
551
882
  try {
883
+ this.consumePtcBudget(camelName);
552
884
  const rawInput = typeof input === "object" && input !== null ? input : {};
553
885
  const result = await t.invoke(rawInput);
554
886
  const val = context.newString(typeof result === "string" ? result : JSON.stringify(result));
@@ -579,23 +911,8 @@ var ReplSession = class ReplSession {
579
911
  * Provides a `js_eval` tool that runs JavaScript in a WASM-sandboxed QuickJS
580
912
  * interpreter. Supports:
581
913
  * - Persistent state across evaluations (true REPL)
582
- * - VFS integration via readFile/writeFile
583
- * - Programmatic tool calling (PTC)
584
- */
585
- /**
586
- * Backend-provided tools excluded from PTC by default.
587
- * These are redundant inside the REPL since VFS helpers (readFile/writeFile)
588
- * already cover file I/O against the agent's in-memory working set.
589
- */
590
- const DEFAULT_PTC_EXCLUDED_TOOLS = [
591
- "ls",
592
- "read_file",
593
- "write_file",
594
- "edit_file",
595
- "glob",
596
- "grep",
597
- "execute"
598
- ];
914
+ * - Programmatic tool calling (PTC) — expose agent or custom tools inside the REPL
915
+ */
599
916
  const REPL_SYSTEM_PROMPT = dedent`
600
917
  ## TypeScript/JavaScript REPL (\`js_eval\`)
601
918
 
@@ -605,37 +922,11 @@ const REPL_SYSTEM_PROMPT = dedent`
605
922
 
606
923
  ### Hard rules
607
924
 
608
- - **No network, no filesystem** — only the helpers below. Do not attempt \`fetch\`, \`require\`, or \`import\`.
925
+ - **No network, no direct filesystem** — only through tools provided in the \`tools\` namespace below.
609
926
  - **Cite your sources** — when reporting values from files, include the path and key/index so the user can verify.
610
927
  - **Use console.log()** for output — it is captured and returned. \`console.warn()\` and \`console.error()\` are also available.
611
928
  - **Reuse state from previous cells** — variables, functions, and results from earlier \`js_eval\` calls persist across calls. Reference them by name in follow-up cells instead of re-embedding data as inline JSON literals.
612
929
 
613
- ### First-time usage
614
-
615
- \`\`\`typescript
616
- // Read a file from the agent's virtual filesystem
617
- const raw: string = await readFile("/data.json");
618
- const data = JSON.parse(raw) as { n: number };
619
- console.log(data);
620
-
621
- // Write results back
622
- await writeFile("/output.txt", JSON.stringify({ result: data.n }));
623
- \`\`\`
624
-
625
- ### API Reference — built-in globals
626
-
627
- \`\`\`typescript
628
- /**
629
- * Read a file from the agent's virtual filesystem. Throws if the file does not exist.
630
- */
631
- async readFile(path: string): Promise<string>
632
-
633
- /**
634
- * Write a file to the agent's virtual filesystem.
635
- */
636
- async writeFile(path: string, content: string): Promise<void>
637
- \`\`\`
638
-
639
930
  ### Limitations
640
931
 
641
932
  - ES2023+ syntax with TypeScript support. No Node.js APIs, no \`require\`, no \`import\`.
@@ -678,74 +969,103 @@ async function generatePtcPrompt(tools) {
678
969
  `;
679
970
  }
680
971
  /**
972
+ * Resolves a mixed list of tool names and tool instances into a flat list of
973
+ * StructuredToolInterface objects. Strings are looked up by name in agentTools;
974
+ * instances are included directly without requiring agent registration. Strings
975
+ * that don't match any agent tool are silently omitted.
976
+ */
977
+ function resolveToolList(items, agentTools) {
978
+ const agentByName = new Map(agentTools.map((t) => [t.name, t]));
979
+ return items.flatMap((item) => {
980
+ if (typeof item === "string") {
981
+ const found = agentByName.get(item);
982
+ return found ? [found] : [];
983
+ }
984
+ return [item];
985
+ });
986
+ }
987
+ /**
988
+ * Pull `skillsMetadata` from the task input, resolve the backend, and push
989
+ * both into the session. Short-circuits with a `SkillNotAvailable` error if
990
+ * the source references skills the agent doesn't have.
991
+ */
992
+ async function prepareSkillsForEval(session, skillsBackend, code) {
993
+ const taskInput = getCurrentTaskInput();
994
+ const metadata = taskInput?.skillsMetadata ?? [];
995
+ const referenced = scanSkillReferences(code);
996
+ if (referenced.size > 0) {
997
+ const known = new Set(metadata.map((m) => m.name));
998
+ const missing = [];
999
+ for (const name of referenced) if (!known.has(name)) missing.push(name);
1000
+ if (missing.length > 0) {
1001
+ session.setSkillsContext(void 0);
1002
+ return formatSkillNotAvailable(missing);
1003
+ }
1004
+ }
1005
+ const resolved = await resolveBackend(skillsBackend, { state: taskInput });
1006
+ session.setSkillsContext({
1007
+ metadata,
1008
+ backend: resolved
1009
+ });
1010
+ }
1011
+ /**
681
1012
  * Create the QuickJS REPL middleware.
682
1013
  */
683
1014
  function createQuickJSMiddleware(options = {}) {
684
- const { backend = (runtime) => new StateBackend(runtime), ptc = false, memoryLimitBytes = DEFAULT_MEMORY_LIMIT, maxStackSizeBytes = DEFAULT_MAX_STACK_SIZE, executionTimeoutMs = DEFAULT_EXECUTION_TIMEOUT, systemPrompt: customSystemPrompt = null } = options;
685
- const usePtc = ptc !== false;
1015
+ const { ptc, memoryLimitBytes = DEFAULT_MEMORY_LIMIT, maxStackSizeBytes = DEFAULT_MAX_STACK_SIZE, executionTimeoutMs = DEFAULT_EXECUTION_TIMEOUT, systemPrompt: customSystemPrompt = null, skillsBackend, maxPtcCalls = 256, maxResultChars = DEFAULT_MAX_RESULTS_CHARS } = options;
1016
+ if (maxPtcCalls !== null && maxPtcCalls !== void 0 && maxPtcCalls < 1) throw new Error("`maxPtcCalls` must be >= 1 or null");
686
1017
  const baseSystemPrompt = customSystemPrompt || REPL_SYSTEM_PROMPT;
1018
+ const middlewareId = crypto.randomUUID();
687
1019
  let cachedPtcPrompt = null;
688
1020
  let ptcTools = [];
689
1021
  function filterToolsForPtc(allTools) {
690
- if (ptc === false) return [];
691
- const candidates = allTools.filter((t) => t.name !== "js_eval");
692
- if (ptc === true) {
693
- const excluded = new Set(DEFAULT_PTC_EXCLUDED_TOOLS);
694
- return candidates.filter((t) => !excluded.has(t.name));
695
- }
696
- if (Array.isArray(ptc)) {
697
- const included = new Set(ptc);
698
- return candidates.filter((t) => included.has(t.name));
699
- }
700
- if ("include" in ptc) {
701
- const included = new Set(ptc.include);
702
- return candidates.filter((t) => included.has(t.name));
703
- }
704
- if ("exclude" in ptc) {
705
- const excluded = new Set([...DEFAULT_PTC_EXCLUDED_TOOLS, ...ptc.exclude]);
706
- return candidates.filter((t) => !excluded.has(t.name));
707
- }
708
- return [];
1022
+ if (!ptc) return [];
1023
+ return resolveToolList(ptc, allTools.filter((t) => t.name !== "js_eval"));
709
1024
  }
710
1025
  return createMiddleware({
711
1026
  name: "QuickJSMiddleware",
712
1027
  tools: [tool(async (input, config) => {
713
- const threadId = config.configurable?.thread_id || "__default__";
714
- const resolvedBackend = await resolveBackend(backend, {
715
- ...config,
716
- state: getCurrentTaskInput(config) || {}
717
- });
718
- const session = ReplSession.getOrCreate(threadId, {
1028
+ const sessionKey = `${config.configurable?.thread_id || "__default__"}:${middlewareId}`;
1029
+ const session = ReplSession.getOrCreate(sessionKey, {
719
1030
  memoryLimitBytes,
720
1031
  maxStackSizeBytes,
721
- backend: resolvedBackend,
722
- tools: ptcTools
1032
+ maxPtcCalls,
1033
+ tools: ptcTools,
1034
+ skillsEnabled: skillsBackend !== void 0,
1035
+ maxResultChars
723
1036
  });
724
- const result = await session.eval(input.code, executionTimeoutMs);
725
- await session.flushWrites(resolvedBackend);
726
- return formatReplResult(result);
1037
+ if (skillsBackend !== void 0) {
1038
+ const setupError = await prepareSkillsForEval(session, skillsBackend, input.code);
1039
+ if (setupError !== void 0) return setupError;
1040
+ }
1041
+ return formatReplResult(await session.eval(input.code, executionTimeoutMs));
727
1042
  }, {
728
1043
  name: "js_eval",
729
1044
  description: dedent`
730
1045
  Evaluate TypeScript/JavaScript code in a sandboxed REPL. State persists across calls.
731
- Use readFile(path) and writeFile(path, content) for file access.
732
1046
  Use console.log() for output. Returns the result of the last expression.
1047
+ If file or other tools are available, call them via the tools namespace: await tools.readFile({ path }).
1048
+ If skills are configured, dynamically import them: await import("@/skills/<name>").
733
1049
  `,
1050
+ metadata: { ls_code_input_language: "javascript" },
734
1051
  schema: z.object({ code: z.string().describe("TypeScript/JavaScript code to evaluate in the sandboxed REPL") })
735
1052
  })],
736
1053
  wrapModelCall: async (request, handler) => {
737
- const agentTools = request.tools || [];
738
- ptcTools = usePtc ? filterToolsForPtc(agentTools) : [];
1054
+ ptcTools = filterToolsForPtc(request.tools || []);
739
1055
  if (ptcTools.length > 0 && !cachedPtcPrompt) cachedPtcPrompt = await generatePtcPrompt(ptcTools);
740
1056
  const systemMessage = request.systemMessage.concat(baseSystemPrompt).concat(cachedPtcPrompt || "");
741
1057
  return handler({
742
1058
  ...request,
743
1059
  systemMessage
744
1060
  });
1061
+ },
1062
+ afterAgent: async (_state, runtime) => {
1063
+ const sessionKey = `${runtime.configurable?.thread_id ?? "__default__"}:${middlewareId}`;
1064
+ ReplSession.deleteSession(sessionKey);
745
1065
  }
746
1066
  });
747
1067
  }
748
1068
  //#endregion
749
- export { DEFAULT_EXECUTION_TIMEOUT, DEFAULT_MAX_STACK_SIZE, DEFAULT_MEMORY_LIMIT, DEFAULT_PTC_EXCLUDED_TOOLS, ReplSession, createQuickJSMiddleware, formatReplResult, toCamelCase, transformForEval };
1069
+ export { DEFAULT_EXECUTION_TIMEOUT, DEFAULT_MAX_PTC_CALLS, DEFAULT_MAX_STACK_SIZE, DEFAULT_MEMORY_LIMIT, MAX_SKILL_BUNDLE_BYTES, PTCCallBudgetExceededError, ReplSession, SKILL_MODULE_EXTENSIONS, createQuickJSMiddleware, formatReplResult, formatSkillNotAvailable, loadSkill, scanSkillReferences, stripTypeSyntax, toCamelCase, transformForEval };
750
1070
 
751
1071
  //# sourceMappingURL=index.js.map