@langchain/quickjs 0.4.0 → 0.5.1

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.cjs CHANGED
@@ -23,21 +23,159 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  //#endregion
24
24
  let langchain = require("langchain");
25
25
  let zod_v4 = require("zod/v4");
26
+ let deepagents = require("deepagents");
26
27
  let dedent = require("dedent");
27
28
  dedent = __toESM(dedent, 1);
28
- let _langchain_langgraph = require("@langchain/langgraph");
29
- let deepagents = require("deepagents");
30
29
  let quickjs_emscripten = require("quickjs-emscripten");
31
30
  let quickjs_emscripten_core = require("quickjs-emscripten-core");
32
- let node_path_posix = require("node:path/posix");
33
- node_path_posix = __toESM(node_path_posix, 1);
31
+ let json_schema_to_typescript = require("json-schema-to-typescript");
32
+ let _langchain_core_utils_json_schema = require("@langchain/core/utils/json_schema");
33
+ let _langchain_langgraph = require("@langchain/langgraph");
34
+ let _langchain_core_messages = require("@langchain/core/messages");
34
35
  let acorn = require("acorn");
35
36
  let _sveltejs_acorn_typescript = require("@sveltejs/acorn-typescript");
36
37
  let estree_walker = require("estree-walker");
37
38
  let magic_string = require("magic-string");
38
39
  magic_string = __toESM(magic_string, 1);
39
- let json_schema_to_typescript = require("json-schema-to-typescript");
40
- let _langchain_core_utils_json_schema = require("@langchain/core/utils/json_schema");
40
+ let p_queue = require("p-queue");
41
+ p_queue = __toESM(p_queue, 1);
42
+ //#region src/errors.ts
43
+ /**
44
+ * Thrown when a single eval exhausts its configured PTC call budget.
45
+ */
46
+ var PTCCallBudgetExceededError = class extends Error {
47
+ limit;
48
+ attempted;
49
+ functionName;
50
+ constructor(options) {
51
+ super(`PTC call budget exceeded (limit=${options.limit}, attempted=${options.attempted}, function=${options.functionName})`);
52
+ this.name = "PTCCallBudgetExceededError";
53
+ this.limit = options.limit;
54
+ this.attempted = options.attempted;
55
+ this.functionName = options.functionName;
56
+ }
57
+ };
58
+ //#endregion
59
+ //#region src/utils.ts
60
+ /**
61
+ * Convert a snake_case or kebab-case string to camelCase.
62
+ */
63
+ function toCamelCase(name) {
64
+ return name.replace(/[-_]([a-z])/g, (_, c) => c.toUpperCase());
65
+ }
66
+ /**
67
+ * Format the result of a REPL evaluation for the agent.
68
+ */
69
+ function formatReplResult(result) {
70
+ const parts = [];
71
+ if (result.logs.length > 0) {
72
+ let logsText = result.logs.join("\n");
73
+ if (result.logsDroppedChars > 0) logsText += `\n[truncated ${result.logsDroppedChars} chars]`;
74
+ parts.push(logsText);
75
+ }
76
+ if (result.ok) {
77
+ if (result.value !== void 0) {
78
+ const formatted = typeof result.value === "string" ? result.value : JSON.stringify(result.value, null, 2);
79
+ parts.push(`→ ${formatted}`);
80
+ }
81
+ } else if (result.error) {
82
+ const errName = result.error.name || "Error";
83
+ const errMsg = result.error.message || "Unknown error";
84
+ parts.push(`${errName}: ${errMsg}`);
85
+ if (result.error.stack) parts.push(result.error.stack);
86
+ }
87
+ return parts.join("\n") || "(no output)";
88
+ }
89
+ function safeToJsonSchema(schema) {
90
+ try {
91
+ return (0, _langchain_core_utils_json_schema.toJsonSchema)(schema);
92
+ } catch {
93
+ return;
94
+ }
95
+ }
96
+ async function schemaToInterface(jsonSchema, interfaceName) {
97
+ return (await (0, json_schema_to_typescript.compile)({
98
+ ...jsonSchema,
99
+ additionalProperties: false
100
+ }, interfaceName, {
101
+ bannerComment: "",
102
+ additionalProperties: false
103
+ })).replace(/^export /, "").trimEnd();
104
+ }
105
+ function capitalize(s) {
106
+ return s.charAt(0).toUpperCase() + s.slice(1);
107
+ }
108
+ async function toolToTypeSignature(name, description, jsonSchema) {
109
+ const inputType = `${capitalize(name)}Input`;
110
+ if (!jsonSchema || !jsonSchema.properties) return dedent.default`
111
+ /**
112
+ * ${description}
113
+ */
114
+ async tools.${name}(input: Record<string, unknown>): Promise<string>
115
+ `;
116
+ return dedent.default`
117
+ ${await schemaToInterface(jsonSchema, inputType)}
118
+
119
+ /**
120
+ * ${description}
121
+ */
122
+ async tools.${name}(input: ${inputType}): Promise<string>
123
+ `;
124
+ }
125
+ //#endregion
126
+ //#region src/coerce.ts
127
+ /**
128
+ * Coercion of tool / subagent return values for the QuickJS bridge.
129
+ *
130
+ * The deepagents `task` tool resolves to a LangGraph `Command` whose payload
131
+ * carries the subagent's final message(s) under `update.messages`; some tools
132
+ * return a `ToolMessage` or a list of messages. The interpreter bridges need
133
+ * the underlying output, not the envelope, so this unwraps those shapes to the
134
+ * content the model actually cares about.
135
+ */
136
+ /**
137
+ * Return the trailing message content from a `Command`'s `update.messages`,
138
+ * scanning from the end for the last message that actually has content. Returns
139
+ * the command unchanged when it has no message-shaped payload.
140
+ */
141
+ function extractCommandContent(command) {
142
+ const update = command.update;
143
+ const messages = update !== null && typeof update === "object" ? update.messages : void 0;
144
+ if (Array.isArray(messages)) for (let i = messages.length - 1; i >= 0; i--) {
145
+ const message = messages[i];
146
+ if (_langchain_core_messages.BaseMessage.isInstance(message) && message.content != null) return message.content;
147
+ }
148
+ return command;
149
+ }
150
+ /**
151
+ * Unwrap a LangChain `Command` / `ToolMessage` / message-list envelope to the
152
+ * underlying content. Non-envelope values (strings, content-block arrays, plain
153
+ * objects) are returned unchanged.
154
+ *
155
+ * @param value The raw value returned by a tool or subagent dispatch.
156
+ * @returns The unwrapped content, or `value` itself when it isn't an envelope.
157
+ */
158
+ function unwrapToolEnvelope(value) {
159
+ if (typeof value === "string") return value;
160
+ if ((0, _langchain_langgraph.isCommand)(value)) {
161
+ const inner = extractCommandContent(value);
162
+ return inner === value ? value : unwrapToolEnvelope(inner);
163
+ }
164
+ if (_langchain_core_messages.BaseMessage.isInstance(value)) return unwrapToolEnvelope(value.content);
165
+ if (Array.isArray(value)) {
166
+ for (let i = value.length - 1; i >= 0; i--) {
167
+ const entry = value[i];
168
+ if (_langchain_core_messages.BaseMessage.isInstance(entry)) return unwrapToolEnvelope(entry.content);
169
+ if ((0, _langchain_langgraph.isCommand)(entry)) {
170
+ const inner = extractCommandContent(entry);
171
+ if (inner !== entry) return unwrapToolEnvelope(inner);
172
+ }
173
+ }
174
+ return value;
175
+ }
176
+ return value;
177
+ }
178
+ //#endregion
41
179
  //#region src/transform.ts
