@namzu/sdk 35.0.0 → 37.0.0

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 (360) hide show
  1. package/CHANGELOG.md +218 -0
  2. package/README.md +68 -41
  3. package/dist/bridge/a2a/mapper.d.ts.map +1 -1
  4. package/dist/bridge/a2a/mapper.js +2 -0
  5. package/dist/bridge/a2a/mapper.js.map +1 -1
  6. package/dist/bridge/sse/mapper.d.ts.map +1 -1
  7. package/dist/bridge/sse/mapper.js +2 -0
  8. package/dist/bridge/sse/mapper.js.map +1 -1
  9. package/dist/compaction/extractor.d.ts +1 -1
  10. package/dist/compaction/extractor.d.ts.map +1 -1
  11. package/dist/compaction/extractor.js +15 -7
  12. package/dist/compaction/extractor.js.map +1 -1
  13. package/dist/compaction/manager.d.ts +2 -0
  14. package/dist/compaction/manager.d.ts.map +1 -1
  15. package/dist/compaction/manager.js +12 -1
  16. package/dist/compaction/manager.js.map +1 -1
  17. package/dist/compaction/verifier.d.ts.map +1 -1
  18. package/dist/compaction/verifier.js +82 -15
  19. package/dist/compaction/verifier.js.map +1 -1
  20. package/dist/contracts/schemas.d.ts +10 -10
  21. package/dist/eval/harness-verification.d.ts +63 -0
  22. package/dist/eval/harness-verification.d.ts.map +1 -0
  23. package/dist/eval/harness-verification.js +165 -0
  24. package/dist/eval/harness-verification.js.map +1 -0
  25. package/dist/eval/index.d.ts +2 -0
  26. package/dist/eval/index.d.ts.map +1 -1
  27. package/dist/eval/index.js +1 -0
  28. package/dist/eval/index.js.map +1 -1
  29. package/dist/execution/code-runtime/json-value.d.ts +10 -0
  30. package/dist/execution/code-runtime/json-value.d.ts.map +1 -0
  31. package/dist/execution/code-runtime/json-value.js +89 -0
  32. package/dist/execution/code-runtime/json-value.js.map +1 -0
  33. package/dist/execution/code-runtime/worker-program.d.ts +8 -0
  34. package/dist/execution/code-runtime/worker-program.d.ts.map +1 -0
  35. package/dist/execution/code-runtime/worker-program.js +240 -0
  36. package/dist/execution/code-runtime/worker-program.js.map +1 -0
  37. package/dist/execution/code-runtime/worker.d.ts +22 -0
  38. package/dist/execution/code-runtime/worker.d.ts.map +1 -1
  39. package/dist/execution/code-runtime/worker.js +148 -111
  40. package/dist/execution/code-runtime/worker.js.map +1 -1
  41. package/dist/manager/agent/lifecycle.d.ts +12 -0
  42. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  43. package/dist/manager/agent/lifecycle.js +306 -37
  44. package/dist/manager/agent/lifecycle.js.map +1 -1
  45. package/dist/manager/run/persistence.d.ts +2 -0
  46. package/dist/manager/run/persistence.d.ts.map +1 -1
  47. package/dist/manager/run/persistence.js +6 -0
  48. package/dist/manager/run/persistence.js.map +1 -1
  49. package/dist/provider/capabilities.d.ts +3 -1
  50. package/dist/provider/capabilities.d.ts.map +1 -1
  51. package/dist/provider/capabilities.js +13 -0
  52. package/dist/provider/capabilities.js.map +1 -1
  53. package/dist/provider/fallback.d.ts.map +1 -1
  54. package/dist/provider/fallback.js +2 -0
  55. package/dist/provider/fallback.js.map +1 -1
  56. package/dist/provider/token-budget.d.ts.map +1 -1
  57. package/dist/provider/token-budget.js +56 -8
  58. package/dist/provider/token-budget.js.map +1 -1
  59. package/dist/public-runtime.d.ts +5 -3
  60. package/dist/public-runtime.d.ts.map +1 -1
  61. package/dist/public-runtime.js +3 -2
  62. package/dist/public-runtime.js.map +1 -1
  63. package/dist/public-tools.d.ts +1 -1
  64. package/dist/public-tools.d.ts.map +1 -1
  65. package/dist/public-tools.js +1 -1
  66. package/dist/public-tools.js.map +1 -1
  67. package/dist/public-types.d.ts +1 -0
  68. package/dist/public-types.d.ts.map +1 -1
  69. package/dist/registry/tool/execute.d.ts +3 -0
  70. package/dist/registry/tool/execute.d.ts.map +1 -1
  71. package/dist/registry/tool/execute.js +11 -80
  72. package/dist/registry/tool/execute.js.map +1 -1
  73. package/dist/run/command-gate.d.ts.map +1 -1
  74. package/dist/run/command-gate.js +65 -12
  75. package/dist/run/command-gate.js.map +1 -1
  76. package/dist/run/index.d.ts +2 -0
  77. package/dist/run/index.d.ts.map +1 -1
  78. package/dist/run/index.js +1 -0
  79. package/dist/run/index.js.map +1 -1
  80. package/dist/run/memory-promoter.d.ts +3 -35
  81. package/dist/run/memory-promoter.d.ts.map +1 -1
  82. package/dist/run/memory-promoter.js +32 -40
  83. package/dist/run/memory-promoter.js.map +1 -1
  84. package/dist/run/memory-recall.d.ts +25 -0
  85. package/dist/run/memory-recall.d.ts.map +1 -0
  86. package/dist/run/memory-recall.js +228 -0
  87. package/dist/run/memory-recall.js.map +1 -0
  88. package/dist/run/reporter.d.ts.map +1 -1
  89. package/dist/run/reporter.js +1 -0
  90. package/dist/run/reporter.js.map +1 -1
  91. package/dist/run/token-budget.d.ts +2 -0
  92. package/dist/run/token-budget.d.ts.map +1 -1
  93. package/dist/run/token-budget.js +4 -0
  94. package/dist/run/token-budget.js.map +1 -1
  95. package/dist/run/workspace-fingerprint.d.ts +10 -3
  96. package/dist/run/workspace-fingerprint.d.ts.map +1 -1
  97. package/dist/run/workspace-fingerprint.js +17 -8
  98. package/dist/run/workspace-fingerprint.js.map +1 -1
  99. package/dist/runtime/query/checkpoint.d.ts +14 -0
  100. package/dist/runtime/query/checkpoint.d.ts.map +1 -1
  101. package/dist/runtime/query/checkpoint.js +82 -1
  102. package/dist/runtime/query/checkpoint.js.map +1 -1
  103. package/dist/runtime/query/executor.d.ts +6 -0
  104. package/dist/runtime/query/executor.d.ts.map +1 -1
  105. package/dist/runtime/query/executor.js +120 -24
  106. package/dist/runtime/query/executor.js.map +1 -1
  107. package/dist/runtime/query/index.d.ts +12 -8
  108. package/dist/runtime/query/index.d.ts.map +1 -1
  109. package/dist/runtime/query/index.js +55 -7
  110. package/dist/runtime/query/index.js.map +1 -1
  111. package/dist/runtime/query/iteration/index.d.ts +13 -3
  112. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  113. package/dist/runtime/query/iteration/index.js +356 -42
  114. package/dist/runtime/query/iteration/index.js.map +1 -1
  115. package/dist/runtime/query/iteration/native-output.d.ts +10 -0
  116. package/dist/runtime/query/iteration/native-output.d.ts.map +1 -0
  117. package/dist/runtime/query/iteration/native-output.js +32 -0
  118. package/dist/runtime/query/iteration/native-output.js.map +1 -0
  119. package/dist/runtime/query/iteration/phases/advisory.d.ts.map +1 -1
  120. package/dist/runtime/query/iteration/phases/advisory.js +18 -13
  121. package/dist/runtime/query/iteration/phases/advisory.js.map +1 -1
  122. package/dist/runtime/query/iteration/phases/compaction.d.ts +2 -0
  123. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  124. package/dist/runtime/query/iteration/phases/compaction.js +32 -11
  125. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  126. package/dist/runtime/query/iteration/phases/context.d.ts +11 -0
  127. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  128. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  129. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  130. package/dist/runtime/query/iteration/phases/tool-review.js +1 -1
  131. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  132. package/dist/runtime/query/iteration/phases/working-memory.d.ts +1 -1
  133. package/dist/runtime/query/iteration/phases/working-memory.d.ts.map +1 -1
  134. package/dist/runtime/query/iteration/phases/working-memory.js +23 -6
  135. package/dist/runtime/query/iteration/phases/working-memory.js.map +1 -1
  136. package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
  137. package/dist/runtime/query/iteration/stream-turn.js +12 -5
  138. package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
  139. package/dist/runtime/query/prompt.d.ts.map +1 -1
  140. package/dist/runtime/query/prompt.js +14 -4
  141. package/dist/runtime/query/prompt.js.map +1 -1
  142. package/dist/runtime/query/steering.d.ts +6 -2
  143. package/dist/runtime/query/steering.d.ts.map +1 -1
  144. package/dist/runtime/query/steering.js +17 -2
  145. package/dist/runtime/query/steering.js.map +1 -1
  146. package/dist/runtime/query/tool-call-budget.d.ts +19 -0
  147. package/dist/runtime/query/tool-call-budget.d.ts.map +1 -0
  148. package/dist/runtime/query/tool-call-budget.js +110 -0
  149. package/dist/runtime/query/tool-call-budget.js.map +1 -0
  150. package/dist/runtime/query/tooling.d.ts +2 -0
  151. package/dist/runtime/query/tooling.d.ts.map +1 -1
  152. package/dist/runtime/query/tooling.js +4 -0
  153. package/dist/runtime/query/tooling.js.map +1 -1
  154. package/dist/sandbox/file-walk-program.d.ts +3 -0
  155. package/dist/sandbox/file-walk-program.d.ts.map +1 -0
  156. package/dist/sandbox/file-walk-program.js +94 -0
  157. package/dist/sandbox/file-walk-program.js.map +1 -0
  158. package/dist/sandbox/file-walk.d.ts +12 -0
  159. package/dist/sandbox/file-walk.d.ts.map +1 -0
  160. package/dist/sandbox/file-walk.js +429 -0
  161. package/dist/sandbox/file-walk.js.map +1 -0
  162. package/dist/sandbox/index.d.ts +2 -0
  163. package/dist/sandbox/index.d.ts.map +1 -1
  164. package/dist/sandbox/index.js +1 -0
  165. package/dist/sandbox/index.js.map +1 -1
  166. package/dist/sandbox/provider/local.d.ts.map +1 -1
  167. package/dist/sandbox/provider/local.js +9 -0
  168. package/dist/sandbox/provider/local.js.map +1 -1
  169. package/dist/scheduler/completion-inbox.d.ts +3 -2
  170. package/dist/scheduler/completion-inbox.d.ts.map +1 -1
  171. package/dist/scheduler/completion-inbox.js +29 -15
  172. package/dist/scheduler/completion-inbox.js.map +1 -1
  173. package/dist/scheduler/local.d.ts.map +1 -1
  174. package/dist/scheduler/local.js +1 -0
  175. package/dist/scheduler/local.js.map +1 -1
  176. package/dist/session/handoff/capacity.d.ts +2 -2
  177. package/dist/session/handoff/capacity.d.ts.map +1 -1
  178. package/dist/session/handoff/capacity.js +3 -2
  179. package/dist/session/handoff/capacity.js.map +1 -1
  180. package/dist/store/memory/disk.d.ts +6 -2
  181. package/dist/store/memory/disk.d.ts.map +1 -1
  182. package/dist/store/memory/disk.js +42 -25
  183. package/dist/store/memory/disk.js.map +1 -1
  184. package/dist/store/memory/index.d.ts +4 -0
  185. package/dist/store/memory/index.d.ts.map +1 -1
  186. package/dist/store/memory/index.js +55 -19
  187. package/dist/store/memory/index.js.map +1 -1
  188. package/dist/store/memory/memory.d.ts +3 -2
  189. package/dist/store/memory/memory.d.ts.map +1 -1
  190. package/dist/store/memory/memory.js +11 -2
  191. package/dist/store/memory/memory.js.map +1 -1
  192. package/dist/store/memory/operation-lock.d.ts +11 -0
  193. package/dist/store/memory/operation-lock.d.ts.map +1 -0
  194. package/dist/store/memory/operation-lock.js +94 -0
  195. package/dist/store/memory/operation-lock.js.map +1 -0
  196. package/dist/tools/builtins/bash.d.ts.map +1 -1
  197. package/dist/tools/builtins/bash.js +215 -59
  198. package/dist/tools/builtins/bash.js.map +1 -1
  199. package/dist/tools/builtins/computer-use.d.ts.map +1 -1
  200. package/dist/tools/builtins/computer-use.js +60 -10
  201. package/dist/tools/builtins/computer-use.js.map +1 -1
  202. package/dist/tools/builtins/glob.d.ts +2 -1
  203. package/dist/tools/builtins/glob.d.ts.map +1 -1
  204. package/dist/tools/builtins/glob.js +95 -103
  205. package/dist/tools/builtins/glob.js.map +1 -1
  206. package/dist/tools/builtins/grep.d.ts.map +1 -1
  207. package/dist/tools/builtins/grep.js +155 -111
  208. package/dist/tools/builtins/grep.js.map +1 -1
  209. package/dist/tools/builtins/ls.js +2 -2
  210. package/dist/tools/builtins/ls.js.map +1 -1
  211. package/dist/tools/builtins/run-code.d.ts +6 -0
  212. package/dist/tools/builtins/run-code.d.ts.map +1 -1
  213. package/dist/tools/builtins/run-code.js +15 -4
  214. package/dist/tools/builtins/run-code.js.map +1 -1
  215. package/dist/tools/builtins/search-tools.d.ts.map +1 -1
  216. package/dist/tools/builtins/search-tools.js +14 -2
  217. package/dist/tools/builtins/search-tools.js.map +1 -1
  218. package/dist/tools/defineTool.d.ts +2 -0
  219. package/dist/tools/defineTool.d.ts.map +1 -1
  220. package/dist/tools/defineTool.js +3 -0
  221. package/dist/tools/defineTool.js.map +1 -1
  222. package/dist/tools/memory/delete.d.ts +4 -0
  223. package/dist/tools/memory/delete.d.ts.map +1 -0
  224. package/dist/tools/memory/delete.js +26 -0
  225. package/dist/tools/memory/delete.js.map +1 -0
  226. package/dist/tools/memory/index.d.ts +2 -0
  227. package/dist/tools/memory/index.d.ts.map +1 -1
  228. package/dist/tools/memory/index.js +6 -0
  229. package/dist/tools/memory/index.js.map +1 -1
  230. package/dist/tools/memory/save.d.ts.map +1 -1
  231. package/dist/tools/memory/save.js +3 -2
  232. package/dist/tools/memory/save.js.map +1 -1
  233. package/dist/tools/memory/search.d.ts.map +1 -1
  234. package/dist/tools/memory/search.js +12 -5
  235. package/dist/tools/memory/search.js.map +1 -1
  236. package/dist/tools/memory/update.d.ts +4 -0
  237. package/dist/tools/memory/update.d.ts.map +1 -0
  238. package/dist/tools/memory/update.js +47 -0
  239. package/dist/tools/memory/update.js.map +1 -0
  240. package/dist/types/agent/scheduler.d.ts +5 -0
  241. package/dist/types/agent/scheduler.d.ts.map +1 -1
  242. package/dist/types/agent/task.d.ts +10 -0
  243. package/dist/types/agent/task.d.ts.map +1 -1
  244. package/dist/types/computer-use/index.d.ts +6 -0
  245. package/dist/types/computer-use/index.d.ts.map +1 -1
  246. package/dist/types/computer-use/index.js.map +1 -1
  247. package/dist/types/errors/catalog.js +1 -1
  248. package/dist/types/errors/catalog.js.map +1 -1
  249. package/dist/types/hitl/index.d.ts +11 -1
  250. package/dist/types/hitl/index.d.ts.map +1 -1
  251. package/dist/types/hitl/index.js.map +1 -1
  252. package/dist/types/memory/index.d.ts +14 -1
  253. package/dist/types/memory/index.d.ts.map +1 -1
  254. package/dist/types/project/entity.d.ts +1 -0
  255. package/dist/types/project/entity.d.ts.map +1 -1
  256. package/dist/types/provider/config.d.ts +2 -0
  257. package/dist/types/provider/config.d.ts.map +1 -1
  258. package/dist/types/provider/model.d.ts +4 -0
  259. package/dist/types/provider/model.d.ts.map +1 -1
  260. package/dist/types/run/answer-review.d.ts +2 -0
  261. package/dist/types/run/answer-review.d.ts.map +1 -1
  262. package/dist/types/run/events.d.ts +9 -0
  263. package/dist/types/run/events.d.ts.map +1 -1
  264. package/dist/types/run/events.js.map +1 -1
  265. package/dist/types/run/prepare-step.d.ts +19 -1
  266. package/dist/types/run/prepare-step.d.ts.map +1 -1
  267. package/dist/types/sandbox/index.d.ts +20 -0
  268. package/dist/types/sandbox/index.d.ts.map +1 -1
  269. package/dist/types/sandbox/index.js.map +1 -1
  270. package/dist/types/structured-output/index.d.ts +8 -2
  271. package/dist/types/structured-output/index.d.ts.map +1 -1
  272. package/dist/types/tool/index.d.ts +15 -0
  273. package/dist/types/tool/index.d.ts.map +1 -1
  274. package/dist/types/tool/index.js.map +1 -1
  275. package/dist/utils/json-snapshot.d.ts +3 -0
  276. package/dist/utils/json-snapshot.d.ts.map +1 -0
  277. package/dist/utils/json-snapshot.js +79 -0
  278. package/dist/utils/json-snapshot.js.map +1 -0
  279. package/package.json +8 -1
  280. package/src/bridge/a2a/mapper.ts +2 -0
  281. package/src/bridge/sse/mapper.ts +2 -0
  282. package/src/compaction/extractor.ts +21 -8
  283. package/src/compaction/manager.ts +11 -1
  284. package/src/compaction/verifier.ts +80 -18
  285. package/src/eval/harness-verification.ts +270 -0
  286. package/src/eval/index.ts +10 -0
  287. package/src/execution/code-runtime/json-value.ts +82 -0
  288. package/src/execution/code-runtime/worker-program.ts +239 -0
  289. package/src/execution/code-runtime/worker.ts +164 -111
  290. package/src/manager/agent/lifecycle.ts +340 -45
  291. package/src/manager/run/persistence.ts +7 -0
  292. package/src/provider/capabilities.ts +25 -1
  293. package/src/provider/fallback.ts +2 -0
  294. package/src/provider/token-budget.ts +52 -8
  295. package/src/public-runtime.ts +6 -1
  296. package/src/public-tools.ts +5 -1
  297. package/src/public-types.ts +1 -0
  298. package/src/registry/tool/execute.ts +17 -79
  299. package/src/run/command-gate.ts +61 -13
  300. package/src/run/index.ts +2 -0
  301. package/src/run/memory-promoter.ts +32 -40
  302. package/src/run/memory-recall.ts +254 -0
  303. package/src/run/reporter.ts +1 -0
  304. package/src/run/token-budget.ts +4 -0
  305. package/src/run/workspace-fingerprint.ts +17 -8
  306. package/src/runtime/query/checkpoint.ts +96 -2
  307. package/src/runtime/query/executor.ts +131 -28
  308. package/src/runtime/query/index.ts +81 -16
  309. package/src/runtime/query/iteration/index.ts +406 -53
  310. package/src/runtime/query/iteration/native-output.ts +35 -0
  311. package/src/runtime/query/iteration/phases/advisory.ts +21 -11
  312. package/src/runtime/query/iteration/phases/compaction.ts +36 -14
  313. package/src/runtime/query/iteration/phases/context.ts +11 -0
  314. package/src/runtime/query/iteration/phases/tool-review.ts +5 -1
  315. package/src/runtime/query/iteration/phases/working-memory.ts +23 -5
  316. package/src/runtime/query/iteration/stream-turn.ts +11 -5
  317. package/src/runtime/query/prompt.ts +21 -4
  318. package/src/runtime/query/steering.ts +19 -2
  319. package/src/runtime/query/tool-call-budget.ts +115 -0
  320. package/src/runtime/query/tooling.ts +6 -0
  321. package/src/sandbox/file-walk-program.ts +93 -0
  322. package/src/sandbox/file-walk.ts +463 -0
  323. package/src/sandbox/index.ts +2 -0
  324. package/src/sandbox/provider/local.ts +13 -0
  325. package/src/scheduler/completion-inbox.ts +29 -19
  326. package/src/scheduler/local.ts +1 -0
  327. package/src/session/handoff/capacity.ts +7 -3
  328. package/src/store/memory/disk.ts +52 -30
  329. package/src/store/memory/index.ts +75 -27
  330. package/src/store/memory/memory.ts +18 -6
  331. package/src/store/memory/operation-lock.ts +99 -0
  332. package/src/tools/builtins/bash.ts +230 -58
  333. package/src/tools/builtins/computer-use.ts +61 -7
  334. package/src/tools/builtins/glob.ts +104 -122
  335. package/src/tools/builtins/grep.ts +166 -132
  336. package/src/tools/builtins/ls.ts +2 -2
  337. package/src/tools/builtins/run-code.ts +24 -5
  338. package/src/tools/builtins/search-tools.ts +18 -3
  339. package/src/tools/defineTool.ts +5 -0
  340. package/src/tools/memory/delete.ts +29 -0
  341. package/src/tools/memory/index.ts +6 -0
  342. package/src/tools/memory/save.ts +4 -2
  343. package/src/tools/memory/search.ts +12 -5
  344. package/src/tools/memory/update.ts +50 -0
  345. package/src/types/agent/scheduler.ts +6 -0
  346. package/src/types/agent/task.ts +13 -0
  347. package/src/types/computer-use/index.ts +6 -0
  348. package/src/types/errors/catalog.ts +1 -1
  349. package/src/types/hitl/index.ts +11 -1
  350. package/src/types/memory/index.ts +16 -1
  351. package/src/types/project/entity.ts +1 -0
  352. package/src/types/provider/config.ts +2 -0
  353. package/src/types/provider/model.ts +4 -0
  354. package/src/types/run/answer-review.ts +2 -0
  355. package/src/types/run/events.ts +10 -0
  356. package/src/types/run/prepare-step.ts +19 -1
  357. package/src/types/sandbox/index.ts +21 -0
  358. package/src/types/structured-output/index.ts +15 -2
  359. package/src/types/tool/index.ts +16 -0
  360. package/src/utils/json-snapshot.ts +76 -0
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Trusted worker bootstrap. Only vm.evalCode executes the untrusted program.
3
+ * Kept as source so installed/bundled SDKs need no separate worker entry file.
4
+ * No Node object or callback is injected into QuickJS: its native functions
5
+ * exchange bounded strings and interpreter-owned promise handles.
6
+ */
7
+ export const QUICKJS_WORKER_SOURCE = `
8
+ const { parentPort, workerData } = require('node:worker_threads')
9
+ const { newQuickJSWASMModuleFromVariant, newVariant } = require(workerData.corePath)
10
+ const variantModule = require(workerData.variantPath)
11
+ const variant = variantModule.default || variantModule
12
+
13
+ async function main() {
14
+ const limits = workerData.limits
15
+ const pages = Math.floor(limits.memoryLimitBytes / 65536)
16
+ const wasmMemory = new WebAssembly.Memory({ initial: Math.min(256, pages), maximum: pages })
17
+ const module = await newQuickJSWASMModuleFromVariant(newVariant(variant, { wasmMemory }))
18
+ const runtime = module.newRuntime()
19
+ runtime.setMemoryLimit(limits.memoryLimitBytes)
20
+ runtime.setMaxStackSize(512 * 1024)
21
+ runtime.setInterruptHandler(() => Date.now() >= workerData.deadline)
22
+ const vm = runtime.newContext()
23
+ const pending = new Map()
24
+ let nextId = 0
25
+ let printBytes = 0
26
+ let printCount = 0
27
+ let truncated = false
28
+ let ended = false
29
+ let pumping = false
30
+ let scheduled = false
31
+ let program
32
+ const owned = []
33
+
34
+ function keep(result) {
35
+ const handle = vm.unwrapResult(result)
36
+ owned.push(handle)
37
+ return handle
38
+ }
39
+ // Capture intrinsics before the program may replace its own globals.
40
+ const encode = keep(vm.evalCode('(' + function () {
41
+ const stringify = JSON.stringify
42
+ const finite = Number.isFinite
43
+ const prototype = Object.getPrototypeOf
44
+ const plain = Object.prototype
45
+ const isArray = Array.isArray
46
+ const keys = Object.keys
47
+ const descriptor = Object.getOwnPropertyDescriptor
48
+ return function (value) {
49
+ if (value === undefined) return undefined
50
+ const parents = new Set()
51
+ function check(item, depth) {
52
+ const kind = typeof item
53
+ if (item === null || kind === 'string' || kind === 'boolean' || (kind === 'number' && finite(item))) return
54
+ if (kind !== 'object' || depth > 64 || parents.has(item)) throw new Error('Code runtime values must be JSON-safe.')
55
+ if (!isArray(item) && prototype(item) !== plain && prototype(item) !== null) throw new Error('Code runtime values must use plain objects and arrays.')
56
+ parents.add(item)
57
+ for (const key of keys(item)) {
58
+ const property = descriptor(item, key)
59
+ if (!property || !('value' in property)) throw new Error('Code runtime values must not contain accessors.')
60
+ check(property.value, depth + 1)
61
+ }
62
+ parents.delete(item)
63
+ }
64
+ check(value, 0)
65
+ return stringify(value)
66
+ }
67
+ }.toString() + ')()'))
68
+ const decode = keep(vm.evalCode('(function () { const parse = JSON.parse; return function(value) { return parse(value); }; })()'))
69
+ const errorText = keep(vm.evalCode('(function () { const string = String; return function(error) { try { return typeof error.message === "string" ? error.message : string(error); } catch { return "The program failed."; } }; })()'))
70
+
71
+ function textValue(handle, maximum) {
72
+ const length = vm.getProp(handle, 'length')
73
+ try {
74
+ if (vm.getNumber(length) > maximum) throw new Error('Code runtime value exceeds ' + maximum + ' bytes.')
75
+ } finally { length.dispose() }
76
+ const text = vm.getString(handle)
77
+ if (Buffer.byteLength(text) > maximum) throw new Error('Code runtime value exceeds ' + maximum + ' bytes.')
78
+ return text
79
+ }
80
+ function serialize(handle, maximum) {
81
+ if (vm.typeof(handle) === 'undefined') return undefined
82
+ const encoded = vm.callFunction(encode, vm.undefined, handle)
83
+ if (encoded.error) {
84
+ encoded.error.dispose()
85
+ throw new Error('Code runtime values must be JSON-safe and serializable.')
86
+ }
87
+ try { return textValue(encoded.value, maximum) }
88
+ finally { encoded.value.dispose() }
89
+ }
90
+ function failureText(handle) {
91
+ const result = vm.callFunction(errorText, vm.undefined, handle)
92
+ if (result.error) { result.error.dispose(); return 'The program failed.' }
93
+ // Diagnostics have their own fixed bound. A deliberately tiny data
94
+ // allowance must not erase the reason or the no-replay guidance for an
95
+ // effect that completed but whose return value could not be delivered.
96
+ try { return textValue(result.value, 4096) }
97
+ catch { return 'The program failed with an oversized error.' }
98
+ finally { result.value.dispose() }
99
+ }
100
+ function dispose() {
101
+ for (const deferred of pending.values()) deferred.dispose()
102
+ pending.clear()
103
+ if (program && program.alive) program.dispose()
104
+ for (const handle of owned) if (handle.alive) handle.dispose()
105
+ vm.dispose()
106
+ runtime.dispose()
107
+ }
108
+ function finish(message) {
109
+ if (ended) return
110
+ ended = true
111
+ // A guest OOM can make QuickJS teardown throw. The worker still owns
112
+ // the whole WASM module and is terminated by its parent on settlement.
113
+ try { dispose() } catch {}
114
+ parentPort.postMessage(message)
115
+ }
116
+ function fail(error) {
117
+ finish(Date.now() >= workerData.deadline
118
+ ? { kind: 'timed-out' }
119
+ : { kind: 'error', error: String(error && error.message || error).slice(0, 4096) })
120
+ }
121
+ function schedule() {
122
+ if (ended || scheduled) return
123
+ scheduled = true
124
+ setImmediate(() => { scheduled = false; pump() })
125
+ }
126
+ function pump() {
127
+ if (ended || pumping) return
128
+ pumping = true
129
+ try {
130
+ const jobs = runtime.executePendingJobs(32)
131
+ if (jobs.error) {
132
+ const error = failureText(jobs.error)
133
+ jobs.error.dispose()
134
+ throw new Error(error)
135
+ }
136
+ const state = vm.getPromiseState(program)
137
+ if (state.type === 'fulfilled') {
138
+ let json
139
+ try { json = serialize(state.value, limits.maxValueBytes) }
140
+ finally { state.value.dispose() }
141
+ finish({ kind: 'done', json })
142
+ } else if (state.type === 'rejected') {
143
+ let error
144
+ try { error = failureText(state.error) }
145
+ finally { state.error.dispose() }
146
+ fail(new Error(error))
147
+ } else if (runtime.hasPendingJob()) schedule()
148
+ } catch (error) { fail(error) }
149
+ finally { pumping = false }
150
+ }
151
+ function rejected(message) {
152
+ const deferred = vm.newPromise()
153
+ const error = vm.newError(message)
154
+ deferred.reject(error)
155
+ error.dispose()
156
+ return deferred.handle
157
+ }
158
+ const call = vm.newFunction('call', (name, input = vm.undefined) => {
159
+ try {
160
+ if (nextId >= limits.maxHostCalls) return rejected('Code runtime host-call limit reached.')
161
+ if (pending.size >= limits.maxPendingHostCalls) return rejected('Code runtime pending host-call limit reached.')
162
+ if (vm.typeof(name) !== 'string') return rejected('A host call requires a tool name string.')
163
+ const tool = textValue(name, Math.min(limits.maxValueBytes, 1024))
164
+ const json = serialize(input, limits.maxValueBytes)
165
+ const id = ++nextId
166
+ const deferred = vm.newPromise()
167
+ pending.set(id, deferred)
168
+ parentPort.postMessage({ kind: 'call', id, name: tool, json })
169
+ return deferred.handle
170
+ } catch (error) { return rejected(String(error.message || error)) }
171
+ })
172
+ vm.setProp(vm.global, 'call', call)
173
+ call.dispose()
174
+ const print = vm.newFunction('print', (...values) => {
175
+ if (truncated) return vm.undefined
176
+ try {
177
+ const parts = []
178
+ let remaining = workerData.maxOutputBytes - printBytes - (printCount > 0 ? 1 : 0)
179
+ for (const value of values) {
180
+ if (parts.length) remaining--
181
+ if (remaining < 0) throw new Error('output limit')
182
+ const text = vm.typeof(value) === 'string' ? textValue(value, remaining) : serialize(value, remaining)
183
+ const part = text === undefined ? '' : text
184
+ remaining -= Buffer.byteLength(part)
185
+ parts.push(part)
186
+ }
187
+ const line = parts.join(' ')
188
+ const size = Buffer.byteLength(line) + (printCount > 0 ? 1 : 0)
189
+ if (printBytes + size > workerData.maxOutputBytes) throw new Error('output limit')
190
+ // Empty prints consume a newline after the first, so the byte
191
+ // budget also bounds the number of worker messages.
192
+ printBytes += size
193
+ printCount++
194
+ parentPort.postMessage({ kind: 'print', line })
195
+ } catch {
196
+ truncated = true
197
+ parentPort.postMessage({ kind: 'truncated' })
198
+ }
199
+ return vm.undefined
200
+ })
201
+ vm.setProp(vm.global, 'print', print)
202
+ print.dispose()
203
+
204
+ parentPort.on('message', (message) => {
205
+ if (ended || message.kind !== 'call-result') return
206
+ const deferred = pending.get(message.id)
207
+ if (!deferred) return
208
+ pending.delete(message.id)
209
+ try {
210
+ if (message.ok) {
211
+ let value = vm.undefined
212
+ if (message.json !== undefined) {
213
+ if (typeof message.json !== 'string' || Buffer.byteLength(message.json) > limits.maxValueBytes) throw new Error('Host result exceeds the value limit.')
214
+ const input = vm.newString(message.json)
215
+ try { value = vm.unwrapResult(vm.callFunction(decode, vm.undefined, input)) }
216
+ finally { input.dispose() }
217
+ }
218
+ deferred.resolve(value)
219
+ if (value !== vm.undefined) value.dispose()
220
+ } else {
221
+ const error = vm.newError(String(message.error).slice(0, 4096))
222
+ deferred.reject(error)
223
+ error.dispose()
224
+ }
225
+ deferred.dispose()
226
+ schedule()
227
+ } catch (error) { deferred.dispose(); fail(error) }
228
+ })
229
+ try {
230
+ const result = vm.evalCode('(async () => {\\n' + workerData.source + '\\n})()', 'model-program.js')
231
+ if (result.error) {
232
+ const error = failureText(result.error)
233
+ result.error.dispose()
234
+ fail(new Error(error))
235
+ } else { program = result.value; pump() }
236
+ } catch (error) { fail(error) }
237
+ }
238
+ main().catch((error) => parentPort.postMessage({ kind: 'error', error: String(error && error.message || error).slice(0, 4096) }))
239
+ `
@@ -1,109 +1,85 @@
1
+ import { createRequire } from 'node:module'
1
2
  import { Worker } from 'node:worker_threads'
