From f42c35d2e84d78077f38c07e9cdf9448cbe0eab9 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Sat, 5 Sep 2026 17:21:07 +0000 Subject: [PATCH 1/7] fix(build): isolate re-bundled runtime entries --- packages/agent-bundle/rslib.config.ts | 105 ++++++++++-------- .../runtime-rebundle/mcp-server-runtime.ts | 5 + .../runtime-rebundle/private-sibling.ts | 9 ++ .../tests/packed-stdio-projection.test.ts | 2 +- .../tests/rstest-rslib-adapter.test.ts | 17 ++- scripts/run-packed-tests.mjs | 8 +- 6 files changed, 94 insertions(+), 52 deletions(-) create mode 100644 packages/agent-bundle/tests/fixtures/runtime-rebundle/mcp-server-runtime.ts create mode 100644 packages/agent-bundle/tests/fixtures/runtime-rebundle/private-sibling.ts diff --git a/packages/agent-bundle/rslib.config.ts b/packages/agent-bundle/rslib.config.ts index 8468fba9d..7525e667b 100644 --- a/packages/agent-bundle/rslib.config.ts +++ b/packages/agent-bundle/rslib.config.ts @@ -59,7 +59,7 @@ const appDeclarationEntrypointPlugin: RsbuildPlugin = { const buildCacheDirectory = process.env['AGENT_BUNDLE_RSLIB_CACHE_DIRECTORY']; /** - * The `id` of the single lib entry. To Rslib an id is a name: it labels the + * The `id` of the public lib entry. To Rslib an id is a name: it labels the * Rsbuild environment the entry becomes (`esm` when unset — * `composeRsbuildEnvironments` in @rslib/core), so it shows in build logs, * selects the entry for `rslib build --lib`, and keys the persistent build @@ -69,6 +69,38 @@ const buildCacheDirectory = process.env['AGENT_BUNDLE_RSLIB_CACHE_DIRECTORY']; * entry's fields into the pools' test build. */ export const agentBundleLibId = 'esm-node'; +export const agentBundleRuntimeLibId = 'runtime-node'; + +const publicEntries = { + api: './src/api.ts', + app: './src/app/index.ts', + cli: './src/cli.ts', + config: './src/config/index.ts', + eval: './src/eval/index.ts', + index: './src/index.ts', + 'lifecycle-render-child': './src/dev/playground/lifecycle-render-child.ts', + 'mcp-apps': './src/mcp-apps.ts', + 'route-invocation-child': './src/dev/routes/route-invocation-child.ts', + rstest: './src/rstest/index.ts', + test: './src/test/index.ts', + 'test/browser': './src/test/browser.ts', +}; + +const runtimeEntries = { + 'cli-entry': './src/cli-entry.ts', + 'event-ipc': './src/events/ipc.ts', + 'event-project': './src/events/project.ts', + 'install-entry': './src/install-entry.ts', + 'launch-env': './src/launch-env.ts', + 'mcp-entry': './src/mcp-entry.ts', + 'mcp-server-runtime': process.env['AGENT_BUNDLE_RUNTIME_REBUNDLE_FIXTURE'] === '1' + ? './tests/fixtures/runtime-rebundle/mcp-server-runtime.ts' + : './src/mcp-server-runtime.ts', + meta: './src/meta.ts', + routes: './src/routes/public.ts', + 'terminal-capability': './src/terminal-capability.ts', + 'web-host': './src/web-host.ts', +}; export default defineConfig({ lib: [ @@ -85,15 +117,36 @@ export default defineConfig({ // no packed declaration, reachable or not, may import a devDependency. dts: true, format: 'esm', + output: { + copy: [ + { from: resolve(import.meta.dirname, '../workbench/dist'), to: 'workbench', info: { minimized: true } }, + { from: resolve(import.meta.dirname, 'web-host-dist'), to: 'web-host', info: { minimized: true } }, + ], + }, + plugins: [appDeclarationEntrypointPlugin], + source: { + entry: publicEntries, + }, + syntax: 'es2022', + }, + { + id: agentBundleRuntimeLibId, + bundle: true, + dts: true, + format: 'esm', + source: { + entry: runtimeEntries, + }, + // These files are inputs to a second Rspack compilation when the + // compiler generates an artifact. Keeping their package build in one + // no-split compilation gives every transitive private module a stable, + // self-contained placement without promoting siblings to entries. + splitChunks: false, syntax: 'es2022', }, ], output: { cleanDistPath: true, - copy: [ - { from: resolve(import.meta.dirname, '../workbench/dist'), to: 'workbench', info: { minimized: true } }, - { from: resolve(import.meta.dirname, 'web-host-dist'), to: 'web-host', info: { minimized: true } }, - ], filenameHash: false, legalComments: 'linked', target: 'node', @@ -104,7 +157,6 @@ export default defineConfig({ plugins: [ // Suggestions stay informational; errors and warnings block publishing. pluginPublint({ throwOn: 'warning' }), - appDeclarationEntrypointPlugin, // The bundled TypeScript 5 parser's eager `getNodeSystem()` reads the // CommonJS `__filename`/`__dirname` globals, which the ESM output does // not define and which Rspack's `node-module` rewrite (disabled below) @@ -141,46 +193,5 @@ export default defineConfig({ define: { __AGENT_BUNDLE_VERSION__: JSON.stringify(packageManifest.version), }, - entry: { - api: './src/api.ts', - app: './src/app/index.ts', - cli: './src/cli.ts', - 'cli-entry': './src/cli-entry.ts', - config: './src/config/index.ts', - eval: './src/eval/index.ts', - 'event-ipc': './src/events/ipc.ts', - 'event-project': './src/events/project.ts', - index: './src/index.ts', - 'install-entry': './src/install-entry.ts', - 'launch-env': './src/launch-env.ts', - 'lifecycle-render-child': './src/dev/playground/lifecycle-render-child.ts', - 'mcp-apps': './src/mcp-apps.ts', - 'mcp-entry': './src/mcp-entry.ts', - meta: './src/meta.ts', - // Same reason as `mcp-tasks` below: the runtime's only other private - // sibling. Concatenated into the runtime's chunk it makes that chunk - // host two modules, so rslib synthesizes the runtime's namespace - // object (for `agent-bundle/test`'s dynamic import) through its own - // `__webpack_require__`, and the generated stdio entry fails to start - // with `__webpack_modules__[moduleId] is not a function`. - 'mcp-schema-projection': './src/mcp-schema-projection.ts', - 'mcp-server-runtime': './src/mcp-server-runtime.ts', - // Its own entry so it is emitted as a chunk beside the runtime rather - // than concatenated into it: a generated artifact bundles - // `dist/mcp-server-runtime.js`, and a chunk that also hosts a sibling - // module carries rslib's `__webpack_require__` runtime import, whose - // identifiers shadow the artifact bundler's own runtime. - 'mcp-tasks': './src/mcp-tasks.ts', - // The route authoring surface: types plus the compile-time helpers a - // route module may import at run time without pulling the compiler - // into its generated bundle. - routes: './src/routes/public.ts', - 'route-invocation-child': './src/dev/routes/route-invocation-child.ts', - rstest: './src/rstest/index.ts', - 'terminal-capability': './src/terminal-capability.ts', - test: './src/test/index.ts', - 'test/browser': './src/test/browser.ts', - 'web-host': './src/web-host.ts', - }, }, }); diff --git a/packages/agent-bundle/tests/fixtures/runtime-rebundle/mcp-server-runtime.ts b/packages/agent-bundle/tests/fixtures/runtime-rebundle/mcp-server-runtime.ts new file mode 100644 index 000000000..fee72ed06 --- /dev/null +++ b/packages/agent-bundle/tests/fixtures/runtime-rebundle/mcp-server-runtime.ts @@ -0,0 +1,5 @@ +import { assertPrivateSiblingLoaded } from './private-sibling.ts'; + +assertPrivateSiblingLoaded(); + +export * from '../../../src/mcp-server-runtime.ts'; diff --git a/packages/agent-bundle/tests/fixtures/runtime-rebundle/private-sibling.ts b/packages/agent-bundle/tests/fixtures/runtime-rebundle/private-sibling.ts new file mode 100644 index 000000000..94666355e --- /dev/null +++ b/packages/agent-bundle/tests/fixtures/runtime-rebundle/private-sibling.ts @@ -0,0 +1,9 @@ +const marker = Symbol.for('agent-bundle.runtime-rebundle-fixture'); + +Reflect.set(globalThis, marker, true); + +export const assertPrivateSiblingLoaded = (): void => { + if (Reflect.get(globalThis, marker) !== true) { + throw new Error('Synthetic runtime sibling did not execute.'); + } +}; diff --git a/packages/agent-bundle/tests/packed-stdio-projection.test.ts b/packages/agent-bundle/tests/packed-stdio-projection.test.ts index d5cedbe9b..9fca2260d 100644 --- a/packages/agent-bundle/tests/packed-stdio-projection.test.ts +++ b/packages/agent-bundle/tests/packed-stdio-projection.test.ts @@ -44,7 +44,7 @@ interface McpJson { * (tests/projection/) covers the same route protocol surface at a fraction of * the cost and explicitly does not claim any of this. */ -it('serves compiled routes and durable state across packed process restarts', async () => { +it('serves compiled routes with a private runtime sibling across packed process restarts', async () => { const [agentBundle, runtime, markdownStream] = await Promise.all([ sharedPackedTarball('agent-bundle'), sharedPackedTarball('runtime'), diff --git a/packages/agent-bundle/tests/rstest-rslib-adapter.test.ts b/packages/agent-bundle/tests/rstest-rslib-adapter.test.ts index df27e6e7d..d3ca4a3cd 100644 --- a/packages/agent-bundle/tests/rstest-rslib-adapter.test.ts +++ b/packages/agent-bundle/tests/rstest-rslib-adapter.test.ts @@ -4,7 +4,7 @@ import type { RslibConfig } from '@rslib/core'; import { withRslibConfig } from '@rstest/adapter-rslib'; import { describe, expect, it, type ExtendConfig } from '@rstest/core'; -import agentBundleRslibConfig from '../rslib.config.ts'; +import agentBundleRslibConfig, { agentBundleRuntimeLibId } from '../rslib.config.ts'; import packageManifest from '../package.json' with { type: 'json' }; import { agentBundleRslibAdapterOptions, rstestHygiene, withAgentBundleRslibConfig } from '../../../rstest.rslib.ts'; import { agentBundlePackageRoot } from './helpers/workspace-paths.ts'; @@ -30,9 +30,20 @@ let poolConfig: Promise | undefined; const resolvedPoolConfig = (): Promise => (poolConfig ??= Promise.resolve(withAgentBundleRslibConfig()({}))); describe('rstest.rslib.ts', () => { - it('passes as libId the id of the single lib entry', () => { + it('selects the public lib while the re-bundled runtime has its own profile', () => { expect(agentBundleRslibAdapterOptions.cwd).toBe(agentBundlePackageRoot); - expect((agentBundleRslibConfig.lib ?? []).map((lib) => lib.id)).toEqual([agentBundleRslibAdapterOptions.libId]); + expect((agentBundleRslibConfig.lib ?? []).map((lib) => lib.id)).toEqual([ + agentBundleRslibAdapterOptions.libId, + agentBundleRuntimeLibId, + ]); + expect(agentBundleRslibConfig.lib?.find((lib) => lib.id === agentBundleRuntimeLibId)).toMatchObject({ + source: { + entry: { + 'mcp-server-runtime': './src/mcp-server-runtime.ts', + }, + }, + splitChunks: false, + }); }); it('reads the lib entry through libId only — the adapter falls back to an empty entry without a diagnostic', async () => { diff --git a/scripts/run-packed-tests.mjs b/scripts/run-packed-tests.mjs index ee58b852d..31dffd4a5 100644 --- a/scripts/run-packed-tests.mjs +++ b/scripts/run-packed-tests.mjs @@ -35,7 +35,13 @@ const run = (command, args, extraEnvironment = {}) => new Promise((resolvePromis }); }); -const buildExitCode = await run('pnpm', ['build'], { NODE_ENV: 'production' }); +// The packed package substitutes a wrapper that imports one extra private +// runtime sibling. The packed deleted-source stdio journey must still start +// after that sibling has crossed both package and artifact bundlers. +const buildExitCode = await run('pnpm', ['build'], { + AGENT_BUNDLE_RUNTIME_REBUNDLE_FIXTURE: '1', + NODE_ENV: 'production', +}); if (buildExitCode !== 0) process.exit(buildExitCode); const packDirectory = await mkdtemp(join(tmpdir(), 'agent-bundle-shared-pack-')); From ce66e4386257fe80b48ea0e9113b65f5f857c620 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Sat, 5 Sep 2026 17:24:08 +0000 Subject: [PATCH 2/7] chore: add runtime boundary changeset --- .changeset/runtime-rebundle-boundary.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/runtime-rebundle-boundary.md diff --git a/.changeset/runtime-rebundle-boundary.md b/.changeset/runtime-rebundle-boundary.md new file mode 100644 index 000000000..ceb8884d1 --- /dev/null +++ b/.changeset/runtime-rebundle-boundary.md @@ -0,0 +1,5 @@ +--- +"agent-bundle": patch +--- + +Keep generated stdio entries startable when private runtime modules are added (#636). From 5bcd2b78347ff34cd83270d17e326182a3812484 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Sat, 5 Sep 2026 17:43:51 +0000 Subject: [PATCH 3/7] fix(test): restore release dist after packed fixture --- packages/agent-bundle/rslib.config.ts | 13 ++++++------- .../fixtures/runtime-rebundle/private-sibling.ts | 6 +++--- .../tests/packed-stdio-projection.test.ts | 1 + .../agent-bundle/tests/rstest-rslib-adapter.test.ts | 3 ++- rstest.rslib.ts | 7 +++---- scripts/run-packed-tests.mjs | 6 ++++++ 6 files changed, 21 insertions(+), 15 deletions(-) diff --git a/packages/agent-bundle/rslib.config.ts b/packages/agent-bundle/rslib.config.ts index 7525e667b..848b37b52 100644 --- a/packages/agent-bundle/rslib.config.ts +++ b/packages/agent-bundle/rslib.config.ts @@ -73,7 +73,6 @@ export const agentBundleRuntimeLibId = 'runtime-node'; const publicEntries = { api: './src/api.ts', - app: './src/app/index.ts', cli: './src/cli.ts', config: './src/config/index.ts', eval: './src/eval/index.ts', @@ -87,6 +86,7 @@ const publicEntries = { }; const runtimeEntries = { + app: './src/app/index.ts', 'cli-entry': './src/cli-entry.ts', 'event-ipc': './src/events/ipc.ts', 'event-project': './src/events/project.ts', @@ -132,16 +132,15 @@ export default defineConfig({ { id: agentBundleRuntimeLibId, bundle: true, - dts: true, + dts: false, format: 'esm', source: { entry: runtimeEntries, }, - // These files are inputs to a second Rspack compilation when the - // compiler generates an artifact. Keeping their package build in one - // no-split compilation gives every transitive private module a stable, - // self-contained placement without promoting siblings to entries. - splitChunks: false, + // These entries are inputs to a second Rspack compilation when the + // compiler generates an artifact. Keeping them outside the public + // graph's dynamic runtime import gives their transitive private modules + // a re-bundle-safe placement without promoting siblings to entries. syntax: 'es2022', }, ], diff --git a/packages/agent-bundle/tests/fixtures/runtime-rebundle/private-sibling.ts b/packages/agent-bundle/tests/fixtures/runtime-rebundle/private-sibling.ts index 94666355e..be981ff15 100644 --- a/packages/agent-bundle/tests/fixtures/runtime-rebundle/private-sibling.ts +++ b/packages/agent-bundle/tests/fixtures/runtime-rebundle/private-sibling.ts @@ -1,9 +1,9 @@ -const marker = Symbol.for('agent-bundle.runtime-rebundle-fixture'); +const marker = 'AGENT_BUNDLE_RUNTIME_REBUNDLE_FIXTURE_EXECUTED'; -Reflect.set(globalThis, marker, true); +process.env[marker] = '1'; export const assertPrivateSiblingLoaded = (): void => { - if (Reflect.get(globalThis, marker) !== true) { + if (process.env[marker] !== '1') { throw new Error('Synthetic runtime sibling did not execute.'); } }; diff --git a/packages/agent-bundle/tests/packed-stdio-projection.test.ts b/packages/agent-bundle/tests/packed-stdio-projection.test.ts index 9fca2260d..0b58b9389 100644 --- a/packages/agent-bundle/tests/packed-stdio-projection.test.ts +++ b/packages/agent-bundle/tests/packed-stdio-projection.test.ts @@ -253,6 +253,7 @@ it('serves compiled routes with a private runtime sibling across packed process // fills `HARNESS_FROM_FILE` and `.env.local`'s `HARNESS_LOCAL`, the host's // exported `HARNESS_HOST_WINS` is untouched, and nothing was logged. for (const [name, value] of [ + ['AGENT_BUNDLE_RUNTIME_REBUNDLE_FIXTURE_EXECUTED', '1'], ['HARNESS_FROM_FILE', 's3cr3t-from-file'], ['HARNESS_LOCAL', 'from-local'], ['HARNESS_HOST_WINS', 'from-host'], diff --git a/packages/agent-bundle/tests/rstest-rslib-adapter.test.ts b/packages/agent-bundle/tests/rstest-rslib-adapter.test.ts index d3ca4a3cd..f62d5a535 100644 --- a/packages/agent-bundle/tests/rstest-rslib-adapter.test.ts +++ b/packages/agent-bundle/tests/rstest-rslib-adapter.test.ts @@ -37,12 +37,13 @@ describe('rstest.rslib.ts', () => { agentBundleRuntimeLibId, ]); expect(agentBundleRslibConfig.lib?.find((lib) => lib.id === agentBundleRuntimeLibId)).toMatchObject({ + dts: false, source: { entry: { + app: './src/app/index.ts', 'mcp-server-runtime': './src/mcp-server-runtime.ts', }, }, - splitChunks: false, }); }); diff --git a/rstest.rslib.ts b/rstest.rslib.ts index a7a35fae5..39785e062 100644 --- a/rstest.rslib.ts +++ b/rstest.rslib.ts @@ -62,10 +62,9 @@ export const rstestHygiene = { * The lib entry is found by `libId` (line 53: `lib.find((l) => l.id === * libId) || {}`); without a `libId`, or with one no entry carries, the entry * is silently `{}` and only the top-level fields count. That is why the entry - * has an `id` and these options pass it: the top-level fields happen to carry - * everything the pools need, so the result was right by accident, and a field - * moved into the entry — `output.target`, `source.define` — would have - * vanished from every pool without a diagnostic. Of the entry, only `source`, + * has an `id` and these options pass it: a field moved into the selected + * public entry — `output.target`, `source.define` — would otherwise vanish + * from every pool without a diagnostic. Of the entry, only `source`, * `output`, `tools`, `plugins`, and `resolve` are merged over the top-level * config (lines 54-61); `format` is read once more, directly, as the fallback * for `output.module` (line 105). diff --git a/scripts/run-packed-tests.mjs b/scripts/run-packed-tests.mjs index 31dffd4a5..f9d8161ad 100644 --- a/scripts/run-packed-tests.mjs +++ b/scripts/run-packed-tests.mjs @@ -68,9 +68,15 @@ try { process.exitCode = await run('pnpm', ['exec', 'rstest', '--config', 'rstest.packed.config.ts', ...rstestArguments], { AGENT_BUNDLE_PACKAGE_PREBUILT: '1', ...(releasePool ? { AGENT_BUNDLE_PACKED_RELEASE: '1' } : {}), + AGENT_BUNDLE_RUNTIME_REBUNDLE_FIXTURE: '1', AGENT_BUNDLE_SHARED_PACK_DIR: packDirectory, AGENT_BUNDLE_WORKBENCH_PREBUILT: '1', }); } finally { await rm(packDirectory, { force: true, recursive: true }); + const restoreExitCode = await run('pnpm', ['--filter', 'agent-bundle', 'build'], { + AGENT_BUNDLE_RUNTIME_REBUNDLE_FIXTURE: '', + NODE_ENV: 'production', + }); + if (restoreExitCode !== 0) process.exitCode = restoreExitCode; } From 7477e65ceb73e1f5bc9e0e0664457ce210103926 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Sat, 5 Sep 2026 17:55:53 +0000 Subject: [PATCH 4/7] test: preserve ad hoc packed runs --- packages/agent-bundle/tests/packed-stdio-projection.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/agent-bundle/tests/packed-stdio-projection.test.ts b/packages/agent-bundle/tests/packed-stdio-projection.test.ts index 0b58b9389..94f58f6f2 100644 --- a/packages/agent-bundle/tests/packed-stdio-projection.test.ts +++ b/packages/agent-bundle/tests/packed-stdio-projection.test.ts @@ -253,7 +253,9 @@ it('serves compiled routes with a private runtime sibling across packed process // fills `HARNESS_FROM_FILE` and `.env.local`'s `HARNESS_LOCAL`, the host's // exported `HARNESS_HOST_WINS` is untouched, and nothing was logged. for (const [name, value] of [ - ['AGENT_BUNDLE_RUNTIME_REBUNDLE_FIXTURE_EXECUTED', '1'], + ...(process.env['AGENT_BUNDLE_RUNTIME_REBUNDLE_FIXTURE'] === '1' + ? [['AGENT_BUNDLE_RUNTIME_REBUNDLE_FIXTURE_EXECUTED', '1'] as const] + : []), ['HARNESS_FROM_FILE', 's3cr3t-from-file'], ['HARNESS_LOCAL', 'from-local'], ['HARNESS_HOST_WINS', 'from-host'], From 0cd885fa44c613f2ce2a209c932fe8f181755890 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Sat, 5 Sep 2026 18:10:57 +0000 Subject: [PATCH 5/7] test: isolate runtime rebundle tarball --- .../tests/packed-stdio-projection.test.ts | 2 +- .../agent-bundle/tests/support/shared-pack.ts | 8 ++- scripts/run-packed-tests.mjs | 62 +++++++++++++++---- 3 files changed, 57 insertions(+), 15 deletions(-) diff --git a/packages/agent-bundle/tests/packed-stdio-projection.test.ts b/packages/agent-bundle/tests/packed-stdio-projection.test.ts index 94f58f6f2..9b840aa56 100644 --- a/packages/agent-bundle/tests/packed-stdio-projection.test.ts +++ b/packages/agent-bundle/tests/packed-stdio-projection.test.ts @@ -46,7 +46,7 @@ interface McpJson { */ it('serves compiled routes with a private runtime sibling across packed process restarts', async () => { const [agentBundle, runtime, markdownStream] = await Promise.all([ - sharedPackedTarball('agent-bundle'), + sharedPackedTarball('agent-bundle-runtime-rebundle'), sharedPackedTarball('runtime'), sharedPackedTarball('markdown-stream'), ]); diff --git a/packages/agent-bundle/tests/support/shared-pack.ts b/packages/agent-bundle/tests/support/shared-pack.ts index 372d6a094..8c6e4f2b5 100644 --- a/packages/agent-bundle/tests/support/shared-pack.ts +++ b/packages/agent-bundle/tests/support/shared-pack.ts @@ -23,11 +23,17 @@ export interface SharedPack { readonly tarball: string; } -export type SharedPackPackage = 'agent-bundle' | 'create-agent-bundle' | 'markdown-stream' | 'runtime'; +export type SharedPackPackage = + | 'agent-bundle' + | 'agent-bundle-runtime-rebundle' + | 'create-agent-bundle' + | 'markdown-stream' + | 'runtime'; /** packages/ directory and npm package name for each shared-pack key. */ const sharedPackPackages: Readonly>> = { 'agent-bundle': { directory: 'agent-bundle', npmName: 'agent-bundle' }, + 'agent-bundle-runtime-rebundle': { directory: 'agent-bundle', npmName: 'agent-bundle' }, 'create-agent-bundle': { directory: 'create-agent-bundle', npmName: 'create-agent-bundle' }, // `@agent-bundle/runtime` depends on it by exact version; a consumer that // installs the runtime tarball needs this one alongside until that version diff --git a/scripts/run-packed-tests.mjs b/scripts/run-packed-tests.mjs index f9d8161ad..a9116c8f8 100644 --- a/scripts/run-packed-tests.mjs +++ b/scripts/run-packed-tests.mjs @@ -9,7 +9,7 @@ * to rstest. */ import { execFile as executeFile, spawn } from 'node:child_process'; -import { mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { cp, mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -35,13 +35,7 @@ const run = (command, args, extraEnvironment = {}) => new Promise((resolvePromis }); }); -// The packed package substitutes a wrapper that imports one extra private -// runtime sibling. The packed deleted-source stdio journey must still start -// after that sibling has crossed both package and artifact bundlers. -const buildExitCode = await run('pnpm', ['build'], { - AGENT_BUNDLE_RUNTIME_REBUNDLE_FIXTURE: '1', - NODE_ENV: 'production', -}); +const buildExitCode = await run('pnpm', ['build'], { NODE_ENV: 'production' }); if (buildExitCode !== 0) process.exit(buildExitCode); const packDirectory = await mkdtemp(join(tmpdir(), 'agent-bundle-shared-pack-')); @@ -65,6 +59,53 @@ try { `${JSON.stringify({ packOutput, tarball: join(packDirectory, packOutput.filename) })}\n`, ); })); + // Build the synthetic private sibling into a separate package image. The + // normal dist and shared release tarball above remain the publish candidate. + const fixtureDist = join(packDirectory, 'runtime-rebundle-dist'); + await execFile(join(repositoryRoot, 'node_modules', '.bin', 'rslib'), [ + 'build', + '--config', + join(repositoryRoot, 'packages', 'agent-bundle', 'rslib.config.ts'), + '--dist-path', + fixtureDist, + ], { + cwd: repositoryRoot, + env: { + ...environment, + AGENT_BUNDLE_RUNTIME_REBUNDLE_FIXTURE: '1', + NODE_ENV: 'production', + }, + }); + const fixturePackage = join(packDirectory, 'runtime-rebundle-package'); + await mkdir(fixturePackage); + await Promise.all([ + 'LICENSE', + 'NOTICE', + 'README.md', + 'bin', + 'package.json', + ].map((name) => cp( + join(repositoryRoot, 'packages', 'agent-bundle', name), + join(fixturePackage, name), + { recursive: true }, + ))); + await cp(fixtureDist, join(fixturePackage, 'dist'), { recursive: true }); + const fixturePackDirectory = join(packDirectory, 'runtime-rebundle-pack'); + await mkdir(fixturePackDirectory); + const { stdout: fixturePacked } = await execFile('npm', [ + 'pack', + '--json', + '--pack-destination', + fixturePackDirectory, + ], { + cwd: fixturePackage, + env: { ...environment, NODE_ENV: 'production' }, + }); + const fixturePackOutput = packOutputFromJson(fixturePacked, 'agent-bundle'); + await writeFile( + join(packDirectory, 'agent-bundle-runtime-rebundle.json'), + `${JSON.stringify({ packOutput: fixturePackOutput, tarball: join(fixturePackDirectory, fixturePackOutput.filename) })}\n`, + ); process.exitCode = await run('pnpm', ['exec', 'rstest', '--config', 'rstest.packed.config.ts', ...rstestArguments], { AGENT_BUNDLE_PACKAGE_PREBUILT: '1', ...(releasePool ? { AGENT_BUNDLE_PACKED_RELEASE: '1' } : {}), @@ -74,9 +115,4 @@ try { }); } finally { await rm(packDirectory, { force: true, recursive: true }); - const restoreExitCode = await run('pnpm', ['--filter', 'agent-bundle', 'build'], { - AGENT_BUNDLE_RUNTIME_REBUNDLE_FIXTURE: '', - NODE_ENV: 'production', - }); - if (restoreExitCode !== 0) process.exitCode = restoreExitCode; } From 85b43f669e792f44739e8de3a9d0c31c9fe148a6 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Sat, 5 Sep 2026 18:23:37 +0000 Subject: [PATCH 6/7] test: scope rebundle fixture to stdio proof --- .../tests/packed-stdio-projection.test.ts | 2 +- .../agent-bundle/tests/support/shared-pack.ts | 4 ++++ scripts/run-packed-tests.mjs | 21 +++++++++---------- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/packages/agent-bundle/tests/packed-stdio-projection.test.ts b/packages/agent-bundle/tests/packed-stdio-projection.test.ts index 9b840aa56..108414380 100644 --- a/packages/agent-bundle/tests/packed-stdio-projection.test.ts +++ b/packages/agent-bundle/tests/packed-stdio-projection.test.ts @@ -253,7 +253,7 @@ it('serves compiled routes with a private runtime sibling across packed process // fills `HARNESS_FROM_FILE` and `.env.local`'s `HARNESS_LOCAL`, the host's // exported `HARNESS_HOST_WINS` is untouched, and nothing was logged. for (const [name, value] of [ - ...(process.env['AGENT_BUNDLE_RUNTIME_REBUNDLE_FIXTURE'] === '1' + ...(agentBundle.variant === 'runtime-rebundle' ? [['AGENT_BUNDLE_RUNTIME_REBUNDLE_FIXTURE_EXECUTED', '1'] as const] : []), ['HARNESS_FROM_FILE', 's3cr3t-from-file'], diff --git a/packages/agent-bundle/tests/support/shared-pack.ts b/packages/agent-bundle/tests/support/shared-pack.ts index 8c6e4f2b5..016537919 100644 --- a/packages/agent-bundle/tests/support/shared-pack.ts +++ b/packages/agent-bundle/tests/support/shared-pack.ts @@ -21,6 +21,7 @@ export interface SharedPack { /** The package's own `npm pack --json` entry recorded when the tarball was produced. */ readonly packOutput: SharedPackOutput; readonly tarball: string; + readonly variant?: 'runtime-rebundle'; } export type SharedPackPackage = @@ -106,6 +107,9 @@ const packOnce = async (packageName: SharedPackPackage): Promise => if (sharedDirectory !== undefined && sharedDirectory.length > 0) { return JSON.parse(await readFile(join(sharedDirectory, `${packageName}.json`), 'utf8')) as SharedPack; } + if (packageName === 'agent-bundle-runtime-rebundle') { + throw new Error('The runtime re-bundle fixture is prepared by `pnpm test:packed`; run the packed pool through that script.'); + } // Ad-hoc single-file runs have no run-level tarball, so build once (unless // the caller marked the workspace dist prebuilt) and pack into a // per-process temporary directory that is dropped on exit. The build diff --git a/scripts/run-packed-tests.mjs b/scripts/run-packed-tests.mjs index a9116c8f8..2dc0ccf54 100644 --- a/scripts/run-packed-tests.mjs +++ b/scripts/run-packed-tests.mjs @@ -9,7 +9,7 @@ * to rstest. */ import { execFile as executeFile, spawn } from 'node:child_process'; -import { cp, mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -78,14 +78,10 @@ try { }); const fixturePackage = join(packDirectory, 'runtime-rebundle-package'); await mkdir(fixturePackage); - await Promise.all([ - 'LICENSE', - 'NOTICE', - 'README.md', - 'bin', - 'package.json', - ].map((name) => cp( - join(repositoryRoot, 'packages', 'agent-bundle', name), + const agentBundlePackageRoot = join(repositoryRoot, 'packages', 'agent-bundle'); + const agentBundleManifest = JSON.parse(await readFile(join(agentBundlePackageRoot, 'package.json'), 'utf8')); + await Promise.all(['package.json', ...agentBundleManifest.files.filter((name) => name !== 'dist')].map((name) => cp( + join(agentBundlePackageRoot, name), join(fixturePackage, name), { recursive: true }, ))); @@ -104,12 +100,15 @@ try { const fixturePackOutput = packOutputFromJson(fixturePacked, 'agent-bundle'); await writeFile( join(packDirectory, 'agent-bundle-runtime-rebundle.json'), - `${JSON.stringify({ packOutput: fixturePackOutput, tarball: join(fixturePackDirectory, fixturePackOutput.filename) })}\n`, + `${JSON.stringify({ + packOutput: fixturePackOutput, + tarball: join(fixturePackDirectory, fixturePackOutput.filename), + variant: 'runtime-rebundle', + })}\n`, ); process.exitCode = await run('pnpm', ['exec', 'rstest', '--config', 'rstest.packed.config.ts', ...rstestArguments], { AGENT_BUNDLE_PACKAGE_PREBUILT: '1', ...(releasePool ? { AGENT_BUNDLE_PACKED_RELEASE: '1' } : {}), - AGENT_BUNDLE_RUNTIME_REBUNDLE_FIXTURE: '1', AGENT_BUNDLE_SHARED_PACK_DIR: packDirectory, AGENT_BUNDLE_WORKBENCH_PREBUILT: '1', }); From 7f784aa9c25e8522885cca2dcc6d6450b3b826d0 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Sat, 5 Sep 2026 18:37:16 +0000 Subject: [PATCH 7/7] test: retain release stdio process proof --- .../tests/packed-stdio-projection.test.ts | 12 +++++++++--- scripts/run-packed-tests.mjs | 1 + 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/agent-bundle/tests/packed-stdio-projection.test.ts b/packages/agent-bundle/tests/packed-stdio-projection.test.ts index 108414380..4b044189b 100644 --- a/packages/agent-bundle/tests/packed-stdio-projection.test.ts +++ b/packages/agent-bundle/tests/packed-stdio-projection.test.ts @@ -44,12 +44,18 @@ interface McpJson { * (tests/projection/) covers the same route protocol surface at a fraction of * the cost and explicitly does not claim any of this. */ -it('serves compiled routes with a private runtime sibling across packed process restarts', async () => { +it.each([ + ['release', 'agent-bundle'], + ['private runtime sibling', 'agent-bundle-runtime-rebundle'], +] as const)('serves compiled routes from the %s package across packed process restarts', async (_variant, packageName) => { const [agentBundle, runtime, markdownStream] = await Promise.all([ - sharedPackedTarball('agent-bundle-runtime-rebundle'), + sharedPackedTarball(packageName), sharedPackedTarball('runtime'), sharedPackedTarball('markdown-stream'), ]); + expect(agentBundle.variant).toBe( + packageName === 'agent-bundle-runtime-rebundle' ? 'runtime-rebundle' : undefined, + ); const consumer = await mkdtemp(join(tmpdir(), 'agent-bundle-packed-stdio-')); const project = join(consumer, 'project'); const artifact = join(project, 'artifact'); @@ -253,7 +259,7 @@ it('serves compiled routes with a private runtime sibling across packed process // fills `HARNESS_FROM_FILE` and `.env.local`'s `HARNESS_LOCAL`, the host's // exported `HARNESS_HOST_WINS` is untouched, and nothing was logged. for (const [name, value] of [ - ...(agentBundle.variant === 'runtime-rebundle' + ...(packageName === 'agent-bundle-runtime-rebundle' ? [['AGENT_BUNDLE_RUNTIME_REBUNDLE_FIXTURE_EXECUTED', '1'] as const] : []), ['HARNESS_FROM_FILE', 's3cr3t-from-file'], diff --git a/scripts/run-packed-tests.mjs b/scripts/run-packed-tests.mjs index 2dc0ccf54..a3b27b135 100644 --- a/scripts/run-packed-tests.mjs +++ b/scripts/run-packed-tests.mjs @@ -72,6 +72,7 @@ try { cwd: repositoryRoot, env: { ...environment, + AGENT_BUNDLE_RSLIB_CACHE_DIRECTORY: join(packDirectory, 'runtime-rebundle-cache'), AGENT_BUNDLE_RUNTIME_REBUNDLE_FIXTURE: '1', NODE_ENV: 'production', },