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
9 changes: 9 additions & 0 deletions .changeset/post-merge-p2-fixes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"agent-bundle": patch
"create-agent-bundle": patch
---

Reject the runtime's reserved notice-ledger state id during extraction, mount
each missing route-unit binding independently when the caller overrides only
one of state or noticeLedger, and document `zod` in the cli-tool migration
steps.
15 changes: 14 additions & 1 deletion packages/agent-bundle/src/config/state-extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ import type { Diagnostic } from '../core/diagnostics.ts';
import { deepFreeze } from '../core/freeze.ts';
import type { NormalizedStateDefinition } from '../core/types.ts';

/** Reserved by the generated runtime for the internal notice ledger store. */
const AGENT_NOTICE_LEDGER_STATE_ID = '@agent-bundle/runtime/agent-notice-ledger/v1';

export interface ExtractedStateDefinition {
readonly definition?: Pick<NormalizedStateDefinition, 'id' | 'lifetime'>;
readonly diagnostics: readonly Diagnostic[];
}

const diagnostic = (
code: 'AB4818' | 'AB4819' | 'AB4820',
code: 'AB4818' | 'AB4819' | 'AB4820' | 'AB4821',
message: string,
recovery: string,
sourcePath: string,
Expand Down Expand Up @@ -108,6 +111,16 @@ export const extractStateDefinition = (
)],
});
}
if (id === AGENT_NOTICE_LEDGER_STATE_ID) {
return deepFreeze({
diagnostics: [diagnostic(
'AB4821',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add AB4821 to the diagnostics reference

When a project uses this reserved ID, the CLI now emits the new stable diagnostic AB4821, but docs/diagnostics.md still labels this family as AB4800AB4820 and ends its exhaustive table at AB4820. Users encountering the error therefore cannot find its documented meaning or recovery in the diagnostics reference; add AB4821 to that section and update the range.

Useful? React with 👍 / 👎.

`State module ${relativePath} uses the reserved notice-ledger id ${AGENT_NOTICE_LEDGER_STATE_ID}.`,
'Choose a project-scoped state id; the generated runtime owns the notice ledger store under that id.',
sourcePath,
)],
});
}
return deepFreeze({
definition: { id, lifetime },
diagnostics: [],
Expand Down
7 changes: 3 additions & 4 deletions packages/agent-bundle/src/test/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,8 +444,7 @@ const mountManifestState = async (
if (
manifest === undefined
|| descriptor === undefined
|| context.state !== undefined
|| context.noticeLedger !== undefined
|| (context.state !== undefined && context.noticeLedger !== undefined)
) return noMountedState;
const loader = registeredStateLoader(manifest);
if (loader === undefined) {
Expand Down Expand Up @@ -478,8 +477,8 @@ const mountManifestState = async (
let closed = false;
return Object.freeze({
context: {
noticeLedger: bindings.noticeLedger,
state: bindings.state,
...(context.noticeLedger === undefined ? { noticeLedger: bindings.noticeLedger } : {}),
...(context.state === undefined ? { state: bindings.state } : {}),
},
async close() {
if (closed) return;
Expand Down
41 changes: 41 additions & 0 deletions packages/agent-bundle/tests/route-unit/render-route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,47 @@ describe('renderRoute through the real renderer', () => {
});
});

it('auto-mounts noticeLedger when the caller supplied state alone', async () => {
const state = {
lifetime: 'workspace-durable',
changes: async function*() {},
dispatch: async () => ({ replayed: false, revision: 0, state: { entries: [] } }),
read: async () => ({ revision: 0, state: { entries: [] } }),
} as never;
const rendered = await renderRoute('tool:harness/publish-notice', {
context: { state },
input: { message: 'partial mount', recipientSession: 'sess-a' },
});

expectDocument(rendered).toHaveStatus('success');
expect(rendered.result).toMatchObject({ state: 'pending' });
expect(typeof (rendered.result as { noticeId: unknown }).noticeId).toBe('string');
});

it('auto-mounts state when the caller supplied noticeLedger alone', async () => {
const noticeLedger = {
expire: async () => ({ notices: [] }),
openRequest: async () => Object.freeze({
close: () => undefined,
handle: Object.freeze({
publish: async () => { throw new Error('unused in journal route'); },
read: async () => ({ notices: [] }),
}),
}),
read: async () => ({ notices: [] }),
withdraw: async () => ({ notices: [] }),
} as never;
const rendered = await renderRoute('tool:harness/journal', {
context: { noticeLedger },
input: { note: 'partial mount' },
});

expectDocument(rendered).toHaveValue({
entries: [{ note: 'partial mount' }],
revision: 1,
});
});

it('records progress even when the caller supplies its own reporter', async () => {
const delegated: unknown[] = [];
const rendered = await renderRoute('tool:harness/echo', {
Expand Down
12 changes: 12 additions & 0 deletions packages/agent-bundle/tests/state-definition-extract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,16 @@ describe('state definition extraction', () => {
expect(result.diagnostics[0]).toMatchObject({ code: 'AB4820', severity: 'error' });
expect(result.diagnostics[0]!.message).toContain('request, process, and workspace-durable');
});

it('rejects the reserved notice-ledger state id', () => {
const result = extract([
'export default defineState({',
" id: '@agent-bundle/runtime/agent-notice-ledger/v1',",
" lifetime: 'workspace-durable',",
'});',
].join('\n'));
expect(result.definition).toBeUndefined();
expect(result.diagnostics[0]).toMatchObject({ code: 'AB4821', severity: 'error' });
expect(result.diagnostics[0]!.message).toContain('notice-ledger');
});
});
9 changes: 6 additions & 3 deletions packages/create-agent-bundle/templates/cli-tool/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,12 @@ export default defineConfig(await agentBundleRstest());
"test:routes": "rstest --config rstest.route-unit.config.ts"
```

Route rendering needs `react` and `@agent-bundle/runtime` (the same packages
the generated entries import) plus `@rstest/core`; install them alongside the
first route module. The `mcp-server` template ships this wiring already.
Route rendering needs `react`, `zod`, and `@agent-bundle/runtime` (the same
packages the generated entries import) plus `@rstest/core`; install them
alongside the first route module. Routed commands export zod-based
`inputSchema` and `resultSchema`, so the scaffold cannot typecheck without
`zod` once `src/cli/**` modules exist. The `mcp-server` template ships this
wiring already.

## The agent-bundle dependency

Expand Down
Loading