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
9 changes: 9 additions & 0 deletions .changeset/fuzzy-buns-travel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@caplets/core": minor
"caplets": minor
"@caplets/sdk": patch
"@caplets/opencode": patch
"@caplets/pi": patch
---

Support Bun 1.3.14 and newer as a Caplets process runtime while retaining Node.js 22 and newer as the default runtime. Use a cross-runtime asynchronous SQLite adapter, runtime-native HTTP and telemetry integrations, and release-blocking Node and Bun verification matrices.
31 changes: 31 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,37 @@ jobs:
- name: Run quality gates
run: pnpm verify

verify-bun:
name: Verify (Bun ${{ matrix.bun-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
bun-version: [1.3.14, latest]
steps:
- name: Checkout
uses: actions/checkout@v7

- name: Setup pnpm
uses: pnpm/action-setup@v6

- name: Setup Node
uses: actions/setup-node@v7
with:
node-version: 24
cache: pnpm

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: ${{ matrix.bun-version }}

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Run Bun-native quality gates
run: pnpm verify:bun

postgres-contracts:
name: PostgreSQL contracts
runs-on: ubuntu-latest
Expand Down
16 changes: 14 additions & 2 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,18 @@ _Avoid_: All runtime files, shared cache
A running server instance participating in one logical Caplets host. Host Nodes share Authoritative Host State but own their live Code Mode heap, bound workspace copy, and any node-local artifacts.
_Avoid_: Independent host, tenant

**Caplets Process**:
A command-line or server process launched through the Caplets executable. Native agent integrations run inside their own extension hosts, and Caplets SDK consumers are outside this boundary.
_Avoid_: Caplets runtime, all Caplets packages

**Native Agent Integration**:
A Caplets adapter loaded into a coding agent's extension host. Its runtime compatibility follows that host's supported environments rather than the Caplets Process contract.
_Avoid_: Caplets subprocess, standalone Caplets runtime

**Target Project Package Manager**:
The package command selected for repository-specific actions authored into a Caplet. It is independent of the environment that runs the Caplets Process.
_Avoid_: Caplets package manager, Caplets runtime

**Caplets exposure projection**:
A shared adapter-neutral runtime view of which local and remote Caplets are exposed as Code Mode handles, progressive tools, direct downstream operations, or direct MCP surfaces, including non-callable diagnostic breadcrumbs for hidden Caplets. MCP, native, and attach host adapters render it differently; they do not own exposure policy or execution behavior.
_Avoid_: Tool registration list, exposure wrapper map, transport-specific surface
Expand Down Expand Up @@ -105,8 +117,8 @@ The canonical `/api/v2/admin/*` HTTP resource interface through which Operator b
_Avoid_: Remote CLI RPC, dashboard backend, mixed-auth endpoint, dashboard Admin alias

**Caplets SDK**:
The typed client Module for the canonical public Caplets HTTP API and the versioned Attach Project Binding WebSocket session protocol. It excludes MCP and dashboard-private authentication ceremonies.
_Avoid_: Admin client, core client, dashboard session client
The typed, web-platform client Module for the canonical public Caplets HTTP API and the versioned Attach Project Binding WebSocket session protocol. Platform-specific host capabilities are separate exports; MCP and dashboard-private authentication ceremonies are excluded.
_Avoid_: Node-only client, Admin client, core client, dashboard session client

**Operator Activity Log**:
A host-owned record of sensitive Operator Client actions performed through the dashboard or operator admin surfaces.
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ requires a fresh dashboard login.

## JavaScript and TypeScript SDK

Install `@caplets/sdk` for an isolated, typed public HTTP client in Node.js 22+ or a modern browser.
Install `@caplets/sdk` for an isolated, typed public HTTP client in a modern web-platform JavaScript runtime.
Pass the Current Host Origin explicitly:

```ts
Expand Down Expand Up @@ -356,8 +356,8 @@ See [GitHub Releases](https://github.com/spiritledsoftware/caplets/releases) for

## Repository

This monorepo uses pnpm. Published packages support Node.js `>=22`; CI verifies
that support floor and the current Node.js LTS, while owned runtime images use current LTS.
This monorepo uses pnpm. Caplets Processes support Node.js `>=22` and Bun `>=1.3.14`; Node remains
the default launcher and image runtime. CI verifies the minimum and current release lines for both.

```sh
pnpm install --frozen-lockfile
Expand Down
3 changes: 3 additions & 0 deletions docs/adr/0009-scope-runtime-support-by-execution-boundary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Scope Runtime Support by Execution Boundary

Caplets Processes support Node.js 22 or newer and Bun 1.3.14 or newer. Node remains the default for the `caplets` shebang and official runtime images; Bun execution is explicit through Bun's launcher. pnpm remains the sole dependency manager, while the complete verification toolchain and built-package runtime smoke are release-blocking under the minimum and latest supported Bun versions. The local backend remains SQLite, but its implementation uses the cross-runtime `@libsql/client` with Drizzle's asynchronous libSQL adapter; all storage operations are asynchronous and SQLite transactions acquire the write lock immediately. Existing SQLite files remain the storage contract, with no hosted Turso or synchronization feature. On Bun, Caplets uses Bun's native HTTP and WebSocket server; its runtime-owned in-flight body allocation is budgeted separately while smoke tests still reject an additional whole-body application buffer. The web-platform Caplets SDK stays capability-based, Native Agent Integrations inherit their peer hosts' supported runtimes, and Target Project Package Managers remain independent of the runtime executing Caplets.
2 changes: 1 addition & 1 deletion mise.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[tools]
bun = "1"
bun = "1.3.14"
node = "24"
pnpm = "11.7.0"
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"telemetry:prepare-release-env": "tsx ./scripts/check-telemetry-release-env.ts --write-bundled-intake",
"typecheck": "tsc --noEmit && turbo typecheck",
"verify": "pnpm format:check && pnpm lint && pnpm code-mode:check-api && pnpm schema:check && pnpm storage:check && pnpm compose:check && pnpm docs:check && pnpm openapi:check && pnpm typecheck && pnpm test && pnpm benchmark:check && pnpm build",
"verify:bun": "pnpm verify && bun scripts/check-package-runtime.mjs",
"version-packages": "changeset version && node scripts/sync-compose-image-version.mjs --write && oxlint --fix --quiet && oxfmt --write ."
},
"devDependencies": {
Expand All @@ -71,6 +72,7 @@
"vitest": "^4.1.10"
},
"engines": {
"bun": ">=1.3.14",
"node": ">=22"
},
"packageManager": "pnpm@11.7.0"
Expand Down
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"vitest": "^4.1.10"
},
"engines": {
"bun": ">=1.3.14",
"node": ">=22"
}
}
7 changes: 4 additions & 3 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,13 @@
"@hono/mcp": "^0.3.1",
"@hono/node-server": "^2.0.10",
"@hono/zod-openapi": "1.5.1",
"@libsql/client": "^0.17.4",
"@modelcontextprotocol/sdk": "^1.29.0",
"@sentry/node": "^10.66.0",
"@sentry/bun": "^10.67.0",
"@sentry/node": "^10.67.0",
"@ungap/structured-clone": "^1.3.3",
"add-mcp": "1.13.0",
"ajv": "^8.20.0",
"better-sqlite3": "^12.11.1",
"commander": "^15.0.0",
"drizzle-orm": "^0.45.2",
"formdata-node": "^6.0.3",
Expand All @@ -135,7 +136,6 @@
"zod": "^4.4.3"
},
"devDependencies": {
"@types/better-sqlite3": "^7.6.13",
"@types/node": "^26.1.1",
"@types/pg": "^8.20.0",
"@types/semver": "^7.7.1",
Expand All @@ -145,6 +145,7 @@
"vitest": "^4.1.10"
},
"engines": {
"bun": ">=1.3.14",
"node": ">=22"
}
}
2 changes: 1 addition & 1 deletion packages/core/rolldown.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default defineConfig([
},
plugins: runtimeSentryPlugins("core"),
external: [
"better-sqlite3",
"@libsql/client",
"jsonc-parser",
"quickjs-emscripten",
"typescript",
Expand Down
34 changes: 33 additions & 1 deletion packages/core/src/admin-api/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,7 @@ async function validateBundleUploadRequest(
throw new CapletsError("REQUEST_INVALID", "The Caplet Bundle upload body is required.");
}
const parsed = await parseAdminBundleUpload({
input: Readable.fromWeb(context.req.raw.body as never),
input: requestBodyReadable(context.req.raw.body),
contentType: context.req.header("Content-Type"),
contentLength: context.req.header("Content-Length"),
admission: options.bundleUploadAdmission,
Expand All @@ -669,6 +669,38 @@ async function validateBundleUploadRequest(
};
}

function requestBodyReadable(body: ReadableStream<Uint8Array>): Readable {
const reader = body.getReader();
const input = Readable.from(requestBodyChunks(reader), {
highWaterMark: 64 * 1024,
objectMode: false,
});
const destroy = input.destroy.bind(input);
let cancelStarted = false;
input.destroy = (error?: Error) => {
if (!input.destroyed && !input.readableEnded && !cancelStarted) {
cancelStarted = true;
void reader.cancel(error).catch(() => undefined);
}
return destroy(error);
};
return input;
}

async function* requestBodyChunks(
reader: ReadableStreamDefaultReader<Uint8Array>,
): AsyncGenerator<Buffer> {
try {
while (true) {
const chunk = await reader.read();
if (chunk.done) return;
yield Buffer.from(chunk.value.buffer, chunk.value.byteOffset, chunk.value.byteLength);
}
} finally {
reader.releaseLock();
}
}

function validateRouteHeaders(context: Context, definition: AdminV2RouteDefinition): void {
const schema = adminV2RequestHeadersForDefinition(definition);
if (!schema) return;
Expand Down
5 changes: 4 additions & 1 deletion packages/core/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ import {
type TelemetrySurface,
writeTelemetryAttribution,
} from "./telemetry";
import { runtimeDescriptor } from "./telemetry/runtime-environment";
import { maybePrintUpdateNotice } from "./update-check";
import {
VAULT_MAX_VALUE_BYTES,
Expand Down Expand Up @@ -615,6 +616,7 @@ async function captureCliTelemetry(

if (options.outcome !== "failure") return;
if (options.error === undefined) return;
const runtime = runtimeDescriptor();
const reliability = buildReliabilityTelemetryEvent({
name: "caplets_reliability_error",
properties: {
Expand All @@ -627,7 +629,8 @@ async function captureCliTelemetry(
diagnostic_category: diagnosticCategoryForError(options.error),
os_family: platform(),
arch: architectureForTelemetry(),
node_major: Number(process.versions.node.split(".")[0] ?? 0),
runtime_name: runtime.name,
runtime_major: runtime.major,
},
error: options.error,
});
Expand Down
Loading
Loading