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/remove-runtime-provider-mcp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"agent-bundle": minor
---

Remove the runtime provider MCP contract from `agent-bundle/api`. `DevRuntimeSession` no longer has `mcpRegistry` or `clientSurface()`, `createRuntimeMcpRegistry` and the `DevRuntimeMcp*`, `RuntimeMcp*`, `DevRuntimeProviderMcpRegistry`, and `DevRuntimeClientSurfaceEndpoint` types are gone, run inspections (`DevRuntimeInspectionEnvelope`) no longer carry `app`, and `DevRuntimeEventInput` and the root `RuntimeEvent` type drop the `runtime.mcp.*` events with their `mcpRegistryRevision`, `mcpSessionId`, and `mcpSessionRevision` fields. The Workbench rejects a run inspection that still includes `app` (`AB8206`). `createRuntimeGenerationStore` and the generation store contracts are unchanged. (#855)
4 changes: 2 additions & 2 deletions agent-patterns/effect-errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ onto those classes at the boundary. It does not replace them.
- Typed fail, framework-process class (dev seam / eval service, extends
`YieldableFrameworkError` or `YieldableCodedError` from
`packages/agent-bundle/src/effect/errors.ts`):
`return yield* new RuntimeMcpRegistryError('RUNTIME_MCP_REGISTRY_CLOSED', message)`.
`Effect.fail(new RuntimeMcpRegistryError(...))` is equally valid; do not
`return yield* new RuntimeGenerationStoreError('RUNTIME_GENERATION_CLOSED', message)`.
`Effect.fail(new RuntimeGenerationStoreError(...))` is equally valid; do not
churn call sites for style.
- Defect (bug): `Effect.die(defect)`: not for expected fail-closed states.
- Recover: `Effect.catch`, `Effect.catchTag` when the error is tagged.
Expand Down
12 changes: 4 additions & 8 deletions docs/architecture/rsc-runtime-workbench.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ packages/
src/dev/runtime-app-message-limits.ts
src/dev/runtime-controller.ts
src/dev/runtime-generation-store.ts
src/dev/runtime-mcp-registry.ts
src/dev/runtime-provider-loader.ts
src/dev/runtime-provider.ts
src/dev/runtime-routes.ts
Expand Down Expand Up @@ -80,7 +79,6 @@ packages/
tests/rsc-runtime-optional-packaging.test.ts
tests/rsc-runtime-topology-script.test.ts
tests/runtime-generation-store.test.ts
tests/runtime-mcp-registry.test.ts
tests/runtime-provider.test.ts
tests/runtime-routes.test.ts
workbench/
Expand Down Expand Up @@ -208,12 +206,10 @@ is local to that invocation. Durable Runtime export/evaluation promotion,
provider adapters, authenticated APIs, and timeline UI ownership are not wired
by this topology.

Artifact epoch, runtime generation, state version, definition digest, MCP
session, and run identity are separate axes. A generation is staged and
validated, then atomically activated; leases keep the immutable generation
available for a selected run. Failed preparation retains the last good active
generation. Static MCP definitions and the broker survive independently of
generation-pinned invocations and binding authority.
Artifact epoch, runtime generation, state version, and run identity are
separate axes. A generation is staged and validated, then atomically
activated; leases keep the immutable generation available for a selected run.
Failed preparation retains the last good active generation.

Host-facing adoption is a further, separately gated axis (#179 / #218 stage 4).
`EpochAdoptionPolicy` sits between `artifact.available` and the two surfaces a
Expand Down
3 changes: 1 addition & 2 deletions docs/effect-conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ Internals keep the existing classes.

Framework-process error classes, the ones raised inside Effect programs in
the dev seam and the eval service whose declarations no package export
reaches (today: `DevCoordinatorCloseError`, `RuntimeMcpRegistryError` /
`RuntimeMcpRegistryCloseError`, `RuntimeGenerationStoreError` /
reaches (today: `DevCoordinatorCloseError`, `RuntimeGenerationStoreError` /
`RuntimeGenerationStoreCloseError`, `DevRuntimeProviderLoadError`,
`ScriptPlaygroundFailure` / `ScriptPlaygroundAbortError`,
`LifecycleReplayRequestError`, `ArtifactInspectionServiceError`,
Expand Down
15 changes: 9 additions & 6 deletions examples/rsc-agent-runtime/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,18 @@ console before rejecting.
The published Agent Bundle library is built with Rslib. This example's separate
production RSC/runtime artifacts are built by its explicit Rsbuild production
command (`pnpm --filter @agent-bundle/rsc-agent-runtime-demo build`); its provider
uses a separate long-lived Rsbuild development/HMR session only when an
`agent-bundle dev` project opts into `dev.runtime.provider`. The session uses
development entries, output roots, and HMR topology while compiling every
uses a separate long-lived Rsbuild development session only when an
`agent-bundle dev` project opts into `dev.runtime.provider`. That session
compiles each change into a runtime generation, publishes its hook, MCP tool,
resource, and App surfaces to the Workbench runtime routes, and serves
generation assets through them; no browser connects to its loopback Rsbuild
server. The session uses development entries and output roots while compiling every
environment in production mode: production decoders cannot read development
Flight payloads, and Rsbuild only inlines the App's scripts and styles in
production mode. `@rsbuild/plugin-react` is configured as
`pluginReact({ fastRefresh: false })`: the compiler App is an opaque srcdoc
child with `hmr: false`, and the runtime-surface outer document owns the one
HMR socket. The `widget` and `app` web environments set
`pluginReact({ fastRefresh: false })` and the App environment sets
`hmr: false`: the self-contained App document never receives a browser HMR
credential or connection. The `widget` and `app` web environments set
`overrideBrowserslist: ['chrome >= 144']` for the Chromium MCP App hosts
(Cursor 3.18.25 still ships Chromium 144; Claude Desktop Electron 42 and
Cursor 3.19.7 ship Chromium 148; ChatGPT/Codex Desktop reports Chromium 151).
Expand Down
93 changes: 11 additions & 82 deletions examples/rsc-agent-runtime/rsbuild.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { dirname, join } from 'node:path';
import {
defineConfig,
type RsbuildConfig,
type RsbuildDevServer,
type RsbuildPlugin,
type Rspack,
} from '@rsbuild/core';
Expand All @@ -29,11 +28,10 @@ import { emitRuntimeArtifacts } from './src/build/emit-artifacts.js';
export const rscRuntimeBrowserHost = Object.freeze(['chrome >= 144'] as const);

/**
* The compiler App is an opaque srcdoc child (`hmr: false`) and the
* runtime-surface outer document owns the one HMR socket. Fast Refresh
* would inject a refresh runtime into a self-contained HTML document that
* must never receive a browser HMR credential. The Flight widget is client
* JS, not a refreshable SPA.
* The development session serves no browser client (`hmr: false`). Fast
* Refresh would inject a refresh runtime into a self-contained HTML document
* that must never receive a browser HMR credential. The Flight widget is
* client JS, not a refreshable SPA.
*/
export const rscRuntimeReactPluginOptions = Object.freeze({ fastRefresh: false } as const);

Expand Down Expand Up @@ -75,13 +73,6 @@ const isCompileEnvironmentName = (value: string): value is RscRuntimeCompileEnvi
export interface RscRuntimeRsbuildConfigOptions {
readonly compilerRoot?: string;
readonly mode: 'development' | 'production';
/**
* Provider-owned reload signal: invoked once for each later successful,
* changed App environment compilation. This callback replaces
* `hot.send('full-reload')`, so no consumer has to parse Rsbuild's private
* WebSocket envelope to learn that the App surface changed.
*/
readonly onAppReload?: () => void;
readonly onCompile?: Readonly<{
/**
* Allocates the monotonic identity for one completed MultiStats cohort.
Expand Down Expand Up @@ -122,65 +113,6 @@ export interface RscRuntimeRsbuildConfigOptions {
}>;
}

const appOutputContentHash = (stats: Rspack.Stats): string | undefined => {
try {
const assets = [...stats.compilation.getAssets()].sort((left, right) =>
left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
const hash = createHash('sha256');
hash.update(`${assets.length}:`);
for (const asset of assets) {
const name = Buffer.from(asset.name);
const content = asset.source.buffer();
hash.update(`${name.byteLength}:`);
hash.update(name);
hash.update(`${content.byteLength}:`);
hash.update(content);
}
return hash.digest('hex');
} catch {
return undefined;
}
};

const runtimeAppReloadPlugin = (
onAppReload: NonNullable<RscRuntimeRsbuildConfigOptions['onAppReload']>,
): RsbuildPlugin => {
let devServer: RsbuildDevServer | undefined;
let lastAppOutput: string | undefined;
return {
name: 'agent-bundle:rsc-runtime-app-reload',
setup(api) {
api.onAfterCreateCompiler(({ environments }) => {
if (environments.app === undefined) {
throw new Error('RSC runtime compiler did not expose the App environment.');
}
});
api.onBeforeStartDevServer(({ server }) => {
devServer = server;
lastAppOutput = undefined;
});
api.onCloseDevServer(() => {
devServer = undefined;
lastAppOutput = undefined;
});
api.onAfterEnvironmentCompile(({ environment, isFirstCompile, stats }) => {
if (devServer === undefined || environment.name !== 'app' || stats === undefined || stats.hasErrors()) return;
// Rspack stats hashes can change across watch completions whose
// emitted App bytes are identical. The complete asset set is the
// browser-visible identity; an unreadable set remains unidentifiable
// and reloads at least once without clobbering the retained identity.
const output = appOutputContentHash(stats);
if (output !== undefined) {
if (lastAppOutput === output) return;
lastAppOutput = output;
}
if (isFirstCompile) return;
onAppReload();
});
},
};
};

const emitRuntimeManifest = (): RsbuildPlugin => ({
apply: 'build',
name: 'emit-rsc-agent-runtime-manifest',
Expand Down Expand Up @@ -316,10 +248,10 @@ const runtimeCompileObserverPlugin = (
const environmentHashes = Object.freeze(Object.fromEntries(
compileEnvironmentNames.map((name) => [name, cohortHashes.get(name) as string]),
)) as RscRuntimeCompileEnvironmentHashes;
// The App environment ships through its own dev-server surface, so
// only the rsc and widget children define the source revision that
// decides whether a new runtime generation is needed. The App child
// hash still selects which staged App checkpoint joins the cohort.
// Only the rsc and widget children define the source revision that
// decides whether a new runtime generation is needed; an App-only
// edit joins the next generation. The App child hash still selects
// which staged App checkpoint joins the cohort.
const hashes = (['rsc', 'widget'] as const).map((name) => [name, cohortHashes.get(name) as string]);
const sourceRevision = createHash('sha256').update(JSON.stringify(hashes)).digest('hex');
snapshot = await observer.capture({
Expand Down Expand Up @@ -383,16 +315,14 @@ export const createRscRuntimeRsbuildConfig = (
// Port 0 lets the OS assign the listener. Rsbuild's default (3000 with an
// incrementing probe) makes every concurrent runtime session on a host
// race for the same first candidate, which surfaces as EADDRINUSE when
// suites run in parallel. Consumers read the resolved port back from
// `rsbuild.context.devServer`.
// suites run in parallel.
server: { host: '127.0.0.1', port: 0, printUrls: false },
} : {}),
plugins: [
pluginReact(rscRuntimeReactPluginOptions),
pluginRSC({ environments: { server: 'rsc', client: 'widget' } }),
emitRuntimeManifest(),
selfContainedAppPlugin(),
...(options.onAppReload === undefined ? [] : [runtimeAppReloadPlugin(options.onAppReload)]),
...(options.onCompile === undefined ? [] : [runtimeCompileObserverPlugin(options.onCompile)]),
],
environments: {
Expand Down Expand Up @@ -456,9 +386,8 @@ export const createRscRuntimeRsbuildConfig = (
app: {
...(development ? {
dev: {
// The trusted runtime-surface outer document owns the one HMR
// socket. The compiler App itself runs in an opaque srcdoc child
// and must never receive a browser HMR credential or connection.
// The development session serves no browser client; the compiled
// App must never receive a browser HMR credential or connection.
hmr: false,
liveReload: false,
},
Expand Down
Loading
Loading