Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/runtime-boundaries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@execbox/core": minor
"@execbox/quickjs": minor
"@execbox/remote": minor
"@execbox/isolated-vm": patch
---

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`.
1 change: 0 additions & 1 deletion CLAUDE.md

This file was deleted.

14 changes: 7 additions & 7 deletions docs/architecture/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ flowchart LR

### Package Roles

| Package | Role |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `@execbox/core` | Core types, provider resolution, shared runner semantics, MCP adapters, and the `@execbox/core/protocol` transport subpath |
| `@execbox/quickjs` | Default QuickJS executor package with inline, worker-hosted, and process-hosted modes plus a reusable runner |
| `@execbox/remote` | Transport-backed executor that reuses the QuickJS protocol endpoint across an app-defined boundary |
| `@execbox/isolated-vm` | Alternate executor backend using a fresh `isolated-vm` context and a reusable isolated-vm runner |
| Package | Role |
| ---------------------- | ------------------------------------------------------------------------------------------------------------ |
| `@execbox/core` | App-facing core types, provider resolution, MCP adapters, plus runtime and protocol subpaths |
| `@execbox/quickjs` | Default QuickJS executor package with inline, worker-hosted, and process-hosted modes plus a reusable runner |
| `@execbox/remote` | Transport-backed executor that runs against an app-defined runner boundary |
| `@execbox/isolated-vm` | Alternate executor backend using a fresh `isolated-vm` context and a reusable isolated-vm runner |

## End-to-End Execution Model

Expand Down Expand Up @@ -100,4 +100,4 @@ Key implications:

## Architecture In One Paragraph

`@execbox/core` owns the stable execution contract, provider resolution, shared runner semantics, MCP adapters, and the `@execbox/core/protocol` transport surface. `@execbox/quickjs` and `@execbox/isolated-vm` each expose a runtime-specific reusable runner. Hosted `@execbox/quickjs` modes and `@execbox/remote` sit on top of `@execbox/core/protocol`, which owns the transport boundary: message shapes, shared host sessions, and reusable resource pools for transport-backed execution.
`@execbox/core` owns the app-facing execution contract, provider resolution, MCP adapters, and the `@execbox/core/protocol` transport surface. Runtime implementers use `@execbox/core/runtime` for shared dispatch, manifest, timeout, log, and normalization helpers. `@execbox/quickjs` and `@execbox/isolated-vm` each expose a runtime-specific reusable runner. Hosted `@execbox/quickjs` modes and `@execbox/remote` sit on top of `@execbox/core/protocol`, which owns the transport boundary: message shapes, shared host sessions, and reusable resource pools for transport-backed execution.
26 changes: 14 additions & 12 deletions docs/architecture/execbox-core.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ This page covers the parts of execbox that stay stable regardless of which execu
The core package exposes three main responsibilities:

- Resolve host-authored tools into a deterministic guest namespace
- Normalize guest code into an executable async shape
- Define the stable execution contract and shared runner semantics used by all executors
- Define the stable app-facing execution contract
- Provide runtime implementers with shared runner semantics through `@execbox/core/runtime`

The main public concepts are:

Expand Down Expand Up @@ -66,7 +66,7 @@ The resolved provider also carries two maps:

## Guest Code Normalization

Executors do not evaluate arbitrary snippets directly. `normalizeCode()` first turns model- or user-produced text into a consistent async function body.
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.

That normalization handles:

Expand All @@ -79,27 +79,28 @@ The practical effect is that all executors can treat guest code as “an async f

## Shared Runner Semantics

The core package owns the small runner-level contract that sits between resolved providers and runtime-specific runners.
The `@execbox/core/runtime` entrypoint owns the small runner-level helper surface that sits between resolved providers and runtime-specific runners.

That contract is intentionally transport-neutral:

- `extractProviderManifests()` converts resolved providers into transport-safe manifests
- `createToolCallDispatcher()` turns a runner-emitted tool call back into a trusted host invocation
- `ExecutorRuntimeOptions` carries timeout, memory, and log limits in a runtime-agnostic form
- `resolveExecutorRuntimeOptions()` applies shared timeout, memory, and log defaults
- `ExecutorRuntimeOptions` carries the runtime-agnostic limit shape used by public executor options

```mermaid
sequenceDiagram
participant Host as Host app
participant Core as execbox core
participant Runtime as core runtime
participant Runner as Runtime-specific runner
participant Tool as Resolved tool wrapper