2
3
 
4
+ import { encodeCodeValue } from './json-value.js'
3
5
  import {
4
6
  type CodeRunResult,
5
7
  type CodeRuntime,
6
8
  HostCallDeniedError,
7
9
  type RunCodeOptions,
8
10
  } from './types.js'
11
+ import { QUICKJS_WORKER_SOURCE } from './worker-program.js'
9
12
 
10
- /**
11
- * A `worker_threads` backend.
12
- *
13
- * Chosen over `vm` because `vm` is not a sandbox and its own documentation
14
- * says so: a context shares the process, so a program that reaches a
15
- * constructor from the host realm is out — `this.constructor.constructor`
16
- * on any leaked object is the whole escape, and it fits in a tweet. A
17
- * worker is a separate V8 isolate with its own heap; escaping it means
18
- * escaping V8.
19
- *
20
- * Chosen over a subprocess because a subprocess is a process: it inherits
21
- * an environment, it can be a fork bomb, and killing it is the process-tree
22
- * problem `process/kill-tree.ts` exists for. A worker is cheaper to start,
23
- * cheaper to kill, and cannot outlive the process that made it.
24
- *
25
- * What a worker does NOT give, stated because it is the reason this is not
26
- * the whole answer: a worker shares the process's filesystem and network
27
- * access. It is not confined by anything the OS enforces. What confines the
28
- * program here is that it is handed a scope with nothing in it — no
29
- * `require`, no `process`, no `fetch` — and a single channel back to the
30
- * host. That is a language-level boundary, and a language-level boundary is
31
- * exactly as strong as the enumeration of what was withheld. So a host that
32
- * needs an OS boundary runs this inside a sandbox that has one, and this
33
- * comment is here so nobody concludes the worker was the boundary.
34
- */
35
-
36
- /**
37
- * The worker's own body, as source.
38
- *
39
- * Inlined rather than a separate file because a separate file has to be
40
- * findable at runtime, and this package is consumed as `dist/` by hosts
41
- * whose bundlers rewrite paths. A string has no path.
42
- */
43
- const WORKER_SOURCE = `
44
- const { parentPort, workerData } = require('node:worker_threads')
45
-
46
- // Every host call is a request/response over the port, keyed by an id so
47
- // several can be in flight — a program that awaits two calls at once is
48
- // ordinary, and a channel that could only carry one would silently
49
- // serialise them.
50
- const pending = new Map()
51
- let nextId = 0
13
+ const requireFromSdk = createRequire(import.meta.url)
52
14
 
53
- function hostCall(name, input) {
54
- const id = ++nextId
55
- return new Promise((resolve, reject) => {
56
- pending.set(id, { resolve, reject })
57
- parentPort.postMessage({ kind: 'call', id, name, input })
58
- })
15
+ /** Resource policy for one fresh QuickJS worker execution. */
16
+ export interface WorkerCodeRuntimeOptions {
17
+ /** QuickJS allocator limit, also the WASM linear-memory ceiling. Does not bound total Node/process memory. Default: 64 MiB. */
18
+ readonly memoryLimitBytes?: number
19
+ /** Maximum UTF-8 program source size. Default: 256 KiB. */
20
+ readonly maxSourceBytes?: number
21
+ /** Maximum UTF-8 JSON size of each input, host result, and program return. Default: 1 MiB. */
22
+ readonly maxValueBytes?: number
23
+ /** Maximum host calls admitted by one program, including refusals. Default: 100. */
24
+ readonly maxHostCalls?: number
25
+ /** Maximum host calls awaiting replies simultaneously. Default: 100. */
26
+ readonly maxPendingHostCalls?: number
59
27
  }
60
28
 
61
- // Posted as it happens, NOT batched until the end. A program that printed
62
- // its progress and then hung has told the host where it got to, and a
63
- // buffer that only ships on completion loses exactly the output a timeout
64
- // most needs to explain itself.
65
- function print(...parts) {
66
- parentPort.postMessage({
67
- kind: 'print',
68
- line: parts.map((p) => (typeof p === 'string' ? p : JSON.stringify(p))).join(' '),
69
- })
29
+ function validateInteger(name: string, value: number, minimum: number, maximum: number): void {
30
+ if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
31
+ throw new RangeError(`${name} must be an integer between ${minimum} and ${maximum}.`)
32
+ }
70
33
  }
71
34
 
72
- parentPort.on('message', (message) => {
73
- if (message.kind !== 'call-result') return
74
- const entry = pending.get(message.id)
75
- if (!entry) return
76
- pending.delete(message.id)
77
- if (message.ok) entry.resolve(message.value)
78
- else entry.reject(new Error(message.error))
79
- })
80
-
81
- async function main() {
82
- // The scope the program runs in, and everything it has. Built with
83
- // \`new Function\` over an explicit parameter list rather than with
84
- // \`with\` or a Proxy: the parameters SHADOW the outer names, so a
85
- // program writing \`require\` gets the parameter, and the parameter is
86
- // undefined.
87
- const names = ['call', 'print', 'require', 'process', 'module', 'exports', 'globalThis', 'fetch']
88
- const values = [hostCall, print, undefined, undefined, undefined, undefined, undefined, undefined]
89
- const body = new Function(...names, '"use strict";return (async () => {' + workerData.source + '\\n})()')
90
- return await body(...values)
35
+ function resolveLimits(options: WorkerCodeRuntimeOptions): Required<WorkerCodeRuntimeOptions> {
36
+ const limits = {
37
+ memoryLimitBytes: options.memoryLimitBytes ?? 64 * 1024 * 1024,
38
+ maxSourceBytes: options.maxSourceBytes ?? 256 * 1024,
39
+ maxValueBytes: options.maxValueBytes ?? 1024 * 1024,
40
+ maxHostCalls: options.maxHostCalls ?? 100,
41
+ maxPendingHostCalls: options.maxPendingHostCalls ?? options.maxHostCalls ?? 100,
42
+ }
43
+ validateInteger('memoryLimitBytes', limits.memoryLimitBytes, 16 * 1024 * 1024, 256 * 1024 * 1024)
44
+ validateInteger('maxSourceBytes', limits.maxSourceBytes, 1, 4 * 1024 * 1024)
45
+ validateInteger('maxValueBytes', limits.maxValueBytes, 1, 16 * 1024 * 1024)
46
+ validateInteger('maxHostCalls', limits.maxHostCalls, 1, 10_000)
47
+ validateInteger('maxPendingHostCalls', limits.maxPendingHostCalls, 1, limits.maxHostCalls)
48
+ return limits
91
49
  }
92
50
 
93
- main().then(
94
- (result) => parentPort.postMessage({ kind: 'done', result }),
95
- (error) => parentPort.postMessage({ kind: 'error', error: String(error && error.message || error) }),
96
- )
97
- `
51
+ function decodeValue(json: unknown, maxBytes: number): unknown {
52
+ if (json === undefined) return undefined
53
+ if (typeof json !== 'string' || Buffer.byteLength(json) > maxBytes) {
54
+ throw new Error('Code runtime value exceeds the serialized value limit.')
55
+ }
56
+ return JSON.parse(json)
57
+ }
98
58
 
