From 324819dccdf18ca9a3682248031422a1d3f6cf1a Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 3 Sep 2026 05:30:15 +0000 Subject: [PATCH] feat(routes): generate typed provider declarations and augment the runtime (#95) AgentProviderValues becomes an augmentable interface (string index of unknown plus the optional framework-owned processLifetime), and the generated .agent-bundle/routes.d.ts declares AgentBundleProviders, ProviderKey, and ProviderValue from each conventional provider factory's awaited return type in execution order, augmenting @agent-bundle/runtime so (await agent()).providers. is typed without a compiler change per provider. Examples adopt the public AgentProviderContext type; framework-mode documents request context and providers. --- .changeset/provider-typegen.md | 14 ++ docs/diagnostics.md | 10 +- docs/framework-mode.md | 46 +++++++ .../src/providers/library.ts | 9 +- .../src/providers/git-worktree.ts | 19 ++- packages/agent-bundle/src/routes/typegen.ts | 59 +++++++- .../tests/provider-typegen.test.ts | 129 ++++++++++++++++++ .../agent-bundle/tests/route-graph.test.ts | 53 +++++++ packages/rsc-runtime/src/agent-request.ts | 25 +++- packages/rsc-runtime/src/plugin.ts | 1 + rstest.integration-tests.ts | 1 + 11 files changed, 341 insertions(+), 25 deletions(-) create mode 100644 .changeset/provider-typegen.md create mode 100644 packages/agent-bundle/tests/provider-typegen.test.ts diff --git a/.changeset/provider-typegen.md b/.changeset/provider-typegen.md new file mode 100644 index 000000000..3c326d0d1 --- /dev/null +++ b/.changeset/provider-typegen.md @@ -0,0 +1,14 @@ +--- +"@agent-bundle/runtime": minor +"agent-bundle": minor +--- + +Type project-defined context providers without a compiler change per +provider. `AgentProviderValues` is now an augmentable interface (string index +of `unknown` plus the optional framework-owned `processLifetime`, exported as +`AgentProcessLifetime`), and the generated `.agent-bundle/routes.d.ts` +declares `AgentBundleProviders` / `ProviderKey` / `ProviderValue` from +each conventional `src/providers/*` factory's awaited return type and augments +`@agent-bundle/runtime` so `(await agent()).providers.` observes that +type. Provider-free graphs emit no augmentation; a graph with providers but no +executable routes keeps the declaration file. diff --git a/docs/diagnostics.md b/docs/diagnostics.md index de389f3bc..05d8f90d0 100644 --- a/docs/diagnostics.md +++ b/docs/diagnostics.md @@ -278,7 +278,15 @@ compiles silently with an empty config. Generated route declarations are published at `.agent-bundle/routes.d.ts` from the same graph. Development writes a sibling temporary file and renames it over the prior complete declaration atomically; invalid source retains the prior -last-good file, while a successful route-free preparation removes it. +last-good file, while a successful route-free, provider-free preparation +removes it. Beside `AgentBundleRoutes`, a graph with conventional providers +declares `AgentBundleProviders` (`ProviderKey`, `ProviderValue`) — each +camel-cased key mapped to its factory's awaited return type, in execution +order — and augments `@agent-bundle/runtime`'s `AgentProviderValues` so +`(await agent()).providers.` observes that type in projects whose +TypeScript program includes the file. Provider-free graphs emit no +augmentation, so the declaration never references a module the project has no +reason to depend on. Conventional `src/scripts/` routes ship through the same pipeline as explicit `scripts` entries (#102 stage 1): a plain module directly under diff --git a/docs/framework-mode.md b/docs/framework-mode.md index d48f21b8d..6f758d3d3 100644 --- a/docs/framework-mode.md +++ b/docs/framework-mode.md @@ -60,6 +60,52 @@ Flight dispatcher and lowers the final Agent Document to legal MCP output. Flight is an implementation transport inside the generated runtime, never a public host wire protocol. +## Request context and providers + +Every generated request scope — MCP tools, resources, and prompts, event +routes, plain and rendered routed CLI commands, rendered scripts, and Workbench +replay — installs the same typed `AgentRequestContext`. `await agent()` +returns the invocation plus `Observed` `host`, `session`, `actor`, and +`workspace` axes (an `available` value with its provenance, or a typed +`unavailable` reason — never a fabricated string), request capabilities, +progress, the request signal, and the `state`, `notices`, and `providers` +slots. The handle is request-scoped: it survives `await`, two concurrent +requests never observe each other, and reading a captured handle after the +request closes throws a typed `AgentRequestError`. + +A **context provider** contributes one request-scoped value without touching +the compiler. Each `src/providers/.{ts,tsx}` module default-exports a +factory receiving the public `AgentProviderContext` (`{ invocation, signal }` +from `agent-bundle`) and its value mounts at +`(await agent()).providers.`: + +```ts +// src/providers/library.ts +import type { AgentProviderContext } from 'agent-bundle'; + +export interface LibraryContext { readonly stages: readonly string[]; readonly surface: string } + +export default async function library({ invocation }: AgentProviderContext): Promise { + return { stages: ['discover', 'curate'], surface: invocation.kind }; +} +``` + +Providers run once per request in deterministic key order before the request +scope opens; a thrown factory fails that request closed, so return an honest +unavailable-shaped value for expected degradation. The compiler validates the +default export (`AB4940`), unique keys (`AB4941`), and the reserved +framework-owned `processLifetime` key (`AB4942`). + +The generated `.agent-bundle/routes.d.ts` declares `AgentBundleProviders` +(`ProviderKey`, `ProviderValue`) from each factory's resolved return type +and augments `@agent-bundle/runtime`'s `AgentProviderValues`, so +`(await agent()).providers.library` is a `LibraryContext` with no cast once +the file is part of the project's TypeScript program (add +`".agent-bundle/routes.d.ts"` to `tsconfig.json` `include`). Undeclared keys +stay `unknown`. Route-unit and CLI-dispatch tests inject fixture values through +`renderRoute(id, { context: { providers: { library } } })`; the harness never +executes provider modules on a test's behalf. + Everything else is power-tier reference: custom/remote server modes and collision recovery are in [Entry conventions](entry-conventions.md); accepted static metadata, generated `.agent-bundle/routes.d.ts`, and diagnostics are in diff --git a/examples/audiobook-curator/src/providers/library.ts b/examples/audiobook-curator/src/providers/library.ts index ce78bfa9b..7d1c3e943 100644 --- a/examples/audiobook-curator/src/providers/library.ts +++ b/examples/audiobook-curator/src/providers/library.ts @@ -1,6 +1,8 @@ import { execFile } from 'node:child_process'; import { promisify } from 'node:util'; +import type { AgentProviderContext } from 'agent-bundle'; + export interface LibraryContext { readonly tooling: { readonly ffmpeg: { @@ -16,11 +18,6 @@ export interface LibraryContext { readonly probedAt: string; } -interface ProviderContext { - readonly invocation: unknown; - readonly signal: AbortSignal; -} - interface ToolProbe { readonly available: boolean; readonly version?: string; @@ -41,7 +38,7 @@ const probeTool = async (tool: 'ffmpeg' | 'ffprobe', signal: AbortSignal): Promi }; export default async function libraryProvider( - { signal }: ProviderContext, + { signal }: AgentProviderContext, ): Promise { const [ffmpeg, ffprobe] = await Promise.all([ probeTool('ffmpeg', signal), diff --git a/examples/worktree-proximity/src/providers/git-worktree.ts b/examples/worktree-proximity/src/providers/git-worktree.ts index ff1afd90b..139f17fc3 100644 --- a/examples/worktree-proximity/src/providers/git-worktree.ts +++ b/examples/worktree-proximity/src/providers/git-worktree.ts @@ -2,22 +2,19 @@ import { execFile } from 'node:child_process'; import { resolve } from 'node:path'; import { promisify } from 'node:util'; -import type { WorktreeProviderValue } from '../api.js'; +import type { AgentProviderContext } from 'agent-bundle'; -interface ProviderContext { - readonly invocation: { - readonly kind: string; - readonly props: Readonly>; - }; - readonly signal: AbortSignal; -} +import type { WorktreeProviderValue } from '../api.js'; const execFileAsync = promisify(execFile); -const eventCwd = (context: ProviderContext): string | undefined => { +// Providers receive the surface-specific invocation, so an event-route +// request can anchor discovery at the host-reported cwd while tool, CLI, and +// script requests fall back to the process cwd. +const eventCwd = (context: AgentProviderContext): string | undefined => { if (context.invocation.kind !== 'event') return undefined; const payload = context.invocation.props.payload; - if (payload === null || typeof payload !== 'object') return undefined; + if (payload === null || typeof payload !== 'object' || Array.isArray(payload)) return undefined; const native = (payload as { readonly native?: unknown }).native; if (native === null || typeof native !== 'object') return undefined; const cwd = (native as { readonly cwd?: unknown }).cwd; @@ -28,7 +25,7 @@ const absoluteGitPath = (cwd: string, value: string): string => resolve(cwd, value); export default async function gitWorktreeProvider( - context: ProviderContext, + context: AgentProviderContext, ): Promise { const nativeCwd = eventCwd(context); const cwd = nativeCwd ?? process.cwd(); diff --git a/packages/agent-bundle/src/routes/typegen.ts b/packages/agent-bundle/src/routes/typegen.ts index c85b15519..0e5d08db8 100644 --- a/packages/agent-bundle/src/routes/typegen.ts +++ b/packages/agent-bundle/src/routes/typegen.ts @@ -2,7 +2,8 @@ import { randomUUID } from 'node:crypto'; import { mkdir, rename, rm, writeFile } from 'node:fs/promises'; import { dirname, extname, join, relative } from 'node:path'; -import type { CompiledAgentRoute, CompiledRouteGraph } from './types.ts'; +import { providerKeyFromName } from './providers.ts'; +import type { CompiledAgentRoute, CompiledProvider, CompiledRouteGraph } from './types.ts'; export const routeTypesRelativePath = '.agent-bundle/routes.d.ts'; @@ -15,19 +16,64 @@ const executableRoutes = (graph: CompiledRouteGraph): readonly CompiledAgentRout .sort((left, right) => left.id.localeCompare(right.id)), ); -const declarationImport = (route: CompiledAgentRoute, index: number): string => { - const relativePath = route.provenance.relativePath; +/** Providers in the exact order the generated request scopes execute them. */ +const orderedProviders = (graph: CompiledRouteGraph): readonly CompiledProvider[] => Object.freeze( + [...graph.providers].sort((left, right) => { + const byKey = providerKeyFromName(left.name).localeCompare(providerKeyFromName(right.name)); + return byKey === 0 ? left.source.localeCompare(right.source) : byKey; + }), +); + +const declarationModulePath = (relativePath: string): string => { const extension = extname(relativePath); - const modulePath = `../${relativePath.slice(0, -extension.length)}.js`; - return `import type * as route${String(index)} from ${JSON.stringify(modulePath)};`; + return `../${relativePath.slice(0, -extension.length)}.js`; }; +const declarationImport = (route: CompiledAgentRoute, index: number): string => + `import type * as route${String(index)} from ${JSON.stringify(declarationModulePath(route.provenance.relativePath))};`; + +const providerImport = (provider: CompiledProvider, index: number): string => + `import type * as provider${String(index)} from ${JSON.stringify(declarationModulePath(provider.provenance.relativePath))};`; + +const providerMember = (provider: CompiledProvider, index: number): string => + ` readonly ${JSON.stringify(providerKeyFromName(provider.name))}: ProviderValueOf;`; + +/** + * The provider half of the generated declarations: `AgentBundleProviders` + * maps each camel-cased key to its factory's awaited return type, and the + * `@agent-bundle/runtime` augmentation makes `(await agent()).providers.` + * observe that type. Omitted for provider-free graphs so the augmentation + * never references a module the project has no reason to depend on. + */ +const providerDeclarations = (providers: readonly CompiledProvider[]): readonly string[] => + providers.length === 0 + ? [] + : [ + 'type ProviderValueOf = Factory extends (...args: never[]) => infer Value ? Awaited : never;', + '', + 'export interface AgentBundleProviders {', + ...providers.map(providerMember), + '}', + '', + 'export type ProviderKey = keyof AgentBundleProviders;', + 'export type ProviderValue = AgentBundleProviders[Key];', + '', + "declare module '@agent-bundle/runtime' {", + ' interface AgentProviderValues {', + ...providers.map(providerMember).map((line) => ` ${line}`), + ' }', + '}', + '', + ]; + /** Deterministic declarations derived from the exact immutable graph used by packaging. */ export const generateRouteTypes = (graph: CompiledRouteGraph): string => { const routes = executableRoutes(graph); + const providers = orderedProviders(graph); return [ '// Generated by agent-bundle. Do not edit.', ...routes.map(declarationImport), + ...providers.map(providerImport), '', 'type SchemaOutput = Schema extends { readonly _output: infer Output } ? Output : never;', 'export type RouteContract = Readonly<{', @@ -60,6 +106,7 @@ export const generateRouteTypes = (graph: CompiledRouteGraph): string => { 'export type RouteInput = ContractInput;', 'export type RouteResult = ContractResult;', '', + ...providerDeclarations(providers), ].join('\n'); }; @@ -69,7 +116,7 @@ export const generateRouteTypes = (graph: CompiledRouteGraph): string => { */ export const writeRouteTypes = async (root: string, graph: CompiledRouteGraph): Promise => { const output = join(root, routeTypesRelativePath); - if (executableRoutes(graph).length === 0) { + if (executableRoutes(graph).length === 0 && graph.providers.length === 0) { await rm(output, { force: true }); return relative(root, output).replaceAll('\\', '/'); } diff --git a/packages/agent-bundle/tests/provider-typegen.test.ts b/packages/agent-bundle/tests/provider-typegen.test.ts new file mode 100644 index 000000000..5ed759784 --- /dev/null +++ b/packages/agent-bundle/tests/provider-typegen.test.ts @@ -0,0 +1,129 @@ +import { mkdir, mkdtemp, readFile, rm, symlink, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { dirname, join } from 'node:path'; + +import { afterEach, expect, it } from '@rstest/core'; +import ts from 'typescript-5'; + +import { inspect } from '../src/api.ts'; + +const roots: string[] = []; + +afterEach(async () => { + await Promise.all(roots.splice(0).map((root) => rm(root, { force: true, recursive: true }))); +}); + +const writeProjectFile = async (root: string, path: string, contents: string): Promise => { + const output = join(root, path); + await mkdir(dirname(output), { recursive: true }); + await writeFile(output, contents); +}; + +const typecheck = (root: string, entry: string): readonly string[] => { + const program = ts.createProgram([join(root, entry), join(root, '.agent-bundle', 'routes.d.ts')], { + exactOptionalPropertyTypes: true, + module: ts.ModuleKind.NodeNext, + moduleResolution: ts.ModuleResolutionKind.NodeNext, + noEmit: true, + skipLibCheck: true, + strict: true, + target: ts.ScriptTarget.ES2022, + }); + return ts.getPreEmitDiagnostics(program) + .map((diagnostic) => ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')); +}; + +/** + * #95 acceptance: a project-defined provider adds a typed context property + * without a compiler change. The compiler publishes `.agent-bundle/routes.d.ts` + * with `AgentBundleProviders` and a `@agent-bundle/runtime` augmentation, so + * `(await agent()).providers.` observes the provider factory's resolved + * return type against the real published runtime declarations. + */ +it('types (await agent()).providers. from the generated provider declarations', { timeout: 60_000 }, async () => { + const root = await mkdtemp(join(tmpdir(), 'agent-bundle-provider-typegen-')); + roots.push(root); + // The audiobook example's installed tree supplies the built @agent-bundle/runtime and zod. + await symlink(join(process.cwd(), 'examples', 'audiobook-curator', 'node_modules'), join(root, 'node_modules'), 'dir'); + await Promise.all([ + writeProjectFile(root, 'package.json', JSON.stringify({ + dependencies: { '@agent-bundle/runtime': 'workspace:*', zod: '4.4.3' }, + name: 'provider-typegen-fixture', + type: 'module', + version: '1.0.0', + })), + writeProjectFile(root, 'agent-bundle.config.ts', [ + "import { defineConfig } from 'agent-bundle/config';", + 'export default defineConfig({', + " plugin: { name: 'provider-typegen-fixture', version: '1.0.0' },", + " targets: ['portable'],", + '});', + '', + ].join('\n')), + writeProjectFile(root, 'src/providers/library.ts', [ + "import type { AgentProviderContext } from 'agent-bundle';", + 'export interface LibraryContext { readonly stages: readonly string[]; readonly surface: string; }', + 'export default async function library({ invocation }: AgentProviderContext): Promise {', + " return { stages: ['discover'], surface: invocation.kind };", + '}', + '', + ].join('\n')), + writeProjectFile(root, 'src/providers/build-number.ts', [ + 'export default function buildNumber(): number {', + ' return 7;', + '}', + '', + ].join('\n')), + writeProjectFile(root, 'src/mcp/curator/tools/status.ts', [ + "import { z } from 'zod';", + 'export const inputSchema = z.object({}).strict();', + "export const resultSchema = z.object({ status: z.literal('ready') }).strict();", + "export default async function Status() { return { status: 'ready' as const }; }", + '', + ].join('\n')), + writeProjectFile(root, 'assertions.ts', [ + "import { agent } from '@agent-bundle/runtime';", + "import type { ProviderKey, ProviderValue } from './.agent-bundle/routes.js';", + "import type { LibraryContext } from './src/providers/library.js';", + '', + 'type Equal =', + ' (() => Value extends Left ? 1 : 2) extends', + ' (() => Value extends Right ? 1 : 2) ? true : false;', + 'type Assert = Value;', + '', + "export type Keys = Assert>;", + "export type Library = Assert, LibraryContext>>;", + "export type Sync = Assert, number>>;", + '', + 'export const stages = async (): Promise => {', + ' const context = await agent();', + ' // Augmented: no cast, no runtime guard needed for declared providers.', + ' const library: LibraryContext = context.providers.library;', + ' const build: number = context.providers.buildNumber;', + ' const lifetime: number | undefined = context.providers.processLifetime?.hits;', + ' // Undeclared keys stay unknown.', + ' const unknownValue: unknown = context.providers.somethingElse;', + ' void build; void lifetime; void unknownValue;', + ' return library.stages;', + '};', + '', + ].join('\n')), + writeProjectFile(root, 'mismatch.ts', [ + "import { agent } from '@agent-bundle/runtime';", + 'export const wrong = async (): Promise => (await agent()).providers.library;', + '', + ].join('\n')), + ]); + + const result = await inspect({ root }); + expect(result.state).toBe('ready'); + const declarations = await readFile(join(root, '.agent-bundle', 'routes.d.ts'), 'utf8'); + expect(declarations).toContain('readonly "buildNumber": ProviderValueOf;'); + expect(declarations).toContain('readonly "library": ProviderValueOf;'); + expect(declarations).toContain("declare module '@agent-bundle/runtime'"); + + expect(typecheck(root, 'assertions.ts')).toEqual([]); + const mismatch = typecheck(root, 'mismatch.ts'); + expect(mismatch).toHaveLength(1); + expect(mismatch[0]).toContain("Type 'LibraryContext' is not assignable to type 'number'"); +}); diff --git a/packages/agent-bundle/tests/route-graph.test.ts b/packages/agent-bundle/tests/route-graph.test.ts index f18bd24ce..176e083b5 100644 --- a/packages/agent-bundle/tests/route-graph.test.ts +++ b/packages/agent-bundle/tests/route-graph.test.ts @@ -624,6 +624,59 @@ it('generates deterministic route-specific types from the compiled graph', () => expect(first).toContain('type ContractResult ='); expect(first).toContain('export type RouteInput = ContractInput;'); expect(first).toContain('export type RouteResult = ContractResult;'); + // A provider-free graph declares no provider surface and never augments the runtime. + expect(first).not.toContain('AgentBundleProviders'); + expect(first).not.toContain("declare module '@agent-bundle/runtime'"); +}); + +it('generates provider declarations and the runtime augmentation in execution order', () => { + const graph: CompiledRouteGraph = { + diagnostics: [], + digest: 'provider-typegen-digest', + events: [], + providers: [ + { + id: 'provider:zeta', + name: 'zeta', + provenance: { kind: 'conventional', relativePath: 'src/providers/zeta.ts' }, + source: '/workspace/project/src/providers/zeta.ts', + }, + { + id: 'provider:project-auth', + name: 'project-auth', + provenance: { kind: 'conventional', relativePath: 'src/providers/project-auth.tsx' }, + source: '/workspace/project/src/providers/project-auth.tsx', + }, + ], + scripts: [], + servers: [{ + id: 'mcp:curator', + mode: 'generated', + name: 'curator', + routes: [{ + config: emptyRouteConfig, + id: 'tool:curator/inspect', + kind: 'tool', + provenance: { kind: 'conventional', relativePath: 'src/mcp/curator/tools/inspect.tsx' }, + serverId: 'mcp:curator', + source: '/workspace/project/src/mcp/curator/tools/inspect.tsx', + }], + }], + }; + + const first = routesModule.generateRouteTypes(graph); + expect(routesModule.generateRouteTypes(structuredClone(graph))).toBe(first); + // Providers import in camel-cased key order — the order generated scopes execute them. + expect(first).toContain('import type * as provider0 from "../src/providers/project-auth.js";'); + expect(first).toContain('import type * as provider1 from "../src/providers/zeta.js";'); + expect(first).toContain('type ProviderValueOf = Factory extends (...args: never[]) => infer Value ? Awaited : never;'); + expect(first).toContain('export interface AgentBundleProviders {'); + expect(first).toContain(' readonly "projectAuth": ProviderValueOf;'); + expect(first).toContain(' readonly "zeta": ProviderValueOf;'); + expect(first).toContain('export type ProviderKey = keyof AgentBundleProviders;'); + expect(first).toContain('export type ProviderValue = AgentBundleProviders[Key];'); + expect(first).toContain("declare module '@agent-bundle/runtime' {\n interface AgentProviderValues {\n readonly \"projectAuth\": ProviderValueOf;\n readonly \"zeta\": ProviderValueOf;\n }\n}"); + expect(first.indexOf('AgentBundleRoutes')).toBeLessThan(first.indexOf('AgentBundleProviders')); }); it('resolves generated helper types for schema and event route contracts', async () => { diff --git a/packages/rsc-runtime/src/agent-request.ts b/packages/rsc-runtime/src/agent-request.ts index 54f574d4c..ad56460eb 100644 --- a/packages/rsc-runtime/src/agent-request.ts +++ b/packages/rsc-runtime/src/agent-request.ts @@ -109,7 +109,30 @@ export interface AgentProgressReporter { export type AgentServiceRegistry = Readonly>; -export type AgentProviderValues = Readonly>; +/** + * The framework-owned `processLifetime` provider every generated request + * scope installs: one identity per generated process (Flight worker, + * rendered-route worker, or routed-CLI executable) with a per-request hit + * counter. Absent outside generated scopes, so it is typed optional. + */ +export interface AgentProcessLifetime { + readonly hits: number; + readonly instanceId: string; + readonly pid: number; +} + +/** + * Request-scoped provider values keyed by camel-cased provider name. This is + * an augmentable interface: the compiler's generated `.agent-bundle/routes.d.ts` + * declares the project's conventional `src/providers/*` keys with their + * resolved factory return types, so `(await agent()).providers.` is typed + * without a framework change per provider. Keys without a declaration remain + * `unknown`. + */ +export interface AgentProviderValues { + readonly [key: string]: unknown; + readonly processLifetime?: AgentProcessLifetime; +} export interface AgentInvocation { readonly artifactEpoch?: string; diff --git a/packages/rsc-runtime/src/plugin.ts b/packages/rsc-runtime/src/plugin.ts index 0adee97db..6a8cb8e06 100644 --- a/packages/rsc-runtime/src/plugin.ts +++ b/packages/rsc-runtime/src/plugin.ts @@ -18,6 +18,7 @@ export type { AgentInvocationInput, AgentInvocationKind, AgentNetworkAuthority, + AgentProcessLifetime, AgentProgressReporter, AgentProgressUpdate, AgentProjectRootAuthority, diff --git a/rstest.integration-tests.ts b/rstest.integration-tests.ts index 4cc2f6e3d..5eab47715 100644 --- a/rstest.integration-tests.ts +++ b/rstest.integration-tests.ts @@ -51,6 +51,7 @@ export const integrationTestFiles: readonly string[] = [ 'packages/agent-bundle/tests/path-token-resolver.test.ts', 'packages/agent-bundle/tests/plugin-bundle.test.ts', 'packages/agent-bundle/tests/prepack.test.ts', + 'packages/agent-bundle/tests/provider-typegen.test.ts', 'packages/agent-bundle/tests/public-api.test.ts', 'packages/agent-bundle/tests/rsc-runtime-topology-script.test.ts', 'packages/agent-bundle/tests/script-playground-service.test.ts',