42
180
  /**
43
181
  * AST-based code transform pipeline for the REPL.
@@ -113,7 +251,11 @@ function transformForEval(code) {
113
251
  }
114
252
  function isTSOnlyNode(node) {
115
253
  const t = node.type;
116
- return t === "TSTypeAliasDeclaration" || t === "TSInterfaceDeclaration" || t === "TSEnumDeclaration" || t === "TSModuleDeclaration" || t === "TSDeclareFunction" || t.startsWith("TS");
254
+ if (t === "TSTypeAliasDeclaration" || t === "TSInterfaceDeclaration" || t === "TSEnumDeclaration" || t === "TSModuleDeclaration" || t === "TSDeclareFunction" || t.startsWith("TS")) return true;
255
+ if (t === "VariableDeclaration" && node.declare === true) return true;
256
+ if (t === "ImportDeclaration" && node.importKind === "type") return true;
257
+ if (t === "ExportNamedDeclaration" && node.exportKind === "type") return true;
258
+ return false;
117
259
  }
118
260
  /**
119
261
  * Rewrite a top-level VariableDeclaration to globalThis assignments.
@@ -165,7 +307,11 @@ function stripTypeAnnotations(s, node) {
165
307
  } });
166
308
  }
167
309
  function stripTypeAnnotationFromNode(s, n, offset = 0) {
168
- if (n.typeAnnotation && n.typeAnnotation.start != null) s.remove(n.typeAnnotation.start - offset, n.typeAnnotation.end - offset);
310
+ if (n.optional === true && n.typeAnnotation && n.typeAnnotation.start != null) s.remove(n.typeAnnotation.start - 1 - offset, n.typeAnnotation.end - offset);
311
+ else if (n.optional === true && !n.typeAnnotation) {
312
+ const nameEnd = n.type === "Identifier" && typeof n.name === "string" ? n.start + n.name.length : null;
313
+ if (nameEnd != null) s.remove(nameEnd - offset, nameEnd + 1 - offset);
314
+ } else if (n.typeAnnotation && n.typeAnnotation.start != null) s.remove(n.typeAnnotation.start - offset, n.typeAnnotation.end - offset);
169
315
  if (n.returnType && n.returnType.start != null) s.remove(n.returnType.start - offset, n.returnType.end - offset);
170
316
  if (n.typeParameters && n.typeParameters.start != null) s.remove(n.typeParameters.start - offset, n.typeParameters.end - offset);
171
317
  if (n.typeArguments && n.typeArguments.start != null) s.remove(n.typeArguments.start - offset, n.typeArguments.end - offset);
@@ -231,228 +377,6 @@ function stripTypeSyntax(code) {
231
377
  return magicString.toString();
232
378
  }
233
379
  //#endregion
234
- //#region src/skills.ts
235
- /**
236
- * File extensions the loader will enumerate from a skill directory.
237
- */
238
- const SKILL_MODULE_EXTENSIONS = [
239
- ".js",
240
- ".mjs",
241
- ".cjs",
242
- ".ts",
243
- ".mts",
244
- ".cts",
245
- ".jsx",
246
- ".tsx"
247
- ];
248
- /**
249
- * Hard cap on total bytes pulled for one skill's bundle (1 MiB).
250
- */
251
- const MAX_SKILL_BUNDLE_BYTES = 1 * 1024 * 1024;
252
- /**
253
- * Validates a skill name against the spec's kebab-case rule.
254
- */
255
- const SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
256
- /**
257
- * Matches `"@/skills/<name>"` or `'@/skills/<name>'` references in source.
258
- * Template literals and computed specifiers are not caught.
259
- */
260
- const SKILL_SPECIFIER_RE = /["']@\/skills\/([a-z0-9]+(?:-[a-z0-9]+)*)["']/g;
261
- /**
262
- * List every code-extension file under `skillDir` (recursive).
263
- */
264
- async function enumerateCodeFiles(backend, skillDir, skillName) {
265
- const seen = /* @__PURE__ */ new Set();
266
- for (const ext of SKILL_MODULE_EXTENSIONS) {
267
- const result = await backend.glob(`**/*${ext}`, skillDir);
268
- if (result.error !== void 0) throw new Error(`Skill '${skillName}': failed to list '${skillDir}': ${result.error}`);
269
- const matches = result.files ?? [];
270
- for (const match of matches) seen.add(match.path);
271
- }
272
- return [...seen].sort();
273
- }
274
- /**
275
- * Decode download responses into [path, source] pairs.
276
- */
277
- function decodeFiles(responses, skillName) {
278
- const decoder = new TextDecoder("utf-8", { fatal: true });
279
- const pairs = [];
280
- for (const response of responses) {
281
- if (response.error !== null || response.content === null) throw new Error(`Skill '${skillName}': failed to download '${response.path}': ${response.error ?? "no content"}`);
282
- let source;
283
- try {
284
- source = decoder.decode(response.content);
285
- } catch {
286
- throw new Error(`Skill '${skillName}': file '${response.path}' is not valid UTF-8`);
287
- }
288
- pairs.push([response.path, source]);
289
- }
290
- return pairs;
291
- }
292
- /**
293
- * Throws an Error when the total decoded size of all files exceeds
294
- * `MAX_SKILL_BUNDLE_BYTES`. Counts characters rather than bytes, which
295
- * over-counts multi-byte UTF-8. Intentionally errs toward rejection.
296
- */
297
- function validateBundleSize(pairs, skillName) {
298
- let total = 0;
299
- for (const [, source] of pairs) total += source.length;
300
- if (total > 1048576) throw new Error(`Skill '${skillName}': bundle exceeds ${MAX_SKILL_BUNDLE_BYTES} bytes (total ${total})`);
301
- }
302
- /**
303
- * Express `absolutePath` as a POSIX-relative path under `skillDir`.
304
- * Throws an Error if the path escapes the skill directory which indicates
305
- * a backend bug, not a user error.
306
- */
307
- function relativeUnder(skillDir, absolutePath, skillName) {
308
- const rel = node_path_posix.relative(skillDir, absolutePath);
309
- if (rel === "" || rel.startsWith("..")) throw new Error(`Skill '${skillName}': file ${absolutePath} is not under '${skillDir}'`);
310
- return rel;
311
- }
312
- /**
313
- * Build the relative-path → source map, applying `stripTypeSyntax` to each file.
314
- */
315
- function buildFilesMap(skillDir, entryRel, pairs, skillName) {
316
- const files = /* @__PURE__ */ new Map();
317
- let entryPresent = false;
318
- for (const [absPath, source] of pairs) {
319
- const rel = relativeUnder(skillDir, absPath, skillName);
320
- files.set(rel, stripTypeSyntax(source));
321
- if (rel === entryRel) entryPresent = true;
322
- }
323
- if (!entryPresent) throw new Error(`Skill '${skillName}': module path '${entryRel}' did not match any file in the skill directory`);
324
- return files;
325
- }
326
- /**
327
- * Build a `LoadedSkill` from a skill's metadata and a backend handle.
328
- *
329
- * Enumerates code files under the skill directory, downloads them,
330
- * strips TypeScript syntax, and validates the entrypoint is present.
331
- */
332
- async function loadSkill(metadata, backend) {
333
- const name = metadata.name;
334
- if (!SKILL_NAME_RE.test(name)) throw new Error(`Skill name '${name}' is not a valid kebab-case identifier`);
335
- const entryRel = metadata.module;
336
- if (entryRel === void 0 || entryRel === "") throw new Error(`Skill '${name}' has no 'module' frontmatter key - only skills with a declared entrypoint are installable`);
337
- const adapted = (0, deepagents.adaptBackendProtocol)(backend);
338
- if (adapted.downloadFiles === void 0) throw new Error(`Skill '${name}': backend does not implement downloadFiles`);
339
- const skillDir = node_path_posix.dirname(metadata.path);
340
- const codeFiles = await enumerateCodeFiles(adapted, skillDir, name);
341
- if (codeFiles.length === 0) throw new Error(`Skill '${name}': no JS/TS files under '${skillDir}'`);
342
- const filePairs = decodeFiles(await adapted.downloadFiles(codeFiles), name);
343
- validateBundleSize(filePairs, name);
344
- const files = buildFilesMap(skillDir, entryRel, filePairs, name);
345
- return {
346
- name,
347
- specifier: `@/skills/${name}`,
348
- entryRel,
349
- files
350
- };
351
- }
352
- /**
353
- * Extract skill names referenced by `"@/skills/<name>"` literals in source.
354
- *
355
- * Used as a pre-eval scan so the middleware can surface `SkillNotAvailable`
356
- * before evaluation starts. Dynamic imports with computed specifiers are
357
- * not detected.
358
- */
359
- function scanSkillReferences(source) {
360
- const names = /* @__PURE__ */ new Set();
361
- const matches = source.matchAll(SKILL_SPECIFIER_RE);
362
- for (const match of matches) names.add(match[1]);
363
- return names;
364
- }
365
- //#endregion
366
- //#region src/errors.ts
367
- /**
368
- * Thrown when a single eval exhausts its configured PTC call budget.
369
- */
370
- var PTCCallBudgetExceededError = class extends Error {
371
- limit;
372
- attempted;
373
- functionName;
374
- constructor(options) {
375
- super(`PTC call budget exceeded (limit=${options.limit}, attempted=${options.attempted}, function=${options.functionName})`);
376
- this.name = "PTCCallBudgetExceededError";
377
- this.limit = options.limit;
378
- this.attempted = options.attempted;
379
- this.functionName = options.functionName;
380
- }
381
- };
382
- //#endregion
383
- //#region src/utils.ts
384
- /**
385
- * Convert a snake_case or kebab-case string to camelCase.
386
- */
387
- function toCamelCase(name) {
388
- return name.replace(/[-_]([a-z])/g, (_, c) => c.toUpperCase());
389
- }
390
- /**
391
- * Format the result of a REPL evaluation for the agent.
392
- */
393
- function formatReplResult(result) {
394
- const parts = [];
395
- if (result.logs.length > 0) {
396
- let logsText = result.logs.join("\n");
397
- if (result.logsDroppedChars > 0) logsText += `\n[truncated ${result.logsDroppedChars} chars]`;
398
- parts.push(logsText);
399
- }
400
- if (result.ok) {
401
- if (result.value !== void 0) {
402
- const formatted = typeof result.value === "string" ? result.value : JSON.stringify(result.value, null, 2);
403
- parts.push(`→ ${formatted}`);
404
- }
405
- } else if (result.error) {
406
- const errName = result.error.name || "Error";
407
- const errMsg = result.error.message || "Unknown error";
408
- parts.push(`${errName}: ${errMsg}`);
409
- if (result.error.stack) parts.push(result.error.stack);
410
- }
411
- return parts.join("\n") || "(no output)";
412
- }
413
- function safeToJsonSchema(schema) {
414
- try {
415
- return (0, _langchain_core_utils_json_schema.toJsonSchema)(schema);
416
- } catch {
417
- return;
418
- }
419
- }
420
- async function schemaToInterface(jsonSchema, interfaceName) {
421
- return (await (0, json_schema_to_typescript.compile)({
422
- ...jsonSchema,
423
- additionalProperties: false
424
- }, interfaceName, {
425
- bannerComment: "",
426
- additionalProperties: false
427
- })).replace(/^export /, "").trimEnd();
428
- }
429
- function capitalize(s) {
430
- return s.charAt(0).toUpperCase() + s.slice(1);
431
- }
432
- async function toolToTypeSignature(name, description, jsonSchema) {
433
- const inputType = `${capitalize(name)}Input`;
434
- if (!jsonSchema || !jsonSchema.properties) return dedent.default`
435
- /**
436
- * ${description}
437
- */
438
- async tools.${name}(input: Record<string, unknown>): Promise<string>
439
- `;
440
- return dedent.default`
441
- ${await schemaToInterface(jsonSchema, inputType)}
442
-
443
- /**
444
- * ${description}
445
- */
446
- async tools.${name}(input: ${inputType}): Promise<string>
447
- `;
448
- }
449
- /**
450
- * Render a pre-eval error when referenced skills are not available on the agent.
451
- */
452
- function formatSkillNotAvailable(missing) {
453
- return `Skills unavailable: ${[...missing].sort().join(", ")}`;
454
- }
455
- //#endregion
456
380
  //#region src/eval-queue.ts
457
381
  /**
458
382
  * Serializes async operations on a shared WASM module.
@@ -509,6 +433,7 @@ const DEFAULT_MAX_STACK_SIZE = 320 * 1024;
509
433
  const DEFAULT_EXECUTION_TIMEOUT = 5e3;
510
434
  const DEFAULT_MAX_PTC_CALLS = 256;
511
435
  const DEFAULT_MAX_RESULTS_CHARS = 4e3;
436
+ const LINE_NUMBER_RE = /^\s*\d+(?:\.\d+)?\t/;
512
437
  const variantImport = import("@jitl/quickjs-ng-wasmfile-release-asyncify");
513
438
  /**
514
439
  * Process-global eval queue. Serializes all evalCodeAsync calls across
@@ -540,59 +465,47 @@ function getSharedModule() {
540
465
  })();
541
466
  return sharedModulePromise;
542
467
  }
543
- function makeErrorSource(message) {
544
- return `throw { name: "Error", message: ${JSON.stringify(message)} };`;
545
- }
546
- /**
547
- * Parse a canonicalized skill specifier into `{ name, rel }`.
548
- * Returns `undefined` for anything that isn't a valid `@/skills/<name>` or
549
- * `@/skills/<name>/<rel>` shape. `rel` is absent for the bare form.
550
- */
551
- function parseSkillSpecifier(specifier) {
552
- if (!specifier.startsWith("@/skills/")) return;
553
- const tail = specifier.slice(9);
554
- const slashIdx = tail.indexOf("/");
555
- const name = slashIdx === -1 ? tail : tail.slice(0, slashIdx);
556
- if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) return;
557
- const rel = slashIdx === -1 ? void 0 : tail.slice(slashIdx + 1);
558
- if (rel !== void 0 && rel === "") return;
559
- return {
560
- name,
561
- rel
562
- };
563
- }
564
468
  /**
565
- * Return the `@/skills/<name>` prefix for the skill that owns `base`, or `undefined`.
469
+ * Unwrap a PTC tool result to a plain string for use inside QuickJS.
470
+ *
471
+ * Tool results may arrive as a raw string, or as an array of LangChain
472
+ * content blocks (`{ type: "text", text: "..." }`). Blocks are joined
473
+ * with newlines; non-text block types are silently skipped. Anything
474
+ * else (objects, nulls) is JSON-serialised as a fallback.
475
+ *
476
+ * @param result - Raw return value from `tool.invoke()`.
477
+ * @returns Plain string representation of the tool output.
566
478
  */
