Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/honest-harness-registry-cwd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"agent-bundle": patch
---

Reject stale consumer test registries before projection helpers read fields
they do not contain, and derive CLI dispatch workspace context from the
invocation working directory to match generated executables.
2 changes: 1 addition & 1 deletion packages/agent-bundle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ import { cliJson, expectEvents, invokeCli, invokeMcpTool } from 'agent-bundle/te

// mcp-in-memory: the generated server projects the document to protocol content.
const call = await invokeMcpTool('summarize', { input: { title: 'Dune' } });
expect(call.result.structuredContent).toEqual({ chapters: 24 });
expect(call.structuredContent).toEqual({ chapters: 24 });

// cli-dispatch: the routed CLI resolves the command, parses argv, and maps the exit code.
const run = await invokeCli(['library', 'audit', './books', '--max-files', '8']);
Expand Down
2 changes: 1 addition & 1 deletion packages/agent-bundle/src/test/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ export const invokeCli = async (
} catch (error) {
throw new CliInputError(error instanceof Error ? error.message : String(error));
}
const root = manifest.projectRoot;
const root = process.cwd();
const result = await runtime.runAgentRequest({
capabilities: {
command: runtime.unavailable(),
Expand Down
2 changes: 1 addition & 1 deletion packages/agent-bundle/src/test/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const AGENT_TEST_REGISTRY_SYMBOL_KEY = 'agent-bundle/test-route-registry'

const REGISTRY_SYMBOL = Symbol.for(AGENT_TEST_REGISTRY_SYMBOL_KEY);

export const AGENT_TEST_REGISTRY_VERSION = 1;
export const AGENT_TEST_REGISTRY_VERSION = 2;

export interface AgentTestRouteRegistry {
/** Lazy loaders keyed by compiled route id, so a test only compiles the routes it renders. */
Expand Down
24 changes: 24 additions & 0 deletions packages/agent-bundle/tests/projection/cli-dispatch.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, expect, it } from '@rstest/core';

import { agent } from '@agent-bundle/runtime';
import { cliJson, invokeCli } from '../../src/test/cli.ts';

/**
Expand Down Expand Up @@ -84,4 +85,27 @@ describe('the CLI dispatch level', () => {
expect(run.exitCode).toBe(0);
expect(progress.map((update) => update.message)).toEqual(['reading inventory', 'inventory ready']);
});

it('derives the request workspace from the invocation cwd like the generated binary', async () => {
const invocationCwd = process.cwd();
let observed: { readonly projectRoot: string | null; readonly workspace: string | null } | undefined;
const run = await invokeCli(['inventory', 'fiction'], {
context: {
progress: {
report: async () => {
const context = await agent();
observed = {
projectRoot: context.capabilities.projectRoot.state === 'available'
? context.capabilities.projectRoot.value.root
: null,
workspace: context.workspace.state === 'available' ? context.workspace.value.root : null,
};
},
},
},
});

expect(invocationCwd).not.toBe(run.provenance.projectRoot);
expect(observed).toEqual({ projectRoot: invocationCwd, workspace: invocationCwd });
});
});
23 changes: 23 additions & 0 deletions packages/agent-bundle/tests/test-harness-manifest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { describe, expect, it } from '@rstest/core';

import { routeTestSetupSource } from '../src/rstest/setup-module.ts';
import { AgentTestError } from '../src/test/errors.ts';
import { invokeCli } from '../src/test/cli.ts';
import { compileTestManifest, testManifestFromRouteGraph } from '../src/test/manifest.ts';
import {
AGENT_TEST_REGISTRY_SYMBOL_KEY,
Expand Down Expand Up @@ -144,6 +145,28 @@ describe('the generated route registry', () => {
expect(() => testManifest()).toThrow('Incompatible Agent Bundle test registry version');
});
});

it('refuses a version-1 manifest before a helper reads fields that version did not carry', async () => {
const versionOneManifest = Object.fromEntries(
Object.entries(manifest).filter(([key]) => key !== 'cliCommands' && key !== 'plugin'),
);
const error = await withRealmRegistry(
{ loaders: {}, manifest: versionOneManifest, version: 1 },
async () => invokeCli(['--help']).catch((thrown: unknown) => thrown),
);

expect(error).toBeInstanceOf(AgentTestError);
expect((error as AgentTestError).code).toBe('manifest-unavailable');
expect((error as AgentTestError).message).toContain('found 1');
expect((error as AgentTestError).message).toContain('Install one agent-bundle version');
});

it('accepts a registry carrying the current manifest version', async () => {
await withRealmRegistry(
{ loaders: {}, manifest, version: AGENT_TEST_REGISTRY_VERSION },
() => expect(testManifest()).toBe(manifest),
);
});
});

describe('route loaders and the manifest that produced them', () => {
Expand Down
Loading