From a438076abddc574a9dfa5558daf0418ddcf783ee Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 16 Sep 2026 08:35:58 +0000 Subject: [PATCH 1/4] refactor(consumers): remove legacy Agent Bundle APIs --- .changeset/refresh-scaffold-consumers.md | 5 + .../hooks-and-scripts/agent-bundle.config.ts | 1 - examples/hooks-and-scripts/package.json | 1 + examples/mcp-app/agent-bundle.config.ts | 1 - examples/mcp-app/package.json | 1 + examples/rsc-agent-runtime/README.md | 18 +- .../src/dev/invocation-worker.ts | 10 +- .../src/flight/request-render.ts | 17 +- .../src/hook/project-document.ts | 11 +- .../rsc-agent-runtime/src/mcp/handlers.ts | 13 +- .../rsc-agent-runtime/src/rsc/components.tsx | 16 +- .../tests/dev-invocation.integration.test.ts | 34 --- .../tests/mcp-lowering.test.tsx | 209 ------------------ .../skills-starter/agent-bundle.config.ts | 1 - examples/skills-starter/package.json | 1 + .../worktree-proximity/agent-bundle.config.ts | 1 - .../templates/cli-tool/package_json | 6 +- .../templates/mcp-server/package_json | 8 +- .../templates/minimal/package_json | 2 +- .../tests/scaffold.test.ts | 6 +- 20 files changed, 64 insertions(+), 298 deletions(-) create mode 100644 .changeset/refresh-scaffold-consumers.md delete mode 100644 examples/rsc-agent-runtime/tests/mcp-lowering.test.tsx diff --git a/.changeset/refresh-scaffold-consumers.md b/.changeset/refresh-scaffold-consumers.md new file mode 100644 index 000000000..78b6e0fd0 --- /dev/null +++ b/.changeset/refresh-scaffold-consumers.md @@ -0,0 +1,5 @@ +--- +"create-agent-bundle": patch +--- + +Update the `minimal`, `mcp-server`, and `cli-tool` templates to `@types/node` 26.5.1, and update the routed `mcp-server` and `cli-tool` templates to React 19.3.0 while preserving the `zod` 4.5.4 runtime peer floor. (#PR) diff --git a/examples/hooks-and-scripts/agent-bundle.config.ts b/examples/hooks-and-scripts/agent-bundle.config.ts index 7402e3fb1..648927947 100644 --- a/examples/hooks-and-scripts/agent-bundle.config.ts +++ b/examples/hooks-and-scripts/agent-bundle.config.ts @@ -8,7 +8,6 @@ export default defineConfig({ plugin: { description: 'Hook simulation, script traces, logs, and recovery.', name: 'hooks-and-scripts', - version: '1.0.0', }, // verify-release ships by convention: unclaimed plain scripts under // src/scripts/ are discovered. detect-risk stays explicitly configured diff --git a/examples/hooks-and-scripts/package.json b/examples/hooks-and-scripts/package.json index c11f72f87..fecc60079 100644 --- a/examples/hooks-and-scripts/package.json +++ b/examples/hooks-and-scripts/package.json @@ -1,5 +1,6 @@ { "name": "@agent-bundle-example/hooks-and-scripts", + "version": "1.0.0", "private": true, "license": "Apache-2.0", "type": "module", diff --git a/examples/mcp-app/agent-bundle.config.ts b/examples/mcp-app/agent-bundle.config.ts index 9252527bd..8f90d541c 100644 --- a/examples/mcp-app/agent-bundle.config.ts +++ b/examples/mcp-app/agent-bundle.config.ts @@ -8,7 +8,6 @@ export default defineConfig({ plugin: { description: 'A unified service-readiness assistant with MCP, Skills, Hooks, scripts, and evaluation.', name: 'mcp-app-example', - version: '1.0.0', }, scripts: { 'check-service-fixture': './src/scripts/check-service-fixture.ts', diff --git a/examples/mcp-app/package.json b/examples/mcp-app/package.json index 13f1d6047..86c2f02f8 100644 --- a/examples/mcp-app/package.json +++ b/examples/mcp-app/package.json @@ -1,5 +1,6 @@ { "name": "@agent-bundle-example/mcp-app", + "version": "1.0.0", "private": true, "license": "Apache-2.0", "type": "module", diff --git a/examples/rsc-agent-runtime/README.md b/examples/rsc-agent-runtime/README.md index 177c1e588..c010377b1 100644 --- a/examples/rsc-agent-runtime/README.md +++ b/examples/rsc-agent-runtime/README.md @@ -8,7 +8,7 @@ This private, opt-in example shows one React Server Components (RSC) runtime sha | --- | --- | --- | | Definition | Static hook matchers, tool schemas, resource URIs, and metadata | Build/startup | | Kernel | Framework state kernel (#98): typed events, monotonic revisions, workspace-durable `node:sqlite` storage | Cross-process | -| RSC render | Hook and MCP result component trees, lowered from Flight | One request | +| RSC render | Agent Document component trees projected from Flight | One request | | MCP App UI | Mounted timeline, Refresh, and recoverable row selection | One UI instance | Native hooks are fresh requests: the compiler-generated client validates one host event, invokes `src/events/tool/after.tsx` in its explicit standalone mode, projects the final Agent Document, and exits. The durable kernel—not a Node module cache or React state—connects later hook processes and MCP calls. @@ -32,14 +32,14 @@ export default async function AfterFileEdit({ canonical }: AgentEventRouteProps< ``` ```tsx -// An MCP JSX route describes protocol blocks, not browser HTML. -import { Mcp } from '@agent-bundle/runtime'; +// An Agent Document route describes protocol-neutral output, not browser HTML. +import { Agent } from '@agent-bundle/runtime'; export function RenderTimeline({ snapshot }: { snapshot: { edits: unknown[]; stateVersion: number } }) { return ( - - {`Showing ${snapshot.edits.length} edits.`} - + + {`Showing ${snapshot.edits.length} edits.`} + ); } ``` @@ -150,12 +150,10 @@ AGENT_RUNTIME_STATE_FILE=/tmp/rsc-agent-state.sqlite PORT=3000 \ | Static surface | Result | | --- | --- | | `recent_edits` | Text plus `structuredContent` snapshot | -| `render_edit_timeline` | RSC-lowered text and snapshot; links the versioned timeline resource | -| `runtime_status` | RSC-lowered text, image, and structured status | +| `render_edit_timeline` | Agent Document text and snapshot; links the versioned timeline resource | +| `runtime_status` | Agent Document text, image, and structured status | | `ui://rsc-agent-runtime/edit-timeline-v1.html` | `text/html;profile=mcp-app` self-contained React timeline | -`Mcp.Text`, `Mcp.Image`, `Mcp.Audio`, `Mcp.ResourceLink`, and `Mcp.EmbeddedResource` lower to their matching MCP content blocks. `Mcp.Result` supplies ordered `content`, optional `structuredContent`, optional `isError`, and optional serializable `_meta`. - ## Native packages and evaluation Claude Code has a native compact manifest, `${CLAUDE_PLUGIN_ROOT}` paths, and a diff --git a/examples/rsc-agent-runtime/src/dev/invocation-worker.ts b/examples/rsc-agent-runtime/src/dev/invocation-worker.ts index 4e9a6e609..5f355c52f 100644 --- a/examples/rsc-agent-runtime/src/dev/invocation-worker.ts +++ b/examples/rsc-agent-runtime/src/dev/invocation-worker.ts @@ -1,6 +1,8 @@ -import { requestAgentDocumentWithFlight, requestFlightRenderWithFlight } from '../flight/request-render.js'; import { writeSync } from 'node:fs'; -import { lowerMcpResult } from '@agent-bundle/runtime'; + +import { documentToCallToolResult } from '@agent-bundle/runtime'; + +import { requestAgentDocumentWithFlight } from '../flight/request-render.js'; import type { DevRuntimeInspectionRequest, DevRuntimeInspectionResponse, @@ -188,11 +190,11 @@ const invoke = async (signal?: AbortSignal): Promise => { }); } - const rendered = await requestFlightRenderWithFlight(renderRequest, { + const rendered = await requestAgentDocumentWithFlight(renderRequest, { maximumFlightBytes: maximumInvocationFlightBytes, signal, }); - const protocol = lowerMcpResult(rendered.node); + const protocol = documentToCallToolResult(rendered.document); return Object.freeze({ flight: Buffer.from(rendered.flight), response: Object.freeze({ diff --git a/examples/rsc-agent-runtime/src/flight/request-render.ts b/examples/rsc-agent-runtime/src/flight/request-render.ts index 1e43abeaf..058f50182 100644 --- a/examples/rsc-agent-runtime/src/flight/request-render.ts +++ b/examples/rsc-agent-runtime/src/flight/request-render.ts @@ -11,24 +11,24 @@ import { createAgentRenderDispatcher, type AgentDocument, type AgentRenderInvoca import type { RenderRequest } from '../runtime/contracts.js'; import { redactInspectionDiagnostics } from '../dev/inspection-security.js'; -export const maximumFlightRenderBytes = 4 * 1024 * 1024; -export const maximumFlightRenderStderrBytes = 256 * 1024; -export const maximumFlightRenderMetadataBytes = 128; +const maximumFlightRenderBytes = 4 * 1024 * 1024; +const maximumFlightRenderStderrBytes = 256 * 1024; +const maximumFlightRenderMetadataBytes = 128; const terminationGraceMs = 100; -export interface FlightRenderResult { +interface FlightRenderResult { readonly flight: Uint8Array; readonly node: ReactNode; /** Exact durable state identity captured by the render worker; never user-visible. */ readonly stateVersion: number; } -export interface AgentDocumentFlightRenderResult extends FlightRenderResult { +interface AgentDocumentFlightRenderResult extends FlightRenderResult { readonly document: AgentDocument; } -export interface FlightRenderOptions { +interface FlightRenderOptions { readonly maximumFlightBytes?: number; readonly signal?: AbortSignal; } @@ -71,7 +71,7 @@ const parseSnapshotMetadata = (metadata: Buffer): number => { return stateVersion; }; -export const requestFlightRenderWithFlight = async ( +const requestFlightRenderWithFlight = async ( request: RenderRequest, options: FlightRenderOptions = {}, ): Promise => { @@ -188,9 +188,6 @@ export const requestFlightRenderWithFlight = async ( }); }; -export const requestFlightRender = async (request: RenderRequest): Promise => - (await requestFlightRenderWithFlight(request)).node; - const renderInvocationFor = (request: RenderRequest): AgentRenderInvocation => { switch (request.type) { case 'hook/after-file-edit': diff --git a/examples/rsc-agent-runtime/src/hook/project-document.ts b/examples/rsc-agent-runtime/src/hook/project-document.ts index 3bd774a65..fadb5dfe9 100644 --- a/examples/rsc-agent-runtime/src/hook/project-document.ts +++ b/examples/rsc-agent-runtime/src/hook/project-document.ts @@ -1,6 +1,13 @@ -import type { AgentDocument, NativePostToolUseOutput } from '@agent-bundle/runtime'; +import type { AgentDocument } from '@agent-bundle/runtime'; -export const projectHookDocument = (document: AgentDocument): NativePostToolUseOutput => { +interface ProjectedPostToolUseOutput { + readonly hookSpecificOutput: { + readonly additionalContext: string; + readonly hookEventName: 'PostToolUse'; + }; +} + +export const projectHookDocument = (document: AgentDocument): ProjectedPostToolUseOutput => { if (document.status === 'failed' || document.root.kind !== 'result') { throw new Error('Hook render requires a successful Agent.Result document'); } diff --git a/examples/rsc-agent-runtime/src/mcp/handlers.ts b/examples/rsc-agent-runtime/src/mcp/handlers.ts index 37b38a9b3..4d973695c 100644 --- a/examples/rsc-agent-runtime/src/mcp/handlers.ts +++ b/examples/rsc-agent-runtime/src/mcp/handlers.ts @@ -1,8 +1,9 @@ -import { createFileRuntimeKernel } from '../runtime/state-file.js'; -import { lowerMcpResult } from '@agent-bundle/runtime'; -import { requestFlightRender } from '../flight/request-render.js'; +import { documentToCallToolResult } from '@agent-bundle/runtime'; import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import { createFileRuntimeKernel } from '../runtime/state-file.js'; +import { requestAgentDocument } from '../flight/request-render.js'; + import { resolveStateFile, type McpRequestExtra, type ResolveStateOptions } from './resolve-state.js'; type ToolInput = { limit?: number }; @@ -22,12 +23,12 @@ export const createMcpHandlers = (options: ResolveStateOptions): Record { const stateFile = await resolveStateFile(options, extra); const snapshot = await createFileRuntimeKernel({ stateFile }).readSnapshot({ limit: input.limit }); - return lowerMcpResult( - await requestFlightRender({ snapshot, stateFile, type: 'mcp/render-timeline' }), + return documentToCallToolResult( + await requestAgentDocument({ snapshot, stateFile, type: 'mcp/render-timeline' }), ); }, runtime_status: async (_input, extra) => { const stateFile = await resolveStateFile(options, extra); - return lowerMcpResult(await requestFlightRender({ stateFile, type: 'mcp/runtime-status' })); + return documentToCallToolResult(await requestAgentDocument({ stateFile, type: 'mcp/runtime-status' })); }, }); diff --git a/examples/rsc-agent-runtime/src/rsc/components.tsx b/examples/rsc-agent-runtime/src/rsc/components.tsx index b849efeb0..6c370a75c 100644 --- a/examples/rsc-agent-runtime/src/rsc/components.tsx +++ b/examples/rsc-agent-runtime/src/rsc/components.tsx @@ -1,6 +1,6 @@ import { basename } from 'node:path'; -import { Agent, Mcp, agent } from '@agent-bundle/runtime'; +import { Agent, agent } from '@agent-bundle/runtime'; import type { CanonicalPostToolUse, RuntimeSnapshot } from '../runtime/contracts.js'; const hookServices = async (): Promise<{ edit: CanonicalPostToolUse; snapshot: RuntimeSnapshot }> => { @@ -31,9 +31,9 @@ export const AfterFileEdit = async () => { }; export const RenderEditTimeline = ({ snapshot }: { snapshot: RuntimeSnapshot }) => ( - - {`Showing ${snapshot.edits.length} recorded edits.`} - + ({ ...edit })), stateVersion: snapshot.stateVersion }}> + {`Showing ${snapshot.edits.length} recorded edits.`} + ); const STATUS_PNG_BASE64 = @@ -44,9 +44,9 @@ export const RuntimeStatus = ({ snapshot }: { snapshot: RuntimeSnapshot }) => { const editNoun = editCount === 1 ? 'edit' : 'edits'; return ( - - {`Runtime state contains ${editCount} ${editNoun}.`} - - + + {`Runtime state contains ${editCount} ${editNoun}.`} + + ); }; diff --git a/examples/rsc-agent-runtime/tests/dev-invocation.integration.test.ts b/examples/rsc-agent-runtime/tests/dev-invocation.integration.test.ts index 009349b7b..a0621036f 100644 --- a/examples/rsc-agent-runtime/tests/dev-invocation.integration.test.ts +++ b/examples/rsc-agent-runtime/tests/dev-invocation.integration.test.ts @@ -177,20 +177,6 @@ const event = (eventId: string) => ({ toolName: 'Write', }); -const oversizedMcpWorker = (payloadBytes: number): string => { - return `const { writeSync } = require('node:fs'); -const payload = 'x'.repeat(${payloadBytes}); -const model = ['$', 'mcp-result', null, { - _meta: '$undefined', - isError: '$undefined', - structuredContent: { payload, stateVersion: 0 }, - children: [['$', 'mcp-text', null, { children: 'ok' }]], -}]; -writeSync(3, Buffer.from('{"stateVersion":0}')); -process.stdout.end(\`0:\${JSON.stringify(model)}\\n\`); -`; -}; - const inspectionShape = (result: { inspection: Record }) => { const { flight: _flight, ...inspection } = result.inspection; return inspection; @@ -549,26 +535,6 @@ test('redacts bounded RSC worker stderr diagnostics', async () => { } }); -test('caps inspection stdout independently after Flight leaves its response envelope', async () => { - const compilerRoot = await mkdtemp(join(tmpdir(), 'rsc-agent-runtime-invoke-')); - try { - const entry = await buildInvocationEntry(compilerRoot); - await writeFile(join(compilerRoot, 'rsc', 'rsc', 'index.js'), oversizedMcpWorker(2_100_000)); - const result = await invoke(entry, { - stateFile: join(compilerRoot, 'events.jsonl'), - stateStoreId: 'fixture-state', - type: 'mcp/runtime-status', - }); - - expect(result.exitCode).not.toBe(0); - expect(result.stdout).toEqual(Buffer.alloc(0)); - expect(result.stderr).toContain('Inspection response exceeded output limit'); - expect(result.stderr).not.toContain('x'.repeat(128)); - } finally { - await rm(compilerRoot, { force: true, recursive: true }); - } -}); - test('bounds Flight output and waits for a SIGKILL cleanup when the RSC child ignores SIGTERM', async () => { const compilerRoot = await mkdtemp(join(tmpdir(), 'rsc-agent-runtime-invoke-')); let childPid: number | undefined; diff --git a/examples/rsc-agent-runtime/tests/mcp-lowering.test.tsx b/examples/rsc-agent-runtime/tests/mcp-lowering.test.tsx deleted file mode 100644 index 2604bf71e..000000000 --- a/examples/rsc-agent-runtime/tests/mcp-lowering.test.tsx +++ /dev/null @@ -1,209 +0,0 @@ -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; -import { expect, test } from '@rstest/core'; -import React from 'react'; - -import { Mcp, attachMcpStructuredContent, lowerMcpResult } from '@agent-bundle/runtime'; - -test('lowers every supported MCP result block in authored order', () => { - const result = lowerMcpResult( - - two edits - - - - - {'{"stateVersion":2}'} - - , - ); - - expect(result).toEqual({ - content: [ - { type: 'text', text: 'two edits' }, - { type: 'image', data: 'iVBORw0KGgo=', mimeType: 'image/png' }, - { type: 'audio', data: 'UklGRg==', mimeType: 'audio/wav' }, - { - type: 'resource_link', - uri: 'file:///demo.txt', - name: 'demo.txt', - mimeType: 'text/plain', - }, - { - type: 'resource', - resource: { - uri: 'runtime://snapshot', - mimeType: 'application/json', - text: '{"stateVersion":2}', - }, - }, - ], - structuredContent: { stateVersion: 2 }, - isError: false, - }); -}); - -test('rejects malformed or nested MCP protocol result trees', () => { - expect(() => - lowerMcpResult( - - {} - , - ), - ).toThrow('mcp-image requires non-empty data and mimeType'); - - expect(() => - lowerMcpResult( - - {} - , - ), - ).toThrow('mcp-audio requires non-empty data and mimeType'); - - expect(() => - lowerMcpResult( - - {} - , - ), - ).toThrow('mcp-embedded-resource accepts exactly one text or blob value'); - - expect(() => - lowerMcpResult( - - - {'{}'} - - , - ), - ).toThrow('mcp-embedded-resource accepts exactly one text or blob value'); - - expect(() => - lowerMcpResult( - - - nested - - , - ), - ).toThrow('mcp-result may not be nested'); - - expect(() => - lowerMcpResult( - - invalid - , - ), - ).toThrow('mcp-result structuredContent must be JSON-serializable'); -}); - -test('follows JSON wire semantics for undefined instead of rejecting it', () => { - // Matches MCP SDK serialization: JSON.stringify drops undefined-valued - // properties and lowers undefined array elements to null. - const result = lowerMcpResult( - - ok - , - ); - - expect(result.structuredContent).toEqual({ edits: [null, 'recorded'], stateVersion: 2 }); - expect(Object.hasOwn(result.structuredContent as object, 'note')).toBe(false); -}); - -test('rejects non-JSON structured content instead of normalizing it', () => { - const cyclic: Record = {}; - cyclic.self = cyclic; - const sparse = new Array(2); - sparse[1] = 'present'; - - for (const value of [ - () => undefined, - Symbol('value'), - Number.NaN, - Number.POSITIVE_INFINITY, - new Date('2026-08-14T00:00:00.000Z'), - new Map(), - sparse, - cyclic, - ]) { - expect(() => - lowerMcpResult( - - invalid - , - ), - ).toThrow('mcp-result structuredContent must be JSON-serializable'); - } -}); - -test('clones recursively valid JSON records for structured content', () => { - const input = Object.assign(Object.create(null), { - nested: { array: [null, false, 2.5, 'value'] }, - stateVersion: 2, - }); - - const result = lowerMcpResult( - - valid - , - ); - - expect(result.structuredContent).toEqual({ - nested: { array: [null, false, 2.5, 'value'] }, - stateVersion: 2, - }); - expect(result.structuredContent).not.toBe(input); -}); - -test('preserves serializable extension metadata alongside complete portable results', () => { - const result = lowerMcpResult( - - two edits - , - ); - - expect(result).toEqual({ - _meta: { 'example.acme/trace': { attempt: 2 } }, - content: [{ text: 'two edits', type: 'text' }], - structuredContent: { stateVersion: 2 }, - }); -}); - -test('preserves an own __proto__ key in valid structured content', () => { - const input = Object.create(null) as Record; - Object.defineProperty(input, '__proto__', { - enumerable: true, - value: { value: 'preserved' }, - }); - - const result = lowerMcpResult( - - valid - , - ); - - expect(Object.getOwnPropertyDescriptor(result.structuredContent as object, '__proto__')?.value).toEqual({ - value: 'preserved', - }); -}); - -// This example hands runtime results to handlers typed by the 1.x MCP SDK -// (src/mcp/handlers.ts), while the runtime itself types against -// @modelcontextprotocol/server 2.x. The annotations are the test: the file -// stops typechecking if a lowered result ever stops being a 1.x -// CallToolResult, or if attachMcpStructuredContent narrows a 1.x result on -// the way back out. -test('lowered results stay assignable to the 1.x SDK CallToolResult, through attachMcpStructuredContent', () => { - const lowered: CallToolResult = lowerMcpResult( - - ready - , - ); - const attached: CallToolResult = attachMcpStructuredContent(lowered, { stateVersion: 3 }); - - expect(attached).toEqual({ - _meta: { progressToken: 'p-1' }, - content: [{ text: 'ready', type: 'text' }], - structuredContent: { stateVersion: 3 }, - }); - expect(attachMcpStructuredContent(lowered, ['not', 'an', 'object'])).toBe(lowered); -}); diff --git a/examples/skills-starter/agent-bundle.config.ts b/examples/skills-starter/agent-bundle.config.ts index 621f59888..20ea28ca8 100644 --- a/examples/skills-starter/agent-bundle.config.ts +++ b/examples/skills-starter/agent-bundle.config.ts @@ -4,7 +4,6 @@ export default defineConfig({ plugin: { description: 'A practical engineering operations bundle for incidents, dependency upgrades, and releases.', name: 'skills-starter', - version: '1.0.0', }, targets: ['portable', 'codex', 'cursor'], }); diff --git a/examples/skills-starter/package.json b/examples/skills-starter/package.json index 39f06292e..2e25b3396 100644 --- a/examples/skills-starter/package.json +++ b/examples/skills-starter/package.json @@ -1,5 +1,6 @@ { "name": "@agent-bundle-example/skills-starter", + "version": "1.0.0", "private": true, "license": "Apache-2.0", "type": "module", diff --git a/examples/worktree-proximity/agent-bundle.config.ts b/examples/worktree-proximity/agent-bundle.config.ts index 757e82625..e4174e03a 100644 --- a/examples/worktree-proximity/agent-bundle.config.ts +++ b/examples/worktree-proximity/agent-bundle.config.ts @@ -4,7 +4,6 @@ export default defineConfig({ plugin: { description: 'Coordinate related agents across linked worktrees without a daemon.', name: 'worktree-proximity', - version: '1.0.0', }, runtime: { node: '22.19.0' }, targets: ['claude', 'codex'], diff --git a/packages/create-agent-bundle/templates/cli-tool/package_json b/packages/create-agent-bundle/templates/cli-tool/package_json index ddd436aa8..d113cb6d3 100644 --- a/packages/create-agent-bundle/templates/cli-tool/package_json +++ b/packages/create-agent-bundle/templates/cli-tool/package_json @@ -25,12 +25,12 @@ }, "devDependencies": { "@rstest/core": "0.11.12", - "@types/node": "26.4.0", + "@types/node": "26.5.1", "agent-bundle": "workspace:*", "typescript": "7.0.2", "@agent-bundle/runtime": "workspace:*", - "react": "19.2.8", - "react-dom": "19.2.8", + "react": "19.3.0", + "react-dom": "19.3.0", "zod": "4.5.4" } } diff --git a/packages/create-agent-bundle/templates/mcp-server/package_json b/packages/create-agent-bundle/templates/mcp-server/package_json index 241d68808..b19a269d3 100644 --- a/packages/create-agent-bundle/templates/mcp-server/package_json +++ b/packages/create-agent-bundle/templates/mcp-server/package_json @@ -27,13 +27,13 @@ }, "devDependencies": { "@rstest/core": "0.11.12", - "@types/node": "26.4.0", + "@types/node": "26.5.1", "agent-bundle": "workspace:*", "typescript": "7.0.2", - "@types/react": "19.2.18", + "@types/react": "19.3.0", "@agent-bundle/runtime": "workspace:*", - "react": "19.2.8", - "react-dom": "19.2.8", + "react": "19.3.0", + "react-dom": "19.3.0", "zod": "4.5.4" } } diff --git a/packages/create-agent-bundle/templates/minimal/package_json b/packages/create-agent-bundle/templates/minimal/package_json index 0f3e87d17..b5603bb8d 100644 --- a/packages/create-agent-bundle/templates/minimal/package_json +++ b/packages/create-agent-bundle/templates/minimal/package_json @@ -17,7 +17,7 @@ }, "devDependencies": { "@rstest/core": "0.11.12", - "@types/node": "26.4.0", + "@types/node": "26.5.1", "agent-bundle": "workspace:*", "typescript": "7.0.2" } diff --git a/packages/create-agent-bundle/tests/scaffold.test.ts b/packages/create-agent-bundle/tests/scaffold.test.ts index 89ea28f3d..5c7989e47 100644 --- a/packages/create-agent-bundle/tests/scaffold.test.ts +++ b/packages/create-agent-bundle/tests/scaffold.test.ts @@ -266,13 +266,13 @@ layer(NodeServices.layer, { excludeTestServices: true })('scaffold (real filesys expect(cliManifest.dependencies).toBeUndefined(); expect(cliManifest.devDependencies['@agent-bundle/runtime']) .toBe(runtimeSpecForFramework(cliTool.frameworkSpec, testPairing)); - expect(cliManifest.devDependencies['react-dom']).toBe('19.2.8'); - expect(cliManifest.devDependencies['zod']).toBeDefined(); + expect(cliManifest.devDependencies['react-dom']).toBe('19.3.0'); + expect(cliManifest.devDependencies['zod']).toBe('4.5.4'); const mcpManifest = yield* readJson<{ readonly devDependencies: Record; }>(path.join(mcpServer.root, 'package.json')); - expect(mcpManifest.devDependencies['react-dom']).toBe('19.2.8'); + expect(mcpManifest.devDependencies['react-dom']).toBe('19.3.0'); })); it.effect('replaces every placeholder and pins the framework spec', () => Effect.gen(function* () { From cc5001d8805ce200a422f37d14865992b6d89056 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 16 Sep 2026 08:36:35 +0000 Subject: [PATCH 2/4] docs(changeset): link consumer migration PR --- .changeset/refresh-scaffold-consumers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/refresh-scaffold-consumers.md b/.changeset/refresh-scaffold-consumers.md index 78b6e0fd0..35e9ba96c 100644 --- a/.changeset/refresh-scaffold-consumers.md +++ b/.changeset/refresh-scaffold-consumers.md @@ -2,4 +2,4 @@ "create-agent-bundle": patch --- -Update the `minimal`, `mcp-server`, and `cli-tool` templates to `@types/node` 26.5.1, and update the routed `mcp-server` and `cli-tool` templates to React 19.3.0 while preserving the `zod` 4.5.4 runtime peer floor. (#PR) +Update the `minimal`, `mcp-server`, and `cli-tool` templates to `@types/node` 26.5.1, and update the routed `mcp-server` and `cli-tool` templates to React 19.3.0 while preserving the `zod` 4.5.4 runtime peer floor. (#808) From 48c4e75d1d44b30c46f1ab016c05076d5d31b8f2 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 16 Sep 2026 09:03:40 +0000 Subject: [PATCH 3/4] test(rsc-example): preserve render size limits --- .changeset/refresh-scaffold-consumers.md | 2 +- examples/rsc-agent-runtime/README.md | 4 +-- .../src/flight/request-render.ts | 5 +++ .../tests/dev-invocation.integration.test.ts | 32 +++++++++++++++++++ 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/.changeset/refresh-scaffold-consumers.md b/.changeset/refresh-scaffold-consumers.md index 35e9ba96c..6f3d6260f 100644 --- a/.changeset/refresh-scaffold-consumers.md +++ b/.changeset/refresh-scaffold-consumers.md @@ -2,4 +2,4 @@ "create-agent-bundle": patch --- -Update the `minimal`, `mcp-server`, and `cli-tool` templates to `@types/node` 26.5.1, and update the routed `mcp-server` and `cli-tool` templates to React 19.3.0 while preserving the `zod` 4.5.4 runtime peer floor. (#808) +Update the `minimal`, `mcp-server`, and `cli-tool` templates to `@types/node` 26.5.1, update the routed templates to React 19.3.0, and move the `mcp-server` template to `@types/react` 19.3.0 while preserving the `zod` 4.5.4 runtime peer floor. (#808) diff --git a/examples/rsc-agent-runtime/README.md b/examples/rsc-agent-runtime/README.md index c010377b1..d1ec1fec7 100644 --- a/examples/rsc-agent-runtime/README.md +++ b/examples/rsc-agent-runtime/README.md @@ -35,7 +35,7 @@ export default async function AfterFileEdit({ canonical }: AgentEventRouteProps< // An Agent Document route describes protocol-neutral output, not browser HTML. import { Agent } from '@agent-bundle/runtime'; -export function RenderTimeline({ snapshot }: { snapshot: { edits: unknown[]; stateVersion: number } }) { +export function RenderTimeline({ snapshot }: { snapshot: { edits: Array>; stateVersion: number } }) { return ( {`Showing ${snapshot.edits.length} edits.`} @@ -183,7 +183,7 @@ through the compiled `src/events/tool/after.tsx` route (Agent Document projectio state-kernel commit), replays the same native tool id from a second hook process to prove cross-process idempotency, then connects a real stdio MCP client to the built server over the same state file and asserts the -RSC-lowered `render_edit_timeline` result, the shared edit snapshot, and the +Agent Document `render_edit_timeline` result, the shared edit snapshot, and the linked MCP App resource. It contacts no real host and needs no credentials. To exercise the Claude package manually when an external host run is separately diff --git a/examples/rsc-agent-runtime/src/flight/request-render.ts b/examples/rsc-agent-runtime/src/flight/request-render.ts index 058f50182..1f919fa88 100644 --- a/examples/rsc-agent-runtime/src/flight/request-render.ts +++ b/examples/rsc-agent-runtime/src/flight/request-render.ts @@ -236,6 +236,11 @@ export const requestAgentDocumentWithFlight = async ( rendered = await requestFlightRenderWithFlight(request, { ...options, signal: dispatch.signal }); return Readable.toWeb(Readable.from([rendered.flight])) as ReadableStream; }, + }, { + limits: { + maxDocumentBytes: maximumFlightRenderBytes, + maxEventBytes: maximumFlightRenderBytes + 1_024, + }, }); const document = await dispatcher.dispatch({ invocation: renderInvocationFor(request), signal }); if (rendered === undefined) throw new Error('Flight execution host returned no render result'); diff --git a/examples/rsc-agent-runtime/tests/dev-invocation.integration.test.ts b/examples/rsc-agent-runtime/tests/dev-invocation.integration.test.ts index a0621036f..ac3d24361 100644 --- a/examples/rsc-agent-runtime/tests/dev-invocation.integration.test.ts +++ b/examples/rsc-agent-runtime/tests/dev-invocation.integration.test.ts @@ -177,6 +177,18 @@ const event = (eventId: string) => ({ toolName: 'Write', }); +const oversizedAgentDocumentWorker = (payloadBytes: number): string => { + return `const { writeSync } = require('node:fs'); +const payload = 'x'.repeat(${payloadBytes}); +const model = ['$', 'agent-result', null, { + value: { payload, stateVersion: 0 }, + children: [['$', 'agent-text', null, { children: 'ok' }]], +}]; +writeSync(3, Buffer.from('{"stateVersion":0}')); +process.stdout.end(\`0:\${JSON.stringify(model)}\\n\`); +`; +}; + const inspectionShape = (result: { inspection: Record }) => { const { flight: _flight, ...inspection } = result.inspection; return inspection; @@ -535,6 +547,26 @@ test('redacts bounded RSC worker stderr diagnostics', async () => { } }); +test('caps inspection stdout independently after Flight leaves its response envelope', async () => { + const compilerRoot = await mkdtemp(join(tmpdir(), 'rsc-agent-runtime-invoke-')); + try { + const entry = await buildInvocationEntry(compilerRoot); + await writeFile(join(compilerRoot, 'rsc', 'rsc', 'index.js'), oversizedAgentDocumentWorker(2_100_000)); + const result = await invoke(entry, { + stateFile: join(compilerRoot, 'events.jsonl'), + stateStoreId: 'fixture-state', + type: 'mcp/runtime-status', + }); + + expect(result.exitCode).not.toBe(0); + expect(result.stdout).toEqual(Buffer.alloc(0)); + expect(result.stderr).toContain('Inspection response exceeded output limit'); + expect(result.stderr).not.toContain('x'.repeat(128)); + } finally { + await rm(compilerRoot, { force: true, recursive: true }); + } +}); + test('bounds Flight output and waits for a SIGKILL cleanup when the RSC child ignores SIGTERM', async () => { const compilerRoot = await mkdtemp(join(tmpdir(), 'rsc-agent-runtime-invoke-')); let childPid: number | undefined; From e87ac03637b3512f79f52d666fd7821499e586d5 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 16 Sep 2026 09:17:47 +0000 Subject: [PATCH 4/4] test(agent-bundle): follow skills-starter release identity into package.json The example now declares its version in package.json, so the synthetic packed consumers need a semantic version to clear AB4013 and the examples contract asserts the resolved 1.0.0 label instead of the dev fallback. --- packages/agent-bundle/tests/examples-contract.test.ts | 8 ++++---- packages/agent-bundle/tests/packed-small-plugin.test.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/agent-bundle/tests/examples-contract.test.ts b/packages/agent-bundle/tests/examples-contract.test.ts index 3a9d2d171..273a26da7 100644 --- a/packages/agent-bundle/tests/examples-contract.test.ts +++ b/packages/agent-bundle/tests/examples-contract.test.ts @@ -31,11 +31,11 @@ it('builds the Skills Starter through public Agent Bundle APIs', async () => { }); expect(inspection.diagnostics).toEqual([]); if (inspection.state !== 'ready') throw new Error('unreachable'); - // Identity stages 1-2 (#94): no package.json version, so the release - // axis is absent and displays fall back to the labeled dev form. + // Release identity comes from package.json alone; the example declares no + // plugin.version, so the display label is the semantic version itself. expect(inspection.projectContext.packageName).toBe('@agent-bundle-example/skills-starter'); - expect(inspection.projectContext.packageVersion).toBeUndefined(); - expect(projectVersionLabel(inspection.projectContext)).toContain('development fallback'); + expect(inspection.projectContext.packageVersion).toBe('1.0.0'); + expect(projectVersionLabel(inspection.projectContext)).toBe('1.0.0'); const built = await build({ output, root }); await expect(validate({ artifact: output, root })).resolves.toEqual({ diagnostics: [] }); expect(built.build.manifest.executables).toEqual({ diff --git a/packages/agent-bundle/tests/packed-small-plugin.test.ts b/packages/agent-bundle/tests/packed-small-plugin.test.ts index 2e25872f3..71e7da0cd 100644 --- a/packages/agent-bundle/tests/packed-small-plugin.test.ts +++ b/packages/agent-bundle/tests/packed-small-plugin.test.ts @@ -126,7 +126,7 @@ it('keeps packed static and plain-hook plugins free of undeclared runtimes', asy }), ]); await Promise.all([staticRoot, hookRoot].map((root) => - writeFile(join(root, 'package.json'), JSON.stringify({ private: true, type: 'module' })))); + writeFile(join(root, 'package.json'), JSON.stringify({ private: true, type: 'module', version: '1.0.0' })))); await mkdir(join(hookRoot, 'src', 'hooks'), { recursive: true }); await Promise.all([ cp(