567
- function matchSkillPrefix(base) {
568
- const parsed = parseSkillSpecifier(base);
569
- if (parsed === void 0) return;
570
- return `@/skills/${parsed.name}`;
479
+ function extractToolText(result) {
480
+ result = unwrapToolEnvelope(result);
481
+ if (typeof result === "string") return result;
482
+ if (Array.isArray(result)) {
483
+ const texts = [];
484
+ for (const block of result) if (typeof block === "object" && block !== null && block.type === "text" && typeof block.text === "string") texts.push(block.text);
485
+ if (texts.length > 0) return texts.join("\n");
486
+ }
487
+ return JSON.stringify(result);
571
488
  }
572
489
  /**
573
- * Return the directory portion of a slash-separated specifier path.
490
+ * Remove the `cat -n` line-number prefix from every line of a string.
491
+ *
492
+ * The filesystem backend formats file content with line numbers in the
493
+ * form `" N\t"` so human readers can navigate by line. That prefix
494
+ * is useful for the agent but noise for QuickJS code that parses the
495
+ * text programmatically (e.g. swarm reading `/context.txt`).
496
+ *
497
+ * The function is conservative: if any non-empty line lacks the prefix,
498
+ * the text is returned unchanged so nothing is silently corrupted.
499
+ *
500
+ * @param text - Raw file content, possibly line-number prefixed.
501
+ * @returns Content with line-number prefixes stripped, or the original
502
+ * text if it doesn't match the expected format throughout.
574
503
  */
575
- function posixDirname(p) {
576
- const idx = p.lastIndexOf("/");
577
- if (idx === -1) return "";
578
- return p.slice(0, idx);
579
- }
580
- /**
581
- * POSIX join for slash-separated specifiers. Avoids `node:path/posix`
582
- * since session.ts is consumed in browser bundles.
583
- */
584
- function posixJoin(base, rel) {
585
- const out = [];
586
- const segments = `${base}/${rel}`.split("/");
587
- for (const segment of segments) {
588
- if (segment === "" || segment === ".") continue;
589
- if (segment === "..") {
590
- out.pop();
591
- continue;
592
- }
593
- out.push(segment);
594
- }
595
- return out.join("/");
504
+ function stripLineNumbers(text) {
505
+ const lines = text.split("\n");
506
+ if (lines.length === 0) return text;
507
+ if (!lines.every((l) => l === "" || LINE_NUMBER_RE.test(l))) return text;
508
+ return lines.map((l) => l.replace(LINE_NUMBER_RE, "")).join("\n");
596
509
  }
597
510
  /**
598
511
  * Fixed-size character buffer for capturing console output from the QuickJS VM.
@@ -654,11 +567,16 @@ var ReplSession = class ReplSession {
654
567
  context = null;
655
568
  consoleBuffer = new ConsoleBuffer(DEFAULT_MAX_RESULTS_CHARS);
656
569
  options;
657
- skillsContext;
658
- skillsLoaded = /* @__PURE__ */ new Map();
659
- skillsFailed = /* @__PURE__ */ new Map();
660
570
  maxPtcCalls;
661
571
  ptcCallsRemaining = null;
572
+ subagentQueue = null;
573
+ bridgeDispatchRef = null;
574
+ /** Allowed keys in the subagent input object. */
575
+ static SUBAGENT_ALLOWED_KEYS = /* @__PURE__ */ new Set([
576
+ "description",
577
+ "subagentType",
578
+ "responseSchema"
579
+ ]);
662
580
  /**
663
581
  * Reset the shared WASM module. Forces the next session to instantiate
664
582
  * a fresh module. Only needed in tests where module state must be
@@ -676,7 +594,7 @@ var ReplSession = class ReplSession {
676
594
  }
677
595
  async ensureStarted() {
678
596
  if (this.runtime) return;
679
- const { memoryLimitBytes = DEFAULT_MEMORY_LIMIT, maxStackSizeBytes = DEFAULT_MAX_STACK_SIZE, tools, skillsEnabled = false, maxResultChars = DEFAULT_MAX_RESULTS_CHARS, captureConsole = true } = this.options;
597
+ const { memoryLimitBytes = DEFAULT_MEMORY_LIMIT, maxStackSizeBytes = DEFAULT_MAX_STACK_SIZE, tools, maxResultChars = DEFAULT_MAX_RESULTS_CHARS, captureConsole = true } = this.options;
680
598
  const runtime = (await getSharedModule()).newRuntime();
681
599
  runtime.setMemoryLimit(memoryLimitBytes);
682
600
  runtime.setMaxStackSize(maxStackSizeBytes);
@@ -686,97 +604,15 @@ var ReplSession = class ReplSession {
686
604
  this.consoleBuffer = new ConsoleBuffer(maxResultChars);
687
605
  if (captureConsole) this.setupConsole();
688
606
  if (tools !== void 0 && tools.length > 0) this.injectTools(tools);
689
- if (skillsEnabled) this.installModuleLoader();
690
- }
691
- /**
692
- * Load the skill into cache on first access and replay cached errors.
693
- */
694
- async ensureSkillLoaded(name) {
695
- const cached = this.skillsLoaded.get(name);
696
- if (cached !== void 0) return cached;
697
- const cachedError = this.skillsFailed.get(name);
698
- if (cachedError !== void 0) throw cachedError;
699
- const ctx = this.skillsContext;
700
- if (ctx === void 0) throw new Error(`Skill '${name}' referenced but skills are not configured for this session`);
701
- const metadata = ctx.metadata.find((m) => m.name === name);
702
- if (metadata === void 0) throw new Error(`Skill '${name}' referenced but not available on this agent`);
703
- try {
704
- const loaded = await loadSkill(metadata, ctx.backend);
705
- this.skillsLoaded.set(name, loaded);
706
- return loaded;
707
- } catch (err) {
708
- this.skillsFailed.set(name, err);
709
- throw err;
710
- }
711
- }
712
- /**
713
- * Pre-load all skills referenced in source code into the in-memory
714
- * cache. Must be called before `evalCodeAsync` so the module loader
715
- * can resolve synchronously. An async loader would cause asyncify
716
- * suspensions on each import, which is incompatible with the shared
717
- * WASM module used by all sessions.
718
- */
719
- async preloadReferencedSkills(code) {
720
- const refs = scanSkillReferences(code);
721
- for (const name of refs) {
722
- if (this.skillsLoaded.has(name) || this.skillsFailed.has(name)) continue;
723
- try {
724
- await this.ensureSkillLoaded(name);
725
- } catch (err) {
726
- if (!this.skillsFailed.has(name)) this.skillsFailed.set(name, err);
727
- }
607
+ const { subagentBridge } = this.options;
608
+ if (subagentBridge) {
609
+ this.subagentQueue = new p_queue.default({ concurrency: subagentBridge.maxConcurrency });
610
+ this.injectSubagentBridge(subagentBridge.dispatch);
728
611
  }
729
- }
730
- /**
731
- * Resolve a module specifier to source code. Strictly synchronous —
732
- * only reads from the in-memory skill cache populated by
733
- * `preloadReferencedSkills`. Returns error source (not a thrown
734
- * exception) for missing or failed skills so QuickJS reports the
735
- * error inside the VM.
736
- */
737
- resolveSpecifier(specifier) {
738
- const parsed = parseSkillSpecifier(specifier);
739
- if (parsed === void 0) return makeErrorSource(`Module not found: ${specifier}`);
740
- const cachedError = this.skillsFailed.get(parsed.name);
741
- if (cachedError !== void 0) return makeErrorSource(cachedError.message ?? String(cachedError));
742
- const loaded = this.skillsLoaded.get(parsed.name);
743
- if (loaded === void 0) return makeErrorSource(`Skill '${parsed.name}' was not preloaded. Ensure the import specifier is a static string literal (dynamic specifiers like \`import("@/skills/" + name)\` are not supported).`);
744
- if (parsed.rel === void 0) {
745
- const source = loaded.files.get(loaded.entryRel);
746
- if (source === void 0) return makeErrorSource(`Skill '${parsed.name}': entrypoint '${loaded.entryRel}' missing from bundle`);
747
- return source;
748
- }
749
- let source = loaded.files.get(parsed.rel);
750
- if (source === void 0 && parsed.rel.endsWith(".js")) source = loaded.files.get(parsed.rel.slice(0, -3) + ".ts");
751
- if (source === void 0) return makeErrorSource(`Skill '${parsed.name}': '${parsed.rel}' not found in bundle`);
752
- return source;
753
- }
754
- /**
755
- * Canonicalize an `import` specifier. Bare specifiers pass through;
756
- * relative specifiers are resolved against the importing module's path.
757
- * Traversal out of a skill's `@/skills/<name>/` namespace is rejected.
758
- */
759
- normalizeSpecifier(base, requested) {
760
- if (!(requested.startsWith("./") || requested.startsWith("../"))) return requested;
761
- const parsed = parseSkillSpecifier(base);
762
- const resolved = posixJoin(parsed !== void 0 && parsed.rel === void 0 ? base : posixDirname(base), requested);
763
- const skillPrefix = matchSkillPrefix(base);
764
- if (skillPrefix === void 0) return resolved;
765
- if (!resolved.startsWith(`${skillPrefix}/`)) return `__resolve_error__:${requested} escapes ${skillPrefix}`;
766
- return resolved;
767
- }
768
- /**
769
- * Wire the QuickJS module loader and normalizer on this session's runtime.
770
- *
771
- * The loader is strictly synchronous — it reads from the in-memory skill
772
- * cache populated by `preloadReferencedSkills`. This is critical: an async
773
- * module loader causes asyncify suspensions on each import, and disposing
774
- * a runtime after multi-file imports corrupts the shared module's asyncify
775
- * state, silently breaking the loader for all subsequent sessions.
776
- */
777
- installModuleLoader() {
778
- if (this.runtime === null) return;
779
- this.runtime.setModuleLoader((specifier) => this.resolveSpecifier(specifier), (base, requested) => this.normalizeSpecifier(base, requested));
612
+ const sessionId = this.options.sessionId ?? "default";
613
+ const sessionIdHandle = context.newString(sessionId);
614
+ context.setProp(context.global, "__sessionId__", sessionIdHandle);
615
+ sessionIdHandle.dispose();
780
616
  }
781
617
  /**
782
618
  * Initialise the per-eval PTC counter. Called at the top of every `eval()`.
@@ -839,14 +675,6 @@ var ReplSession = class ReplSession {
839
675
  if (session) session.dispose();
840
676
  }
841
677
  /**
842
- * Push the current skills metadata + backend into the session.
843
- * Called by the middleware once per `eval` invocation, before eval runs.
844
- * Pass `undefined` to clear the context (no skill imports will resolve).
845
- */
846
- setSkillsContext(ctx) {
847
- this.skillsContext = ctx;
848
- }
849
- /**
850
678
  * Evaluate code in this session.
851
679
  *
852
680
  * Lazily starts the QuickJS runtime on the first call. Code is
@@ -859,7 +687,6 @@ var ReplSession = class ReplSession {
859
687
  await this.ensureStarted();
860
688
  const runtime = this.runtime;
861
689
  const context = this.context;
862
- await this.preloadReferencedSkills(code);
863
690
  const drainLogs = () => {
864
691
  const [raw, dropped] = this.consoleBuffer.drain();
865
692
  return {
@@ -1007,8 +834,9 @@ var ReplSession = class ReplSession {
1007
834
  try {
1008
835
  this.consumePtcBudget(camelName);
1009
836
  const rawInput = typeof input === "object" && input !== null ? input : {};
1010
- const result = await t.invoke(rawInput);
1011
- const val = context.newString(typeof result === "string" ? result : JSON.stringify(result));
837
+ let text = extractToolText(await t.invoke(rawInput));
838
+ if (t.name === "read_file") text = stripLineNumbers(text);
839
+ const val = context.newString(text);
1012
840
  promise.resolve(val);
1013
841
  val.dispose();
1014
842
  } catch (e) {
@@ -1027,8 +855,113 @@ var ReplSession = class ReplSession {
1027
855
  context.setProp(context.global, "tools", toolsNs);
1028
856
  toolsNs.dispose();
1029
857
  }
858
+ /**
859
+ * Install the `task` global on the QuickJS context.
860
+ *
861
+ * Registers the host function directly as `globalThis.task`,
862
+ * then freezes it via `evalCode`. Structured results (when
863
+ * responseSchema is provided) are marshaled into native QuickJS
864
+ * objects on the host side — no JS wrapper needed.
865
+ */
866
+ /**
867
+ * Replace the active bridge dispatch with a fresh one.
868
+ *
869
+ * Call this before each eval so the dispatch closure carries
870
+ * the current invocation's config (tracing callbacks, run ID, etc.)
871
+ * rather than the stale config from session creation.
872
+ */
873
+ updateBridgeDispatch(dispatch) {
874
+ if (this.bridgeDispatchRef) this.bridgeDispatchRef.current = dispatch;
875
+ }
876
+ injectSubagentBridge(dispatch) {
877
+ const context = this.context;
878
+ const queue = this.subagentQueue;
879
+ this.bridgeDispatchRef = { current: dispatch };
880
+ const ref = this.bridgeDispatchRef;
881
+ const hostFn = context.newFunction("task", (inputHandle) => {
882
+ const input = context.dump(inputHandle);
883
+ const promise = context.newPromise();
884
+ (async () => {
885
+ try {
886
+ if (input == null || typeof input !== "object" || Array.isArray(input)) throw new Error("task: expected an object argument");
887
+ const obj = { ...input };
888
+ if ("subagent_type" in obj) {
889
+ obj.subagentType ??= obj.subagent_type;
890
+ delete obj.subagent_type;
891
+ }
892
+ if ("response_schema" in obj) {
893
+ obj.responseSchema ??= obj.response_schema;
894
+ delete obj.response_schema;
895
+ }
896
+ const unknownKeys = Object.keys(obj).filter((k) => !ReplSession.SUBAGENT_ALLOWED_KEYS.has(k));
897
+ if (unknownKeys.length > 0) throw new Error(`task: unknown keys: ${unknownKeys.join(", ")}. Allowed: ${[...ReplSession.SUBAGENT_ALLOWED_KEYS].join(", ")}`);
898
+ const { description, subagentType, responseSchema } = obj;
899
+ if (typeof description !== "string" || description.length === 0) throw new Error("task: 'description' is required and must be a non-empty string");
900
+ if (typeof subagentType !== "string" || subagentType.length === 0) throw new Error("task: 'subagentType' is required and must be a non-empty string");
901
+ if (responseSchema !== void 0 && (responseSchema == null || typeof responseSchema !== "object" || Array.isArray(responseSchema))) throw new Error("task: 'responseSchema' must be a plain object (JSON Schema) when provided");
902
+ const result = await queue.add(() => ref.current({
903
+ description,
904
+ subagentType,
905
+ ...responseSchema !== void 0 && { responseSchema }
906
+ }));
907
+ if (typeof result === "string") {
908
+ const val = context.newString(result);
909
+ promise.resolve(val);
910
+ val.dispose();
911
+ } else {
912
+ const jsonResult = context.evalCode(`(${JSON.stringify(result)})`);
913
+ if (jsonResult.error) {
914
+ const errDump = context.dump(jsonResult.error);
915
+ jsonResult.error.dispose();
916
+ throw new Error(`task: failed to marshal structured response: ${JSON.stringify(errDump)}`);
917
+ }
918
+ promise.resolve(jsonResult.value);
919
+ jsonResult.value.dispose();
920
+ }
921
+ } catch (e) {
922
+ const msg = e != null && typeof e.message === "string" ? e.message : String(e);
923
+ const err = context.newError(msg);
924
+ promise.reject(err);
925
+ err.dispose();
926
+ }
927
+ promise.settled.then(context.runtime.executePendingJobs);
928
+ })();
929
+ return promise.handle;
930
+ });
931
+ context.setProp(context.global, "task", hostFn);
932
+ hostFn.dispose();
933
+ context.evalCode("Object.freeze(globalThis.task);Object.defineProperty(globalThis, 'task', { value: globalThis.task, writable: false, configurable: false,}); undefined");
934
+ }
1030
935
  };
1031
936
  //#endregion
937
+ //#region src/subagent-dispatch.ts
938
+ const SCHEMA_MAX_BYTES = 4096;
939
+ const SCHEMA_MAX_DEPTH = 5;
940
+ const SCHEMA_MAX_PROPERTIES = 32;
941
+ /**
942
+ * Validate that a response schema does not exceed size, depth, or
943
+ * property-count limits.
944
+ *
945
+ * @throws Error if any limit is exceeded.
946
+ */
947
+ function validateResponseSchema(schema) {
948
+ const serialized = JSON.stringify(schema);
949
+ if (serialized.length > SCHEMA_MAX_BYTES) throw new Error(`responseSchema exceeds ${SCHEMA_MAX_BYTES} byte limit (${serialized.length} bytes)`);
950
+ function check(node, depth, propCount) {
951
+ if (depth > SCHEMA_MAX_DEPTH) throw new Error(`responseSchema exceeds maximum nesting depth of ${SCHEMA_MAX_DEPTH}`);
952
+ const props = node.properties;
953
+ if (props != null && typeof props === "object" && !Array.isArray(props)) {
954
+ const propObj = props;
955
+ propCount.value += Object.keys(propObj).length;
956
+ if (propCount.value > SCHEMA_MAX_PROPERTIES) throw new Error(`responseSchema exceeds maximum of ${SCHEMA_MAX_PROPERTIES} properties`);
957
+ for (const value of Object.values(propObj)) if (value != null && typeof value === "object" && !Array.isArray(value)) check(value, depth + 1, propCount);
958
+ }
959
+ const items = node.items;
960
+ if (items != null && typeof items === "object" && !Array.isArray(items)) check(items, depth + 1, propCount);
961
+ }
962
+ check(schema, 0, { value: 0 });
963
+ }
964
+ //#endregion
1032
965
  //#region src/middleware.ts
1033
966
  /**
1034
967
  * Code Interpreter middleware for deepagents.
@@ -1039,14 +972,242 @@ var ReplSession = class ReplSession {
1039
972
  * - Programmatic tool calling (PTC) — expose agent or custom tools inside the REPL
1040
973
  */
1041
974
  const DEFAULT_TOOL_NAME = "eval";
975
+ /**
976
+ * Render the subagent dispatch prompt section for the system message.
977
+ * Ported from the Python `_SUBAGENT_SYSTEM_PROMPT_TEMPLATE`.
978
+ */
979
+ function renderSubagentPrompt(toolName) {
980
+ return dedent.default`
981
+
982
+ ### Dispatching Subagents with \`task\`
983
+
984
+ \`task\` is your primitive for running configured subagents from inside the
985
+ JavaScript REPL. Your job here is to DISTRIBUTE work, not to do it yourself:
986
+ write JavaScript that fans work out to subagents and assembles their results.
987
+ You handle the orchestration - fan-out, filtering, deduplication, multi-stage
988
+ flow, and synthesis - in plain JavaScript.
989
+
990
+ #### The primitive
991
+
992
+ \`\`\`javascript
993
+ await task({
994
+ description, // full autonomous task prompt
995
+ subagentType, // configured subagent name
996
+ responseSchema, // optional JSON Schema for structured output
997
+ }); // -> Promise<unknown>
998
+ \`\`\`
999
+
1000
+ \`task\` runs a full agentic loop for the selected configured subagent. The
1001
+ subagent can use whatever tools it was configured with, iterate, inspect
1002
+ context, and return one final result. \`subagentType\` is required; use one of
1003
+ the configured subagent names.
1004
+
1005
+ \`description\` is the only prompt the subagent receives for this dispatch. Make
1006
+ it complete: the goal, the constraints, what to inspect, and the exact shape
1007
+ or level of detail you expect back. Give context as locators — file paths and
1008
+ symbol names — not as pasted file contents. If you already read a file while
1009
+ exploring, still pass its path and let the subagent read it; do not paste back
1010
+ what you read. Each dispatch is stateless from the caller's perspective; you
1011
+ cannot send follow-up messages to the same subagent run.
1012
+
1013
+ \`responseSchema\` is optional, but set it on any dispatch whose result feeds
1014
+ later code. A deterministic, typed shape is what lets you compose the next
1015
+ stage reliably — index it, sort it, compare fields, branch on it, merge it —
1016
+ instead of parsing free-form text. This is what makes a whole workflow
1017
+ composable as one script. When provided, the resolved value is already a typed
1018
+ JavaScript value matching the schema; do not call \`JSON.parse\` unless the
1019
+ subagent intentionally returned a JSON string. Dynamic schemas work for
1020
+ declarative subagents; runnable-backed subagents reject dynamic schemas because
1021
+ their runnable is already compiled.
1022
+
1023
+ #### Approval model
1024
+
1025
+ \`task\` dispatches from inside the already-running \`${toolName}\` call. It
1026
+ does not route through the parent agent's \`ToolNode\`-managed \`task\` tool and
1027
+ does not trigger parent-level \`interrupt_on\` / HITL approval for each dispatch.
1028
+ Declarative subagents still honor approval middleware configured inside their
1029
+ own spec. If you need approval before launching a subagent from the parent, use
1030
+ the normal \`task\` tool outside JavaScript or ensure the \`${toolName}\` call
1031
+ itself is approval-gated.
1032
+
1033
+ #### Mental model
1034
+
1035
+ Hold your work in JS: an array of items in, an array of results out. Merge each
1036
+ dispatch result back onto its item. Multi-stage analysis means: run a pass,
1037
+ filter or regroup the array in JS, then run another pass over the survivors.
1038
+
1039
+ You can run the whole workflow in one \`${toolName}\` call or split it across
1040
+ several — both are fine. A single end-to-end script (generate, compare, pick a
1041
+ winner; or review every item, then synthesize) is clean when you can write it
1042
+ in one go; splitting is also fine when you want to inspect results between
1043
+ stages. Either way, don't redo work across calls — reuse what is already in
1044
+ scope (see "Reuse what earlier evals left in scope" below).
1045
+
1046
+ #### Fan out with bounded concurrency
1047
+
1048
+ Dispatch independent work in parallel with \`Promise.all\`, but in explicit
1049
+ batches around 10 so you do not launch hundreds of subagents at once. The bridge
1050
+ enforces a hard per-REPL cap of 32 concurrent subagent calls.
1051
+
1052
+ \`\`\`javascript
1053
+ const files = ["/src/a.ts", "/src/b.ts", "/src/c.ts"]; // found while exploring
1054
+ const batchSize = 10;
1055
+ const reviewed = [];
1056
+ for (let i = 0; i < files.length; i += batchSize) {
1057
+ const batch = files.slice(i, i + batchSize);
1058
+ reviewed.push(...(await Promise.all(batch.map(async (file) => {
1059
+ const result = await task({
1060
+ description: "Read " + file + " and review it for SQL injection. " +
1061
+ "Cite line numbers.",
1062
+ subagentType: "reviewer",
1063
+ responseSchema: {
1064
+ type: "object",
1065
+ properties: {
1066
+ vulnerabilities: {
1067
+ type: "array",
1068
+ items: {
1069
+ type: "object",
1070
+ properties: {
1071
+ type: { type: "string" },
1072
+ line: { type: "number" },
1073
+ evidence: { type: "string" },
1074
+ },
1075
+ required: ["type", "line", "evidence"],
1076
+ },
1077
+ },
1078
+ },
1079
+ required: ["vulnerabilities"],
1080
+ },
1081
+ });
1082
+ return { file, ...result };
1083
+ }))));
1084
+ }
1085
+ \`\`\`
1086
+
1087
+ #### Explore with your own tools first, then distribute
1088
+
1089
+ You already have your normal tools for reading, listing, globbing, and
1090
+ grepping files. Use them to explore and understand the task BEFORE you write
1091
+ the orchestration script. These are ordinary tool calls, separate from the
1092
+ \`${toolName}\` tool: read the data file, list or glob the directory, grep for
1093
+ what matters, then decide how to split the work.
1094
+
1095
+ Never write \`${toolName}\` code that spawns a subagent just to read or parse a
1096
+ file or list a directory. That is a deterministic step you do yourself with a
1097
+ direct tool call; spending a whole agent loop on it is wasteful.
1098
+
1099
+ Once you understand the shape of the work, you have creative freedom in how
1100
+ you split it:
1101
+
1102
+ - One dispatch per file or per record, when the items are already separate.
1103
+ - Chunk a large input yourself — read it, split it, optionally write a small
1104
+ input file per chunk — and dispatch one subagent per chunk.
1105
+ - A cheap classification pass first, then deeper dispatches only for the items
1106
+ that warrant them.
1107
+
1108
+ Then write JavaScript in the \`${toolName}\` tool that distributes the heavy,
1109
+ agentic work to subagents with \`task()\`: analyzing file contents, exploring a
1110
+ codebase, making judgment calls, rewriting code, or synthesizing a report.
1111
+
1112
+ Hand each subagent a locator, not a payload. Subagents have their own file
1113
+ tools, so for anything that lives in a file — a file to review, rewrite, or
1114
+ audit — pass the path and let the subagent read it. Do NOT read a whole file
1115
+ just to paste its contents into the description; that bloats every dispatch
1116
+ and duplicates the file across them. Reserve inline content for small or
1117
+ derived data that has no path of its own: a single parsed record, or a chunk
1118
+ you split out of a larger input (write the chunk to its own file and pass that
1119
+ path if it is large). Assemble the results in JS.
1120
+
1121
+ #### Compose multiple stages
1122
+
1123
+ Filter the array in JS between passes. For example: first ask subagents for a
1124
+ cheap classification, filter to the risky items, then dispatch deeper reviews
1125
+ only for those items.
1126
+
1127
+ \`\`\`javascript
1128
+ const tagged = await Promise.all(files.map((file) =>
1129
+ task({
1130
+ description: "Read " + file + " and classify it as handler, util, " +
1131
+ "test, or config.",
1132
+ subagentType: "reviewer",
1133
+ responseSchema: {
1134
+ type: "object",
1135
+ properties: { kind: { type: "string" }, risky: { type: "boolean" } },
1136
+ required: ["kind", "risky"],
1137
+ },
1138
+ }).then((tag) => ({ file, ...tag }))
1139
+ ));
1140
+
1141
+ const riskyHandlers = tagged.filter((it) => it.kind === "handler" && it.risky);
1142
+ const deepReviews = await Promise.all(riskyHandlers.map((it) =>
1143
+ task({
1144
+ description: "Deep security review of " + it.file + ". Cite line numbers.",
1145
+ subagentType: "reviewer",
1146
+ }).then((review) => ({ ...it, review }))
1147
+ ));
1148
+ \`\`\`
1149
+
1150
+ #### Return results via the last expression, not \`console.log\`
1151
+
1152
+ The value of the last expression in an \`${toolName}\` call (or a resolved
1153
+ top-level \`await\`) is returned to you as the result. Make that final
1154
+ expression the variable holding your result and read it from there.
1155
+ \`console.log\` is only for incidental debugging: its output is capped and
1156
+ truncated, while the returned value is not, so never \`console.log\` your
1157
+ actual results.
1158
+
1159
+ Keep large intermediate sets in JS variables and return only a compact
1160
+ summary or a small slice, not the entire dataset. To persist full output,
1161
+ have a subagent write it, or write it with your own file tool outside the
1162
+ \`${toolName}\` call.
1163
+
1164
+ #### Reuse what earlier evals left in scope
1165
+
1166
+ The REPL is persistent within a turn: every top-level variable, function, and
1167
+ class you declare is kept and is available in your next \`${toolName}\` call
1168
+ (each is hoisted to global scope). So if a later step needs something an
1169
+ earlier eval produced or bound, **reference that variable by name** — do not
1170
+ write a new literal that re-types data a previous eval already returned or
1171
+ computed.
1172
+
1173
+ If you catch yourself pasting a big array or object of values you produced in
1174
+ an earlier call, that is the tell: the variable is still in scope, so use it.
1175
+ Re-typing prior results as a fresh literal wastes tokens and drifts from what
1176
+ actually ran.
1177
+
1178
+ \`\`\`javascript
1179
+ // An earlier eval bound this:
1180
+ // const auditResults = await Promise.all(files.map(/* ...audit... */));
1181
+
1182
+ // A later eval — reference it; do NOT paste the findings back in as a literal:
1183
+ const findings = auditResults.flatMap((r) =>
1184
+ r.findings.map((f) => ({ ...f, file: r.file }))
1185
+ );
1186
+ const verified = await Promise.all(findings.map((f) =>
1187
+ task({ description: "Verify this finding: " + f.evidence, subagentType: "verifier" })
1188
+ .then((v) => ({ ...f, ...v }))
1189
+ ));
1190
+ \`\`\`
1191
+
1192
+ #### When the user asks for a "workflow"
1193
+
1194
+ If the user's request mentions running a "workflow" (or otherwise uses the
1195
+ word "workflow"), fan the work out to subagents rather than doing it all
1196
+ yourself. Explore with your own tools first as needed, then write JavaScript
1197
+ in the \`${toolName}\` tool that dispatches subagents with \`task()\` and
1198
+ assembles their results. The point is to distribute the heavy work in
1199
+ parallel, not to grind through it one tool call at a time.
1200
+ `;
1201
+ }
1042
1202
  function renderReplSystemPrompt(opts) {
1203
+ const sideEffects = opts.hasPtc ? " External side effects from inside the REPL are reachable only via the `tools.*` namespace documented below." : " The REPL is pure computation; do any filesystem or other I/O with your normal tools, outside this tool.";
1043
1204
  return dedent.default`
1044
1205
  ### Interpreter
1045
1206
 
1046
1207
  An \`${opts.toolName}\` tool is available. It runs JavaScript in a persistent REPL.
1047
1208
  - State (variables, functions) persists across tool calls within a single turn of conversation. They DO NOT persist across multiple turns.
1048
1209
  - Top-level \`await\` works; Promises resolve before the call returns.
1049
- - Sandboxed: no filesystem, no stdlib, no network, no real clock, no \`fetch\`, no \`require\`.
1210
+ - Runtime sandbox: no built-in filesystem, network, stdlib, or wall-clock APIs (\`fetch\`, \`require\`, \`fs\`, \`process\`, real \`Date.now()\` are unavailable or stubbed).${sideEffects}
1050
1211
  - Timeout: ${opts.timeout}s per call. Memory: ${opts.memoryLimitMb} MB total.
1051
1212
  - \`console.log\` output is captured and returned alongside the result.
1052
1213
  `;
@@ -1103,64 +1264,65 @@ function resolveToolList(items, agentTools) {
1103
1264
  });
1104
1265
  }
1105
1266
  /**
1106
- * Pull `skillsMetadata` from the task input, resolve the backend, and push
1107
- * both into the session. Short-circuits with a `SkillNotAvailable` error if
1108
- * the source references skills the agent doesn't have.
1109
- */
1110
- async function prepareSkillsForEval(session, skillsBackend, code) {
1111
- const taskInput = (0, _langchain_langgraph.getCurrentTaskInput)();
1112
- const metadata = taskInput?.skillsMetadata ?? [];
1113
- const referenced = scanSkillReferences(code);
1114
- if (referenced.size > 0) {
1115
- const known = new Set(metadata.map((m) => m.name));
1116
- const missing = [];
1117
- for (const name of referenced) if (!known.has(name)) missing.push(name);
1118
- if (missing.length > 0) {
1119
- session.setSkillsContext(void 0);
1120
- return formatSkillNotAvailable(missing);
1121
- }
1122
- }
1123
- const resolved = await (0, deepagents.resolveBackend)(skillsBackend, { state: taskInput });
1124
- session.setSkillsContext({
1125
- metadata,
1126
- backend: resolved
1127
- });
1128
- }
1129
- /**
1130
1267
  * Create the Code Interpreter middleware.
1131
1268
  */
1132
1269
  function createCodeInterpreterMiddleware(options = {}) {
1133
- 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, toolName = DEFAULT_TOOL_NAME, captureConsole = true } = options;
1270
+ const { ptc, memoryLimitBytes = DEFAULT_MEMORY_LIMIT, maxStackSizeBytes = DEFAULT_MAX_STACK_SIZE, executionTimeoutMs = DEFAULT_EXECUTION_TIMEOUT, systemPrompt: customSystemPrompt = null, maxPtcCalls = 256, maxResultChars = DEFAULT_MAX_RESULTS_CHARS, toolName = DEFAULT_TOOL_NAME, captureConsole = true, subagents = true } = options;
1271
+ const maxSubagentConcurrency = subagents ? 32 : 0;
1134
1272
  if (maxPtcCalls !== null && maxPtcCalls !== void 0 && maxPtcCalls < 1) throw new Error("`maxPtcCalls` must be >= 1 or null");
1135
- const baseSystemPrompt = customSystemPrompt || renderReplSystemPrompt({
1136
- toolName,
1137
- timeout: executionTimeoutMs / 1e3,
1138
- memoryLimitMb: Math.floor(memoryLimitBytes / (1024 * 1024))
1139
- });
1140
1273
  const middlewareId = crypto.randomUUID();
1141
1274
  let cachedPtcPrompt = null;
1142
1275
  let ptcTools = [];
1276
+ let taskTool = null;
1143
1277
  function filterToolsForPtc(allTools) {
1144
1278
  if (!ptc) return [];
1145
1279
  return resolveToolList(ptc, allTools.filter((t) => t.name !== toolName));
1146
1280
  }
1281
+ function findTaskTool(tools) {
1282
+ return tools.find((t) => t.name === "task") ?? null;
1283
+ }
1284
+ function createBridgeDispatch(subagentTaskTool, config) {
1285
+ return async (input) => {
1286
+ const hasSchema = input.responseSchema != null;
1287
+ if (hasSchema) validateResponseSchema(input.responseSchema);
1288
+ const toolConfig = {
1289
+ ...config,
1290
+ configurable: {
1291
+ ...config.configurable,
1292
+ ...hasSchema && { [deepagents.SUBAGENT_RESPONSE_FORMAT_CONFIG_KEY]: input.responseSchema }
1293
+ }
1294
+ };
1295
+ const content = unwrapToolEnvelope(await subagentTaskTool.invoke({
1296
+ description: input.description,
1297
+ subagent_type: input.subagentType
1298
+ }, toolConfig));
1299
+ if (hasSchema && typeof content === "string") try {
1300
+ return JSON.parse(content);
1301
+ } catch {
1302
+ return content;
1303
+ }
1304
+ return content;
1305
+ };
1306
+ }
1147
1307
  return (0, langchain.createMiddleware)({
1148
1308
  name: "CodeInterpreterMiddleware",
1149
1309
  tools: [(0, langchain.tool)(async (input, config) => {
1150
- const sessionKey = `${config.configurable?.thread_id || "__default__"}:${middlewareId}`;
1310
+ const threadId = config.configurable?.thread_id || "__default__";
1311
+ const sessionKey = `${threadId}:${middlewareId}`;
1151
1312
  const session = ReplSession.getOrCreate(sessionKey, {
1152
1313
  memoryLimitBytes,
1153
1314
  maxStackSizeBytes,
1154
1315
  maxPtcCalls,
1155
1316
  tools: ptcTools,
1156
- skillsEnabled: skillsBackend !== void 0,
1157
1317
  maxResultChars,
1158
- captureConsole
1318
+ captureConsole,
1319
+ sessionId: threadId,
1320
+ subagentBridge: taskTool && maxSubagentConcurrency > 0 ? {
1321
+ dispatch: createBridgeDispatch(taskTool, config),
1322
+ maxConcurrency: maxSubagentConcurrency
1323
+ } : void 0
1159
1324
  });
1160
- if (skillsBackend !== void 0) {
1161
- const setupError = await prepareSkillsForEval(session, skillsBackend, input.code);
1162
- if (setupError !== void 0) return setupError;
1163
- }
1325
+ if (taskTool && maxSubagentConcurrency > 0) session.updateBridgeDispatch(createBridgeDispatch(taskTool, config));
1164
1326
  return formatReplResult(await session.eval(input.code, executionTimeoutMs));
1165
1327
  }, {
1166
1328
  name: toolName,
@@ -1168,15 +1330,23 @@ function createCodeInterpreterMiddleware(options = {}) {
1168
1330
  Evaluate TypeScript/JavaScript code in a sandboxed REPL. State persists across calls.
1169
1331
  Use console.log() for output. Returns the result of the last expression.
1170
1332
  If file or other tools are available, call them via the tools namespace: await tools.readFile({ path }).
1171
- If skills are configured, dynamically import them: await import("@/skills/<name>").
1172
1333
  `,
1173
1334
  metadata: { ls_code_input_language: "javascript" },
1174
1335
  schema: zod_v4.z.object({ code: zod_v4.z.string().describe("TypeScript/JavaScript code to evaluate in the sandboxed REPL") })
1175
1336
  })],
1176
1337
  wrapModelCall: async (request, handler) => {
1177
- ptcTools = filterToolsForPtc(request.tools || []);
1338
+ const agentTools = request.tools || [];
1339
+ ptcTools = filterToolsForPtc(agentTools);
1340
+ if (!taskTool && maxSubagentConcurrency > 0) taskTool = findTaskTool(agentTools);
1178
1341
  if (ptcTools.length > 0 && !cachedPtcPrompt) cachedPtcPrompt = await generatePtcPrompt(ptcTools);
1179
- const systemMessage = request.systemMessage.concat(baseSystemPrompt).concat(cachedPtcPrompt || "");
1342
+ const baseSystemPrompt = customSystemPrompt || renderReplSystemPrompt({
1343
+ toolName,
1344
+ timeout: executionTimeoutMs / 1e3,
1345
+ memoryLimitMb: Math.floor(memoryLimitBytes / (1024 * 1024)),
1346
+ hasPtc: ptcTools.length > 0
1347
+ });
1348
+ const subagentPrompt = taskTool && maxSubagentConcurrency > 0 ? renderSubagentPrompt(toolName) : "";
1349
+ const systemMessage = request.systemMessage.concat(baseSystemPrompt).concat(subagentPrompt).concat(cachedPtcPrompt || "");
1180
1350
  return handler({
1181
1351
  ...request,
1182
1352
  systemMessage
@@ -1193,17 +1363,13 @@ exports.DEFAULT_EXECUTION_TIMEOUT = DEFAULT_EXECUTION_TIMEOUT;
1193
1363
  exports.DEFAULT_MAX_PTC_CALLS = DEFAULT_MAX_PTC_CALLS;
1194
1364
  exports.DEFAULT_MAX_STACK_SIZE = DEFAULT_MAX_STACK_SIZE;
1195
1365
  exports.DEFAULT_MEMORY_LIMIT = DEFAULT_MEMORY_LIMIT;
1196
- exports.MAX_SKILL_BUNDLE_BYTES = MAX_SKILL_BUNDLE_BYTES;
1197
1366
  exports.PTCCallBudgetExceededError = PTCCallBudgetExceededError;
1198
1367
  exports.ReplSession = ReplSession;
1199
- exports.SKILL_MODULE_EXTENSIONS = SKILL_MODULE_EXTENSIONS;
1200
1368
  exports.createCodeInterpreterMiddleware = createCodeInterpreterMiddleware;
1201
1369
  exports.formatReplResult = formatReplResult;
1202
- exports.formatSkillNotAvailable = formatSkillNotAvailable;
1203
- exports.loadSkill = loadSkill;
1204
- exports.scanSkillReferences = scanSkillReferences;
1205
1370
  exports.stripTypeSyntax = stripTypeSyntax;
1206
1371
  exports.toCamelCase = toCamelCase;
1207
1372
  exports.transformForEval = transformForEval;
1373
+ exports.validateResponseSchema = validateResponseSchema;
1208
1374
 
1209
1375
  //# sourceMappingURL=index.cjs.map