@poncho-ai/harness 0.35.0 → 0.36.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.
Files changed (49) hide show
  1. package/.turbo/turbo-build.log +12 -11
  2. package/CHANGELOG.md +25 -0
  3. package/dist/index.d.ts +485 -29
  4. package/dist/index.js +2839 -2114
  5. package/dist/isolate-TCWTUVG4.js +1532 -0
  6. package/package.json +23 -4
  7. package/scripts/migrate-to-engine.mjs +556 -0
  8. package/src/config.ts +106 -1
  9. package/src/harness.ts +226 -91
  10. package/src/index.ts +5 -0
  11. package/src/isolate/bindings.ts +206 -0
  12. package/src/isolate/bundler.ts +179 -0
  13. package/src/isolate/index.ts +10 -0
  14. package/src/isolate/polyfills.ts +796 -0
  15. package/src/isolate/run-code-tool.ts +220 -0
  16. package/src/isolate/runtime.ts +286 -0
  17. package/src/isolate/type-stubs.ts +196 -0
  18. package/src/memory.ts +129 -198
  19. package/src/reminder-store.ts +3 -237
  20. package/src/secrets-store.ts +2 -91
  21. package/src/state.ts +11 -1302
  22. package/src/storage/engine.ts +106 -0
  23. package/src/storage/index.ts +59 -0
  24. package/src/storage/memory-engine.ts +588 -0
  25. package/src/storage/postgres-engine.ts +139 -0
  26. package/src/storage/schema.ts +145 -0
  27. package/src/storage/sql-dialect.ts +963 -0
  28. package/src/storage/sqlite-engine.ts +99 -0
  29. package/src/storage/store-adapters.ts +100 -0
  30. package/src/todo-tools.ts +1 -136
  31. package/src/upload-store.ts +1 -0
  32. package/src/vfs/bash-manager.ts +120 -0
  33. package/src/vfs/bash-tool.ts +59 -0
  34. package/src/vfs/create-bash-fs.ts +32 -0
  35. package/src/vfs/edit-file-tool.ts +72 -0
  36. package/src/vfs/index.ts +5 -0
  37. package/src/vfs/poncho-fs-adapter.ts +267 -0
  38. package/src/vfs/protected-fs.ts +177 -0
  39. package/src/vfs/read-file-tool.ts +103 -0
  40. package/src/vfs/write-file-tool.ts +49 -0
  41. package/test/harness.test.ts +30 -36
  42. package/test/isolate-vfs.test.ts +453 -0
  43. package/test/isolate.test.ts +252 -0
  44. package/test/state.test.ts +4 -27
  45. package/test/storage-engine.test.ts +250 -0
  46. package/test/vfs.test.ts +242 -0
  47. package/.turbo/turbo-lint.log +0 -6
  48. package/.turbo/turbo-test.log +0 -11931
  49. package/src/kv-store.ts +0 -216
