From 1c6660f3690e5b447a696df7a1856bfd0dcf2d15 Mon Sep 17 00:00:00 2001 From: Mouaad Aallam Date: Sun, 3 May 2026 01:33:30 +0200 Subject: [PATCH] refactor!: remove remote runtime package BREAKING CHANGE: @execbox/remote and @execbox/quickjs/remote-endpoint are removed from the v1 runtime surface. --- .changeset/tidy-v1-api-surface.md | 3 +- AGENTS.md | 11 +- CONTRIBUTING.md | 3 +- README.md | 5 +- benchmarks/results.md | 1 - docs/astro.config.mjs | 8 - .../content/docs/architecture/execbox-core.md | 7 +- .../docs/architecture/execbox-executors.md | 49 +-- .../architecture/execbox-mcp-and-protocol.md | 21 +- .../execbox-protocol-reference.md | 24 +- .../architecture/execbox-remote-workflow.md | 139 ------ .../execbox-runner-specification.md | 409 ------------------ docs/src/content/docs/architecture/index.md | 15 +- docs/src/content/docs/examples.md | 1 - docs/src/content/docs/getting-started.md | 1 - docs/src/content/docs/index.md | 11 +- docs/src/content/docs/performance/index.md | 24 +- docs/src/content/docs/runtime-choices.md | 23 +- docs/src/content/docs/security.md | 23 +- examples/README.md | 1 - examples/execbox-remote.ts | 109 ----- package-lock.json | 18 +- package.json | 6 +- packages/core/CHANGELOG.md | 2 - packages/core/README.md | 15 +- packages/quickjs/CHANGELOG.md | 4 +- packages/quickjs/README.md | 8 +- .../quickjs/__tests__/quickjsExecutor.test.ts | 2 +- .../__tests__/security/remoteEndpoint.test.ts | 158 ------- .../execbox-quickjs-remote-endpoint.api.md | 20 - packages/quickjs/package.json | 9 - packages/quickjs/src/quickjsExecutor.ts | 3 +- packages/quickjs/src/remoteEndpoint.ts | 79 ---- packages/quickjs/tsdown.config.ts | 2 +- packages/remote/CHANGELOG.md | 92 ---- packages/remote/LICENSE | 21 - packages/remote/README.md | 90 ---- packages/remote/__tests__/executor.test.ts | 10 - .../__tests__/security/penetration.test.ts | 10 - packages/remote/etc/execbox-remote.api.md | 29 -- packages/remote/package.json | 50 --- packages/remote/src/index.ts | 6 - packages/remote/src/remoteExecutor.ts | 74 ---- packages/remote/src/types.ts | 20 - .../test-support/createLoopbackTransport.ts | 77 ---- packages/remote/tsconfig.json | 9 - packages/remote/tsdown.config.ts | 5 - scripts/test-dist-smoke.ts | 8 - scripts/workspace-entrypoints.ts | 16 - tsconfig.json | 7 +- 50 files changed, 97 insertions(+), 1641 deletions(-) delete mode 100644 docs/src/content/docs/architecture/execbox-remote-workflow.md delete mode 100644 docs/src/content/docs/architecture/execbox-runner-specification.md delete mode 100644 examples/execbox-remote.ts delete mode 100644 packages/quickjs/__tests__/security/remoteEndpoint.test.ts delete mode 100644 packages/quickjs/etc/execbox-quickjs-remote-endpoint.api.md delete mode 100644 packages/quickjs/src/remoteEndpoint.ts delete mode 100644 packages/remote/CHANGELOG.md delete mode 100644 packages/remote/LICENSE delete mode 100644 packages/remote/README.md delete mode 100644 packages/remote/__tests__/executor.test.ts delete mode 100644 packages/remote/__tests__/security/penetration.test.ts delete mode 100644 packages/remote/etc/execbox-remote.api.md delete mode 100644 packages/remote/package.json delete mode 100644 packages/remote/src/index.ts delete mode 100644 packages/remote/src/remoteExecutor.ts delete mode 100644 packages/remote/src/types.ts delete mode 100644 packages/remote/test-support/createLoopbackTransport.ts delete mode 100644 packages/remote/tsconfig.json delete mode 100644 packages/remote/tsdown.config.ts diff --git a/.changeset/tidy-v1-api-surface.md b/.changeset/tidy-v1-api-surface.md index 198901e..9c8a139 100644 --- a/.changeset/tidy-v1-api-surface.md +++ b/.changeset/tidy-v1-api-surface.md @@ -1,7 +1,6 @@ --- "@execbox/core": minor "@execbox/quickjs": minor -"@execbox/remote": minor --- -Tighten the pre-1.0 public API surface by keeping low-level core helpers out of the main `@execbox/core` entrypoint, removing unsupported QuickJS runner subpath exports, and keeping runner-side remote endpoint types with `@execbox/quickjs/remote-endpoint`. +Tighten the pre-1.0 public API surface by keeping low-level core helpers out of the main `@execbox/core` entrypoint and removing unsupported QuickJS runner subpath exports. The v1 runtime surface is now inline QuickJS plus worker-hosted QuickJS. diff --git a/AGENTS.md b/AGENTS.md index ae3cd4b..8cdd671 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ - `execbox` is a Node.js 22+ npm workspace that publishes the `@execbox/*` package family. - Core source lives under `packages/*/src`, tests live under `packages/*/__tests__`, runnable examples live under `examples/`, and the public docs site lives under `docs/`. -- The workspace currently contains `@execbox/core`, `@execbox/quickjs`, and `@execbox/remote`. +- The workspace currently contains `@execbox/core` and `@execbox/quickjs`. - Keep changes aligned with existing package boundaries. Prefer changing the owning package instead of introducing cross-package shortcuts. ## Setup Commands @@ -30,8 +30,9 @@ - For most code changes, run `npm run format:check`, `npm run lint`, `npm run typecheck`, `npm test`, and `npm run build`. - If you change package exports, manifest fields, or published type-resolution behavior, also run `npm run package:check`. -- If you change the public API of any entrypoint listed in `scripts/workspace-entrypoints.ts`, including `@execbox/core/runtime` and `@execbox/quickjs/remote-endpoint`, also run `npm run api:check`. -- If you change docs site content, navigation, or VitePress config, also run `npm run docs:build`. +- If you change the public API of any entrypoint listed in `scripts/workspace-entrypoints.ts`, including `@execbox/core/runtime`, also run `npm run api:check`. +- If you change examples or runtime guidance, also run `npm run examples`. +- If you change docs site content, navigation, or Starlight/Astro config, also run `npm run docs:build`. - If you touch execution boundaries, timeout handling, abort propagation, schema validation, or log/memory controls, also run `npm run test:security`. ## Security Notes @@ -51,6 +52,6 @@ ## Useful References - Start with `README.md` for the package map. -- Use `docs/getting-started.md` for install and example expectations. -- Use `docs/security.md` and `docs/architecture/README.md` before changing execution boundaries or runtime claims. +- Use `docs/src/content/docs/getting-started.md` for install and example expectations. +- Use `docs/src/content/docs/security.md` and `docs/src/content/docs/architecture/index.md` before changing execution boundaries or runtime claims. - For the human-oriented contribution workflow, see `CONTRIBUTING.md`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b59f9eb..3da3d4c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,12 +21,13 @@ This guide is for both humans and coding agents. Agent-specific operating instru - General code changes: `npm run format:check`, `npm run lint`, `npm run typecheck`, `npm test`, and `npm run build` - Package export, manifest, or published type-resolution changes: `npm run package:check` +- Example or runtime guidance changes: `npm run examples` - Docs site changes: `npm run docs:build` - Security or execution-boundary changes: `npm run test:security` Choose the smallest verification set that covers your change, and include the commands you ran in your PR or handoff notes when the context would help reviewers. -- Public API changes to any entrypoint listed in `scripts/workspace-entrypoints.ts`, including `@execbox/core/runtime` and `@execbox/quickjs/remote-endpoint`: run `npm run api:check` +- Public API changes to any entrypoint listed in `scripts/workspace-entrypoints.ts`, including `@execbox/core/runtime`: run `npm run api:check` - Put security-focused specs under `packages/*/__tests__/security/`; `npm run test:security` runs those grouped suites. ## Changesets diff --git a/README.md b/README.md index 4151081..20800e9 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,11 @@ Portable code execution for [Model Context Protocol](https://modelcontextprotoco [![License](https://img.shields.io/github/license/aallam/execbox?style=flat-square)](https://github.com/aallam/execbox/blob/main/LICENSE) [![Docs](https://img.shields.io/badge/docs-site-0ea5e9?style=flat-square)](https://execbox.aallam.com) -[![Packages](https://img.shields.io/badge/packages-3-111827?style=flat-square)](#package-map) +[![Packages](https://img.shields.io/badge/packages-2-111827?style=flat-square)](#package-map) -Execbox turns host tool catalogs into callable guest namespaces, supports MCP wrapping on both sides of the boundary, and lets you place guest JavaScript where it fits your deployment: inline QuickJS, worker-hosted QuickJS, or a remote runner behind your own transport. +Execbox turns host tool catalogs into callable guest namespaces, supports MCP wrapping on both sides of the boundary, and lets you run guest JavaScript through inline QuickJS or worker-hosted QuickJS. ## Package Map @@ -18,7 +18,6 @@ Execbox turns host tool catalogs into callable guest namespaces, supports MCP wr | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | | [`@execbox/core`](./packages/core/) | [![npm](https://img.shields.io/npm/v/%40execbox%2Fcore?style=flat-square)](https://www.npmjs.com/package/@execbox/core) | Core execution contract, provider resolution, MCP adapters, and runtime/protocol subpaths | | [`@execbox/quickjs`](./packages/quickjs/) | [![npm](https://img.shields.io/npm/v/%40execbox%2Fquickjs?style=flat-square)](https://www.npmjs.com/package/@execbox/quickjs) | QuickJS executor for inline and worker hosts | -| [`@execbox/remote`](./packages/remote/) | [![npm](https://img.shields.io/npm/v/%40execbox%2Fremote?style=flat-square)](https://www.npmjs.com/package/@execbox/remote) | Advanced transport-backed executor for app-owned runtime boundaries | ## Examples diff --git a/benchmarks/results.md b/benchmarks/results.md index 467987c..75b16c1 100644 --- a/benchmarks/results.md +++ b/benchmarks/results.md @@ -139,4 +139,3 @@ This suite only measures the parent Node process. ### What this snapshot does not prove - It does not prove exact throughput rankings for every workload or host. The concurrency and tool-call suites are still sensitive to local scheduler noise. -- It does not measure `RemoteExecutor`, because remote performance depends on the caller-owned transport and remote runtime deployment. diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index b40070e..38ef982 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -43,10 +43,6 @@ export default defineConfig({ label: "MCP Provider", slug: "architecture/execbox-mcp-and-protocol", }, - { - label: "Remote Runner", - slug: "architecture/execbox-remote-workflow", - }, ], }, { @@ -64,10 +60,6 @@ export default defineConfig({ label: "Protocol", slug: "architecture/execbox-protocol-reference", }, - { - label: "Runner Specification", - slug: "architecture/execbox-runner-specification", - }, ], }, ], diff --git a/docs/src/content/docs/architecture/execbox-core.md b/docs/src/content/docs/architecture/execbox-core.md index 668cf74..a6cc028 100644 --- a/docs/src/content/docs/architecture/execbox-core.md +++ b/docs/src/content/docs/architecture/execbox-core.md @@ -71,7 +71,7 @@ The resolved provider also carries two maps: ## Guest Code Normalization -Executors do not evaluate arbitrary snippets directly. Runtime implementers import `normalizeCode()` from `@execbox/core/runtime` to turn model- or user-produced text into a consistent async function body. +Executors normalize snippets before evaluation. Runtime implementers import `normalizeCode()` from `@execbox/core/runtime` to turn model- or user-produced text into a consistent async function body. That normalization handles: @@ -129,7 +129,7 @@ interface Executor { } ``` -The core package intentionally does not decide where the code runs. It only defines what the runtime must honor. +The core package defines what every runtime must honor while executor packages decide where code runs. ```mermaid sequenceDiagram @@ -194,12 +194,11 @@ Executors are responsible for their own runtime-specific classification rules, b ## Why the Core Stays Small -The core package does not own QuickJS, worker threads, process boundaries, or transport mechanics. That separation keeps the core useful for: +The core package stays focused on provider, execution, MCP, runtime-helper, and protocol contracts. That separation keeps the core useful for: - direct in-process runtimes - worker-backed runtimes - MCP wrapper servers -- remote execution models The consequence is deliberate separation between: diff --git a/docs/src/content/docs/architecture/execbox-executors.md b/docs/src/content/docs/architecture/execbox-executors.md index 9733436..42415b4 100644 --- a/docs/src/content/docs/architecture/execbox-executors.md +++ b/docs/src/content/docs/architecture/execbox-executors.md @@ -1,37 +1,31 @@ --- title: Execbox Executors -description: Compare inline QuickJS, worker-hosted QuickJS, and remote executor trade-offs. +description: Compare inline QuickJS and worker-hosted QuickJS trade-offs. --- This page explains how the available executors differ and what trade-offs they make. ## Executor Comparison -| Executor or mode | Runtime boundary | Tool bridge style | Main strengths | Main constraints | -| --------------------------------------- | ------------------------------------- | ------------------------------- | ------------------------------------------------ | ----------------------------------------------- | -| `QuickJsExecutor` | Fresh in-process QuickJS runtime | Shared runner callback | No native addon, simple install, default backend | Still in-process | -| `QuickJsExecutor` with `host: "worker"` | Worker thread + fresh QuickJS runtime | Shared host session + messages | Hard-stop worker termination, pooled by default | Still same OS process; ephemeral mode is slower | -| `RemoteExecutor` | App-defined remote transport boundary | Shared host session + transport | Same API across a remote boundary | You own transport/runtime deployment | +| Executor or mode | Runtime boundary | Tool bridge style | Main strengths | Main constraints | +| --------------------------------------- | ------------------------------------- | ------------------------------ | ------------------------------------------------ | ----------------------------------------------- | +| `QuickJsExecutor` | Fresh in-process QuickJS runtime | Shared runner callback | No native addon, simple install, default backend | Still in-process | +| `QuickJsExecutor` with `host: "worker"` | Worker thread + fresh QuickJS runtime | Shared host session + messages | Hard-stop worker termination, pooled by default | Still same OS process; ephemeral mode is slower | ```mermaid flowchart LR HOST["Host application"] QJS["QuickJsExecutor"] QJSRT["QuickJS runtime"] - REM["RemoteExecutor"] - REMRT["Remote runner"] WQJS["QuickJsExecutor\nhost: worker"] THREAD["Worker thread"] RUNNER["core runner semantics"] - PROTO["@execbox/core/protocol
messages + host session"] + PROTO["@execbox/core/protocol
worker messages + host session"] WQJSRT["QuickJS runtime in worker"] HOST --> QJS --> QJSRT - HOST --> REM --> REMRT HOST --> WQJS --> THREAD --> WQJSRT QJS --> RUNNER - REM --> PROTO - REMRT --> RUNNER WQJS --> PROTO THREAD --> RUNNER ``` @@ -43,11 +37,11 @@ flowchart LR That design gives QuickJS two useful properties: - the runtime semantics are centralized in one runner implementation -- the same guest/tool-call model can be reused behind worker-hosted and remote transport boundaries +- the same guest/tool-call model can be reused behind worker-hosted execution ## Worker-Hosted QuickJS -`QuickJsExecutor` with `host: "worker"` uses a worker thread for lifecycle isolation, but it does not invent a second scripting model. It loads the same QuickJS session runner used by the inline QuickJS executor, reuses the shared QuickJS protocol endpoint inside the worker, and uses the shared `@execbox/core/protocol` host session on the parent side. By default it keeps a worker shell warm between executions; `mode: "ephemeral"` switches to a fresh worker per execution. +`QuickJsExecutor` with `host: "worker"` uses a worker thread for lifecycle isolation while keeping the same scripting model as inline QuickJS. It loads the same QuickJS session runner used by the inline QuickJS executor, reuses the shared QuickJS protocol endpoint inside the worker, and uses the shared `@execbox/core/protocol` host session on the parent side. By default it keeps a worker shell warm between executions; `mode: "ephemeral"` switches to a fresh worker per execution. ```mermaid sequenceDiagram @@ -70,19 +64,18 @@ sequenceDiagram The available executors expose the same public result shape, but they enforce limits differently. -| Concern | QuickJS inline | Remote | QuickJS host: worker | -| ------------------- | ----------------------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | -| Timeout | QuickJS interrupt/deadline handling | Shared host-session timeout + remote cancel + transport teardown | Shared host-session timeout + worker cancellation + worker termination backstop | -| Memory | QuickJS runtime memory limit | Remote runtime decides the hard boundary; execbox still forwards limits | QuickJS memory limit inside worker, optional worker resource limits as backstop | -| Abort to host tools | Abort signal passed through core callback | Abort signal passed through shared host session | Abort signal passed through shared host session | -| Log capture | Captured inside runner | Captured inside the remote runner and returned over the transport boundary | Captured inside worker-side QuickJS runner | +| Concern | QuickJS inline | QuickJS host: worker | +| ------------------- | ----------------------------------------- | ------------------------------------------------------------------------------- | +| Timeout | QuickJS interrupt/deadline handling | Shared host-session timeout + worker cancellation + worker termination backstop | +| Memory | QuickJS runtime memory limit | QuickJS memory limit inside worker, optional worker resource limits as backstop | +| Abort to host tools | Abort signal passed through core callback | Abort signal passed through shared host session | +| Log capture | Captured inside runner | Captured inside worker-side QuickJS runner | ## Security and Operational Trade-offs -- All executor modes provide defense-in-depth measures, not standalone hard hostile-code boundaries. +- All executor modes provide defense-in-depth measures around guest execution. - QuickJS is the easiest operational default and has the cleanest shared runtime story. -- Remote execution keeps the same executor API while moving the runtime behind an app-defined boundary, but execbox deliberately does not ship the network stack for you. -- Worker-hosted QuickJS improves lifecycle isolation and hard-stop behavior, but not process-level trust isolation. +- Worker-hosted QuickJS improves lifecycle isolation and hard-stop behavior inside the host process. ## Pooled QuickJS Shells @@ -99,7 +92,7 @@ Pooling is implemented at the host-shell layer, not at the QuickJS runtime layer - `@execbox/core/protocol` exposes a small bounded async `createResourcePool()` helper that owns reusable shells, idle eviction, and `prewarm()` / `dispose()` support. - Worker-hosted `QuickJsExecutor` pools `Worker` shells. Each shell owns one long-lived transport wrapper plus one attached QuickJS protocol endpoint. - The worker entrypoint only attaches `attachQuickJsProtocolEndpoint(...)` once. That endpoint accepts one active `execute` message at a time and starts a fresh `runQuickJsSession()` for each message. -- Concurrency therefore comes from pool size, not from multiplexing several executions through one shell. +- Concurrency comes from pool size: each shell handles one active execution at a time. At execution time the flow is: @@ -116,7 +109,7 @@ If all shells are busy and the pool is already at `maxSize`, the next `acquire() ### Reuse And Eviction Rules - Successful executions return the shell to the pool. -- Normal guest/runtime/tool failures also return the shell, because they do not imply a poisoned host shell. +- Normal guest/runtime/tool failures also return the shell, because the host shell remains reusable after those outcomes. - `timeout` and `internal_error` results evict the shell, because those outcomes mean the worker or transport state may no longer be trustworthy. - Idle pooled shells are evicted after `idleTimeoutMs`, down to `minSize`. - `dispose()` tears down the executor-owned pool and any idle shells it still owns. @@ -125,13 +118,7 @@ If all shells are busy and the pool is already at `maxSize`, the next `acquire() In pooled mode, a worker can exit before the host session subscribes to close events. The pooled transport wrappers retain the first close reason and replay it to later `onClose(...)` subscribers, so an early shell death still resolves as `internal_error` instead of hanging the execution. -### What Is Not Pooled - -- `QuickJsExecutor` stays in-process and ephemeral because there is no expensive transport shell to reuse. -- `RemoteExecutor` stays transport-factory based and ephemeral because transport ownership belongs to the caller. - ## Choosing an Executor - Choose `QuickJsExecutor` when you want the default backend with the least operational friction. -- Choose `RemoteExecutor` when you want the same execution API but need the runtime to live behind an application-defined process, container, VM, or network boundary. - Choose `QuickJsExecutor` with `host: "worker"` when you want the QuickJS semantics off the main thread with a hard-stop termination path and low-latency pooled reuse by default. diff --git a/docs/src/content/docs/architecture/execbox-mcp-and-protocol.md b/docs/src/content/docs/architecture/execbox-mcp-and-protocol.md index 5e7fe0c..022fbe5 100644 --- a/docs/src/content/docs/architecture/execbox-mcp-and-protocol.md +++ b/docs/src/content/docs/architecture/execbox-mcp-and-protocol.md @@ -8,7 +8,7 @@ This page covers two related but separate parts of the execbox architecture: - MCP adapters in `@execbox/core` - transport-safe execution plumbing in `@execbox/core/protocol` -Use this page as the overview. For the remote execution control flow, read [Remote Workflow](/architecture/execbox-remote-workflow/). For the message-level protocol contract, read [Protocol Reference](/architecture/execbox-protocol-reference/). For the normative runner specification, read [Runner Specification](/architecture/execbox-runner-specification/). +Use this page as the overview. For the message-level protocol contract behind worker-hosted QuickJS, read [Protocol Reference](/architecture/execbox-protocol-reference/). ## MCP Adapter Responsibilities @@ -31,7 +31,7 @@ The MCP adapter layer lets execbox sit on either side of an MCP tool catalog: It owns: - the `execute`, `cancel`, `started`, `tool_call`, `tool_result`, and `done` message types -- the shared host transport session used by worker-hosted `@execbox/quickjs` and `@execbox/remote` +- the shared host transport session used by worker-hosted `@execbox/quickjs` - Node transport bootstrap helpers for worker execution - the reusable async resource pool used by pooled worker shells @@ -45,8 +45,6 @@ The architecture split is: - `QuickJsExecutor` uses the shared runner semantics from `@execbox/core/runtime` directly. - `QuickJsExecutor` in `host: "worker"` mode uses the shared host session from `@execbox/core/protocol` plus the shared QuickJS protocol endpoint inside the worker shell. -- `RemoteExecutor` uses that same host session across an app-owned transport boundary. -- The runner side of a remote deployment attaches a runtime-owned endpoint adapter; `@execbox/quickjs/remote-endpoint` is the shipped QuickJS adapter. - Pooled worker execution reuses only the outer host shell. Each `execute()` call still starts a fresh QuickJS runtime through the shared protocol endpoint. ```mermaid @@ -59,32 +57,27 @@ flowchart TB QJS["QuickJsExecutor"] end - subgraph TransportBacked["Transport-backed executors"] + subgraph WorkerHosted["Worker-hosted execution"] PROTO["@execbox/core/protocol
messages + host session + resource pool"] HOSTED["QuickJsExecutor host: worker"] - REM["RemoteExecutor"] QJS_ENDPOINT["QuickJS protocol endpoint
worker side"] - REMOTE_ENDPOINT["Runtime-owned remote endpoint
QuickJS adapter is shipped"] end CORE --> QJS CORE --> PROTO HOSTED --> PROTO - REM --> PROTO HOSTED --> QJS_ENDPOINT - REM -. runner side .-> REMOTE_ENDPOINT - REMOTE_ENDPOINT -. QuickJS example .-> QJS_ENDPOINT ``` -## Transport-Backed Execution Flow +## Worker-Hosted Execution Flow -The same host-session model is used for worker-hosted QuickJS and remote execution: +Worker-hosted QuickJS uses the host-session model to keep host tool closures on the trusted side while QuickJS runs in the worker shell: ```mermaid sequenceDiagram participant Host as Trusted host participant Session as Host transport session - participant Runner as Transport-backed runner + participant Runner as Worker-side QuickJS endpoint participant Tool as Resolved tool wrapper Host->>Session: execute(code, providers, options) @@ -109,4 +102,4 @@ Important implications: - MCP adapters decide how tool catalogs are discovered, wrapped, and re-exposed. - The protocol decides how a transport-backed runtime asks the trusted host to invoke those tools. - The provider surface remains the real capability boundary. -- Transport-backed execution changes where guest code runs, not who owns host capabilities. +- Worker-hosted execution changes where guest code runs while host capabilities remain controlled by the provider surface. diff --git a/docs/src/content/docs/architecture/execbox-protocol-reference.md b/docs/src/content/docs/architecture/execbox-protocol-reference.md index a8b7b7d..6eaa0e6 100644 --- a/docs/src/content/docs/architecture/execbox-protocol-reference.md +++ b/docs/src/content/docs/architecture/execbox-protocol-reference.md @@ -1,13 +1,11 @@ --- title: Execbox Core Protocol Reference -description: Protocol messages and session semantics for transport-backed execbox runtimes. +description: Protocol messages and session semantics for worker-hosted execbox runtimes. --- This page is the message-level reference for `@execbox/core/protocol`. -It describes the wire shapes and session semantics used by transport-backed execbox runtimes such as worker-hosted QuickJS and remote execution. This is an advanced reference for runtime and remote-runner implementers; most application users should start with [Getting Started](/getting-started/), [Executors](/architecture/execbox-executors/), and [Security & Boundaries](/security/). - -For the higher-level control-flow explanation, read [Remote Workflow](/architecture/execbox-remote-workflow/). For the normative runner specification, read [Runner Specification](/architecture/execbox-runner-specification/). +It describes the wire shapes and session semantics used by worker-hosted QuickJS. This is an advanced reference for execbox runtime maintainers; most application users should start with [Getting Started](/getting-started/), [Executors](/architecture/execbox-executors/), and [Security & Boundaries](/security/). ## Table of Contents @@ -186,7 +184,7 @@ Two ids are used for different scopes: Host-session behavior: -- runner messages with an `id` that does not match the active session are ignored +- runner messages are accepted only when their `id` matches the active session - `tool_result` is correlated by `callId` - `done` settles the execution and ends the session @@ -209,7 +207,7 @@ Host-session behavior: } ``` -The manifest intentionally does not include: +The manifest includes only runtime-safe metadata. Host-owned values stay on the trusted side: - executable host closures - upstream client objects @@ -240,7 +238,7 @@ Host-session behavior: - caller abort or timeout sends `cancel` - host also aborts its own tool-dispatch signal immediately -- if the runner does not finish within the configured cancel grace window, the host may terminate the transport +- the host may terminate the transport after the configured cancel grace window - unexpected transport close or transport error fails the session Important nuance: @@ -256,7 +254,7 @@ Trusted host guarantees: - host tool dispatch validates and classifies tool failures before sending `tool_result` - non-serializable host values are rejected before they are sent back to the runner -- guest runtime does not receive host closures directly +- guest runtime receives proxy metadata while host closures stay on the trusted side Runner guarantees: @@ -277,9 +275,9 @@ Example execution with one tool call: ## Scope Of This Reference -This reference describes the message contract and host-session semantics used inside the execbox package family. It does not specify: +This reference describes the message contract and host-session semantics used inside the execbox package family. Application deployments own the surrounding operational policy: -- a built-in network transport such as HTTP or WebSocket -- multi-execution multiplexing over one shared long-lived channel -- authentication, tenancy, or deployment policy -- a formal compatibility promise beyond the current implementation +- transport selection such as worker threads, HTTP, or WebSocket +- session ownership and channel lifecycle +- authentication, tenancy, and deployment policy +- compatibility policy for external runner implementations diff --git a/docs/src/content/docs/architecture/execbox-remote-workflow.md b/docs/src/content/docs/architecture/execbox-remote-workflow.md deleted file mode 100644 index b87c6d6..0000000 --- a/docs/src/content/docs/architecture/execbox-remote-workflow.md +++ /dev/null @@ -1,139 +0,0 @@ ---- -title: Execbox Remote Execution Workflow -description: The end-to-end control flow for transport-backed execbox remote execution. ---- - -This page explains how `@execbox/remote` fits into the execbox architecture, with emphasis on where code runs, when the transport is opened, and how guest tool calls pause and resume. - -Use this page when you want the control-flow story. For the wire-format reference, read [Protocol Reference](/architecture/execbox-protocol-reference/). For the normative runner specification, read [Runner Specification](/architecture/execbox-runner-specification/). - -## The Boundary In One Sentence - -`RemoteExecutor` moves the guest JavaScript runtime behind an app-owned transport, but the trusted host still owns providers, tool closures, upstream MCP clients, API clients, tenant maps, and secrets. - -## Main Roles - -| Role | Lives where | Responsibility | -| -------------------- | ----------------------------------- | --------------------------------------------------------------------------------- | -| Downstream client | outside execbox | Calls wrapper tools such as `mcp_execute_code` | -| Wrapped MCP server | host process | Exposes `mcp_search_tools`, `mcp_execute_code`, and `mcp_code` | -| Host execbox session | host process | Owns the transport session, dispatches tool calls, enforces cancellation backstop | -| Remote runner | remote runtime | Runs guest JavaScript and emits `tool_call` messages | -| Upstream tools | host process or upstream dependency | Do the real work when a guest tool call is dispatched | - -## Connection Lifecycle - -`RemoteExecutor` does not open a connection when you construct it. It opens a fresh transport when `execute(...)` starts, uses that single bidirectional channel for the full execution, then disposes it when the execution finishes or fails. - -```mermaid -sequenceDiagram - participant Host as Host app - participant Exec as RemoteExecutor - participant Transport as App-owned transport - participant Runner as Remote runner - - Host->>Exec: new RemoteExecutor({ connectTransport }) - Note over Exec: No connection yet - Host->>Exec: execute(code, providers, options?) - Exec->>Transport: connectTransport() - Transport->>Runner: open one fresh session - Runner-->>Transport: use same channel for execute/tool_call/tool_result/done - Exec->>Transport: dispose() after completion -``` - -Important implications: - -- transport establishment happens per execution, not per executor instance -- the remote side does not create a separate callback connection for tool calls -- `execute`, `tool_call`, `tool_result`, `done`, and `cancel` all use the same open channel - -## End-to-End Flow - -The same flow works whether the provider wraps local tools or an upstream MCP server. The important split is between guest execution and trusted tool dispatch. - -```mermaid -sequenceDiagram - participant Client as Downstream MCP client - participant Wrapper as Wrapped MCP server - participant Host as Host execbox session - participant Runner as Remote runner - participant Upstream as Upstream tools or MCP server - - Client->>Wrapper: callTool("mcp_execute_code", { code }) - Wrapper->>Host: executor.execute(code, [provider]) - Host->>Runner: execute(code, runtime options, provider manifests) - Runner-->>Host: started - Runner->>Runner: run guest code - Runner-->>Host: tool_call(providerName, safeToolName, input) - Host->>Upstream: dispatch trusted tool - Upstream-->>Host: result or error - Host-->>Runner: tool_result(ok/result or error) - Runner->>Runner: resume guest code after await - Runner-->>Host: done(ExecuteResult) - Host-->>Wrapper: execution result - Wrapper-->>Client: MCP tool response -``` - -## Where Guest Code Pauses - -Execbox does not parse JavaScript and look for tool calls ahead of time. The pause happens because injected guest tools are Promise-returning proxy functions. - -When guest code runs: - -```js -const toolResult = await tools.echo({ ok: true }); -const message = toolResult.ok ? "done" : "failed"; -``` - -the remote runner: - -1. calls the injected proxy function for `tools.echo` -2. creates a deferred Promise for that call -3. emits `tool_call` to the host -4. suspends normal async execution at `await` -5. waits for `tool_result` -6. resolves or rejects the Promise -7. resumes the same JavaScript execution after the `await` - -So the tool implementation runs on the host, but the surrounding `if`, `throw`, destructuring, and return value construction all continue in the remote runtime after the Promise settles. - -## What Crosses The Remote Boundary - -What is sent to the remote runner: - -- the code string -- runtime limits such as timeout, memory, and log bounds -- execution id -- provider manifests: namespace name, safe tool names, descriptions, and generated types -- JSON-serializable tool inputs and outputs - -What does not cross the boundary unless a tool explicitly returns or logs it: - -- provider closures -- upstream MCP client instances -- API clients -- tenant maps -- API keys and other secrets - -This is why the provider surface is the real capability boundary. The remote runner only sees the metadata needed to build guest-side tool proxies. - -## Wrapped MCP Servers And Remote Execution - -When `codeMcpServer()` is combined with `RemoteExecutor`, there are two separate protocols in play: - -- MCP between downstream clients, wrapper servers, and any upstream MCP source -- execbox transport messages between the trusted host session and the remote runner - -Those layers are intentionally separate: - -- MCP chooses how tool catalogs are discovered or exposed -- execbox remote protocol chooses how guest JavaScript asks the trusted host to invoke a tool - -That means a wrapped MCP tool can still be invoked by the host while the guest runtime itself lives remotely. - -## Security Notes - -- `execbox-remote` improves deployment flexibility and lets you move the runtime out of the application process. -- It does not make the remote runner the capability owner. The host-side provider surface still decides what guest code can do. -- Secrets stay host-side as long as host tool implementations do not return or log them. -- The actual trust boundary depends on the runtime and infrastructure you deploy behind the transport, not on the protocol alone. diff --git a/docs/src/content/docs/architecture/execbox-runner-specification.md b/docs/src/content/docs/architecture/execbox-runner-specification.md deleted file mode 100644 index 6721a9c..0000000 --- a/docs/src/content/docs/architecture/execbox-runner-specification.md +++ /dev/null @@ -1,409 +0,0 @@ ---- -title: Execbox Runner Specification -description: The normative runner contract for transport-backed execbox runtimes. ---- - -This page defines the runner specification for transport-backed execbox runners. It is an advanced reference for users implementing their own runner boundary. - -Use it when you want to implement a non-TypeScript runner, such as a Go remote runner, without reverse-engineering the shipped TypeScript implementation. For the control-flow walkthrough, read [Remote Workflow](/architecture/execbox-remote-workflow/). For the message catalog, read [Protocol Reference](/architecture/execbox-protocol-reference/). - -## Table of Contents - -- [Status And Scope](#status-and-scope) -- [Core Model](#core-model) -- [Session Model](#session-model) -- [Inputs To The Runner](#inputs-to-the-runner) -- [Guest Namespace Contract](#guest-namespace-contract) -- [Tool Call Contract](#tool-call-contract) -- [Serialization Contract](#serialization-contract) -- [Logs](#logs) -- [Final Result Contract](#final-result-contract) -- [Cancellation And Transport Failure](#cancellation-and-transport-failure) -- [Execution Id And Message Correlation](#execution-id-and-message-correlation) -- [Minimal Success Transcript](#minimal-success-transcript) -- [Minimal Cancellation Transcript](#minimal-cancellation-transcript) - -## Status And Scope - -This is the normative runner specification for the execbox runner contract. - -It defines: - -- the execution lifecycle a conforming runner must implement -- what crosses the host/runner boundary -- how guest-visible tools behave -- how results, logs, cancellation, and failures must surface - -It does not define: - -- a built-in network transport such as HTTP or WebSocket -- authentication, tenancy, or deployment policy -- how a runner internally embeds JavaScript - -A conforming runner may use a different language or engine internally, as long as its externally visible behavior matches this contract. - -## Core Model - -Execbox splits responsibility between a trusted host and an untrusted or less-trusted runner: - -- the host owns providers, tool closures, validation, upstream clients, API clients, and secrets -- the runner owns guest JavaScript execution, guest-visible tool proxies, console capture, and final result emission - -```mermaid -flowchart LR - subgraph Host["Trusted host"] - EXEC["execute(code, providers, options)"] - MANIFEST["Provider manifests"] - DISPATCH["Trusted tool dispatch"] - CAPS["Closures, clients, secrets"] - end - - subgraph Runner["Conforming runner"] - JS["Guest JavaScript"] - PROXY["Injected async tool proxies"] - LOGS["Captured logs"] - DONE["Final ExecuteResult"] - end - - EXEC --> MANIFEST --> PROXY - JS --> PROXY - PROXY --> DISPATCH - DISPATCH --> PROXY - JS --> LOGS - JS --> DONE - CAPS -. stay on host .-> DISPATCH -``` - -The runner never receives host closures or secrets. It only receives code, runtime options, and provider metadata. - -## Session Model - -A transport-backed runner covered by this specification is single-execution and single-session: - -- one transport session carries exactly one active execution -- the transport is bidirectional and sustained for the lifetime of that execution -- the host may open a fresh transport per execution -- the runner must not require a second callback connection for tool calls - -If the runner receives a second `execute` while one execution is active, it should reject that new request with a terminal `done` carrying `internal_error`. - -### State Machine - -```mermaid -stateDiagram-v2 - [*] --> Idle - Idle --> Starting: execute - Starting --> Running: started - Starting --> Finished: done - Running --> WaitingOnHost: tool_call - WaitingOnHost --> Running: tool_result - Running --> Cancelling: cancel - WaitingOnHost --> Cancelling: cancel - Cancelling --> Finished: done - Running --> Finished: done - WaitingOnHost --> Finished: done - Finished --> [*] -``` - -Specification rules: - -- a successful session must end with exactly one `done` -- `started` should be emitted once after the runner accepts `execute` and before any `tool_call` or `done` -- `tool_call` and `tool_result` may repeat zero or more times before `done` -- after `done`, the session is complete and no further protocol messages are valid for that execution - -## Inputs To The Runner - -The runner receives one `execute` message: - -```ts -{ - type: "execute"; - id: string; - code: string; - options: ExecutorRuntimeOptions; - providers: ProviderManifest[]; -} -``` - -Requirements: - -- `id` identifies the execution session -- `code` is the full guest JavaScript program to run -- `options` carries timeout, memory, and log limits -- `providers` is metadata only; it is not executable capability - -The runner must not assume any provider manifest contains: - -- host closures -- upstream MCP clients -- tenant maps -- API keys or other secrets - -## Guest Namespace Contract - -Each `ProviderManifest` becomes one guest-visible global namespace whose property names are the manifest tool `safeName` values. - -For a provider manifest like: - -```ts -{ - name: "firecrawl", - tools: { - scrape_url: { - safeName: "scrape_url", - originalName: "scrape-url" - } - }, - types: "declare namespace firecrawl { ... }" -} -``` - -the guest runtime must expose: - -```js -await firecrawl.scrape_url(input); -``` - -Injected-tool requirements: - -- each tool must be an async or Promise-returning function -- only the first guest argument is transported as tool input -- omitted input is treated as `undefined` -- the emitted tool input must be transport-safe -- the proxy must suspend normal async execution until a matching `tool_result` arrives - -### Pause/Resume Semantics - -When guest code runs: - -```js -const page = await firecrawl.scrape_url({ url: "https://example.com" }); -const title = page.title ?? null; -``` - -the conforming runner must: - -1. create a pending Promise for that tool call -2. emit `tool_call` -3. let JavaScript pause at the `await` -4. wait for a `tool_result` with the same `callId` -5. resolve or reject the pending Promise -6. resume the same guest execution after the `await` - -This pause is normal JavaScript Promise suspension, not a source-to-source rewrite pass. - -## Tool Call Contract - -Each guest tool invocation emits: - -```ts -{ - type: "tool_call"; - callId: string; - providerName: string; - safeToolName: string; - input: unknown; -} -``` - -Specification rules: - -- `callId` must uniquely identify one tool invocation within the execution -- `providerName` and `safeToolName` must match the injected namespace/tool pair -- the runner must not emit a second `tool_call` with the same `callId` - -The host responds with exactly one matching `tool_result`: - -```ts -{ - type: "tool_result"; - callId: string; - ok: true; - result: unknown; -} -``` - -or: - -```ts -{ - type: "tool_result"; - callId: string; - ok: false; - error: { - code: ExecuteErrorCode; - message: string; - } -} -``` - -Specification rules: - -- `tool_result.callId` must correlate exactly one pending tool Promise -- a successful `tool_result` resolves the Promise to `result` -- a failing `tool_result` rejects the Promise with an Error-like value whose `.message` is the trusted host message and whose `.code` is the trusted host error code -- trusted host-originated tool failures must remain distinguishable from guest-created errors so the final uncaught result can preserve the trusted host error code - -## Serialization Contract - -Every value that crosses the host/runner boundary must be transport-safe. - -The current runner specification allows: - -- `undefined` for omitted tool input and successful expressions that evaluate to `undefined` -- `null` -- strings -- booleans -- finite numbers -- arrays of serializable values -- plain objects with serializable values - -The runner specification rejects: - -- `bigint` -- functions -- symbols -- non-finite numbers -- cyclic values -- non-plain objects as transported results - -Requirements: - -- non-serializable tool inputs must not be surfaced to the host as successful structured values -- non-serializable tool results must surface as `serialization_error` -- non-serializable final guest results must surface as `serialization_error` - -### JSON Transport Note - -The protocol types are defined as JavaScript values, but many real transports will serialize them as JSON. - -Guidance for JSON-backed transports: - -- omitted tool input may be represented by a missing `input` field and interpreted as `undefined` -- a successful final result of `undefined` may be represented by an omitted `result` field when `ok: true` -- a failed result still requires an explicit `error` - -## Logs - -Logs are captured runner-side and returned in the terminal `done` message as `string[]`. - -Requirements: - -- expose `console.log`, `console.info`, `console.warn`, and `console.error` -- each call appends one log line -- one log line is the space-joined formatting of the console arguments -- `undefined` formats as the literal string `undefined` -- non-string values should be JSON-stringified when possible, with fallback string conversion when needed - -### Truncation - -Log truncation is externally visible behavior and is part of this specification: - -- first apply `maxLogLines` by keeping only the earliest lines up to the limit -- then apply `maxLogChars` cumulatively across those remaining lines -- if the character limit is reached in the middle of a line, that final line is clipped -- truncated logs are returned from `done` - -## Final Result Contract - -The runner must terminate with one `done` message: - -```ts -{ - type: "done"; - id: string; - ok: boolean; - durationMs: number; - logs: string[]; - result?: unknown; - error?: { - code: ExecuteErrorCode; - message: string; - }; -} -``` - -Requirements: - -- `id` must match the active execution id -- exactly one of `result` or `error` must be present according to `ok` -- `logs` must be a `string[]` -- `durationMs` must measure execution wall time for that run -- `result` must be transport-safe, with omitted `result` interpreted as `undefined` on successful JSON-serialized responses - -### Stable Error Codes - -A conforming runner must use the current public error code set: - -- `timeout` -- `memory_limit` -- `validation_error` -- `tool_error` -- `runtime_error` -- `serialization_error` -- `internal_error` - -### Error Mapping Rules - -Terminal-failure requirements: - -- trusted host tool failures may surface as their trusted host code when uncaught by guest code -- guest-thrown values must not be upgraded into trusted host errors solely because their text mentions timeout or memory -- timeout must be reserved for real timeout or cancellation behavior -- memory-limit classification must be reserved for real runtime memory failures -- unknown or unexpected runner failures should surface as `internal_error` or `runtime_error`, not as a forged trusted host error - -## Cancellation And Transport Failure - -The host may send: - -```ts -{ - type: "cancel"; - id: string; -} -``` - -Requirements: - -- if `id` matches the active execution, the runner must promptly abort execution -- pending guest tool awaits should reject promptly so the execution can unwind -- cancellation should result in a terminal timeout-shaped failure for the caller - -Host-session semantics that a conforming runner must tolerate: - -- `cancel` may arrive before any `tool_call` -- `cancel` may arrive while the runner is waiting on a `tool_result` -- the host may force-terminate the transport shortly after `cancel` if the runner does not finish -- unexpected transport close or transport error before `done` is terminal for the session - -## Execution Id And Message Correlation - -Specification rules: - -- `id` identifies the execution session -- `callId` identifies one tool invocation inside that session -- the host may ignore runner messages whose `id` does not match the active execution -- the runner must match `tool_result` by `callId` -- after a tool Promise settles, the associated `callId` is no longer active - -## Minimal Success Transcript - -```json -{"type":"execute","id":"exec-1","code":"const value = await tools.echo({\"ok\":true}); value.ok","options":{"timeoutMs":1000,"memoryLimitBytes":67108864,"maxLogLines":100,"maxLogChars":64000},"providers":[{"name":"tools","tools":{"echo":{"safeName":"echo","originalName":"echo","description":"Echo input"}},"types":"declare namespace tools { ... }"}]} -{"type":"started","id":"exec-1"} -{"type":"tool_call","callId":"call-1","providerName":"tools","safeToolName":"echo","input":{"ok":true}} -{"type":"tool_result","callId":"call-1","ok":true,"result":{"ok":true}} -{"type":"done","id":"exec-1","ok":true,"durationMs":3,"logs":[],"result":true} -``` - -## Minimal Cancellation Transcript - -```json -{"type":"execute","id":"exec-2","code":"await tools.hang({})","options":{"timeoutMs":1000,"memoryLimitBytes":67108864,"maxLogLines":100,"maxLogChars":64000},"providers":[{"name":"tools","tools":{"hang":{"safeName":"hang","originalName":"hang"}},"types":"declare namespace tools { ... }"}]} -{"type":"started","id":"exec-2"} -{"type":"tool_call","callId":"call-9","providerName":"tools","safeToolName":"hang","input":{}} -{"type":"cancel","id":"exec-2"} -{"type":"done","id":"exec-2","ok":false,"durationMs":1005,"logs":[],"error":{"code":"timeout","message":"Execution timed out"}} -``` diff --git a/docs/src/content/docs/architecture/index.md b/docs/src/content/docs/architecture/index.md index e431118..6f6060c 100644 --- a/docs/src/content/docs/architecture/index.md +++ b/docs/src/content/docs/architecture/index.md @@ -8,17 +8,15 @@ Execbox is the code-execution part of the `execbox` workspace. It turns host too This Concepts section is for library users choosing how to integrate execbox: - start here when you need the package map, trust model, and overall flow -- use the deeper pages when you are choosing a runtime, wrapping MCP tools, or implementing a remote runner +- use the deeper pages when you are choosing a runtime, wrapping MCP tools, or understanding the worker protocol ## Reading guide - Start here for the package map, trust model, and overall flow. - Read [Core](/architecture/execbox-core/) for provider resolution, execution contracts, and error handling. -- Read [Executors](/architecture/execbox-executors/) for inline QuickJS, worker-hosted QuickJS, and remote execution trade-offs. +- Read [Executors](/architecture/execbox-executors/) for inline QuickJS and worker-hosted QuickJS trade-offs. - Read [MCP And Protocol](/architecture/execbox-mcp-and-protocol/) for MCP wrapping and where `@execbox/core/protocol` fits. -- Read [Remote Workflow](/architecture/execbox-remote-workflow/) for the end-to-end remote execution control flow. - Read [Protocol Reference](/architecture/execbox-protocol-reference/) for the protocol message catalog and session rules. -- Read [Runner Specification](/architecture/execbox-runner-specification/) for the normative runner specification for non-TypeScript runners. - Read [Security & Boundaries](/security/) before choosing a production trust boundary. - Read [Performance](/performance/) for latency, pooling, and executor sizing guidance. @@ -28,17 +26,14 @@ This Concepts section is for library users choosing how to integrate execbox: flowchart LR APP["Host application"] CORE["@execbox/core
provider resolution + MCP adapters + runtime helpers"] - QJS["@execbox/quickjs
QuickJS executor + reusable runner"] - REM["@execbox/remote
transport-backed remote executor"] - PROTO["@execbox/core/protocol
transport messages + shared host session"] + QJS["@execbox/quickjs
inline + worker-hosted QuickJS executor"] + PROTO["@execbox/core/protocol
worker messages + shared host session"] MCP["MCP sources and wrapped servers"] APP --> CORE APP --> QJS - APP --> REM CORE --> MCP QJS --> PROTO - REM --> PROTO ``` ## End-to-end execution model @@ -58,5 +53,5 @@ Key implications: - The provider/tool surface is the capability boundary, not the JavaScript syntax itself. - Fresh runtimes, schema validation, JSON-only boundaries, timeouts, memory limits, and bounded logs are defense-in-depth features. -- In-process and worker-hosted execution still share the host process. Use `@execbox/remote` behind a separate process, container, VM, or similar boundary when the code source is hostile or multi-tenant. +- In-process and worker-hosted execution share the host process. For hostile-code or multi-tenant deployments, run the application-level execution service behind a process, container, VM, or equivalent operational boundary. - Wrapping third-party MCP servers is a separate dependency-trust decision from letting end users author guest code. diff --git a/docs/src/content/docs/examples.md b/docs/src/content/docs/examples.md index e014563..f8d907c 100644 --- a/docs/src/content/docs/examples.md +++ b/docs/src/content/docs/examples.md @@ -19,7 +19,6 @@ npm run examples | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------- | | [`execbox-basic.ts`](https://github.com/aallam/execbox/blob/main/examples/execbox-basic.ts) | Resolve a provider and execute guest code with QuickJS. | You want the smallest end-to-end example. | | [`execbox-worker.ts`](https://github.com/aallam/execbox/blob/main/examples/execbox-worker.ts) | Run the same provider flow with QuickJS hosted in a worker thread. | You want QuickJS off the main thread without leaving the process. | -| [`execbox-remote.ts`](https://github.com/aallam/execbox/blob/main/examples/execbox-remote.ts) | Run the same provider flow through a transport-backed executor. | You already own the transport/runtime boundary and want execbox to plug into it. | | [`execbox-mcp-provider.ts`](https://github.com/aallam/execbox/blob/main/examples/execbox-mcp-provider.ts) | Wrap MCP tools into a provider and execute against them. | You want guest code to call upstream MCP tools as code. | | [`execbox-mcp-server.ts`](https://github.com/aallam/execbox/blob/main/examples/execbox-mcp-server.ts) | Expose `mcp_search_tools`, `mcp_execute_code`, and `mcp_code`. | You want downstream MCP clients to execute code against a wrapped tool namespace. | diff --git a/docs/src/content/docs/getting-started.md b/docs/src/content/docs/getting-started.md index 3562e98..1aea3b8 100644 --- a/docs/src/content/docs/getting-started.md +++ b/docs/src/content/docs/getting-started.md @@ -50,7 +50,6 @@ console.log(result); - Use `@execbox/quickjs` first for trusted code and the smallest setup. - Use `new QuickJsExecutor({ host: "worker" })` when you want QuickJS off the main thread with pooled worker shells. -- Use `@execbox/remote` when your application owns the process, container, VM, or network boundary for the runtime. Worker-hosted QuickJS improves local lifecycle control, but it still shares the host process. Read [Security](/security/) before treating any runtime placement as a production trust boundary. diff --git a/docs/src/content/docs/index.md b/docs/src/content/docs/index.md index af41ceb..d3ab1f7 100644 --- a/docs/src/content/docs/index.md +++ b/docs/src/content/docs/index.md @@ -7,7 +7,7 @@ next: false Execbox is a Node.js 22+ library for running guest JavaScript against host-defined tools and wrapped MCP servers. Start with the QuickJS executor, get one provider call working, then choose a runtime placement that matches your -deployment boundary. +deployment needs. ## Install @@ -26,7 +26,6 @@ from guest JavaScript through `QuickJsExecutor`. | ------------------ | ------------------------------------------------------------ | | `@execbox/core` | Provider contracts, MCP adapters, and shared runtime helpers | | `@execbox/quickjs` | Inline and worker-hosted QuickJS execution | -| `@execbox/remote` | Transport-backed execution with an app-owned remote runtime | ## Read next @@ -34,20 +33,18 @@ from guest JavaScript through `QuickJsExecutor`. - [Getting Started](/getting-started/) - install execbox and run the smallest QuickJS flow -- [Runtime Choices](/runtime-choices/) - choose between inline, worker, and - remote execution +- [Runtime Choices](/runtime-choices/) - choose between inline and worker-hosted + QuickJS execution ### Use - [Examples](/examples/) - run the main example flows from the repo - [MCP Provider](/architecture/execbox-mcp-and-protocol/) - wrap upstream MCP tools as guest-callable namespaces -- [Remote Runner](/architecture/execbox-remote-workflow/) - move guest - execution behind your own transport ### Understand - [Security & Boundaries](/security/) - understand what execbox does and does - not isolate + isolate - [Architecture](/architecture/) - review the package and runtime model - [Performance](/performance/) - compare runtime placement and pooling tradeoffs diff --git a/docs/src/content/docs/performance/index.md b/docs/src/content/docs/performance/index.md index 46558fd..7626aa2 100644 --- a/docs/src/content/docs/performance/index.md +++ b/docs/src/content/docs/performance/index.md @@ -9,12 +9,11 @@ This page summarizes practical performance guidance for choosing and configuring Each executor makes a different trade-off between runtime placement, startup cost, and steady-state latency. -| Executor | Runtime placement | Relative Latency | Best For | -| ----------------------------------------------- | ------------------------------------------------------------ | ---------------------------------------- | ------------------------------------------------------------------- | -| `QuickJsExecutor` | In-process QuickJS runtime | Fastest steady-state path | Trusted code and the lowest possible latency | -| `QuickJsExecutor` (`host: "worker"`, pooled) | Worker thread shell with a fresh guest runtime per execution | Low latency after startup | Off-main-thread execution with good local throughput | -| `QuickJsExecutor` (`host: "worker"`, ephemeral) | Fresh worker per execution | High startup cost | Fresh worker lifecycle per execution when higher latency is allowed | -| `RemoteExecutor` | Caller-owned transport to a remote runner | Depends on the transport and remote host | App-owned runtime boundaries, remote capacity, or remote scheduling | +| Executor | Runtime placement | Relative Latency | Best For | +| ----------------------------------------------- | ------------------------------------------------------------ | ------------------------- | ------------------------------------------------------------------- | +| `QuickJsExecutor` | In-process QuickJS runtime | Fastest steady-state path | Trusted code and the lowest possible latency | +| `QuickJsExecutor` (`host: "worker"`, pooled) | Worker thread shell with a fresh guest runtime per execution | Low latency after startup | Off-main-thread execution with good local throughput | +| `QuickJsExecutor` (`host: "worker"`, ephemeral) | Fresh worker per execution | High startup cost | Fresh worker lifecycle per execution when higher latency is allowed | ## Practical Characteristics @@ -22,7 +21,7 @@ Each executor makes a different trade-off between runtime placement, startup cos Worker-hosted `QuickJsExecutor` defaults to pooled mode. Pooling reuses the worker shell while still creating a fresh guest runtime for each execution. That keeps the off-main-thread shell warm without paying worker startup cost on every call. -If you care about request latency or throughput, start with pooled mode. Ephemeral mode is primarily an isolation choice, not a speed choice. +If you care about request latency or throughput, start with pooled mode. Ephemeral mode prioritizes a fresh worker lifecycle for every execution. ### Tool-call overhead is usually not the bottleneck @@ -55,12 +54,11 @@ const executor = new QuickJsExecutor({ ## Choosing An Executor -| Priority | Recommended | -| --------------------------------------------- | ------------------------------------------------------ | -| Lowest latency for trusted code | `QuickJsExecutor` | -| Best local off-main-thread throughput | `QuickJsExecutor` with `host: "worker"` in pooled mode | -| Fresh worker lifecycle every execution | `QuickJsExecutor` with worker `mode: "ephemeral"` | -| App-owned runtime boundary or remote capacity | `RemoteExecutor` | +| Priority | Recommended | +| -------------------------------------- | ------------------------------------------------------ | +| Lowest latency for trusted code | `QuickJsExecutor` | +| Best local off-main-thread throughput | `QuickJsExecutor` with `host: "worker"` in pooled mode | +| Fresh worker lifecycle every execution | `QuickJsExecutor` with worker `mode: "ephemeral"` | For most applications, start with `QuickJsExecutor` and move to `host: "worker"` when you want local execution off the main thread without leaving the package. diff --git a/docs/src/content/docs/runtime-choices.md b/docs/src/content/docs/runtime-choices.md index 6c13bc2..d0f5863 100644 --- a/docs/src/content/docs/runtime-choices.md +++ b/docs/src/content/docs/runtime-choices.md @@ -1,6 +1,6 @@ --- title: Runtime Choices -description: Choose between inline QuickJS, worker-hosted QuickJS, and remote execution for execbox. +description: Choose between inline QuickJS and worker-hosted QuickJS for execbox. --- Execbox keeps the execution contract stable while letting you move guest JavaScript to the runtime boundary that fits your deployment. @@ -15,7 +15,7 @@ import { QuickJsExecutor } from "@execbox/quickjs"; const executor = new QuickJsExecutor(); ``` -Inline QuickJS is the lowest-friction path for trusted code. It gives each execution fresh runtime state, but it still runs inside the host process. +Inline QuickJS is the lowest-friction path for trusted code. It gives each execution fresh runtime state inside the host process. ## Move QuickJS to a worker @@ -29,21 +29,7 @@ const executor = new QuickJsExecutor({ }); ``` -Worker mode is useful for lifecycle control, pooled worker reuse, and keeping guest runtime work away from the main thread. It is still not a hard tenant boundary because the worker shares the host process. - -## Use a remote runner - -Use `@execbox/remote` when your application owns a process, container, VM, or network boundary for guest execution. - -```ts -import { RemoteExecutor } from "@execbox/remote"; - -const executor = new RemoteExecutor({ - connectTransport, -}); -``` - -Remote execution is the right direction for hostile-code or multi-tenant deployments, but the trust boundary is the infrastructure you deploy behind the transport. The host-side provider surface still decides what guest code can do. +Worker mode is useful for lifecycle control, pooled worker reuse, and keeping guest runtime work away from the main thread. It runs in a worker thread that shares the host process. ## Decision guide @@ -51,12 +37,9 @@ Remote execution is the right direction for hostile-code or multi-tenant deploym | ------------------------------------------- | ---------------------------------------- | | Smallest install and development path | `@execbox/quickjs` | | Off-main-thread local execution | `@execbox/quickjs` with `host: "worker"` | -| App-owned runtime or isolation boundary | `@execbox/remote` | -| Non-TypeScript runner implementation | Runner specification | | Host wrapping of upstream MCP tool catalogs | MCP provider guide | Next: - [Getting Started](/getting-started/) for the smallest working example -- [Remote Runner](/architecture/execbox-remote-workflow/) for the transport-backed control flow - [Security](/security/) before choosing a production boundary diff --git a/docs/src/content/docs/security.md b/docs/src/content/docs/security.md index 04eba43..9a1e255 100644 --- a/docs/src/content/docs/security.md +++ b/docs/src/content/docs/security.md @@ -3,9 +3,9 @@ title: Security & Boundaries description: Understand execbox's defense-in-depth controls, capability boundary, and production trust model. --- -Execbox provides defense-in-depth controls for guest code execution. The isolation level you get depends on the executor and deployment boundary you choose. +Execbox provides defense-in-depth controls for guest code execution. The supported v1 runtime choices are inline QuickJS and worker-hosted QuickJS, with the provider surface acting as the capability boundary. -## What execbox provides +## Built-in controls - Fresh execution state per call - JSON-only tool and result boundaries @@ -14,27 +14,20 @@ Execbox provides defense-in-depth controls for guest code execution. The isolati - Timeout and memory controls - Abort propagation into in-flight host tool work -## What execbox does not provide - -- A hard security boundary for hostile or multi-tenant code by default -- That in-process runtimes are equivalent to a container, VM, or separate trust domain -- That wrapping a third-party MCP server removes the need to evaluate that dependency - ## The real capability boundary The provider/tool surface is the capability boundary. -Providers are explicit capability grants. If guest code can call a dangerous tool, guest code can exercise that authority. Execbox changes how tool access is exposed and controlled; it does not erase the authority behind the tool itself. +Providers are explicit capability grants. Guest code receives only the tool namespaces you resolve and pass into an executor. Keep production providers small, tenant-aware, and scoped to the exact operations the guest code should be able to request. ## Choosing the right boundary -| Need | Recommended path | -| ----------------------------------------- | ---------------------------------------- | -| Lowest friction | `@execbox/quickjs` | -| Off-main-thread lifecycle isolation | `@execbox/quickjs` with `host: "worker"` | -| Application-owned remote/runtime boundary | `@execbox/remote` | +| Need | Recommended path | +| ----------------------------------- | ---------------------------------------- | +| Lowest friction | `@execbox/quickjs` | +| Off-main-thread lifecycle isolation | `@execbox/quickjs` with `host: "worker"` | -For hostile-code or multi-tenant deployments, prefer `@execbox/remote` behind a container, VM, or equivalent boundary that you control operationally. +For hostile-code or multi-tenant deployments, run the application-level execution service behind a process, container, VM, or equivalent boundary that you control operationally, and keep the provider surface minimal. ## Deeper reading diff --git a/examples/README.md b/examples/README.md index 01b7625..c1de905 100644 --- a/examples/README.md +++ b/examples/README.md @@ -20,7 +20,6 @@ See the public [Examples docs](https://execbox.aallam.com/examples) for when to | File | What it shows | | ------------------------------------------------------ | ----------------------------------------------------------------- | | [`execbox-basic.ts`](./execbox-basic.ts) | Resolve a provider and execute guest code with QuickJS | -| [`execbox-remote.ts`](./execbox-remote.ts) | Run the same provider flow through a transport-backed executor | | [`execbox-worker.ts`](./execbox-worker.ts) | Run the same provider flow with QuickJS hosted in a worker thread | | [`execbox-mcp-provider.ts`](./execbox-mcp-provider.ts) | Wrap MCP tools into a provider and execute against them | | [`execbox-mcp-server.ts`](./execbox-mcp-server.ts) | Expose `mcp_search_tools`, `mcp_execute_code`, and `mcp_code` | diff --git a/examples/execbox-remote.ts b/examples/execbox-remote.ts deleted file mode 100644 index 3eec05e..0000000 --- a/examples/execbox-remote.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { resolveProvider } from "@execbox/core"; -import { attachQuickJsRemoteEndpoint } from "@execbox/quickjs/remote-endpoint"; -import { RemoteExecutor } from "@execbox/remote"; -import type { - DispatcherMessage, - HostTransport, - RunnerMessage, - TransportCloseReason, -} from "@execbox/core/protocol"; - -type CloseHandler = (reason?: TransportCloseReason) => void; -type ErrorHandler = (error: Error) => void; -type MessageHandler = (message: RunnerMessage) => void; -type RunnerMessageHandler = (message: DispatcherMessage) => void; - -function createInMemoryRemoteTransport(): HostTransport { - const closeHandlers = new Set(); - const errorHandlers = new Set(); - const messageHandlers = new Set(); - const runnerHandlers = new Set(); - let closed = false; - - attachQuickJsRemoteEndpoint({ - onClose(handler) { - closeHandlers.add(handler); - return () => closeHandlers.delete(handler); - }, - onMessage(handler) { - runnerHandlers.add(handler as RunnerMessageHandler); - return () => runnerHandlers.delete(handler as RunnerMessageHandler); - }, - send(message) { - queueMicrotask(() => { - for (const handler of messageHandlers) { - handler(message as RunnerMessage); - } - }); - }, - }); - - const emitClose = (reason?: TransportCloseReason) => { - closed = true; - for (const handler of closeHandlers) { - handler(reason); - } - }; - - return { - dispose() { - closed = true; - }, - onClose(handler) { - closeHandlers.add(handler); - return () => closeHandlers.delete(handler); - }, - onError(handler) { - errorHandlers.add(handler); - return () => errorHandlers.delete(handler); - }, - onMessage(handler) { - messageHandlers.add(handler); - return () => messageHandlers.delete(handler); - }, - send(message) { - if (closed) { - for (const handler of errorHandlers) { - handler(new Error("Remote transport closed")); - } - return; - } - - queueMicrotask(() => { - for (const handler of runnerHandlers) { - handler(message); - } - }); - }, - terminate() { - emitClose({ message: "Remote transport terminated" }); - }, - }; -} - -async function main(): Promise { - const provider = resolveProvider({ - name: "tools", - tools: { - echo: { - execute: async (input) => input, - }, - }, - }); - - const executor = new RemoteExecutor({ - connectTransport: () => createInMemoryRemoteTransport(), - timeoutMs: 1_000, - }); - - const result = await executor.execute( - "await tools.echo({ ok: true, via: 'remote' })", - [provider], - { timeoutMs: 250 }, - ); - - console.log("execbox remote example result"); - console.log(JSON.stringify(result, null, 2)); -} - -void main(); diff --git a/package-lock.json b/package-lock.json index 1037e9b..0ab70c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,7 @@ "license": "MIT", "workspaces": [ "packages/core", - "packages/quickjs", - "packages/remote" + "packages/quickjs" ], "devDependencies": { "@arethetypeswrong/core": "^0.18.2", @@ -1566,10 +1565,6 @@ "resolved": "packages/quickjs", "link": true }, - "node_modules/@execbox/remote": { - "resolved": "packages/remote", - "link": true - }, "node_modules/@expressive-code/core": { "version": "0.41.7", "resolved": "https://registry.npmjs.org/@expressive-code/core/-/core-0.41.7.tgz", @@ -14781,17 +14776,6 @@ "engines": { "node": ">=22" } - }, - "packages/remote": { - "name": "@execbox/remote", - "version": "0.3.1", - "license": "MIT", - "dependencies": { - "@execbox/core": "^0.5.0" - }, - "engines": { - "node": ">=22" - } } } } diff --git a/package.json b/package.json index 0f907bd..6121ac6 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,7 @@ }, "workspaces": [ "packages/core", - "packages/quickjs", - "packages/remote" + "packages/quickjs" ], "scripts": { "api:check": "npm run build && node --import tsx scripts/run-api-extractor.ts", @@ -23,10 +22,9 @@ "example:execbox": "node --import tsx examples/execbox-basic.ts", "example:execbox-mcp-provider": "node --import tsx examples/execbox-mcp-provider.ts", "example:execbox-mcp-server": "node --import tsx examples/execbox-mcp-server.ts", - "example:execbox-remote": "node --import tsx examples/execbox-remote.ts", "example:execbox-worker": "node --import tsx examples/execbox-worker.ts", "benchmark": "node --expose-gc --import tsx benchmarks/benchmark.ts", - "examples": "npm run example:execbox && npm run example:execbox-remote && npm run example:execbox-worker && npm run example:execbox-mcp-provider && npm run example:execbox-mcp-server", + "examples": "npm run example:execbox && npm run example:execbox-worker && npm run example:execbox-mcp-provider && npm run example:execbox-mcp-server", "format": "prettier --write .", "format:check": "prettier --check .", "lint": "eslint . --ext .ts", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 4e808f8..5fb1118 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -6,8 +6,6 @@ - 02330b6: Simplify pre-1.0 runtime boundaries. `@execbox/core/runtime` replaces the unsupported `@execbox/core/_internal` subpath and now owns executor-author helpers such as runtime option resolution, manifest dispatch, code normalization, timeout helpers, log formatting, and error normalization. The main `@execbox/core` entrypoint is now focused on app-facing provider, executor, result, error, and typegen APIs. - Move the QuickJS remote runner endpoint to `@execbox/quickjs/remote-endpoint` and remove the hidden `@execbox/quickjs` dependency from `@execbox/remote`. Remote transports now expose only `RemoteExecutor`, `RemoteRunnerPort`, and transport contracts from `@execbox/remote`. - ## 0.4.1 ### Patch Changes diff --git a/packages/core/README.md b/packages/core/README.md index 525e477..58cab14 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -9,17 +9,16 @@ Core execution contract for execbox. Use it to resolve host tools into callable ## Use `@execbox/core` When - you want to expose host capabilities to guest code through explicit tool providers -- you want one execution contract across QuickJS and remote transport-backed runtimes +- you want one execution contract across inline and worker-hosted QuickJS - you want to wrap MCP servers or clients into callable namespaces instead of exposing raw tool loops ## Pair It With an Executor `@execbox/core` defines the provider and tool boundary, but it does not execute guest code on its own. -| Package | Start here when | -| -------------------------------------------------------------------- | -------------------------------------------------------------------------- | -| [`@execbox/quickjs`](https://www.npmjs.com/package/@execbox/quickjs) | You want the default path with inline or worker-hosted QuickJS. | -| [`@execbox/remote`](https://www.npmjs.com/package/@execbox/remote) | Your runtime already lives behind an application-owned transport boundary. | +| Package | Start here when | +| -------------------------------------------------------------------- | --------------------------------------------------------------- | +| [`@execbox/quickjs`](https://www.npmjs.com/package/@execbox/quickjs) | You want the default path with inline or worker-hosted QuickJS. | ## Install @@ -29,14 +28,12 @@ Most users start with QuickJS: npm install @execbox/core @execbox/quickjs ``` -Swap in `@execbox/remote` only when you already own the runtime transport boundary. - ## Runtime Implementer Surface Most application code can skip this section. -Application code should usually import from `@execbox/core`, `@execbox/core/mcp`, or `@execbox/core/protocol`. -Executor and runner packages should import shared runtime helpers from `@execbox/core/runtime` instead. That subpath contains the manifest dispatcher, runtime option defaults, timeout helpers, log formatting, code normalization, and error normalization used to keep runtime implementations aligned. +Application code should usually import from `@execbox/core` or `@execbox/core/mcp`. +The `@execbox/core/protocol` and `@execbox/core/runtime` subpaths exist for execbox-owned runtime packages. `@execbox/core/protocol` carries the worker-hosted QuickJS message contract, while `@execbox/core/runtime` contains the manifest dispatcher, runtime option defaults, timeout helpers, log formatting, code normalization, and error normalization used to keep runtime implementations aligned. ## Smallest Working Usage diff --git a/packages/quickjs/CHANGELOG.md b/packages/quickjs/CHANGELOG.md index ff265ce..c1b4d02 100644 --- a/packages/quickjs/CHANGELOG.md +++ b/packages/quickjs/CHANGELOG.md @@ -4,7 +4,7 @@ ### Minor Changes -- 07534e2: Remove `QuickJsExecutor({ host: "process" })` and its process-hosted runner entrypoint. Use `host: "worker"` for local hosted QuickJS execution, or `@execbox/remote` for app-owned process, container, or VM boundaries. +- 07534e2: Remove `QuickJsExecutor({ host: "process" })` and its process-hosted runner entrypoint. Use `host: "worker"` for local hosted QuickJS execution. ## 0.5.0 @@ -12,8 +12,6 @@ - 02330b6: Simplify pre-1.0 runtime boundaries. `@execbox/core/runtime` replaces the unsupported `@execbox/core/_internal` subpath and now owns executor-author helpers such as runtime option resolution, manifest dispatch, code normalization, timeout helpers, log formatting, and error normalization. The main `@execbox/core` entrypoint is now focused on app-facing provider, executor, result, error, and typegen APIs. - Move the QuickJS remote runner endpoint to `@execbox/quickjs/remote-endpoint` and remove the hidden `@execbox/quickjs` dependency from `@execbox/remote`. Remote transports now expose only `RemoteExecutor`, `RemoteRunnerPort`, and transport contracts from `@execbox/remote`. - ### Patch Changes - Updated dependencies [02330b6] diff --git a/packages/quickjs/README.md b/packages/quickjs/README.md index 6862584..21f0242 100644 --- a/packages/quickjs/README.md +++ b/packages/quickjs/README.md @@ -62,15 +62,11 @@ const executor = new QuickJsExecutor({ await executor.prewarm(); ``` -## Advanced Imports - -- `@execbox/quickjs/remote-endpoint` adapts the QuickJS protocol loop to `@execbox/remote` runner ports - ## Operational Notes - Each execution gets a fresh QuickJS runtime with JSON-only tool and result boundaries. -- This package is the default deployment path, not a hard security boundary for hostile or multi-tenant code. -- If you need a stronger deployment boundary, move execution behind `@execbox/remote` and a process, container, VM, or network boundary your application owns. +- Inline mode and worker mode are execution placement choices, not hard security boundaries for hostile or multi-tenant code. +- Worker mode moves QuickJS off the main thread but still shares the same host process. ## Read Next diff --git a/packages/quickjs/__tests__/quickjsExecutor.test.ts b/packages/quickjs/__tests__/quickjsExecutor.test.ts index 4e8c16e..deef580 100644 --- a/packages/quickjs/__tests__/quickjsExecutor.test.ts +++ b/packages/quickjs/__tests__/quickjsExecutor.test.ts @@ -6,7 +6,7 @@ describe("QuickJsExecutor host selection", () => { expect( () => new QuickJsExecutor({ host: "process" } as never), ).toThrowErrorMatchingInlineSnapshot( - `[Error: QuickJsExecutor host "process" is no longer supported. Use host "worker" for local hosted execution, or @execbox/remote for process, container, or VM boundaries.]`, + `[Error: QuickJsExecutor host "process" is no longer supported. Use host "worker" for local hosted execution.]`, ); }); }); diff --git a/packages/quickjs/__tests__/security/remoteEndpoint.test.ts b/packages/quickjs/__tests__/security/remoteEndpoint.test.ts deleted file mode 100644 index d305df7..0000000 --- a/packages/quickjs/__tests__/security/remoteEndpoint.test.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { describe, expect, it } from "vitest"; -import type { - DispatcherMessage, - ExecuteMessage, - RunnerMessage, - TransportCloseReason, -} from "@execbox/core/protocol"; - -import { attachQuickJsRemoteEndpoint } from "../../src/remoteEndpoint"; - -type MessageHandler = (message: DispatcherMessage) => void; -type CloseHandler = (reason?: TransportCloseReason) => void; - -async function waitFor( - predicate: () => boolean, - timeoutMs = 250, -): Promise { - const deadline = Date.now() + timeoutMs; - - while (Date.now() <= deadline) { - if (predicate()) { - return; - } - - await new Promise((resolve) => setTimeout(resolve, 0)); - } - - throw new Error("Timed out waiting for runner endpoint state"); -} - -function createPort() { - const sent: RunnerMessage[] = []; - const messageHandlers = new Set(); - const closeHandlers = new Set(); - - return { - port: { - onClose(handler: CloseHandler) { - closeHandlers.add(handler); - return () => closeHandlers.delete(handler); - }, - onMessage(handler: MessageHandler) { - messageHandlers.add(handler); - return () => messageHandlers.delete(handler); - }, - send(message: RunnerMessage) { - sent.push(message); - }, - }, - emitClose(reason?: TransportCloseReason) { - for (const handler of closeHandlers) { - handler(reason); - } - }, - emitMessage(message: ExecuteMessage) { - for (const handler of messageHandlers) { - handler(message); - } - }, - sent, - }; -} - -describe("attachQuickJsRemoteEndpoint", () => { - it("detaches itself when the transport closes", async () => { - const { emitClose, emitMessage, port, sent } = createPort(); - attachQuickJsRemoteEndpoint(port); - - emitMessage({ - code: "await tools.hang({})", - id: "first", - options: { - maxLogChars: 64_000, - maxLogLines: 100, - memoryLimitBytes: 64 * 1024 * 1024, - timeoutMs: 1_000, - }, - providers: [ - { - name: "tools", - tools: { - hang: { - originalName: "hang", - safeName: "hang", - }, - }, - types: "declare namespace tools {}", - }, - ], - type: "execute", - }); - await waitFor(() => sent.length >= 2); - - expect(sent).toEqual( - expect.arrayContaining([ - expect.objectContaining({ id: "first", type: "started" }), - expect.objectContaining({ - providerName: "tools", - safeToolName: "hang", - type: "tool_call", - }), - ]), - ); - - sent.length = 0; - emitClose({ message: "socket closed" }); - emitMessage({ - code: "1 + 1", - id: "second", - options: { - maxLogChars: 64_000, - maxLogLines: 100, - memoryLimitBytes: 64 * 1024 * 1024, - timeoutMs: 1_000, - }, - providers: [], - type: "execute", - }); - await new Promise((resolve) => setTimeout(resolve, 0)); - - expect(sent).toEqual([]); - }); - - it("ignores malformed inbound payloads and still handles later execute messages", async () => { - const { emitMessage, port, sent } = createPort(); - attachQuickJsRemoteEndpoint(port); - - emitMessage({ - id: "bad", - type: "execute", - } as unknown as ExecuteMessage); - await new Promise((resolve) => setTimeout(resolve, 0)); - expect(sent).toEqual([]); - - emitMessage({ - code: "1 + 1", - id: "good", - options: { - maxLogChars: 64_000, - maxLogLines: 100, - memoryLimitBytes: 64 * 1024 * 1024, - timeoutMs: 1_000, - }, - providers: [], - type: "execute", - }); - await waitFor(() => - sent.some((message) => message.type === "done" && message.id === "good"), - ); - - expect(sent).toEqual( - expect.arrayContaining([ - expect.objectContaining({ id: "good", type: "started" }), - expect.objectContaining({ id: "good", type: "done" }), - ]), - ); - }); -}); diff --git a/packages/quickjs/etc/execbox-quickjs-remote-endpoint.api.md b/packages/quickjs/etc/execbox-quickjs-remote-endpoint.api.md deleted file mode 100644 index 276db8e..0000000 --- a/packages/quickjs/etc/execbox-quickjs-remote-endpoint.api.md +++ /dev/null @@ -1,20 +0,0 @@ -## API Report File for "@execbox/quickjs" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { TransportCloseReason } from '@execbox/core/protocol'; - -// @public -export function attachQuickJsRemoteEndpoint(port: QuickJsRemoteEndpointPort): () => void; - -// @public -export interface QuickJsRemoteEndpointPort { - onClose?(handler: (reason?: TransportCloseReason) => void): void | (() => void); - onError?(handler: (error: Error) => void): void | (() => void); - onMessage(handler: (message: unknown) => void): void | (() => void); - send(message: unknown): void | Promise; -} - -``` diff --git a/packages/quickjs/package.json b/packages/quickjs/package.json index 98289a7..ce91772 100644 --- a/packages/quickjs/package.json +++ b/packages/quickjs/package.json @@ -19,15 +19,6 @@ }, "import": "./dist/index.js", "require": "./dist/index.cjs" - }, - "./remote-endpoint": { - "source": "./src/remoteEndpoint.ts", - "types": { - "import": "./dist/remoteEndpoint.d.ts", - "require": "./dist/remoteEndpoint.d.cts" - }, - "import": "./dist/remoteEndpoint.js", - "require": "./dist/remoteEndpoint.cjs" } }, "sideEffects": false, diff --git a/packages/quickjs/src/quickjsExecutor.ts b/packages/quickjs/src/quickjsExecutor.ts index 88aaab3..8578279 100644 --- a/packages/quickjs/src/quickjsExecutor.ts +++ b/packages/quickjs/src/quickjsExecutor.ts @@ -51,8 +51,7 @@ export class QuickJsExecutor implements Executor { if (unsupportedHost !== undefined) { throw new Error( `QuickJsExecutor host "${unsupportedHost}" is no longer supported. ` + - 'Use host "worker" for local hosted execution, or @execbox/remote ' + - "for process, container, or VM boundaries.", + 'Use host "worker" for local hosted execution.', ); } diff --git a/packages/quickjs/src/remoteEndpoint.ts b/packages/quickjs/src/remoteEndpoint.ts deleted file mode 100644 index 5a2f33c..0000000 --- a/packages/quickjs/src/remoteEndpoint.ts +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @packageDocumentation - * QuickJS remote runner endpoint for `@execbox/remote` transports. - */ -import { - isDispatcherMessage, - type DispatcherMessage, - type RunnerMessage, - type TransportCloseReason, -} from "@execbox/core/protocol"; - -import { attachQuickJsProtocolEndpoint } from "./runner/protocolEndpoint.ts"; - -/** - * Minimal runner-side port for transport-backed QuickJS execution. - */ -export interface QuickJsRemoteEndpointPort { - /** Registers a close callback for transport shutdown notifications. */ - onClose?( - handler: (reason?: TransportCloseReason) => void, - ): void | (() => void); - - /** Registers an error callback for transport-level failures. */ - onError?(handler: (error: Error) => void): void | (() => void); - - /** Registers a handler for inbound runner messages. */ - onMessage(handler: (message: unknown) => void): void | (() => void); - - /** Sends a transport message to the attached host session. */ - send(message: unknown): void | Promise; -} - -/** - * Attaches the shared QuickJS protocol endpoint to a remote runner transport - * and tears it down automatically when the transport closes or errors. - */ -export function attachQuickJsRemoteEndpoint( - port: QuickJsRemoteEndpointPort, -): () => void { - const detachProtocol = attachQuickJsProtocolEndpoint({ - onMessage(handler: (message: DispatcherMessage) => void): () => void { - const maybeDetach = port.onMessage((message: unknown) => { - if (!isDispatcherMessage(message)) { - return; - } - - handler(message); - }); - - return () => { - if (typeof maybeDetach === "function") { - maybeDetach(); - } - }; - }, - send(message: RunnerMessage): void { - void Promise.resolve(port.send(message)).catch(() => {}); - }, - }); - - const offClose = port.onClose?.(() => { - cleanup(); - }); - const offError = port.onError?.(() => { - cleanup(); - }); - - function cleanup(): void { - if (typeof offClose === "function") { - offClose(); - } - if (typeof offError === "function") { - offError(); - } - detachProtocol(); - } - - return cleanup; -} diff --git a/packages/quickjs/tsdown.config.ts b/packages/quickjs/tsdown.config.ts index 4f67d0d..92267a4 100644 --- a/packages/quickjs/tsdown.config.ts +++ b/packages/quickjs/tsdown.config.ts @@ -1,5 +1,5 @@ import { definePackageBuildConfig } from "../../scripts/tsdown-config.ts"; export default definePackageBuildConfig({ - entry: ["src/index.ts", "src/remoteEndpoint.ts", "src/workerEntry.ts"], + entry: ["src/index.ts", "src/workerEntry.ts"], }); diff --git a/packages/remote/CHANGELOG.md b/packages/remote/CHANGELOG.md deleted file mode 100644 index 7ac6c51..0000000 --- a/packages/remote/CHANGELOG.md +++ /dev/null @@ -1,92 +0,0 @@ -# @execbox/remote - -## 0.3.1 - -### Patch Changes - -- 3778810: Add source export conditions for workspace-aware TypeScript tooling. - -## 0.3.0 - -### Minor Changes - -- 02330b6: Simplify pre-1.0 runtime boundaries. `@execbox/core/runtime` replaces the unsupported `@execbox/core/_internal` subpath and now owns executor-author helpers such as runtime option resolution, manifest dispatch, code normalization, timeout helpers, log formatting, and error normalization. The main `@execbox/core` entrypoint is now focused on app-facing provider, executor, result, error, and typegen APIs. - - Move the QuickJS remote runner endpoint to `@execbox/quickjs/remote-endpoint` and remove the hidden `@execbox/quickjs` dependency from `@execbox/remote`. Remote transports now expose only `RemoteExecutor`, `RemoteRunnerPort`, and transport contracts from `@execbox/remote`. - -### Patch Changes - -- Updated dependencies [02330b6] - - @execbox/core@0.5.0 - -## 0.2.0 - -### Minor Changes - -- bb3b2a0: Move the standalone protocol helpers into the new `@execbox/core/protocol` entrypoint and remove the separate `@execbox/protocol` package. Transport-backed integrations should now import protocol messages, host-session helpers, and resource-pool utilities from `@execbox/core/protocol`. - -### Patch Changes - -- Updated dependencies [bb3b2a0] - - @execbox/core@0.4.0 - - @execbox/quickjs@0.4.0 - -## 0.1.5 - -### Patch Changes - -- Updated dependencies [148303a] - - @execbox/quickjs@0.3.0 - - @execbox/protocol@0.3.0 - -## 0.1.4 - -### Patch Changes - -- 4d8aeeb: Fix published export metadata so CommonJS entrypoints resolve their `.d.cts` - declaration files correctly, and add package validation with `publint` and - Are the Types Wrong in CI. -- Updated dependencies [4d8aeeb] - - @execbox/core@0.3.1 - - @execbox/protocol@0.2.1 - - @execbox/quickjs@0.2.1 - -## 0.1.3 - -### Patch Changes - -- Updated dependencies [737b151] - - @execbox/core@0.3.0 - - @execbox/protocol@0.2.0 - - @execbox/quickjs@0.2.0 - -## 0.1.2 - -### Patch Changes - -- b68b70a: Harden transport and executor security coverage, ignore malformed protocol frames safely, and avoid duplicate hard-stop termination in process and worker executors. -- Updated dependencies [b68b70a] - - @execbox/protocol@0.1.2 - - @execbox/quickjs@0.1.2 - -## 0.1.1 - -### Patch Changes - -- Updated dependencies [8cd7b02] - - @execbox/core@0.2.0 - - @execbox/protocol@0.1.1 - - @execbox/quickjs@0.1.1 - -## 0.1.0 - -### Minor Changes - -- 5d5fbe2: Initial release of the execbox monorepo under the `@execbox/*` scope. - -### Patch Changes - -- Updated dependencies [5d5fbe2] - - @execbox/core@0.1.0 - - @execbox/protocol@0.1.0 - - @execbox/quickjs@0.1.0 diff --git a/packages/remote/LICENSE b/packages/remote/LICENSE deleted file mode 100644 index a87bcf9..0000000 --- a/packages/remote/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2026 Mouaad Aallam - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/packages/remote/README.md b/packages/remote/README.md deleted file mode 100644 index 848d9d8..0000000 --- a/packages/remote/README.md +++ /dev/null @@ -1,90 +0,0 @@ -# @execbox/remote - -Transport-backed executor for execbox. Use it when you want the execbox API on the host side, but the actual runtime to live behind a transport and deployment boundary that your application already owns. - -[![npm version](https://img.shields.io/npm/v/%40execbox%2Fremote?style=flat-square)](https://www.npmjs.com/package/@execbox/remote) -[![License](https://img.shields.io/github/license/aallam/execbox?style=flat-square)](https://github.com/aallam/execbox/blob/main/LICENSE) -[![Docs](https://img.shields.io/badge/docs-site-0ea5e9?style=flat-square)](https://execbox.aallam.com) - -## Use `@execbox/remote` When - -- you want execbox execution to live outside the application process -- you already own the network stack, process topology, or runtime placement -- you want to keep the same provider/executor model while moving the runtime behind your own process, container, VM, or network boundary - -## Install - -Host side: - -```bash -npm install @execbox/core @execbox/remote -``` - -Runner side, when using the shipped QuickJS endpoint: - -```bash -npm install @execbox/quickjs -``` - -## How It Fits - -`@execbox/remote` has two sides: - -- the host uses `RemoteExecutor` and supplies `HostTransport` instances -- the runner attaches a runtime-owned endpoint adapter to an app-owned transport port - -The package stays intentionally small. It does not create servers, own authentication, or prescribe an HTTP or WebSocket framework. - -## Smallest Working Usage - -Host side: - -```ts -import { resolveProvider } from "@execbox/core"; -import { RemoteExecutor } from "@execbox/remote"; - -const provider = resolveProvider({ - name: "tools", - tools: { - echo: { - execute: async (input) => input, - }, - }, -}); - -const executor = new RemoteExecutor({ - connectTransport: async () => myHostTransport, - timeoutMs: 1000, -}); - -const result = await executor.execute(`await tools.echo({ ok: true })`, [ - provider, -]); - -console.log(result); -``` - -Runner side, using the QuickJS endpoint shipped by `@execbox/quickjs`: - -```ts -import { attachQuickJsRemoteEndpoint } from "@execbox/quickjs/remote-endpoint"; - -attachQuickJsRemoteEndpoint(myRunnerPort); -``` - -## Operational Notes - -- `RemoteExecutor` is intentionally ephemeral and asks `connectTransport()` for a fresh transport per `execute()` call. -- Connection reuse, authentication, and transport lifecycle stay under caller control. -- Runtime-specific runner endpoints are owned by runtime packages such as `@execbox/quickjs`. -- Providers are still the capability boundary. Moving execution behind a transport does not change what guest code is allowed to call. -- The real trust boundary depends on the runtime and operational controls you deploy behind that transport. - -## Read Next - -- [Getting Started](https://execbox.aallam.com/getting-started) -- [Examples](https://execbox.aallam.com/examples) -- [Security & Boundaries](https://execbox.aallam.com/security) -- [Remote Workflow](https://execbox.aallam.com/architecture/execbox-remote-workflow) -- [Protocol Reference](https://execbox.aallam.com/architecture/execbox-protocol-reference) -- [Runner Specification](https://execbox.aallam.com/architecture/execbox-runner-specification) diff --git a/packages/remote/__tests__/executor.test.ts b/packages/remote/__tests__/executor.test.ts deleted file mode 100644 index 445e6c2..0000000 --- a/packages/remote/__tests__/executor.test.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { runExecutorContractSuite } from "../../core/test-support/runExecutorContractSuite"; -import { createLoopbackTransport } from "../test-support/createLoopbackTransport"; -import { RemoteExecutor } from "../src/index"; - -runExecutorContractSuite("RemoteExecutor", (options) => { - return new RemoteExecutor({ - ...options, - connectTransport: () => createLoopbackTransport(), - }); -}); diff --git a/packages/remote/__tests__/security/penetration.test.ts b/packages/remote/__tests__/security/penetration.test.ts deleted file mode 100644 index b033ed4..0000000 --- a/packages/remote/__tests__/security/penetration.test.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { runWrappedMcpPenetrationSuite } from "../../../core/test-support/runWrappedMcpPenetrationSuite"; -import { RemoteExecutor } from "../../src/index"; -import { createLoopbackTransport } from "../../test-support/createLoopbackTransport"; - -runWrappedMcpPenetrationSuite("RemoteExecutor wrapped MCP", (options) => { - return new RemoteExecutor({ - ...options, - connectTransport: () => createLoopbackTransport(), - }); -}); diff --git a/packages/remote/etc/execbox-remote.api.md b/packages/remote/etc/execbox-remote.api.md deleted file mode 100644 index d30cba1..0000000 --- a/packages/remote/etc/execbox-remote.api.md +++ /dev/null @@ -1,29 +0,0 @@ -## API Report File for "@execbox/remote" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { ExecuteResult } from '@execbox/core'; -import { ExecutionOptions } from '@execbox/core'; -import { Executor } from '@execbox/core'; -import { ExecutorRuntimeOptions } from '@execbox/core'; -import { HostTransport } from '@execbox/core/protocol'; -import { ResolvedToolProvider } from '@execbox/core'; - -// @public -export class RemoteExecutor implements Executor { - constructor(options: RemoteExecutorOptions); - execute(code: string, providers: ResolvedToolProvider[], options?: ExecutionOptions): Promise; -} - -// @public -export interface RemoteExecutorOptions extends ExecutorRuntimeOptions { - cancelGraceMs?: number; - connectTransport: RemoteTransportFactory; -} - -// @public -export type RemoteTransportFactory = () => HostTransport | Promise; - -``` diff --git a/packages/remote/package.json b/packages/remote/package.json deleted file mode 100644 index 0375828..0000000 --- a/packages/remote/package.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "@execbox/remote", - "version": "0.3.1", - "description": "Transport-backed remote executor for the execbox core package.", - "license": "MIT", - "type": "module", - "engines": { - "node": ">=22" - }, - "main": "./dist/index.cjs", - "module": "./dist/index.js", - "types": "./dist/index.d.ts", - "exports": { - ".": { - "source": "./src/index.ts", - "types": { - "import": "./dist/index.d.ts", - "require": "./dist/index.d.cts" - }, - "import": "./dist/index.js", - "require": "./dist/index.cjs" - } - }, - "sideEffects": false, - "files": [ - "dist", - "README.md", - "LICENSE" - ], - "scripts": { - "build": "tsdown" - }, - "keywords": [ - "mcp", - "model-context-protocol", - "remote", - "sandbox", - "code-execution" - ], - "repository": { - "type": "git", - "url": "git+https://github.com/aallam/execbox.git", - "directory": "packages/remote" - }, - "homepage": "https://github.com/aallam/execbox/tree/main/packages/remote#readme", - "bugs": "https://github.com/aallam/execbox/issues", - "dependencies": { - "@execbox/core": "^0.5.0" - } -} diff --git a/packages/remote/src/index.ts b/packages/remote/src/index.ts deleted file mode 100644 index 5388120..0000000 --- a/packages/remote/src/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * @packageDocumentation - * Public API for the `@execbox/remote` package. - */ -export { RemoteExecutor } from "./remoteExecutor"; -export type { RemoteExecutorOptions, RemoteTransportFactory } from "./types"; diff --git a/packages/remote/src/remoteExecutor.ts b/packages/remote/src/remoteExecutor.ts deleted file mode 100644 index 250e730..0000000 --- a/packages/remote/src/remoteExecutor.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { randomUUID } from "node:crypto"; - -import { - runHostTransportSession, - type HostTransport, -} from "@execbox/core/protocol"; -import { - createTimeoutExecuteResult, - resolveExecutorRuntimeOptions, -} from "@execbox/core/runtime"; -import type { - ExecutionOptions, - ExecuteResult, - Executor, - ResolvedToolProvider, -} from "@execbox/core"; - -import type { RemoteExecutorOptions } from "./types"; - -const DEFAULT_CANCEL_GRACE_MS = 25; -/** - * Transport-backed executor that runs guest code outside the host process. - */ -export class RemoteExecutor implements Executor { - private readonly cancelGraceMs: number; - private readonly options: RemoteExecutorOptions; - - /** - * Creates a transport-backed executor with caller-supplied remote connectivity. - */ - constructor(options: RemoteExecutorOptions) { - this.cancelGraceMs = options.cancelGraceMs ?? DEFAULT_CANCEL_GRACE_MS; - this.options = options; - } - - /** - * Executes JavaScript against the provided tool namespaces over a remote transport. - */ - async execute( - code: string, - providers: ResolvedToolProvider[], - options: ExecutionOptions = {}, - ): Promise { - if (options.signal?.aborted) { - return createTimeoutExecuteResult(); - } - - let transport: HostTransport; - - try { - transport = await this.options.connectTransport(); - } catch (error) { - return { - durationMs: 0, - error: { - code: "internal_error", - message: error instanceof Error ? error.message : String(error), - }, - logs: [], - ok: false, - }; - } - - return await runHostTransportSession({ - cancelGraceMs: this.cancelGraceMs, - code, - executionId: randomUUID(), - providers, - runtimeOptions: resolveExecutorRuntimeOptions(this.options, options), - signal: options.signal, - transport, - }); - } -} diff --git a/packages/remote/src/types.ts b/packages/remote/src/types.ts deleted file mode 100644 index 15c1ba0..0000000 --- a/packages/remote/src/types.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { ExecutorRuntimeOptions } from "@execbox/core"; -import type { HostTransport } from "@execbox/core/protocol"; - -/** - * Factory that creates a fresh transport connection for one remote execution. - */ -export type RemoteTransportFactory = () => - | HostTransport - | Promise; - -/** - * Options for constructing a {@link RemoteExecutor}. - */ -export interface RemoteExecutorOptions extends ExecutorRuntimeOptions { - /** Time to wait before forcefully tearing down a hung remote session. */ - cancelGraceMs?: number; - - /** Factory that establishes one fresh transport for an execution. */ - connectTransport: RemoteTransportFactory; -} diff --git a/packages/remote/test-support/createLoopbackTransport.ts b/packages/remote/test-support/createLoopbackTransport.ts deleted file mode 100644 index 9e0e4cb..0000000 --- a/packages/remote/test-support/createLoopbackTransport.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { attachQuickJsRemoteEndpoint } from "@execbox/quickjs/remote-endpoint"; -import type { - DispatcherMessage, - HostTransport, - RunnerMessage, - TransportCloseReason, -} from "@execbox/core/protocol"; - -type CloseHandler = (reason?: TransportCloseReason) => void; -type ErrorHandler = (error: Error) => void; -type MessageHandler = (message: RunnerMessage) => void; -type RunnerMessageHandler = (message: DispatcherMessage) => void; - -export function createLoopbackTransport(): HostTransport { - const closeHandlers = new Set(); - const errorHandlers = new Set(); - const messageHandlers = new Set(); - const runnerHandlers = new Set(); - let closed = false; - - const emitClose = (reason?: TransportCloseReason) => { - closed = true; - for (const handler of closeHandlers) { - handler(reason); - } - }; - - attachQuickJsRemoteEndpoint({ - onMessage(handler) { - const runnerHandler = handler as RunnerMessageHandler; - runnerHandlers.add(runnerHandler); - return () => runnerHandlers.delete(runnerHandler); - }, - send(message) { - queueMicrotask(() => { - for (const handler of messageHandlers) { - handler(message as RunnerMessage); - } - }); - }, - }); - - return { - dispose() { - closed = true; - }, - onClose(handler) { - closeHandlers.add(handler); - return () => closeHandlers.delete(handler); - }, - onError(handler) { - errorHandlers.add(handler); - return () => errorHandlers.delete(handler); - }, - onMessage(handler) { - messageHandlers.add(handler); - return () => messageHandlers.delete(handler); - }, - send(message) { - if (closed) { - for (const handler of errorHandlers) { - handler(new Error("Remote transport closed")); - } - return; - } - - queueMicrotask(() => { - for (const handler of runnerHandlers) { - handler(message); - } - }); - }, - terminate() { - emitClose({ message: "Remote transport terminated" }); - }, - }; -} diff --git a/packages/remote/tsconfig.json b/packages/remote/tsconfig.json deleted file mode 100644 index c5fcf23..0000000 --- a/packages/remote/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "outDir": "dist", - "rootDir": "src" - }, - "include": ["src/**/*.ts", "__tests__/**/*.ts"], - "exclude": ["dist", "node_modules"] -} diff --git a/packages/remote/tsdown.config.ts b/packages/remote/tsdown.config.ts deleted file mode 100644 index 2cdad4e..0000000 --- a/packages/remote/tsdown.config.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { definePackageBuildConfig } from "../../scripts/tsdown-config.ts"; - -export default definePackageBuildConfig({ - entry: ["src/index.ts"], -}); diff --git a/scripts/test-dist-smoke.ts b/scripts/test-dist-smoke.ts index d2c165f..c8856a3 100644 --- a/scripts/test-dist-smoke.ts +++ b/scripts/test-dist-smoke.ts @@ -25,10 +25,6 @@ const coreRuntime = await import( const coreRuntimeCjs = require( path.join(repoRoot, "packages/core/dist/runtime.cjs"), ) as Record; -const quickjsRemoteEndpoint = await import( - pathToFileURL(path.join(repoRoot, "packages/quickjs/dist/remoteEndpoint.js")) - .href -); assert.equal(typeof core.resolveProvider, "function"); assert.equal(core.assertValidIdentifier, undefined); @@ -48,9 +44,5 @@ assert.equal(typeof coreRuntime.resolveExecutorRuntimeOptions, "function"); assert.equal(typeof coreRuntime.createTimeoutExecuteResult, "function"); assert.equal(typeof coreRuntimeCjs.resolveExecutorRuntimeOptions, "function"); assert.equal(typeof coreRuntimeCjs.createTimeoutExecuteResult, "function"); -assert.equal( - typeof quickjsRemoteEndpoint.attachQuickJsRemoteEndpoint, - "function", -); console.log("Built dist smoke test passed"); diff --git a/scripts/workspace-entrypoints.ts b/scripts/workspace-entrypoints.ts index 9926874..7f47380 100644 --- a/scripts/workspace-entrypoints.ts +++ b/scripts/workspace-entrypoints.ts @@ -55,22 +55,6 @@ export const workspaceEntrypoints = [ packageName: "@execbox/quickjs", sourcePath: "packages/quickjs/src/index.ts", }, - { - apiReportFileName: "execbox-quickjs-remote-endpoint.api.md", - declarationPath: "dist/remoteEndpoint.d.ts", - exportPath: "./remote-endpoint", - packageDir: "packages/quickjs", - packageName: "@execbox/quickjs", - sourcePath: "packages/quickjs/src/remoteEndpoint.ts", - }, - { - apiReportFileName: "execbox-remote.api.md", - declarationPath: "dist/index.d.ts", - exportPath: ".", - packageDir: "packages/remote", - packageName: "@execbox/remote", - sourcePath: "packages/remote/src/index.ts", - }, ] as const satisfies readonly WorkspaceEntrypoint[]; export function createEntrypointSpecifier( diff --git a/tsconfig.json b/tsconfig.json index dcebd1a..f3d75ed 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,11 +7,7 @@ "@execbox/core/runtime": ["./packages/core/src/runtime.ts"], "@execbox/core/mcp": ["./packages/core/src/mcp/index.ts"], "@execbox/core/protocol": ["./packages/core/src/protocol/index.ts"], - "@execbox/quickjs": ["./packages/quickjs/src/index.ts"], - "@execbox/quickjs/remote-endpoint": [ - "./packages/quickjs/src/remoteEndpoint.ts" - ], - "@execbox/remote": ["./packages/remote/src/index.ts"] + "@execbox/quickjs": ["./packages/quickjs/src/index.ts"] }, "types": ["node", "vitest/globals"] }, @@ -19,7 +15,6 @@ "examples/**/*.ts", "packages/core/**/*.ts", "packages/quickjs/**/*.ts", - "packages/remote/**/*.ts", "scripts/**/*.ts", "vitest.config.ts" ],