Expand description
QuickJS-backed implementation of the workflow JavaScript runtime boundary.
Runtime overview:
-
Create a restricted QuickJS context with only the intrinsics needed by the workflow sandbox (
Promise,Proxy, JSON, collections, regexps, etc.). QuickJS’s eval intrinsic is still required for host-owned source/module evaluation, but user-visible dynamic evaluation is disabled before user workflow code runs. Node/browser/host globals are not provided and a second hardening pass replaces or hides known escape hatches such aseval,Function,Date, host IO globals,Date, andMath.random. -
Evaluate
sandbox_prelude.js. The prelude intentionally contains only the small JS-native pieces that are easier to express in JavaScript: the readonlyProxyfactory plus pure helper globals likeparallelandpipeline. Rust captures the temporary__readonlyhelper and removes it before user workflow code runs. -
Install Rust-owned workflow globals (
args,budget,agent,workflow,log, andphase). Rust exposes two different kinds of protection and both are required:- Global/property binding protection is done with
define_readonly_data_property, which defines non-writable, non-configurable properties. Use this for public globals, hidden bootstrap helpers, disabled host globals, and host object properties likeMath.random. - Object/value mutation protection is done with the captured readonly
proxy. Use this for mutable-looking objects exposed from Rust, such as
argsandbudget, so nested writes, new properties, deletes, and prototype changes throw instead of mutating the underlying object.
A protected global binding alone is not enough for object values: it stops
globalThis.args = ..., but notargs.nested.value = .... Therefore any object/array exposed from Rust that should be immutable to workflow code must be wrapped withreadonly_proxybefore it is installed or passed to user code.agent(...)andworkflow(...)do not perform provider work inside QuickJS; they create pending JS promises, enqueue Rust-side requests, and save the JS resolve/reject functions for later. - Global/property binding protection is done with
-
Declare and evaluate the user source as a real ES module. Module evaluation is represented by a QuickJS promise so top-level
awaitis naturally supported. Literal top-levelreturnis not supported because the source is parsed as ESM. -
Once module evaluation resolves, Rust reads the module namespace and starts the
defaultexport. Function defaults are called asdefault(args, ctx), wherectxcontains the workflow helpers. Value or promise defaults are used directly. Rust normalizes both forms into a single workflow promise. -
Polling drains the QuickJS job queue, emits queued calls/requests to the workflow core, and completes when the workflow promise resolves. The core later resolves requests through
resolve_request, which resumes the saved JS promises by calling their captured resolve/reject functions.
Structs§
- RQuickJS
Workflow Runtime - Workflow JavaScript runtime backed by QuickJS via
rquickjs.