@@ -0,0 +1,1532 @@
1
+ // src/isolate/run-code-tool.ts
2
+ import { defineTool } from "@poncho-ai/sdk";
3
+
4
+ // src/isolate/runtime.ts
5
+ var ivmModule;
6
+ async function loadIvm() {
7
+ if (ivmModule) return ivmModule;
8
+ try {
9
+ const mod = await import("isolated-vm");
10
+ ivmModule = mod.default ?? mod;
11
+ return ivmModule;
12
+ } catch {
13
+ throw new Error(
14
+ "Code execution requires isolated-vm. Run: pnpm add isolated-vm"
15
+ );
16
+ }
17
+ }
18
+ function buildRuntimePreamble() {
19
+ return `
20
+ // --- console capture ---
21
+ const __stdout = [];
22
+ const __stderr = [];
23
+ let __outputBytes = 0;
24
+ const __outputLimit = typeof __OUTPUT_LIMIT === "number" ? __OUTPUT_LIMIT : 65536;
25
+
26
+ function __serialize(v) {
27
+ if (typeof v === "string") return v;
28
+ try {
29
+ const seen = new WeakSet();
30
+ return JSON.stringify(v, function(_k, val) {
31
+ if (typeof val === "object" && val !== null) {
32
+ if (seen.has(val)) return "[Circular]";
33
+ seen.add(val);
34
+ }
35
+ return val;
36
+ }, 2);
37
+ } catch { return String(v); }
38
+ }
39
+
40
+ function __capture(arr, args) {
41
+ const line = Array.from(args).map(__serialize).join(" ");
42
+ const bytes = line.length;
43
+ if (__outputBytes + bytes > __outputLimit) {
44
+ arr.push("[output truncated at " + __outputLimit + " bytes]");
45
+ __outputBytes = __outputLimit;
46
+ return;
47
+ }
48
+ __outputBytes += bytes;
49
+ arr.push(line);
50
+ }
51
+
52
+ const console = {
53
+ log: function() { __capture(__stdout, arguments); },
54
+ info: function() { __capture(__stdout, arguments); },
55
+ warn: function() { __capture(__stderr, arguments); },
56
+ error: function() { __capture(__stderr, arguments); },
57
+ debug: function() { __capture(__stdout, arguments); },
58
+ };
59
+ `;
60
+ }
61
+ function createIsolateRuntime(config) {
62
+ return {
63
+ async execute(code, bindings, preamble, signal, polyfillPreamble) {
64
+ const ivm = await loadIvm();
65
+ const isolate = new ivm.Isolate({
66
+ memoryLimit: config.memoryLimit
67
+ });
68
+ let abortHandler;
69
+ let aborted = false;
70
+ if (signal) {
71
+ if (signal.aborted) {
72
+ isolate.dispose();
73
+ return {
74
+ stdout: "",
75
+ stderr: "",
76
+ error: { message: "Execution cancelled", name: "AbortError" },
77
+ executionTimeMs: 0
78
+ };
79
+ }
80
+ abortHandler = () => {
81
+ aborted = true;
82
+ isolate.dispose();
83
+ };
84
+ signal.addEventListener("abort", abortHandler, { once: true });
85
+ }
86
+ const t0 = performance.now();
87
+ let context;
88
+ try {
89
+ context = await isolate.createContext();
90
+ const jail = context.global;
91
+ jail.setSync("__OUTPUT_LIMIT", config.outputLimit);
92
+ const bindingNames = Object.keys(bindings);
93
+ const wrapperDecls = [];
94
+ for (const name of bindingNames) {
95
+ const binding = bindings[name];
96
+ const ref = new ivm.Reference(async (inputJson) => {
97
+ const input = JSON.parse(inputJson);
98
+ const result2 = await binding.handler(input);
99
+ return JSON.stringify(result2 ?? null);
100
+ });
101
+ jail.setSync(`__binding_${name}`, ref);
102
+ wrapperDecls.push(
103
+ `async function ${name}(input) {
104
+ const raw = await __binding_${name}.apply(undefined, [JSON.stringify(input)], { result: { promise: true, copy: true } });
105
+ return JSON.parse(raw);
106
+ }`
107
+ );
108
+ }
109
+ const runtimePreamble = buildRuntimePreamble() + "\n" + wrapperDecls.join("\n");
110
+ await context.eval(runtimePreamble, { filename: "<runtime>" });
111
+ if (polyfillPreamble) {
112
+ await context.eval(polyfillPreamble, { filename: "<polyfills>" });
113
+ }
114
+ if (preamble) {
115
+ await context.eval(preamble, { filename: "<libraries>" });
116
+ }
117
+ const wrapped = `(async () => {
118
+ ${code}
119
+ })()`;
120
+ const rawResult = await context.eval(wrapped, {
121
+ filename: "<user-code>",
122
+ promise: true,
123
+ copy: true,
124
+ timeout: config.timeout
125
+ });
126
+ const stdout = await context.eval("__stdout.join('\\n')", { copy: true });
127
+ const stderr = await context.eval("__stderr.join('\\n')", { copy: true });
128
+ let result;
129
+ try {
130
+ result = rawResult === void 0 || rawResult === null ? rawResult : JSON.parse(JSON.stringify(rawResult));
131
+ } catch {
132
+ result = void 0;
133
+ }
134
+ return {
135
+ result,
136
+ stdout,
137
+ stderr,
138
+ executionTimeMs: performance.now() - t0
139
+ };
140
+ } catch (err) {
141
+ const elapsed = performance.now() - t0;
142
+ if (aborted) {
143
+ return {
144
+ stdout: "",
145
+ stderr: "",
146
+ error: { message: "Execution cancelled", name: "AbortError" },
147
+ executionTimeMs: elapsed
148
+ };
149
+ }
150
+ let stdout = "";
151
+ let stderr = "";
152
+ if (context) {
153
+ try {
154
+ stdout = await context.eval("__stdout.join('\\n')", { copy: true });
155
+ stderr = await context.eval("__stderr.join('\\n')", { copy: true });
156
+ } catch {
157
+ }
158
+ }
159
+ const error = err instanceof Error ? err : new Error(String(err));
160
+ const parsed = parseV8Error(error);
161
+ return {
162
+ stdout,
163
+ stderr,
164
+ error: parsed,
165
+ executionTimeMs: elapsed
166
+ };
167
+ } finally {
168
+ if (abortHandler && signal) {
169
+ signal.removeEventListener("abort", abortHandler);
170
+ }
171
+ try {
172
+ isolate.dispose();
173
+ } catch {
174
+ }
175
+ }
176
+ }
177
+ };
178
+ }
179
+ function parseV8Error(error) {
180
+ const result = {
181
+ message: error.message,
182
+ name: error.name
183
+ };
184
+ const match = error.stack?.match(/<user-code>:(\d+):(\d+)/);
185
+ if (match) {
186
+ const rawLine = parseInt(match[1], 10);
187
+ result.line = Math.max(1, rawLine - 1);
188
+ result.column = parseInt(match[2], 10);
189
+ }
190
+ return result;
191
+ }
192
+
193
+ // src/isolate/bindings.ts
194
+ function createVfsBindings(adapter) {
195
+ return {
196
+ __poncho_fs_read: {
197
+ description: "Read a text file from the VFS",
198
+ inputSchema: {
199
+ type: "object",
200
+ properties: { path: { type: "string" } },
201
+ required: ["path"]
202
+ },
203
+ handler: async (input) => {
204
+ return await adapter.readFile(input.path);
205
+ }
206
+ },
207
+ __poncho_fs_write: {
208
+ description: "Write a text file to the VFS",
209
+ inputSchema: {
210
+ type: "object",
211
+ properties: {
212
+ path: { type: "string" },
213
+ content: { type: "string" }
214
+ },
215
+ required: ["path", "content"]
216
+ },
217
+ handler: async (input) => {
218
+ await adapter.writeFile(input.path, input.content);
219
+ }
220
+ },
221
+ __poncho_fs_read_binary: {
222
+ description: "Read a binary file (returns base64)",
223
+ inputSchema: {
224
+ type: "object",
225
+ properties: { path: { type: "string" } },
226
+ required: ["path"]
227
+ },
228
+ handler: async (input) => {
229
+ const buf = await adapter.readFileBuffer(input.path);
230
+ return Buffer.from(buf).toString("base64");
231
+ }
232
+ },
233
+ __poncho_fs_write_binary: {
234
+ description: "Write a binary file (content is base64)",
235
+ inputSchema: {
236
+ type: "object",
237
+ properties: {
238
+ path: { type: "string" },
239
+ content: { type: "string" }
240
+ },
241
+ required: ["path", "content"]
242
+ },
243
+ handler: async (input) => {
244
+ const buf = Buffer.from(input.content, "base64");
245
+ await adapter.writeFile(input.path, buf);
246
+ }
247
+ },
248
+ __poncho_fs_list: {
249
+ description: "List directory entries",
250
+ inputSchema: {
251
+ type: "object",
252
+ properties: { path: { type: "string" } },
253
+ required: ["path"]
254
+ },
255
+ handler: async (input) => {
256
+ return await adapter.readdir(input.path);
257
+ }
258
+ },
259
+ __poncho_fs_exists: {
260
+ description: "Check if path exists",
261
+ inputSchema: {
262
+ type: "object",
263
+ properties: { path: { type: "string" } },
264
+ required: ["path"]
265
+ },
266
+ handler: async (input) => {
267
+ return await adapter.exists(input.path);
268
+ }
269
+ },
270
+ __poncho_fs_delete: {
271
+ description: "Delete a file or directory",
272
+ inputSchema: {
273
+ type: "object",
274
+ properties: { path: { type: "string" } },
275
+ required: ["path"]
276
+ },
277
+ handler: async (input) => {
278
+ await adapter.rm(input.path, { force: true });
279
+ }
280
+ },
281
+ __poncho_fs_mkdir: {
282
+ description: "Create a directory (recursive)",
283
+ inputSchema: {
284
+ type: "object",
285
+ properties: { path: { type: "string" } },
286
+ required: ["path"]
287
+ },
288
+ handler: async (input) => {
289
+ await adapter.mkdir(input.path, { recursive: true });
290
+ }
291
+ },
292
+ __poncho_fs_stat: {
293
+ description: "Get file metadata",
294
+ inputSchema: {
295
+ type: "object",
296
+ properties: { path: { type: "string" } },
297
+ required: ["path"]
298
+ },
299
+ handler: async (input) => {
300
+ const stat = await adapter.stat(input.path);
301
+ return {
302
+ isFile: stat.isFile,
303
+ isDirectory: stat.isDirectory,
304
+ size: stat.size,
305
+ mtime: stat.mtime.toISOString()
306
+ };
307
+ }
308
+ }
309
+ };
310
+ }
311
+ function createFetchBinding(allowedDomains, network) {
312
+ const allowAll = network?.dangerouslyAllowAll === true;
313
+ const domainSet = new Set(allowedDomains.map((d) => d.toLowerCase()));
314
+ return {
315
+ description: "Internal fetch binding",
316
+ inputSchema: {
317
+ type: "object",
318
+ properties: {
319
+ url: { type: "string" },
320
+ method: { type: "string" },
321
+ headers: { type: "object", additionalProperties: { type: "string" } },
322
+ body: { type: "string" },
323
+ binary: { type: "boolean" }
324
+ },
325
+ required: ["url"]
326
+ },
327
+ handler: async (input) => {
328
+ const url = new URL(input.url);
329
+ if (!allowAll && !domainSet.has(url.hostname.toLowerCase())) {
330
+ throw new Error(
331
+ `Fetch blocked: domain "${url.hostname}" is not in the allowed list [${allowedDomains.join(", ")}]`
332
+ );
333
+ }
334
+ const resp = await fetch(input.url, {
335
+ method: input.method ?? "GET",
336
+ headers: input.headers ?? void 0,
337
+ body: input.body ?? void 0,
338
+ redirect: "follow"
339
+ });
340
+ const headers = {};
341
+ resp.headers.forEach((v, k) => {
342
+ headers[k] = v;
343
+ });
344
+ if (input.binary) {
345
+ const buf = await resp.arrayBuffer();
346
+ const base64 = Buffer.from(buf).toString("base64");
347
+ return { status: resp.status, statusText: resp.statusText, headers, body: base64, encoding: "base64" };
348
+ }
349
+ const body = await resp.text();
350
+ return { status: resp.status, statusText: resp.statusText, headers, body };
351
+ }
352
+ };
353
+ }
354
+ function mergeBuilderBindings(configBindings) {
355
+ return { ...configBindings };
356
+ }
357
+
358
+ // src/isolate/polyfills.ts
359
+ function buildPolyfillPreamble(hasNetwork) {
360
+ return [
361
+ POLYFILL_TEXT_ENCODING,
362
+ POLYFILL_ATOB_BTOA,
363
+ POLYFILL_BUFFER,
364
+ POLYFILL_PATH,
365
+ POLYFILL_FS,
366
+ hasNetwork ? POLYFILL_FETCH : POLYFILL_FETCH_STUB,
367
+ POLYFILL_TIMERS,
368
+ POLYFILL_CRYPTO,
369
+ POLYFILL_CONSOLE_EXTRAS,
370
+ POLYFILL_BLOB,
371
+ POLYFILL_STRUCTUREDCLONE
372
+ ].join("\n\n");
373
+ }
374
+ var POLYFILL_TEXT_ENCODING = `
375
+ // --- TextEncoder / TextDecoder polyfill ---
376
+ (function() {
377
+ if (typeof globalThis.TextEncoder === "undefined") {
378
+ globalThis.TextEncoder = class TextEncoder {
379
+ encode(str) {
380
+ str = String(str);
381
+ const buf = [];
382
+ for (let i = 0; i < str.length; i++) {
383
+ let code = str.charCodeAt(i);
384
+ if (code < 0x80) {
385
+ buf.push(code);
386
+ } else if (code < 0x800) {
387
+ buf.push(0xC0 | (code >> 6), 0x80 | (code & 0x3F));
388
+ } else if (code >= 0xD800 && code <= 0xDBFF && i + 1 < str.length) {
389
+ const next = str.charCodeAt(i + 1);
390
+ if (next >= 0xDC00 && next <= 0xDFFF) {
391
+ code = ((code - 0xD800) << 10) + (next - 0xDC00) + 0x10000;
392
+ i++;
393
+ buf.push(0xF0 | (code >> 18), 0x80 | ((code >> 12) & 0x3F), 0x80 | ((code >> 6) & 0x3F), 0x80 | (code & 0x3F));
394
+ }
395
+ } else {
396
+ buf.push(0xE0 | (code >> 12), 0x80 | ((code >> 6) & 0x3F), 0x80 | (code & 0x3F));
397
+ }
398
+ }
399
+ return new Uint8Array(buf);
400
+ }
401
+ };
402
+ }
403
+
404
+ if (typeof globalThis.TextDecoder === "undefined") {
405
+ globalThis.TextDecoder = class TextDecoder {
406
+ decode(input) {
407
+ if (!input || input.length === 0) return "";
408
+ const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
409
+ let result = "";
410
+ for (let i = 0; i < bytes.length;) {
411
+ const b = bytes[i];
412
+ if (b < 0x80) {
413
+ result += String.fromCharCode(b);
414
+ i++;
415
+ } else if ((b & 0xE0) === 0xC0) {
416
+ result += String.fromCharCode(((b & 0x1F) << 6) | (bytes[i + 1] & 0x3F));
417
+ i += 2;
418
+ } else if ((b & 0xF0) === 0xE0) {
419
+ result += String.fromCharCode(((b & 0x0F) << 12) | ((bytes[i + 1] & 0x3F) << 6) | (bytes[i + 2] & 0x3F));
420
+ i += 3;
421
+ } else if ((b & 0xF8) === 0xF0) {
422
+ const code = ((b & 0x07) << 18) | ((bytes[i + 1] & 0x3F) << 12) | ((bytes[i + 2] & 0x3F) << 6) | (bytes[i + 3] & 0x3F);
423
+ const adjusted = code - 0x10000;
424
+ result += String.fromCharCode(0xD800 + (adjusted >> 10), 0xDC00 + (adjusted & 0x3FF));
425
+ i += 4;
426
+ } else {
427
+ result += "\\uFFFD";
428
+ i++;
429
+ }
430
+ }
431
+ return result;
432
+ }
433
+ };
434
+ }
435
+ })();
436
+ `;
437
+ var POLYFILL_BUFFER = `
438
+ // --- Buffer polyfill ---
439
+ (function() {
440
+ if (typeof globalThis.Buffer !== "undefined") return;
441
+
442
+ class Buffer extends Uint8Array {
443
+ static from(input, encodingOrOffset, length) {
444
+ if (typeof input === "string") {
445
+ const encoding = (encodingOrOffset || "utf-8").toLowerCase();
446
+ if (encoding === "base64") {
447
+ const bin = atob(input);
448
+ const arr = new Uint8Array(bin.length);
449
+ for (let i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i);
450
+ return Object.setPrototypeOf(arr, Buffer.prototype);
451
+ }
452
+ if (encoding === "hex") {
453
+ const arr = new Uint8Array(input.length / 2);
454
+ for (let i = 0; i < input.length; i += 2)
455
+ arr[i / 2] = parseInt(input.slice(i, i + 2), 16);
456
+ return Object.setPrototypeOf(arr, Buffer.prototype);
457
+ }
458
+ // utf-8 default
459
+ const encoded = new TextEncoder().encode(input);
460
+ return Object.setPrototypeOf(encoded, Buffer.prototype);
461
+ }
462
+ if (input instanceof ArrayBuffer) {
463
+ const arr = new Uint8Array(input, encodingOrOffset, length);
464
+ return Object.setPrototypeOf(arr, Buffer.prototype);
465
+ }
466
+ if (ArrayBuffer.isView(input)) {
467
+ const arr = new Uint8Array(input.buffer, input.byteOffset, input.byteLength);
468
+ return Object.setPrototypeOf(arr, Buffer.prototype);
469
+ }
470
+ if (Array.isArray(input)) {
471
+ const arr = new Uint8Array(input);
472
+ return Object.setPrototypeOf(arr, Buffer.prototype);
473
+ }
474
+ throw new TypeError("Buffer.from: unsupported input type");
475
+ }
476
+
477
+ static alloc(size, fill) {
478
+ const arr = new Uint8Array(size);
479
+ if (fill !== undefined) {
480
+ const fillByte = typeof fill === "number" ? fill : (typeof fill === "string" ? fill.charCodeAt(0) : 0);
481
+ arr.fill(fillByte);
482
+ }
483
+ return Object.setPrototypeOf(arr, Buffer.prototype);
484
+ }
485
+
486
+ static allocUnsafe(size) { return Buffer.alloc(size); }
487
+
488
+ static concat(list, totalLength) {
489
+ if (!totalLength) totalLength = list.reduce((s, b) => s + b.length, 0);
490
+ const result = Buffer.alloc(totalLength);
491
+ let offset = 0;
492
+ for (const buf of list) {
493
+ result.set(buf, offset);
494
+ offset += buf.length;
495
+ }
496
+ return result;
497
+ }
498
+
499
+ static isBuffer(obj) { return obj instanceof Buffer || obj instanceof Uint8Array; }
500
+
501
+ toString(encoding) {
502
+ encoding = (encoding || "utf-8").toLowerCase();
503
+ if (encoding === "base64") {
504
+ let bin = "";
505
+ for (let i = 0; i < this.length; i++) bin += String.fromCharCode(this[i]);
506
+ return btoa(bin);
507
+ }
508
+ if (encoding === "hex") {
509
+ return Array.from(this).map(b => b.toString(16).padStart(2, "0")).join("");
510
+ }
511
+ return new TextDecoder().decode(this);
512
+ }
513
+
514
+ toJSON() {
515
+ return { type: "Buffer", data: Array.from(this) };
516
+ }
517
+
518
+ write(string, offset, length, encoding) {
519
+ offset = offset || 0;
520
+ const encoded = Buffer.from(string, encoding || "utf-8");
521
+ const bytesToCopy = Math.min(encoded.length, length || this.length - offset);
522
+ this.set(encoded.subarray(0, bytesToCopy), offset);
523
+ return bytesToCopy;
524
+ }
525
+
526
+ copy(target, targetStart, sourceStart, sourceEnd) {
527
+ targetStart = targetStart || 0;
528
+ sourceStart = sourceStart || 0;
529
+ sourceEnd = sourceEnd || this.length;
530
+ const slice = this.subarray(sourceStart, sourceEnd);
531
+ target.set(slice, targetStart);
532
+ return slice.length;
533
+ }
534
+
535
+ equals(other) {
536
+ if (this.length !== other.length) return false;
537
+ for (let i = 0; i < this.length; i++) {
538
+ if (this[i] !== other[i]) return false;
539
+ }
540
+ return true;
541
+ }
542
+
543
+ slice(start, end) {
544
+ const sliced = Uint8Array.prototype.slice.call(this, start, end);
545
+ return Object.setPrototypeOf(sliced, Buffer.prototype);
546
+ }
547
+ }
548
+
549
+ globalThis.Buffer = Buffer;
550
+ })();
551
+ `;
552
+ var POLYFILL_ATOB_BTOA = `
553
+ // --- atob / btoa polyfill ---
554
+ (function() {
555
+ const B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
556
+ const B64_LOOKUP = new Uint8Array(128);
557
+ for (let i = 0; i < B64.length; i++) B64_LOOKUP[B64.charCodeAt(i)] = i;
558
+
559
+ if (typeof globalThis.atob === "undefined") {
560
+ globalThis.atob = function(input) {
561
+ input = String(input).replace(/[\\s=]+/g, "");
562
+ let output = "";
563
+ let bits = 0, collected = 0;
564
+ for (let i = 0; i < input.length; i++) {
565
+ bits = (bits << 6) | B64_LOOKUP[input.charCodeAt(i)];
566
+ collected += 6;
567
+ if (collected >= 8) {
568
+ collected -= 8;
569
+ output += String.fromCharCode((bits >> collected) & 0xFF);
570
+ }
571
+ }
572
+ return output;
573
+ };
574
+ }
575
+
576
+ if (typeof globalThis.btoa === "undefined") {
577
+ globalThis.btoa = function(input) {
578
+ input = String(input);
579
+ let output = "";
580
+ for (let i = 0; i < input.length; i += 3) {
581
+ const a = input.charCodeAt(i);
582
+ const b = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
583
+ const c = i + 2 < input.length ? input.charCodeAt(i + 2) : 0;
584
+ output += B64[a >> 2];
585
+ output += B64[((a & 3) << 4) | (b >> 4)];
586
+ output += i + 1 < input.length ? B64[((b & 15) << 2) | (c >> 6)] : "=";
587
+ output += i + 2 < input.length ? B64[c & 63] : "=";
588
+ }
589
+ return output;
590
+ };
591
+ }
592
+ })();
593
+ `;
594
+ var POLYFILL_PATH = `
595
+ // --- path module polyfill ---
596
+ (function() {
597
+ const path = {
598
+ sep: "/",
599
+ join: function() {
600
+ const parts = Array.from(arguments).filter(Boolean);
601
+ return path.normalize(parts.join("/"));
602
+ },
603
+ resolve: function() {
604
+ let resolved = "";
605
+ for (let i = arguments.length - 1; i >= 0; i--) {
606
+ const part = arguments[i];
607
+ if (!part) continue;
608
+ resolved = part + (resolved ? "/" + resolved : "");
609
+ if (part.startsWith("/")) break;
610
+ }
611
+ return path.normalize(resolved.startsWith("/") ? resolved : "/" + resolved);
612
+ },
613
+ normalize: function(p) {
614
+ const parts = p.split("/");
615
+ const result = [];
616
+ for (const part of parts) {
617
+ if (part === "." || part === "") continue;
618
+ if (part === ".." && result.length > 0 && result[result.length - 1] !== "..") {
619
+ result.pop();
620
+ } else {
621
+ result.push(part);
622
+ }
623
+ }
624
+ return (p.startsWith("/") ? "/" : "") + result.join("/");
625
+ },
626
+ basename: function(p, ext) {
627
+ const base = p.split("/").filter(Boolean).pop() || "";
628
+ if (ext && base.endsWith(ext)) return base.slice(0, -ext.length);
629
+ return base;
630
+ },
631
+ dirname: function(p) {
632
+ const parts = p.split("/").filter(Boolean);
633
+ parts.pop();
634
+ return (p.startsWith("/") ? "/" : "") + parts.join("/") || ".";
635
+ },
636
+ extname: function(p) {
637
+ const base = path.basename(p);
638
+ const dot = base.lastIndexOf(".");
639
+ return dot > 0 ? base.slice(dot) : "";
640
+ },
641
+ isAbsolute: function(p) { return p.startsWith("/"); },
642
+ parse: function(p) {
643
+ return {
644
+ root: p.startsWith("/") ? "/" : "",
645
+ dir: path.dirname(p),
646
+ base: path.basename(p),
647
+ ext: path.extname(p),
648
+ name: path.basename(p, path.extname(p)),
649
+ };
650
+ },
651
+ };
652
+ globalThis.__modules = globalThis.__modules || {};
653
+ globalThis.__modules.path = path;
654
+ globalThis.path = path;
655
+ })();
656
+ `;
657
+ var POLYFILL_FS = `
658
+ // --- fs module polyfill ---
659
+ (function() {
660
+ function _b64ToUint8(b64) {
661
+ const bin = atob(b64);
662
+ const arr = new Uint8Array(bin.length);
663
+ for (let i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i);
664
+ return arr;
665
+ }
666
+
667
+ function _uint8ToB64(u8) {
668
+ let bin = "";
669
+ for (let i = 0; i < u8.length; i++) bin += String.fromCharCode(u8[i]);
670
+ return btoa(bin);
671
+ }
672
+
673
+ function _toBuffer(data, encoding) {
674
+ if (typeof data === "string") return Buffer.from(data, encoding || "utf-8");
675
+ if (data instanceof Uint8Array) return data;
676
+ return Buffer.from(String(data), "utf-8");
677
+ }
678
+
679
+ const fs = {
680
+ // --- Async API ---
681
+ readFile: async function(path, options) {
682
+ const encoding = typeof options === "string" ? options : options?.encoding;
683
+ if (!encoding || encoding === "buffer") {
684
+ const b64 = await __poncho_fs_read_binary({ path });
685
+ return Buffer.from(_b64ToUint8(b64));
686
+ }
687
+ return await __poncho_fs_read({ path });
688
+ },
689
+
690
+ writeFile: async function(path, data, options) {
691
+ const encoding = typeof options === "string" ? options : options?.encoding;
692
+ if (data instanceof Uint8Array || data instanceof ArrayBuffer) {
693
+ const u8 = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
694
+ await __poncho_fs_write_binary({ path, content: _uint8ToB64(u8) });
695
+ return;
696
+ }
697
+ if (encoding === "base64") {
698
+ await __poncho_fs_write_binary({ path, content: data });
699
+ return;
700
+ }
701
+ await __poncho_fs_write({ path, content: String(data) });
702
+ },
703
+
704
+ readdir: async function(path) {
705
+ return await __poncho_fs_list({ path: path || "/" });
706
+ },
707
+
708
+ mkdir: async function(path, options) {
709
+ await __poncho_fs_mkdir({ path });
710
+ },
711
+
712
+ stat: async function(path) {
713
+ const s = await __poncho_fs_stat({ path });
714
+ return {
715
+ isFile: function() { return s.isFile; },
716
+ isDirectory: function() { return s.isDirectory; },
717
+ size: s.size,
718
+ mtime: new Date(s.mtime),
719
+ };
720
+ },
721
+
722
+ unlink: async function(path) {
723
+ await __poncho_fs_delete({ path });
724
+ },
725
+
726
+ rm: async function(path) {
727
+ await __poncho_fs_delete({ path });
728
+ },
729
+
730
+ exists: async function(path) {
731
+ return await __poncho_fs_exists({ path });
732
+ },
733
+
734
+ // --- Sync-style API (actually async under the hood, but works with await) ---
735
+ readFileSync: function(path, options) {
736
+ return fs.readFile(path, options);
737
+ },
738
+
739
+ writeFileSync: function(path, data, options) {
740
+ return fs.writeFile(path, data, options);
741
+ },
742
+
743
+ readdirSync: function(path) { return fs.readdir(path); },
744
+ mkdirSync: function(path, options) { return fs.mkdir(path, options); },
745
+ statSync: function(path) { return fs.stat(path); },
746
+ unlinkSync: function(path) { return fs.unlink(path); },
747
+ existsSync: function(path) { return fs.exists(path); },
748
+ rmSync: function(path) { return fs.rm(path); },
749
+
750
+ promises: {},
751
+ };
752
+
753
+ // fs.promises mirrors the async API
754
+ for (const key of Object.keys(fs)) {
755
+ if (typeof fs[key] === "function" && key !== "promises") {
756
+ fs.promises[key] = fs[key];
757
+ }
758
+ }
759
+
760
+ globalThis.__modules = globalThis.__modules || {};
761
+ globalThis.__modules.fs = fs;
762
+ globalThis.__modules["fs/promises"] = fs.promises;
763
+ globalThis.__modules["node:fs"] = fs;
764
+ globalThis.__modules["node:fs/promises"] = fs.promises;
765
+ globalThis.fs = fs;
766
+ })();
767
+ `;
768
+ var POLYFILL_FETCH = `
769
+ // --- fetch polyfill ---
770
+ (function() {
771
+ class Headers {
772
+ constructor(init) {
773
+ this._map = {};
774
+ if (init) {
775
+ const entries = typeof init.entries === "function"
776
+ ? Array.from(init.entries())
777
+ : Object.entries(init);
778
+ for (const [k, v] of entries) this._map[k.toLowerCase()] = String(v);
779
+ }
780
+ }
781
+ get(name) { return this._map[name.toLowerCase()] ?? null; }
782
+ set(name, value) { this._map[name.toLowerCase()] = String(value); }
783
+ has(name) { return name.toLowerCase() in this._map; }
784
+ delete(name) { delete this._map[name.toLowerCase()]; }
785
+ forEach(cb) { for (const [k, v] of Object.entries(this._map)) cb(v, k, this); }
786
+ entries() { return Object.entries(this._map)[Symbol.iterator](); }
787
+ keys() { return Object.keys(this._map)[Symbol.iterator](); }
788
+ values() { return Object.values(this._map)[Symbol.iterator](); }
789
+ }
790
+
791
+ class Response {
792
+ constructor(result, binary) {
793
+ this.status = result.status;
794
+ this.statusText = result.statusText || "";
795
+ this.ok = result.status >= 200 && result.status < 300;
796
+ this.headers = new Headers(result.headers);
797
+ this._body = result.body;
798
+ this._binary = binary;
799
+ this._consumed = false;
800
+ }
801
+
802
+ _checkConsumed() {
803
+ if (this._consumed) throw new TypeError("Body already consumed");
804
+ this._consumed = true;
805
+ }
806
+
807
+ async text() {
808
+ this._checkConsumed();
809
+ if (this._binary) {
810
+ // Decode base64 \u2192 binary string \u2192 UTF-8
811
+ const bytes = _fetchB64ToUint8(this._body);
812
+ return new TextDecoder().decode(bytes);
813
+ }
814
+ return this._body;
815
+ }
816
+
817
+ async json() {
818
+ const text = await this.text();
819
+ return JSON.parse(text);
820
+ }
821
+
822
+ async arrayBuffer() {
823
+ this._checkConsumed();
824
+ if (this._binary) {
825
+ const bytes = _fetchB64ToUint8(this._body);
826
+ return bytes.buffer;
827
+ }
828
+ return new TextEncoder().encode(this._body).buffer;
829
+ }
830
+
831
+ async blob() {
832
+ const ab = await this.arrayBuffer();
833
+ const type = this.headers.get("content-type") || "";
834
+ return new Blob([ab], { type });
835
+ }
836
+ }
837
+
838
+ function _fetchB64ToUint8(b64) {
839
+ const bin = atob(b64);
840
+ const arr = new Uint8Array(bin.length);
841
+ for (let i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i);
842
+ return arr;
843
+ }
844
+
845
+ globalThis.fetch = async function(input, init) {
846
+ const url = typeof input === "string" ? input : (input?.url || String(input));
847
+ const method = init?.method || "GET";
848
+ const headers = {};
849
+ if (init?.headers) {
850
+ const entries = typeof init.headers.entries === "function"
851
+ ? Array.from(init.headers.entries())
852
+ : Object.entries(init.headers);
853
+ for (const [k, v] of entries) headers[k] = String(v);
854
+ }
855
+ const body = init?.body ? String(init.body) : undefined;
856
+
857
+ // Always fetch as binary to preserve data integrity
858
+ const result = await __poncho_fetch({ url, method, headers, body, binary: true });
859
+ return new Response(result, true);
860
+ };
861
+
862
+ globalThis.Headers = Headers;
863
+ globalThis.Response = Response;
864
+ })();
865
+ `;
866
+ var POLYFILL_FETCH_STUB = `
867
+ // --- fetch stub (network not configured) ---
868
+ (function() {
869
+ globalThis.fetch = async function() {
870
+ throw new Error(
871
+ "fetch() is not available. Enable network access in poncho.config.js:\\n" +
872
+ " network: { allowedUrls: [\\"https://...\\"]}\\n" +
873
+ " // or: network: { dangerouslyAllowAll: true }"
874
+ );
875
+ };
876
+ })();
877
+ `;
878
+ var POLYFILL_TIMERS = `
879
+ // --- Timers polyfill ---
880
+ (function() {
881
+ let __timerId = 0;
882
+ const __timers = new Map();
883
+
884
+ globalThis.setTimeout = function(fn, delay) {
885
+ const id = ++__timerId;
886
+ const ms = Math.max(0, Number(delay) || 0);
887
+ const start = Date.now();
888
+ __timers.set(id, { fn, ms, start, type: "timeout" });
889
+ // In the isolate, setTimeout returns the id but the callback is
890
+ // executed via a polling mechanism in the async wrapper.
891
+ // For simple cases (delay=0), we can use a microtask.
892
+ if (ms === 0) {
893
+ Promise.resolve().then(() => {
894
+ if (__timers.has(id)) {
895
+ __timers.delete(id);
896
+ fn();
897
+ }
898
+ });
899
+ }
900
+ return id;
901
+ };
902
+
903
+ globalThis.clearTimeout = function(id) {
904
+ __timers.delete(id);
905
+ };
906
+
907
+ globalThis.setInterval = function(fn, delay) {
908
+ const id = ++__timerId;
909
+ const ms = Math.max(1, Number(delay) || 1);
910
+ const wrapper = () => {
911
+ if (!__timers.has(id)) return;
912
+ fn();
913
+ if (__timers.has(id)) {
914
+ globalThis.setTimeout(wrapper, ms);
915
+ }
916
+ };
917
+ __timers.set(id, { fn: wrapper, ms, type: "interval" });
918
+ globalThis.setTimeout(wrapper, ms);
919
+ return id;
920
+ };
921
+
922
+ globalThis.clearInterval = function(id) {
923
+ __timers.delete(id);
924
+ };
925
+
926
+ // queueMicrotask if not available
927
+ if (typeof globalThis.queueMicrotask === "undefined") {
928
+ globalThis.queueMicrotask = function(fn) { Promise.resolve().then(fn); };
929
+ }
930
+ })();
931
+ `;
932
+ var POLYFILL_CRYPTO = `
933
+ // --- crypto polyfill ---
934
+ (function() {
935
+ if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.getRandomValues) return;
936
+
937
+ function getRandomValues(arr) {
938
+ for (let i = 0; i < arr.length; i++) {
939
+ arr[i] = Math.floor(Math.random() * 256);
940
+ }
941
+ return arr;
942
+ }
943
+
944
+ function randomUUID() {
945
+ const bytes = new Uint8Array(16);
946
+ getRandomValues(bytes);
947
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
948
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
949
+ const hex = Array.from(bytes).map(b => b.toString(16).padStart(2, "0")).join("");
950
+ return hex.slice(0, 8) + "-" + hex.slice(8, 12) + "-" + hex.slice(12, 16) + "-" + hex.slice(16, 20) + "-" + hex.slice(20);
951
+ }
952
+
953
+ globalThis.crypto = {
954
+ getRandomValues,
955
+ randomUUID,
956
+ subtle: {
957
+ async digest(algorithm, data) {
958
+ // Simple SHA-256 not feasible in pure JS without a library.
959
+ // Provide a helpful error for now.
960
+ throw new Error("crypto.subtle.digest is not available in the sandbox. Use a bundled library instead.");
961
+ },
962
+ },
963
+ };
964
+ })();
965
+ `;
966
+ var POLYFILL_CONSOLE_EXTRAS = `
967
+ // --- console extras ---
968
+ (function() {
969
+ const __timeLabels = new Map();
970
+
971
+ console.table = function(data) {
972
+ try {
973
+ if (Array.isArray(data)) {
974
+ const keys = data.length > 0 && typeof data[0] === "object" ? Object.keys(data[0]) : null;
975
+ if (keys) {
976
+ const header = ["(index)", ...keys].join("\\t");
977
+ const rows = data.map((row, i) => [i, ...keys.map(k => row[k] ?? "")].join("\\t"));
978
+ console.log(header + "\\n" + rows.join("\\n"));
979
+ return;
980
+ }
981
+ }
982
+ console.log(JSON.stringify(data, null, 2));
983
+ } catch { console.log(String(data)); }
984
+ };
985
+
986
+ console.time = function(label) {
987
+ __timeLabels.set(label || "default", Date.now());
988
+ };
989
+
990
+ console.timeEnd = function(label) {
991
+ label = label || "default";
992
+ const start = __timeLabels.get(label);
993
+ if (start !== undefined) {
994
+ __timeLabels.delete(label);
995
+ console.log(label + ": " + (Date.now() - start) + "ms");
996
+ }
997
+ };
998
+
999
+ console.timeLog = function(label) {
1000
+ label = label || "default";
1001
+ const start = __timeLabels.get(label);
1002
+ if (start !== undefined) {
1003
+ console.log(label + ": " + (Date.now() - start) + "ms");
1004
+ }
1005
+ };
1006
+
1007
+ console.assert = function(condition) {
1008
+ if (!condition) {
1009
+ const args = Array.from(arguments).slice(1);
1010
+ console.error("Assertion failed:", ...args);
1011
+ }
1012
+ };
1013
+
1014
+ console.dir = function(obj) { console.log(obj); };
1015
+ console.count = (function() {
1016
+ const counts = {};
1017
+ return function(label) {
1018
+ label = label || "default";
1019
+ counts[label] = (counts[label] || 0) + 1;
1020
+ console.log(label + ": " + counts[label]);
1021
+ };
1022
+ })();
1023
+ })();
1024
+ `;
1025
+ var POLYFILL_BLOB = `
1026
+ // --- Blob polyfill ---
1027
+ (function() {
1028
+ if (typeof globalThis.Blob !== "undefined") return;
1029
+
1030
+ class Blob {
1031
+ constructor(parts, options) {
1032
+ this.type = (options && options.type) || "";
1033
+ const chunks = [];
1034
+ if (parts) {
1035
+ for (const part of parts) {
1036
+ if (typeof part === "string") {
1037
+ chunks.push(new TextEncoder().encode(part));
1038
+ } else if (part instanceof ArrayBuffer) {
1039
+ chunks.push(new Uint8Array(part));
1040
+ } else if (ArrayBuffer.isView(part)) {
1041
+ chunks.push(new Uint8Array(part.buffer, part.byteOffset, part.byteLength));
1042
+ } else if (part instanceof Blob) {
1043
+ chunks.push(part._data);
1044
+ }
1045
+ }
1046
+ }
1047
+ let totalLen = 0;
1048
+ for (const c of chunks) totalLen += c.length;
1049
+ this._data = new Uint8Array(totalLen);
1050
+ let offset = 0;
1051
+ for (const c of chunks) {
1052
+ this._data.set(c, offset);
1053
+ offset += c.length;
1054
+ }
1055
+ this.size = this._data.length;
1056
+ }
1057
+
1058
+ async arrayBuffer() { return this._data.buffer.slice(this._data.byteOffset, this._data.byteOffset + this._data.byteLength); }
1059
+ async text() { return new TextDecoder().decode(this._data); }
1060
+ slice(start, end, type) {
1061
+ const sliced = this._data.slice(start || 0, end || this._data.length);
1062
+ const blob = new Blob([], { type: type || this.type });
1063
+ blob._data = sliced;
1064
+ blob.size = sliced.length;
1065
+ return blob;
1066
+ }
1067
+ }
1068
+
1069
+ globalThis.Blob = Blob;
1070
+ })();
1071
+ `;
1072
+ var POLYFILL_STRUCTUREDCLONE = `
1073
+ // --- structuredClone polyfill ---
1074
+ (function() {
1075
+ if (typeof globalThis.structuredClone !== "undefined") return;
1076
+ globalThis.structuredClone = function(value) {
1077
+ return JSON.parse(JSON.stringify(value));
1078
+ };
1079
+ })();
1080
+ `;
1081
+
1082
+ // src/isolate/run-code-tool.ts
1083
+ var esbuildTransform;
1084
+ async function loadEsbuild() {
1085
+ if (esbuildTransform) return esbuildTransform;
1086
+ try {
1087
+ const mod = await import("esbuild");
1088
+ esbuildTransform = mod.transform;
1089
+ return esbuildTransform;
1090
+ } catch {
1091
+ throw new Error(
1092
+ "Code execution requires esbuild for TypeScript stripping. Run: pnpm add esbuild"
1093
+ );
1094
+ }
1095
+ }
1096
+ async function stripTypeScript(code) {
1097
+ const transform = await loadEsbuild();
1098
+ const wrapped = "async function __poncho_wrapper__() {\n" + code + "\n}";
1099
+ const result = await transform(wrapped, { loader: "ts" });
1100
+ const stripped = result.code.replace(/^async function __poncho_wrapper__\(\)\s*\{\n?/, "").replace(/\n?\}\s*$/, "");
1101
+ return stripped;
1102
+ }
1103
+ var ALLOWED_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".ts", ".mjs", ".mts"]);
1104
+ function hasAllowedExtension(path) {
1105
+ const dot = path.lastIndexOf(".");
1106
+ if (dot === -1) return false;
1107
+ return ALLOWED_EXTENSIONS.has(path.slice(dot).toLowerCase());
1108
+ }
1109
+ function createRunCodeTool(opts) {
1110
+ const { config, bashManager, libraryPreamble, description } = opts;
1111
+ const memoryLimit = config.memoryLimit ?? 128;
1112
+ const timeout = config.timeLimit ?? 1e4;
1113
+ const outputLimit = config.outputLimit ?? 65536;
1114
+ const codeLimit = config.codeLimit ?? 102400;
1115
+ const runtime = createIsolateRuntime({
1116
+ memoryLimit,
1117
+ timeout,
1118
+ outputLimit
1119
+ });
1120
+ const staticBindings = {};
1121
+ if (config.apis?.fetch) {
1122
+ staticBindings.__poncho_fetch = createFetchBinding(config.apis.fetch.allowedDomains);
1123
+ } else if (opts.network) {
1124
+ const net = opts.network;
1125
+ if (net.dangerouslyAllowAll) {
1126
+ staticBindings.__poncho_fetch = createFetchBinding([], net);
1127
+ } else if (net.allowedUrls?.length) {
1128
+ const domains = [];
1129
+ for (const entry of net.allowedUrls) {
1130
+ const urlStr = typeof entry === "string" ? entry : entry.url;
1131
+ try {
1132
+ domains.push(new URL(urlStr).hostname);
1133
+ } catch {
1134
+ }
1135
+ }
1136
+ if (domains.length > 0) {
1137
+ staticBindings.__poncho_fetch = createFetchBinding(domains, net);
1138
+ }
1139
+ }
1140
+ }
1141
+ if (config.bindings) {
1142
+ Object.assign(staticBindings, mergeBuilderBindings(config.bindings));
1143
+ }
1144
+ const hasNetwork = "__poncho_fetch" in staticBindings;
1145
+ const polyfillPreamble = buildPolyfillPreamble(hasNetwork);
1146
+ return defineTool({
1147
+ name: "run_code",
1148
+ description,
1149
+ inputSchema: {
1150
+ type: "object",
1151
+ properties: {
1152
+ code: {
1153
+ type: "string",
1154
+ description: "JavaScript or TypeScript code to execute"
1155
+ },
1156
+ file: {
1157
+ type: "string",
1158
+ description: "Path to a .js/.ts file in the VFS to execute instead of inline code"
1159
+ }
1160
+ },
1161
+ additionalProperties: false
1162
+ },
1163
+ handler: async (input, context) => {
1164
+ const code = input.code;
1165
+ const file = input.file;
1166
+ if (code && file) {
1167
+ return { error: "Provide either `code` or `file`, not both." };
1168
+ }
1169
+ if (!code && !file) {
1170
+ return { error: "Provide either `code` (inline) or `file` (VFS path)." };
1171
+ }
1172
+ let source;
1173
+ if (file) {
1174
+ if (!hasAllowedExtension(file)) {
1175
+ return {
1176
+ error: `File must have a .js, .ts, .mjs, or .mts extension. Got: "${file}"`
1177
+ };
1178
+ }
1179
+ const tenantId2 = context.tenantId ?? "__default__";
1180
+ const adapter2 = bashManager.getAdapter(tenantId2);
1181
+ try {
1182
+ source = await adapter2.readFile(file);
1183
+ } catch (err) {
1184
+ return {
1185
+ error: `Failed to read file "${file}": ${err instanceof Error ? err.message : String(err)}`
1186
+ };
1187
+ }
1188
+ } else {
1189
+ source = code;
1190
+ }
1191
+ if (source.length > codeLimit) {
1192
+ return {
1193
+ error: `Code exceeds size limit: ${source.length} bytes > ${codeLimit} byte max.`
1194
+ };
1195
+ }
1196
+ let jsCode;
1197
+ try {
1198
+ jsCode = await stripTypeScript(source);
1199
+ } catch (err) {
1200
+ const msg = err instanceof Error ? err.message : String(err);
1201
+ return { error: `TypeScript parse error: ${msg}` };
1202
+ }
1203
+ const tenantId = context.tenantId ?? "__default__";
1204
+ const adapter = bashManager.getAdapter(tenantId);
1205
+ const vfsBindings = createVfsBindings(adapter);
1206
+ const allBindings = {
1207
+ ...vfsBindings,
1208
+ ...staticBindings
1209
+ };
1210
+ const result = await runtime.execute(
1211
+ jsCode,
1212
+ allBindings,
1213
+ libraryPreamble,
1214
+ context.abortSignal,
1215
+ polyfillPreamble
1216
+ );
1217
+ if (result.error) {
1218
+ return {
1219
+ error: result.error.message,
1220
+ errorName: result.error.name,
1221
+ line: result.error.line,
1222
+ column: result.error.column,
1223
+ stdout: result.stdout || void 0,
1224
+ stderr: result.stderr || void 0,
1225
+ executionTimeMs: result.executionTimeMs
1226
+ };
1227
+ }
1228
+ return {
1229
+ result: result.result,
1230
+ stdout: result.stdout || void 0,
1231
+ stderr: result.stderr || void 0,
1232
+ executionTimeMs: result.executionTimeMs
1233
+ };
1234
+ }
1235
+ });
1236
+ }
1237
+
1238
+ // src/isolate/type-stubs.ts
1239
+ function generateIsolateTypeStubs(config) {
1240
+ const lines = [];
1241
+ lines.push(
1242
+ "// Standard Web/Node.js APIs available in the sandbox",
1243
+ "",
1244
+ "// --- fetch (standard Web API) ---",
1245
+ "declare function fetch(url: string, init?: { method?: string; headers?: Record<string, string>; body?: string }): Promise<Response>;",
1246
+ "declare class Response {",
1247
+ " readonly ok: boolean;",
1248
+ " readonly status: number;",
1249
+ " readonly statusText: string;",
1250
+ " readonly headers: Headers;",
1251
+ " text(): Promise<string>;",
1252
+ " json(): Promise<any>;",
1253
+ " arrayBuffer(): Promise<ArrayBuffer>;",
1254
+ " blob(): Promise<Blob>;",
1255
+ "}",
1256
+ "",
1257
+ "// --- fs (Node.js-compatible) ---",
1258
+ "declare const fs: {",
1259
+ " readFile(path: string, encoding?: string): Promise<string | Buffer>;",
1260
+ " writeFile(path: string, data: string | Buffer | Uint8Array): Promise<void>;",
1261
+ " readdir(path: string): Promise<string[]>;",
1262
+ " mkdir(path: string): Promise<void>;",
1263
+ " stat(path: string): Promise<{ isFile(): boolean; isDirectory(): boolean; size: number; mtime: Date }>;",
1264
+ " exists(path: string): Promise<boolean>;",
1265
+ " unlink(path: string): Promise<void>;",
1266
+ " rm(path: string): Promise<void>;",
1267
+ "};",
1268
+ "",
1269
+ "// --- path ---",
1270
+ "declare const path: {",
1271
+ " join(...parts: string[]): string;",
1272
+ " resolve(...parts: string[]): string;",
1273
+ " basename(p: string, ext?: string): string;",
1274
+ " dirname(p: string): string;",
1275
+ " extname(p: string): string;",
1276
+ "};",
1277
+ "",
1278
+ "// --- Buffer, encoding, crypto ---",
1279
+ "declare class Buffer extends Uint8Array {",
1280
+ " static from(input: string | ArrayBuffer | Uint8Array | number[], encoding?: string): Buffer;",
1281
+ " static alloc(size: number, fill?: number): Buffer;",
1282
+ " static concat(list: Uint8Array[]): Buffer;",
1283
+ " toString(encoding?: 'utf-8' | 'base64' | 'hex'): string;",
1284
+ "}",
1285
+ "declare function atob(data: string): string;",
1286
+ "declare function btoa(data: string): string;",
1287
+ "declare function setTimeout(fn: () => void, ms?: number): number;",
1288
+ "declare function clearTimeout(id: number): void;",
1289
+ "declare const crypto: { randomUUID(): string; getRandomValues(arr: Uint8Array): Uint8Array };",
1290
+ "declare function structuredClone<T>(value: T): T;"
1291
+ );
1292
+ lines.push(
1293
+ "",
1294
+ "// Console (output captured and returned in tool result)",
1295
+ "declare const console: {",
1296
+ " log(...args: unknown[]): void; error(...args: unknown[]): void;",
1297
+ " warn(...args: unknown[]): void; info(...args: unknown[]): void;",
1298
+ " table(data: unknown): void; time(label?: string): void; timeEnd(label?: string): void;",
1299
+ "};"
1300
+ );
1301
+ if (config.bindings) {
1302
+ const entries = Object.entries(config.bindings);
1303
+ if (entries.length > 0) {
1304
+ lines.push("", "// Custom bindings");
1305
+ for (const [name, binding] of entries) {
1306
+ lines.push(formatBindingStub(name, binding));
1307
+ }
1308
+ }
1309
+ }
1310
+ if (config.libraries?.length) {
1311
+ lines.push(
1312
+ "",
1313
+ `// Pre-bundled libraries (use require())`,
1314
+ `declare function require(name: ${config.libraries.map((l) => `"${l}"`).join(" | ")}): any;`
1315
+ );
1316
+ }
1317
+ return lines.join("\n");
1318
+ }
1319
+ function buildRunCodeDescription(config, hasNetwork) {
1320
+ const parts = [
1321
+ "Execute JavaScript/TypeScript code in a sandboxed V8 isolate with standard Node.js/Web APIs.",
1322
+ "",
1323
+ "Input: provide either `code` (inline string) or `file` (path to a .js/.ts file in the VFS).",
1324
+ "",
1325
+ "Available standard APIs:",
1326
+ "- fs.readFile(path, encoding?) / fs.writeFile(path, data) / fs.readdir(path) / fs.mkdir(path)",
1327
+ "- fs.stat(path) / fs.exists(path) / fs.unlink(path)",
1328
+ "- path.join() / path.resolve() / path.basename() / path.dirname() / path.extname()",
1329
+ "- Buffer.from() / Buffer.alloc() / Buffer.concat() / buf.toString(encoding)",
1330
+ "- atob() / btoa() / setTimeout() / crypto.randomUUID() / structuredClone()",
1331
+ "- console.log() / console.error() / console.table()"
1332
+ ];
1333
+ if (hasNetwork || config.apis?.fetch) {
1334
+ parts.push(
1335
+ "- fetch(url, init?) \u2014 standard Web fetch API with Response.text(), .json(), .arrayBuffer()"
1336
+ );
1337
+ } else {
1338
+ parts.push(
1339
+ "- fetch() \u2014 not available (enable `network` in poncho.config.js)"
1340
+ );
1341
+ }
1342
+ if (config.bindings) {
1343
+ for (const [name, binding] of Object.entries(config.bindings)) {
1344
+ parts.push(`- ${name}({...}) \u2014 ${binding.description}`);
1345
+ }
1346
+ }
1347
+ if (config.libraries?.length) {
1348
+ parts.push(
1349
+ "",
1350
+ `Pre-bundled libraries (use require()):`,
1351
+ `- ${config.libraries.join(", ")}`
1352
+ );
1353
+ }
1354
+ const memoryLimit = config.memoryLimit ?? 128;
1355
+ const timeLimit = config.timeLimit ?? 1e4;
1356
+ const codeLimit = config.codeLimit ?? 102400;
1357
+ parts.push(
1358
+ "",
1359
+ "Notes:",
1360
+ "- Code is wrapped in an async IIFE. Use `return` to return a value.",
1361
+ "- Files written during execution persist even if the code throws an error.",
1362
+ "- TypeScript is supported (type annotations are stripped before execution).",
1363
+ `- Execution timeout: ${timeLimit / 1e3}s. Memory limit: ${memoryLimit}MB. Max code size: ${Math.round(codeLimit / 1024)}KB.`
1364
+ );
1365
+ return parts.join("\n");
1366
+ }
1367
+ function formatBindingStub(name, binding) {
1368
+ const schema = binding.inputSchema;
1369
+ const props = schema.properties ?? {};
1370
+ const required = new Set(schema.required ?? []);
1371
+ const params = Object.entries(props).map(([k, v]) => {
1372
+ const opt = required.has(k) ? "" : "?";
1373
+ const tsType = jsonSchemaToTsType(v);
1374
+ return `${k}${opt}: ${tsType}`;
1375
+ }).join("; ");
1376
+ return `declare function ${name}(input: { ${params} }): Promise<unknown>; // ${binding.description}`;
1377
+ }
1378
+ function jsonSchemaToTsType(schema) {
1379
+ switch (schema.type) {
1380
+ case "string":
1381
+ return "string";
1382
+ case "number":
1383
+ case "integer":
1384
+ return "number";
1385
+ case "boolean":
1386
+ return "boolean";
1387
+ case "array":
1388
+ return "unknown[]";
1389
+ case "object":
1390
+ return "Record<string, unknown>";
1391
+ default:
1392
+ return "unknown";
1393
+ }
1394
+ }
1395
+
1396
+ // src/isolate/bundler.ts
1397
+ import { resolve } from "path";
1398
+ import { existsSync, readFileSync } from "fs";
1399
+ var esbuildBuild;
1400
+ async function loadEsbuild2() {
1401
+ if (esbuildBuild) return esbuildBuild;
1402
+ try {
1403
+ const mod = await import("esbuild");
1404
+ esbuildBuild = mod.build;
1405
+ return esbuildBuild;
1406
+ } catch {
1407
+ throw new Error(
1408
+ "Library bundling requires esbuild. Run: pnpm add esbuild"
1409
+ );
1410
+ }
1411
+ }
1412
+ var BLOCKED_BUILTINS = /* @__PURE__ */ new Set([
1413
+ "fs",
1414
+ "fs/promises",
1415
+ "net",
1416
+ "tls",
1417
+ "child_process",
1418
+ "cluster",
1419
+ "dgram",
1420
+ "dns",
1421
+ "http",
1422
+ "http2",
1423
+ "https",
1424
+ "inspector",
1425
+ "module",
1426
+ "os",
1427
+ "perf_hooks",
1428
+ "readline",
1429
+ "repl",
1430
+ "stream",
1431
+ "tty",
1432
+ "v8",
1433
+ "vm",
1434
+ "worker_threads",
1435
+ "wasi"
1436
+ ]);
1437
+ function blockedBuiltinsPlugin() {
1438
+ return {
1439
+ name: "blocked-builtins",
1440
+ setup(build) {
1441
+ const filter = new RegExp(
1442
+ `^(node:)?(${[...BLOCKED_BUILTINS].join("|")})$`
1443
+ );
1444
+ build.onResolve({ filter }, (args) => ({
1445
+ path: args.path,
1446
+ namespace: "blocked-builtin"
1447
+ }));
1448
+ build.onLoad(
1449
+ { filter: /.*/, namespace: "blocked-builtin" },
1450
+ (args) => {
1451
+ const name = args.path.replace(/^node:/, "");
1452
+ return {
1453
+ contents: `throw new Error("Module '${name}' is not available in the isolate. Use the injected fs_* functions instead.");`,
1454
+ loader: "js"
1455
+ };
1456
+ }
1457
+ );
1458
+ }
1459
+ };
1460
+ }
1461
+ async function bundleLibraries(libraries, projectDir) {
1462
+ if (libraries.length === 0) return null;
1463
+ const build = await loadEsbuild2();
1464
+ const chunks = [];
1465
+ const moduleEntries = [];
1466
+ for (const lib of libraries) {
1467
+ const pkgPath = resolve(projectDir, "node_modules", lib, "package.json");
1468
+ if (!existsSync(pkgPath)) {
1469
+ throw new Error(
1470
+ `Library '${lib}' is declared in isolate.libraries but not installed. Run: pnpm add ${lib}`
1471
+ );
1472
+ }
1473
+ let version = "unknown";
1474
+ try {
1475
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
1476
+ version = pkg.version ?? "unknown";
1477
+ } catch {
1478
+ }
1479
+ const safeName = lib.replace(/[^a-zA-Z0-9_]/g, "_");
1480
+ const globalName = `__lib_${safeName}`;
1481
+ const result = await build({
1482
+ entryPoints: [lib],
1483
+ bundle: true,
1484
+ write: false,
1485
+ format: "iife",
1486
+ globalName,
1487
+ platform: "neutral",
1488
+ target: "es2022",
1489
+ // Resolve from the project's node_modules
1490
+ nodePaths: [resolve(projectDir, "node_modules")],
1491
+ plugins: [blockedBuiltinsPlugin()],
1492
+ logLevel: "silent",
1493
+ minify: false
1494
+ });
1495
+ if (result.outputFiles?.[0]) {
1496
+ chunks.push(
1497
+ `// --- ${lib}@${version} ---
1498
+ ${result.outputFiles[0].text}`
1499
+ );
1500
+ moduleEntries.push(` ${JSON.stringify(lib)}: ${globalName}`);
1501
+ } else {
1502
+ throw new Error(
1503
+ `Failed to bundle library '${lib}': esbuild produced no output.`
1504
+ );
1505
+ }
1506
+ }
1507
+ const requireShim = `
1508
+ // --- require() shim ---
1509
+ var __modules = {
1510
+ ${moduleEntries.join(",\n")}
1511
+ };
1512
+ function require(name) {
1513
+ var mod = __modules[name];
1514
+ if (!mod) throw new Error('Module "' + name + '" is not available. Available: ${libraries.join(", ")}');
1515
+ // Handle both default and namespace exports
1516
+ if (mod && mod.default !== undefined && Object.keys(mod).length === 1) return mod.default;
1517
+ return mod;
1518
+ }
1519
+ `;
1520
+ return chunks.join("\n\n") + "\n\n" + requireShim;
1521
+ }
1522
+ export {
1523
+ buildPolyfillPreamble,
1524
+ buildRunCodeDescription,
1525
+ bundleLibraries,
1526
+ createFetchBinding,
1527
+ createIsolateRuntime,
1528
+ createRunCodeTool,
1529
+ createVfsBindings,
1530
+ generateIsolateTypeStubs,
1531
+ mergeBuilderBindings
1532
+ };