Host->>Core: extractProviderManifests(providers)
Host->>Runtime: extractProviderManifests(providers)
Host->>Runner: runSession(code, manifests, onToolCall)
Runner->>Core: onToolCall({ providerName, safeToolName, input })
Core->>Tool: descriptor.execute(input, context)
Tool-->>Core: JSON-safe result or ExecuteError
Core-->>Runner: ToolCallResult
Runner->>Runtime: onToolCall({ providerName, safeToolName, input })
Runtime->>Tool: descriptor.execute(input, context)
Tool-->>Runtime: JSON-safe result or ExecuteError
Runtime-->>Runner: ToolCallResult
```

This seam is what lets execbox share semantics across:
Expand Down Expand Up @@ -198,7 +199,8 @@ The core package does not own QuickJS, `isolated-vm`, worker threads, or transpo

The consequence is deliberate separation between:

- core execution and runner semantics in `@execbox/core`
- app-facing execution contracts in `@execbox/core`
- runtime implementer helpers in `@execbox/core/runtime`
- transport/session mechanics in `@execbox/core/protocol`
- runtime-specific bridge code in executor packages

Expand Down
3 changes: 2 additions & 1 deletion examples/execbox-remote.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { resolveProvider } from "@execbox/core";
import { RemoteExecutor, attachQuickJsRemoteEndpoint } from "@execbox/remote";
import { attachQuickJsRemoteEndpoint } from "@execbox/quickjs/remote-endpoint";
import { RemoteExecutor } from "@execbox/remote";
import type {
DispatcherMessage,
HostTransport,
Expand Down
5 changes: 2 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"package:check": "npm_config_cache=$PWD/.npm-cache CI=1 npm run build",
"test": "vitest run",
"test:dist-smoke": "node --import tsx scripts/test-dist-smoke.ts",
"test:security": "node ./node_modules/vitest/vitest.mjs run packages/core/__tests__/security/isJsonSerializable.test.ts packages/core/__tests__/core/createToolCallDispatcher.test.ts packages/core/__tests__/protocol/hostSession.test.ts packages/quickjs/__tests__/protocolEndpoint.test.ts packages/remote/__tests__/runnerEndpoint.test.ts && node ./node_modules/vitest/vitest.mjs run packages/core/__tests__/mcp/penetration.test.ts packages/remote/__tests__/penetration.test.ts packages/quickjs/__tests__/hostedPenetration.test.ts && node ./node_modules/vitest/vitest.mjs run packages/quickjs/__tests__/processHostLifecycle.test.ts packages/quickjs/__tests__/workerHostLifecycle.test.ts",
"test:security": "node ./node_modules/vitest/vitest.mjs run packages/core/__tests__/security/isJsonSerializable.test.ts packages/core/__tests__/core/createToolCallDispatcher.test.ts packages/core/__tests__/protocol/hostSession.test.ts packages/quickjs/__tests__/protocolEndpoint.test.ts packages/quickjs/__tests__/remoteEndpoint.test.ts && node ./node_modules/vitest/vitest.mjs run packages/core/__tests__/mcp/penetration.test.ts packages/remote/__tests__/penetration.test.ts packages/quickjs/__tests__/hostedPenetration.test.ts && node ./node_modules/vitest/vitest.mjs run packages/quickjs/__tests__/processHostLifecycle.test.ts packages/quickjs/__tests__/workerHostLifecycle.test.ts",
"test:isolated-vm": "VITEST_INCLUDE_ISOLATED_VM=1 NODE_OPTIONS=--no-node-snapshot node --no-node-snapshot ./node_modules/vitest/vitest.mjs run packages/isolated-vm/__tests__",
"test:watch": "vitest",
"typecheck": "tsc --noEmit",
Expand Down
5 changes: 5 additions & 0 deletions packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ npm install @execbox/core @execbox/quickjs

Swap in `@execbox/remote` or `@execbox/isolated-vm` when you need a different runtime boundary.

## Runtime Implementer Surface

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.

## Smallest Working Usage

```ts
Expand Down
4 changes: 2 additions & 2 deletions packages/core/__tests__/core/createToolCallDispatcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { describe, expect, it } from "vitest";
import {
createToolCallDispatcher,
getExecutionTimeoutMessage,
resolveProvider,
} from "@execbox/core";
} from "@execbox/core/runtime";
import { resolveProvider } from "@execbox/core";

describe("createToolCallDispatcher", () => {
it("does not start new host tool work after the execution has been aborted", async () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/__tests__/core/normalize.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { normalizeCode } from "@execbox/core";
import { normalizeCode } from "@execbox/core/runtime";

describe("normalizeCode", () => {
it("strips fenced code blocks before wrapping", () => {
Expand Down
20 changes: 20 additions & 0 deletions packages/core/__tests__/core/runtimeEntrypoint.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { describe, expect, it } from "vitest";
import * as core from "@execbox/core";
import {
createToolCallDispatcher,
resolveExecutorRuntimeOptions,
} from "@execbox/core/runtime";

describe("@execbox/core/runtime", () => {
it("exposes executor-author helpers from the runtime entrypoint", () => {
expect(typeof resolveExecutorRuntimeOptions).toBe("function");
expect(typeof createToolCallDispatcher).toBe("function");
});

it("keeps executor-author helpers out of the app-facing core entrypoint", () => {
expect(core).not.toHaveProperty("createToolCallDispatcher");
expect(core).not.toHaveProperty("createTimeoutExecuteResult");
expect(core).not.toHaveProperty("formatConsoleLine");
expect(core).not.toHaveProperty("normalizeThrownMessage");
});
});
26 changes: 0 additions & 26 deletions packages/core/api-extractor.json

This file was deleted.

26 changes: 0 additions & 26 deletions packages/core/api-extractor.mcp.json

This file was deleted.

34 changes: 0 additions & 34 deletions packages/core/api-extractor.protocol.json

This file was deleted.

Loading
Loading