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
5 changes: 5 additions & 0 deletions .changeset/prebuilt-payload-adapters.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"agent-bundle": minor
---

Prebuilt payload adapter mode (RFC #50 Phase 3): the top-level `payload` block declares already-built directory trees that `agent-bundle build` packages byte-for-byte at stable paths, and `{ prebuilt: ... }` markers on MCP server entries and hook handlers point the generated host manifests at files inside those payloads. Prebuilt entries skip compilation but flow through the same adapter lowering as compiled entries — path-token expansion in every target's MCP document, the injected `AGENT_BUNDLE_PLUGIN_ROOT` env anchor, generated `hooks/hooks.json` commands (with shell-safe prebuilt hook `args`), and artifact-reference validation. Payload files are recorded in the artifact manifest with the new `prebuilt` file kind and hash into `project.sourceInputs`; declaration provenance is recorded as `kind: 'prebuilt'`. New diagnostics: `AB4740`–`AB4746` at validation (missing payloads and prebuilt files warn so development flows work before the consumer's own build has run), `AB4747`–`AB4749` as build-time refusals, and the `AB4750` staleness nudge.
1 change: 0 additions & 1 deletion docs/architecture/rsc-runtime-workbench.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ examples/
scripts/eval-evidence.mjs
scripts/eval-host-environment.mjs
scripts/eval-hosts.mjs
scripts/package-hosts.mjs
src/build/emit-artifacts.ts
src/build/serialize-definition.ts
src/definition.ts
Expand Down
24 changes: 24 additions & 0 deletions docs/diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ gate a build, a validation, or a dev rebuild.
| `AB471x` | Package build `lib` configuration. |
| `AB472x` | The `tools.rsbuild` / `tools.rspack` escape hatch. |
| `AB473x` | Migration nudges (informational; see below). |
| `AB474x`/`AB4750` | Prebuilt payloads and prebuilt entries (see below). |
| `AB5000` | General CLI and adapter failures. |
| `AB7xxx` | Project preparation and development rebuilds. |
| `AB8xxx` | Development server configuration. |
Expand Down Expand Up @@ -94,6 +95,29 @@ document beats a generated one — so the component module never compiles.
Adopt: remove `SKILL.md` so the rendered skill compiles at build. Silence:
remove the component module.

## Prebuilt payloads (`AB4740`–`AB4750`)

The `payload` block and `{ prebuilt: ... }` entries (see
`docs/entry-conventions.md`) package files the framework did not compile.
The consumer's own build produces them, so their diagnostics split by
moment: configuration mistakes are validation **errors**, a payload that has
simply not been built yet is a validation **warning** that only
`agent-bundle build` escalates, and freshness is an **info** nudge.

| Code | Severity | Trigger |
| --- | --- | --- |
| `AB4740` | error | The `payload` block, one entry, or its `targets` list is malformed, or a payload selects an unknown target. |
| `AB4741` | error | A payload destination is not a safe directory name, or shadows a compiler-owned artifact namespace (`assets`, `hooks`, `mcp`, `mcp-apps`, `scripts`, `skills`, root documents). |
| `AB4742` | error | A payload source escapes the project root, is not a directory, or contains another payload's source. |
| `AB4743` | warning | A declared payload directory does not exist yet or contains no files. Run the project's own build first. |
| `AB4744` | error | A `{ prebuilt: ... }` entry (MCP server or hook handler) does not resolve inside a declared payload, or its payload does not select every target the component needs. |
| `AB4745` | warning | A declared prebuilt entry file does not exist yet. Run the project's own build first. |
| `AB4746` | error | Hook `args` on a non-prebuilt handler, or arguments outside the shell-safe charset. |
| `AB4747` | error (build) | `agent-bundle build` refuses an empty or missing payload. |
| `AB4748` | error (build) | `agent-bundle build` refuses a prebuilt entry file absent from its payload. |
| `AB4749` | error (build) | A payload directory overlaps the artifact `--output` root. |
| `AB4750` | info | A payload is older than the newest project source file and may be stale; rerun the project's own build if so. |

## Development package build (`AB7103`)

`agent-bundle dev` rebuilds the framework-owned package build (`dist/` bin
Expand Down
76 changes: 76 additions & 0 deletions docs/entry-conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,82 @@ Export detection is a static scan of the entry source (comment-, string-, and
template-safe). The generated shells re-verify the export shape at runtime
with a clear error.

## Prebuilt payloads — package what you compiled yourself

Some projects legitimately own their compilation — a coordinated
multi-environment bundler topology the per-entry `tools` hatch cannot
express — but still want framework-owned host packaging (manifests, hook
documents, env anchors, provenance, validation). The `payload` block
declares already-built directory trees the build packages **as-is**, and the
`{ prebuilt: ... }` marker points MCP entries and hook handlers at files
inside them:

```ts
export default defineConfig({
payload: {
// key = artifact-root destination directory, value = the built tree
app: './dist/app',
runtime: { source: './dist/runtime', targets: ['claude', 'codex'] },
},
mcp: {
servers: {
timeline: {
entry: { prebuilt: './dist/runtime/mcp/stdio.js' },
transport: 'stdio',
},
},
},
hooks: {
afterTool: [{
args: ['--host', 'claude'],
handler: { prebuilt: './dist/runtime/hook/index.js' },
targets: ['claude'],
tools: ['file.write'],
}],
},
});
```

- **Stable paths, not content-hashing.** Every payload file keeps its exact
relative path under the destination directory. The framework did not
compile these files, so it cannot rewrite the references inside them —
sibling chunk imports, worker entries resolved from `import.meta.url` —
and hosts, manuals, and tests pin the entry paths. Integrity stays
content-addressed anyway: each payload file lands in the artifact manifest
with its SHA-256 and the `prebuilt` file kind, and the payload files hash
into `project.sourceInputs`, so the project revision changes whenever the
payload bytes do.
- **The same adapter lowering.** A prebuilt MCP entry normalizes to a
command-shaped stdio server whose first argument is the payload path
anchored on the plugin-root token, so every target renders it natively
(`${CLAUDE_PLUGIN_ROOT}/runtime/mcp/stdio.js`, Codex's `./runtime/…` with
`cwd: "./"`, `${PLUGIN_ROOT}/…`), the `AGENT_BUNDLE_PLUGIN_ROOT` env
anchor is injected as usual, and artifact validation confirms the
referenced file is present and manifested. A prebuilt hook emits its
native command as `node "<root>/<payload path>" <args…>` — one config
declaration replaces a hand-rolled `hooks/hooks.json` per host. Prebuilt
hook `args` (for example `--host claude`) accept shell-safe strings only.
- **Prebuilt means opaque.** Payload files are exempt from generated-output
content validation (bundled-ESM import graphs, strict generated JSON) but
remain hash-locked to the manifest. Declaration provenance is recorded as
`kind: 'prebuilt'`. Hooks with prebuilt handlers are packaged like native
hook documents: they do not compile wrappers and do not appear in the
simulatable hook index. MCP Apps declared on a prebuilt server stay a
development surface (the Workbench compiles them live); the build assumes
the payload already serves the resource.
- **Ordering.** Run your own build before `agent-bundle build`: a missing or
empty payload is a validation warning (`AB4743`/`AB4745`) so `dev` works
from a clean checkout, but `agent-bundle build` refuses it
(`AB4747`/`AB4748`). Payload directories must not overlap the artifact
`--output` root (`AB4749`) — with payloads under `dist/`, pass an output
like `dist/plugins`. See `docs/diagnostics.md` for the full `AB474x`
table.

`examples/rsc-agent-runtime` is the reference consumer: its Rsbuild build
owns a three-environment RSC compilation, and `agent-bundle build` packages
the resulting `dist/runtime` and `dist/app` trees into the Claude, Codex,
and portable artifacts.

## `tools` — THE escape hatch

`tools.rsbuild` (an Rsbuild environment-config fragment) and `tools.rspack`
Expand Down
8 changes: 6 additions & 2 deletions examples/rsc-agent-runtime/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,19 @@ uses a separate long-lived Rsbuild development/HMR session only when an
entry stays self-built inside those Rsbuild artifacts but consumes the
framework's stdio lifecycle (console-to-stderr guard, SIGINT/SIGTERM exit
codes, stdin-EOF shutdown) through the public `agent-bundle/mcp-entry` API.
Host packaging is framework-owned: `agent-bundle.config.ts` declares the
Rsbuild output trees as prebuilt `payload` directories with prebuilt MCP and
hook entries, and `agent-bundle build` copies them byte-for-byte at their
stable paths while generating every host manifest.
Installing
`agent-bundle` alone does not install or activate this example provider. See
[the optional RSC Runtime topology](../../docs/architecture/rsc-runtime-workbench.md)
for the full ownership boundary.

The build emits `dist/runtime` (including `dist/runtime/agent-runtime.manifest.json`), self-contained `dist/app` MCP App documents, and two self-contained native plugin artifacts under `dist/plugins`. It runs `package:hosts` automatically; it can also be run directly:
The build emits `dist/runtime` (including `dist/runtime/agent-runtime.manifest.json`), self-contained `dist/app` MCP App documents, and self-contained native plugin artifacts under `dist/plugins`. The packaging step can also be rerun directly against the current Rsbuild output:

```bash
pnpm --filter @agent-bundle/rsc-agent-runtime-demo package:hosts
pnpm --filter @agent-bundle/rsc-agent-runtime-demo exec agent-bundle build --json --output dist/plugins
```

To exercise one hook manually, give it an explicit state file and native Claude-shaped JSON:
Expand Down
32 changes: 26 additions & 6 deletions examples/rsc-agent-runtime/agent-bundle.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { defineConfig } from 'agent-bundle/config';

// The RSC runtime and App payloads are compiled by this example's own
// multi-environment Rsbuild build (see rsbuild.config.ts); agent-bundle
// packages those prebuilt trees verbatim and generates the host manifests,
// so this file is the single declaration for both development and packaging.
export default defineConfig({
// Kept deliberately: the empty per-target sections are not redundant with
// `targets:` — normalization materializes each one as a `model.extensions`
Expand All @@ -8,12 +12,24 @@ export default defineConfig({
codex: {},
dev: { runtime: { provider: './src/dev/provider.ts' } },
hooks: {
afterTool: {
handler: './src/hook/cli.ts',
targets: ['claude', 'codex'],
tools: ['file.write'],
},
afterTool: [
{
args: ['--host', 'claude'],
handler: { prebuilt: './dist/runtime/hook/index.js' },
targets: ['claude'],
timeout: 30,
tools: ['file.write'],
},
{
args: ['--host', 'codex'],
handler: { prebuilt: './dist/runtime/hook/index.js' },
targets: ['codex'],
timeout: 30,
tools: ['file.write'],
},
],
},
marketplace: true,
mcp: {
servers: {
timeline: {
Expand All @@ -27,12 +43,16 @@ export default defineConfig({
targets: ['portable', 'claude', 'codex'],
},
},
entry: './src/mcp/stdio.ts',
entry: { prebuilt: './dist/runtime/mcp/stdio.js' },
targets: ['portable', 'claude', 'codex'],
transport: 'stdio',
},
},
},
payload: {
app: './dist/app',
runtime: './dist/runtime',
},
portable: {},
plugin: {
description: 'React Server Components agent runtime demonstration.',
Expand Down
3 changes: 1 addition & 2 deletions examples/rsc-agent-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
"private": true,
"type": "module",
"scripts": {
"build": "rsbuild build --mode production && pnpm package:hosts",
"package:hosts": "node scripts/package-hosts.mjs",
"build": "rsbuild build --mode production && agent-bundle build --json --output dist/plugins",
"test": "rstest --config rstest.config.ts",
"typecheck": "tsc -p tsconfig.json --noEmit",
"validate": "agent-bundle validate",
Expand Down

This file was deleted.

9 changes: 0 additions & 9 deletions examples/rsc-agent-runtime/packaging/claude/.mcp.json

This file was deleted.

16 changes: 0 additions & 16 deletions examples/rsc-agent-runtime/packaging/claude/hooks/hooks.json

This file was deleted.

This file was deleted.

This file was deleted.

10 changes: 0 additions & 10 deletions examples/rsc-agent-runtime/packaging/codex/.mcp.json

This file was deleted.

16 changes: 0 additions & 16 deletions examples/rsc-agent-runtime/packaging/codex/hooks/hooks.json

This file was deleted.

7 changes: 7 additions & 0 deletions examples/rsc-agent-runtime/rsbuild.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,13 @@ export const createRscRuntimeRsbuildConfig = (
development ? join(options.compilerRoot as string, name) : productionRoot;

return {
// Pinned, not derived from ambient NODE_ENV: react and react-server-dom
// must compile as their production variants so Flight payloads stay
// compact model rows without development debug and timing frames, and so
// the in-worker dev server serves the production surface layout its
// session URLs are built for. `options.mode` still selects the compile
// topology (dev entries, compiler roots) independently of this flavor.
mode: 'production',
...(development ? {
dev: { writeToDisk: true },
// Port 0 lets the OS assign the listener. Rsbuild's default (3000 with an
Expand Down
Loading
Loading