59
+ /**
60
+ * Runs untrusted JavaScript in a QuickJS interpreter inside a worker thread.
61
+ * QuickJS owns the guest globals, functions, and promises. Node capabilities
62
+ * never enter that realm; constructors and dynamic imports cannot recover them.
63
+ * A fresh interpreter and bounded imported WASM memory are created per run.
64
+ * The worker makes wall-clock cancellation independent of interpreter progress.
65
+ */
99
66
  export class WorkerCodeRuntime implements CodeRuntime {
100
67
  readonly id = 'worker_threads'
68
+ private readonly limits: Required<WorkerCodeRuntimeOptions>
69
+
70
+ constructor(options: WorkerCodeRuntimeOptions = {}) {
71
+ this.limits = resolveLimits(options)
72
+ }
101
73
 
102
74
  async run(options: RunCodeOptions): Promise<CodeRunResult> {
75
+ validateInteger('timeoutMs', options.timeoutMs, 1, 2_147_483_647)
76
+ validateInteger('maxOutputBytes', options.maxOutputBytes, 0, 16 * 1024 * 1024)
103
77
  const allowed = new Set(options.allowedCalls)
104
78
  const calls: { name: string; ok: boolean }[] = []
105
79
  let output = ''
106
80
  let truncated = false
81
+ let printCount = 0
82
+ const seenCalls = new Set<number>()
107
83
 
108
84
  // A withdrawn caller owns admission. Do not start an isolate merely to
109
85
  // discover the signal was already aborted after construction.
@@ -116,10 +92,25 @@ export class WorkerCodeRuntime implements CodeRuntime {
116
92
  }
117
93
  }
118
94
 
95
+ if (
96
+ options.source.length > this.limits.maxSourceBytes ||
97
+ Buffer.byteLength(options.source) > this.limits.maxSourceBytes
98
+ ) {
99
+ return {
100
+ outcome: {
101
+ status: 'failed',
102
+ error: `Program source exceeds ${this.limits.maxSourceBytes} bytes.`,
103
+ },
104
+ output,
105
+ outputTruncated: truncated,
106
+ calls,
107
+ }
108
+ }
109
+
119
110
  const appendOutput = (lines: readonly string[]): void => {
120
111
  for (const line of lines) {
121
112
  if (truncated) return
122
- const next = output.length === 0 ? line : `${output}\n${line}`
113
+ const next = printCount === 0 ? line : `${output}\n${line}`
123
114
  if (Buffer.byteLength(next) > options.maxOutputBytes) {
124
115
  // Cut at the LINE that would exceed, and say so. A cut
125
116
  // mid-JSON produces output a reader cannot parse and cannot
@@ -128,26 +119,24 @@ export class WorkerCodeRuntime implements CodeRuntime {
128
119
  return
129
120
  }
130
121
  output = next
122
+ printCount++
131
123
  }
132
124
  }
133
125
 
134
- const worker = new Worker(WORKER_SOURCE, {
126
+ const worker = new Worker(QUICKJS_WORKER_SOURCE, {
135
127
  eval: true,
136
- workerData: { source: options.source },
137
- // Nothing from the host's environment.
138
- //
139
- // **Unobservable today, and kept deliberately.** `process` is
140
- // shadowed in the program's scope, so nothing the program can
141
- // write reaches `process.env` either way — a mutation removing
142
- // this line passes every test, correctly. It is defence behind the
143
- // shadowing rather than beside it: the shadowing is a
144
- // language-level boundary, exactly as strong as the enumeration of
145
- // what was withheld, and the day that enumeration misses something
146
- // this is what the leak finds. The cost is one empty object.
128
+ workerData: {
129
+ source: options.source,
130
+ corePath: requireFromSdk.resolve('quickjs-emscripten-core'),
131
+ variantPath: requireFromSdk.resolve('@jitl/quickjs-wasmfile-release-sync'),
132
+ limits: this.limits,
133
+ maxOutputBytes: options.maxOutputBytes,
134
+ deadline: Date.now() + options.timeoutMs,
135
+ },
136
+ // Trusted bootstrap requires Node; guest code runs only inside QuickJS.
137
+ // Avoid inheriting --input-type or test loaders into the CJS bootstrap.
138
+ execArgv: [],
147
139
  env: {},
148
- // No stdio inheritance: the program prints through `print`, which
149
- // is bounded. A worker writing to the host's stdout would bypass
150
- // the cap and interleave with the operator's own output.
151
140
  stdout: true,
152
141
  stderr: true,
153
142
  })
@@ -212,13 +201,59 @@ export class WorkerCodeRuntime implements CodeRuntime {
212
201
  }
213
202
 
214
203
  worker.on('message', (message: Record<string, unknown>) => {
204
+ if (settled) return
205
+ if (message.kind === 'truncated') {
206
+ truncated = true
207
+ return
208
+ }
209
+ if (message.kind === 'timed-out') {
210
+ operation.abort(new Error(`Code runtime exceeded ${options.timeoutMs}ms`))
211
+ finish({
212
+ outcome: { status: 'timed-out' },
213
+ output,
214
+ outputTruncated: truncated,
215
+ calls,
216
+ })
217
+ return
218
+ }
215
219
  if (message.kind === 'call') {
216
220
  const name = String(message.name)
217
221
  const id = message.id
222
+ if (
223
+ typeof id !== 'number' ||
224
+ !Number.isSafeInteger(id) ||
225
+ id < 1 ||
226
+ seenCalls.has(id) ||
227
+ seenCalls.size >= this.limits.maxHostCalls ||
228
+ inFlight.size >= this.limits.maxPendingHostCalls
229
+ ) {
230
+ finish({
231
+ outcome: {
232
+ status: 'failed',
233
+ error: 'Invalid or over-budget host call.',
234
+ },
235
+ output,
236
+ outputTruncated: truncated,
237
+ calls,
238
+ })
239
+ return
240
+ }
241
+ seenCalls.add(id)
242
+ let input: unknown
243
+ try {
244
+ input = decodeValue(message.json, this.limits.maxValueBytes)
245
+ } catch (error) {
246
+ finish({
247
+ outcome: { status: 'failed', error: String(error) },
248
+ output,
249
+ outputTruncated: truncated,
250
+ calls,
251
+ })
252
+ return
253
+ }
218
254
  if (!allowed.has(name)) {
219
- // Refused HERE, not in the worker. The allow-list lives on
220
- // the host side because a check inside the worker is a
221
- // check the program shares a heap with.
255
+ // Authorization remains host-owned even though the interpreter
256
+ // also bounds its own bridge. A worker never expands the grant.
222
257
  calls.push({ name, ok: false })
223
258
  worker.postMessage({
224
259
  kind: 'call-result',
@@ -234,20 +269,27 @@ export class WorkerCodeRuntime implements CodeRuntime {
234
269
  const runtimeToolCallId = String(id)
235
270
  const hostCall = Promise.resolve()
236
271
  .then(() =>
237
- options.onHostCall(
238
- { name, input: message.input },
239
- { runtimeToolCallId, signal: operation.signal },
240
- ),
272
+ options.onHostCall({ name, input }, { runtimeToolCallId, signal: operation.signal }),
241
273
  )
242
274
  .then((result) => {
243
275
  if (settled) return
276
+ let json: string | undefined
277
+ try {
278
+ json = result.ok
279
+ ? encodeCodeValue(result.value, this.limits.maxValueBytes)
280
+ : undefined
281
+ } catch (error) {
282
+ throw new Error(
283
+ `Host call "${name}" completed, but its result could not be delivered: ${error instanceof Error ? error.message : String(error)} Do not repeat a state-changing call solely to recover its output.`,
284
+ )
285
+ }
244
286
  calls.push({ name, ok: result.ok })
245
287
  worker.postMessage({
246
288
  kind: 'call-result',
247
289
  id,
248
290
  ok: result.ok,
249
- value: result.value,
250
- error: result.error,
291
+ json,
292
+ error: result.error?.slice(0, 4096),
251
293
  })
252
294
  })
253
295
  .catch((err: unknown) => {
@@ -257,7 +299,7 @@ export class WorkerCodeRuntime implements CodeRuntime {
257
299
  kind: 'call-result',
258
300
  id,
259
301
  ok: false,
260
- error: err instanceof Error ? err.message : String(err),
302
+ error: (err instanceof Error ? err.message : String(err)).slice(0, 4096),
261
303
  })
262
304
  })
263
305
  .finally(() => {
@@ -278,8 +320,20 @@ export class WorkerCodeRuntime implements CodeRuntime {
278
320
  // declaring its JavaScript body complete is not evidence that the
279
321
  // effect it started is complete, so keep the runtime open until the
280
322
  // already-admitted host calls settle (or the deadline revokes them).
323
+ let value: unknown
324
+ try {
325
+ value = decodeValue(message.json, this.limits.maxValueBytes)
326
+ } catch (error) {
327
+ finish({
328
+ outcome: { status: 'failed', error: String(error) },
329
+ output,
330
+ outputTruncated: truncated,
331
+ calls,
332
+ })
333
+ return
334
+ }
281
335
  terminal = {
282
- outcome: { status: 'completed', result: message.result },
336
+ outcome: { status: 'completed', result: value },
283
337
  output,
284
338
  outputTruncated: truncated,
285
339
  calls,
@@ -311,9 +365,8 @@ export class WorkerCodeRuntime implements CodeRuntime {
311
365
  })
312
366
 
313
367
  worker.on('exit', (code) => {
314
- // Only reached when the worker exited without a `done` or
315
- // `error` message — a hard crash, or a program that called
316
- // `process.exit` through something we failed to withhold.
368
+ // A bootstrap or interpreter failure may exit before a terminal
369
+ // message. Guest code has no access to Node's process object.
317
370
  finish({
318
371
  outcome: {
319
372
  status: 'failed',