Skip to content

Commit 116ea16

Browse files
refactor(dev)!: delete the runtime provider MCP contract (#855)
* refactor(dev)!: delete the runtime provider MCP contract Nothing in the host consumes a provider's MCP registry, clientSurface(), or run results' app binding since the runtime App path was removed (#852). Delete them from agent-bundle: the RuntimeMcpRegistry implementation and createRuntimeMcpRegistry, DevRuntimeSession.mcpRegistry and clientSurface(), DevRuntimeInspectionEnvelope.app, the runtime.mcp.* events and their fields, and every DevRuntimeMcp*/RuntimeMcp* type. The Workbench runtime client now rejects an app field in run inspections. Rework examples/rsc-agent-runtime so its dev provider no longer hosts an in-process MCP registry or App client surface: activation commits only the generation store, generation metadata drops the registry descriptors and digests, and MCP tool runs no longer return an App binding. Hooks and MCP tools still share the durable kernel, which a new lifecycles e2e step checks end to end. * fix(example): recover from a failed prepared-runtime reconcile Return the runtime to active once a reconcile succeeds after a failed one, abort a prepared activation when the test seam throws, cover a provider-path latch on an active session, and describe what the dev session still serves. * docs(changeset): reference #855
1 parent 1a77058 commit 116ea16

36 files changed

Lines changed: 343 additions & 4976 deletions
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"agent-bundle": minor
3+
---
4+
5+
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)

‎agent-patterns/effect-errors.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ onto those classes at the boundary. It does not replace them.
2626
- Typed fail, framework-process class (dev seam / eval service, extends
2727
`YieldableFrameworkError` or `YieldableCodedError` from
2828
`packages/agent-bundle/src/effect/errors.ts`):
29-
`return yield* new RuntimeMcpRegistryError('RUNTIME_MCP_REGISTRY_CLOSED', message)`.
30-
`Effect.fail(new RuntimeMcpRegistryError(...))` is equally valid; do not
29+
`return yield* new RuntimeGenerationStoreError('RUNTIME_GENERATION_CLOSED', message)`.
30+
`Effect.fail(new RuntimeGenerationStoreError(...))` is equally valid; do not
3131
churn call sites for style.
3232
- Defect (bug): `Effect.die(defect)`: not for expected fail-closed states.
3333
- Recover: `Effect.catch`, `Effect.catchTag` when the error is tagged.

‎docs/architecture/rsc-runtime-workbench.md‎

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ packages/
3939
src/dev/runtime-app-message-limits.ts
4040
src/dev/runtime-controller.ts
4141
src/dev/runtime-generation-store.ts
42-
src/dev/runtime-mcp-registry.ts
4342
src/dev/runtime-provider-loader.ts
4443
src/dev/runtime-provider.ts
4544
src/dev/runtime-routes.ts
@@ -80,7 +79,6 @@ packages/
8079
tests/rsc-runtime-optional-packaging.test.ts
8180
tests/rsc-runtime-topology-script.test.ts
8281
tests/runtime-generation-store.test.ts
83-
tests/runtime-mcp-registry.test.ts
8482
tests/runtime-provider.test.ts
8583
tests/runtime-routes.test.ts
8684
workbench/
@@ -208,12 +206,10 @@ is local to that invocation. Durable Runtime export/evaluation promotion,
208206
provider adapters, authenticated APIs, and timeline UI ownership are not wired
209207
by this topology.
210208

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

218214
Host-facing adoption is a further, separately gated axis (#179 / #218 stage 4).
219215
`EpochAdoptionPolicy` sits between `artifact.available` and the two surfaces a

‎docs/effect-conventions.md‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,7 @@ Internals keep the existing classes.
6666

6767
Framework-process error classes, the ones raised inside Effect programs in
6868
the dev seam and the eval service whose declarations no package export
69-
reaches (today: `DevCoordinatorCloseError`, `RuntimeMcpRegistryError` /
70-
`RuntimeMcpRegistryCloseError`, `RuntimeGenerationStoreError` /
69+
reaches (today: `DevCoordinatorCloseError`, `RuntimeGenerationStoreError` /
7170
`RuntimeGenerationStoreCloseError`, `DevRuntimeProviderLoadError`,
7271
`ScriptPlaygroundFailure` / `ScriptPlaygroundAbortError`,
7372
`LifecycleReplayRequestError`, `ArtifactInspectionServiceError`,

‎examples/rsc-agent-runtime/README.md‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,18 @@ console before rejecting.
8080
The published Agent Bundle library is built with Rslib. This example's separate
8181
production RSC/runtime artifacts are built by its explicit Rsbuild production
8282
command (`pnpm --filter @agent-bundle/rsc-agent-runtime-demo build`); its provider
83-
uses a separate long-lived Rsbuild development/HMR session only when an
84-
`agent-bundle dev` project opts into `dev.runtime.provider`. The session uses
85-
development entries, output roots, and HMR topology while compiling every
83+
uses a separate long-lived Rsbuild development session only when an
84+
`agent-bundle dev` project opts into `dev.runtime.provider`. That session
85+
compiles each change into a runtime generation, publishes its hook, MCP tool,
86+
resource, and App surfaces to the Workbench runtime routes, and serves
87+
generation assets through them; no browser connects to its loopback Rsbuild
88+
server. The session uses development entries and output roots while compiling every
8689
environment in production mode: production decoders cannot read development
8790
Flight payloads, and Rsbuild only inlines the App's scripts and styles in
8891
production mode. `@rsbuild/plugin-react` is configured as
89-
`pluginReact({ fastRefresh: false })`: the compiler App is an opaque srcdoc
90-
child with `hmr: false`, and the runtime-surface outer document owns the one
91-
HMR socket. The `widget` and `app` web environments set
92+
`pluginReact({ fastRefresh: false })` and the App environment sets
93+
`hmr: false`: the self-contained App document never receives a browser HMR
94+
credential or connection. The `widget` and `app` web environments set
9295
`overrideBrowserslist: ['chrome >= 144']` for the Chromium MCP App hosts
9396
(Cursor 3.18.25 still ships Chromium 144; Claude Desktop Electron 42 and
9497
Cursor 3.19.7 ship Chromium 148; ChatGPT/Codex Desktop reports Chromium 151).

‎examples/rsc-agent-runtime/rsbuild.config.ts‎

Lines changed: 11 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { dirname, join } from 'node:path';
55
import {
66
defineConfig,
77
type RsbuildConfig,
8-
type RsbuildDevServer,
98
type RsbuildPlugin,
109
type Rspack,
1110
} from '@rsbuild/core';
@@ -29,11 +28,10 @@ import { emitRuntimeArtifacts } from './src/build/emit-artifacts.js';
2928
export const rscRuntimeBrowserHost = Object.freeze(['chrome >= 144'] as const);
3029

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

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

125-
const appOutputContentHash = (stats: Rspack.Stats): string | undefined => {
126-
try {
127-
const assets = [...stats.compilation.getAssets()].sort((left, right) =>
128-
left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
129-
const hash = createHash('sha256');
130-
hash.update(`${assets.length}:`);
131-
for (const asset of assets) {
132-
const name = Buffer.from(asset.name);
133-
const content = asset.source.buffer();
134-
hash.update(`${name.byteLength}:`);
135-
hash.update(name);
136-
hash.update(`${content.byteLength}:`);
137-
hash.update(content);
138-
}
139-
return hash.digest('hex');
140-
} catch {
141-
return undefined;
142-
}
143-
};
144-
145-
const runtimeAppReloadPlugin = (
146-
onAppReload: NonNullable<RscRuntimeRsbuildConfigOptions['onAppReload']>,
147-
): RsbuildPlugin => {
148-
let devServer: RsbuildDevServer | undefined;
149-
let lastAppOutput: string | undefined;
150-
return {
151-
name: 'agent-bundle:rsc-runtime-app-reload',
152-
setup(api) {
153-
api.onAfterCreateCompiler(({ environments }) => {
154-
if (environments.app === undefined) {
155-
throw new Error('RSC runtime compiler did not expose the App environment.');
156-
}
157-
});
158-
api.onBeforeStartDevServer(({ server }) => {
159-
devServer = server;
160-
lastAppOutput = undefined;
161-
});
162-
api.onCloseDevServer(() => {
163-
devServer = undefined;
164-
lastAppOutput = undefined;
165-
});
166-
api.onAfterEnvironmentCompile(({ environment, isFirstCompile, stats }) => {
167-
if (devServer === undefined || environment.name !== 'app' || stats === undefined || stats.hasErrors()) return;
168-
// Rspack stats hashes can change across watch completions whose
169-
// emitted App bytes are identical. The complete asset set is the
170-
// browser-visible identity; an unreadable set remains unidentifiable
171-
// and reloads at least once without clobbering the retained identity.
172-
const output = appOutputContentHash(stats);
173-
if (output !== undefined) {
174-
if (lastAppOutput === output) return;
175-
lastAppOutput = output;
176-
}
177-
if (isFirstCompile) return;
178-
onAppReload();
179-
});
180-
},
181-
};
182-
};
183-
184116
const emitRuntimeManifest = (): RsbuildPlugin => ({
185117
apply: 'build',
186118
name: 'emit-rsc-agent-runtime-manifest',
@@ -316,10 +248,10 @@ const runtimeCompileObserverPlugin = (
316248
const environmentHashes = Object.freeze(Object.fromEntries(
317249
compileEnvironmentNames.map((name) => [name, cohortHashes.get(name) as string]),
318250
)) as RscRuntimeCompileEnvironmentHashes;
319-
// The App environment ships through its own dev-server surface, so
320-
// only the rsc and widget children define the source revision that
321-
// decides whether a new runtime generation is needed. The App child
322-
// hash still selects which staged App checkpoint joins the cohort.
251+
// Only the rsc and widget children define the source revision that
252+
// decides whether a new runtime generation is needed; an App-only
253+
// edit joins the next generation. The App child hash still selects
254+
// which staged App checkpoint joins the cohort.
323255
const hashes = (['rsc', 'widget'] as const).map((name) => [name, cohortHashes.get(name) as string]);
324256
const sourceRevision = createHash('sha256').update(JSON.stringify(hashes)).digest('hex');
325257
snapshot = await observer.capture({
@@ -383,16 +315,14 @@ export const createRscRuntimeRsbuildConfig = (
383315
// Port 0 lets the OS assign the listener. Rsbuild's default (3000 with an
384316
// incrementing probe) makes every concurrent runtime session on a host
385317
// race for the same first candidate, which surfaces as EADDRINUSE when
386-
// suites run in parallel. Consumers read the resolved port back from
387-
// `rsbuild.context.devServer`.
318+
// suites run in parallel.
388319
server: { host: '127.0.0.1', port: 0, printUrls: false },
389320
} : {}),
390321
plugins: [
391322
pluginReact(rscRuntimeReactPluginOptions),
392323
pluginRSC({ environments: { server: 'rsc', client: 'widget' } }),
393324
emitRuntimeManifest(),
394325
selfContainedAppPlugin(),
395-
...(options.onAppReload === undefined ? [] : [runtimeAppReloadPlugin(options.onAppReload)]),
396326
...(options.onCompile === undefined ? [] : [runtimeCompileObserverPlugin(options.onCompile)]),
397327
],
398328
environments: {
@@ -456,9 +386,8 @@ export const createRscRuntimeRsbuildConfig = (
456386
app: {
457387
...(development ? {
458388
dev: {
459-
// The trusted runtime-surface outer document owns the one HMR
460-
// socket. The compiler App itself runs in an opaque srcdoc child
461-
// and must never receive a browser HMR credential or connection.
389+
// The development session serves no browser client; the compiled
390+
// App must never receive a browser HMR credential or connection.
462391
hmr: false,
463392
liveReload: false,
464393
},

0 commit comments

Comments
 (0)