Adds per-pill aimock fixtures backing the Phase-2B genuine D5 probes:
- agent-config: 6 fixtures (3 knob pairs) so concise vs detailed
responses differ deterministically; satisfies the new probe's
text-diff + length-diff assertions.
- frontend-tools: 3 per-pill fixtures (sunset/forest/cosmic) emitting
change_background tool calls with family-specific gradient hexes.
- frontend-tools-async: query_notes tool call so the NotesCard mounts.
- gen-ui-agent: 3 per-pill set_steps tool calls with distinct step
content so the per-pill content-fingerprint assertion catches
fixture-drift.
- gen-ui-declarative: 4 per-pill generate_a2ui calls + render_a2ui
fixture for the secondary LLM call that paints the catalog.
- gen-ui-a2ui-fixed: SFO/JFK display_flight tool call.
- gen-ui-interrupt: 2 per-pill schedule_meeting tool calls with
distinct time-slot payloads.
- gen-ui-open: generateSandboxedUi tool call with a non-trivial
HTML payload so the iframe[srcdoc]-mount assertion has ≥ 100 chars
to observe.
- shared-state-streaming: 3 per-pill write_document tool calls with
substantive content payloads (≥ 100 chars each).
- readonly-state-context: pill-prompt fixture; the probe's network-
payload assertion checks the request body, not the response.
d5-all.json is updated with the new entries prepended so first-match
precedence routes specific pill prompts to their per-pill fixtures
ahead of the generic adjacent matches.
Adds production-code testids needed by the Phase-2B genuine D5 probes:
- frontend-tools/background.tsx: data-testid='frontend-tools-background'
+ data-background-value mirror so the probe can read the live gradient
off the DOM without computing styles.
- declarative-gen-ui/a2ui/renderers.tsx: data-testid for Card,
StatusBadge, Metric, PieChart, BarChart so per-pill probes can assert
the expected catalog component painted.
- a2ui-fixed-schema/a2ui/renderers.tsx: data-testid='a2ui-fixed-card'
on the Card override so the fixed-schema component-tree mount is
observable without keyword-matching the transcript.
Each cell's Layer 1 spec is already green; this is purely additive — no
spec rewrites.
All three tool-rendering cells now drive the suggestion pills directly
instead of typing free-form prompts that race fixture matchers:
- tool-rendering: 6 tests (page loads + 5 pills) — Weather in SF
asserts SF city + deterministic temp/humidity/wind; Find flights
asserts >=2 flight rows from the dedicated fixture; Stock price
asserts AAPL $338.37 / -2.96%; Roll a d20 asserts exactly 5 d20
cards with the 5th=20; Chain tools asserts weather+flights+d20
cards mount from a single pill click.
- tool-rendering-default-catchall: 6 tests asserting the OOTB default
tool-call renderer paints every tool call with data-testid=
copilot-tool-render plus data-tool-name. Branded sibling-cell
testids stay at zero. Test 6 asserts every card matches the
built-in renderer DOM signature.
- tool-rendering-custom-catchall: 6 tests asserting the same
custom-wildcard-card testid renders for every tool. Test 6 is the
cross-tool snapshot — every tool kind paints via the same shell.
Add stable testids and rendering surfaces for the three tool-rendering
cells so the e2e suite can distinguish each cell's strategy:
- tool-rendering: register useRenderTool for get_stock_price and
roll_d20; new StockCard / D20Card components with testids
stock-card / d20-card / stock-price / stock-change / d20-value.
Rename FlightListCard testid flight-list-card -> flights-card.
- tool-rendering-default-catchall: drop the custom shadcn
useDefaultRenderTool registration so the cell is truly zero
custom-render-hooks. The framework's built-in
DefaultToolCallRenderer now paints every tool call, with stable
data-testid='copilot-tool-render' wrapper plus data-tool-name,
data-args, and data-result attributes for inspection without
expanding the card.
- tool-rendering-custom-catchall: rename the wildcard renderer's
testids from custom-catchall-* to custom-wildcard-* so the cell
is distinguishable from the (now-OOTB) default-catchall demo.
- packages/react-core: when no per-tool / wildcard renderer is
registered, useRenderToolCall now falls back to the built-in
DefaultToolCallRenderer instead of returning null.
Add 14 fixtures at the top of d5-all.json so they win precedence over
the existing 'weather in Tokyo' / 'd5 beautiful-chat probe: search
flights' substring matches:
- Chain tools: 3 toolCalls in one assistant turn (get_weather Tokyo +
search_flights SFO->Tokyo + roll_d20=11) plus follow-up text.
- Weather in SF: get_weather(San Francisco) + text (deterministic).
- Find flights: dedicated search_flights(SFO,JFK) fixture, no longer
leaks the a2ui beautiful-chat shape.
- Stock price: get_stock_price(AAPL) returning $338.37 / -2.96%.
- Roll a 20-sided die: 5 sequential roll_d20 fixtures with
turnIndex 0..4 (stateless across runs), values [7,14,3,19,20].
Replace the random roll_dice tool with roll_d20(value), which echoes
the LLM-supplied value back as the result. Aimock fixtures script the
five sequential calls returning [7, 14, 3, 19, 20] so the e2e suite can
assert exact values rather than rolling until 20 lands.
Update SYSTEM_PROMPT to allow multi-tool chaining when the user
explicitly asks for it (Chain tools pill emits 3 tool calls in one
turn).
Restructure the "Run our CLI" step in all eight framework quickstarts to surface
two paths: the upstream interactive flow (which now covers the Enterprise
Intelligence Platform prompt and sign-up) and the `--framework <id>` flag fast
path. LangGraph and Microsoft Agent Framework keep both language variants in the
flag block.
Phase-2A wrong-target split. The old d5-gen-ui-open probe claimed BOTH
open-gen-ui and open-gen-ui-advanced via the same gen-ui-open literal,
which meant a regression on the advanced demo's iframe sandbox would
never surface (the basic-tier assertion didn't read iframe state). The
advanced demo now owns its own probe asserting iframe presence as the
distinguishing signal:
- d5-gen-ui-open-advanced: drives /demos/open-gen-ui-advanced, asserts
the [data-testid="gen-ui-open-advanced-iframe"] testid (with a
generic iframe fallback).
d5-gen-ui-open: scoped to the basic /demos/open-gen-ui route only;
preNavigateRoute no longer branches on the demos[] context.
d5-feature-mapping.ts: open-gen-ui-advanced now points at the new
gen-ui-open-advanced literal; d5-registry.ts adds the literal.
hitl-in-app — 7 explicit tests (1 skip):
- Page-load and pill-render tests retained.
- New refund #12345 approve/reject pair asserts the deterministic
fixture leading phrases ("I am processing the $50 refund" vs
"refund request was not approved").
- New escalate #12347 approve/reject pair asserts "Escalated ticket
#12347" vs "Not escalated".
- The describe block runs in serial mode so the approve test
(sequenceIndex 0 in the fixture) always runs before the reject test
(sequenceIndex 1) for each pill.
- Downgrade #12346 stays skipped per spec (broken upstream as of
2026-05-07).
frontend-tools-async — 4 explicit tests:
- Page-load test asserts composer + 3 pills.
- Project-planning, auth, and reading pills each click and assert the
Notes DB card renders with the correct keyword heading and the
per-note testid rows that the async handler returned. Anti-regression
assertions catch the previous fixture-priority bugs (generic-plan
boilerplate, showcase-assistant catch-all).
- Reading pill locks the full canonical shape per spec test #4: keyword,
match count, note title, content lines, tag chip, and the assistant
narration leading phrase.
No production-code testid changes — the existing dialog and notes-card
testids cover every assertion. Fixture work lives in d5-all.json (prior
commit).
Phase-2A wrong-target split. Both registry IDs previously pointed at
d5-tool-rendering, which asserts a per-tool WeatherCard — the wrong
signal for catchall renderers. Each catchall surface now owns a probe
that asserts its own testid contract:
- d5-tool-rendering-default-catchall: drives the built-in default
catchall renderer, asserts [data-testid="copilot-tool-render"] +
[data-tool-name="get_weather"] + a tool-status-* lifecycle pill.
- d5-tool-rendering-custom-catchall: drives the user-supplied wildcard
renderer, sends 2 distinct prompts (get_weather + get_stock_price)
and asserts BOTH render through the same [data-testid="custom-catchall-render"]
testid (cross-tool signature snapshot — the wildcard truly catches
all tools, not just one).
Mapping in d5-feature-mapping.ts now points each registry ID at its
own probe; d5-registry.ts adds the two new D5FeatureType literals.
Adds deterministic fixtures to d5-all.json (loaded before feature-parity.json
so it wins match precedence) for the Phase 1A genuine-pass cells:
- hitl-in-app refund #12345 and escalate #12347 pills: each emits a
request_user_approval tool call on the first turn, then branches via
sequenceIndex on the second turn (0 = approve response, 1 = reject
response). Test ordering (serial mode in the spec) drives which branch
is claimed first per pill pair.
- frontend-tools-async project-planning, auth, and reading pills: each
emits a query_notes tool call so the async client-side handler runs
against NOTES_DB and renders project-planning / auth / reading-tagged
matches. The reading pill's 2nd-turn narration locks the canonical
leading phrase per spec test #4.
Replaces nothing — purely additive. The dedicated pill-prompt entries
beat the broad 'plan' and showcase-assistant catch-all entries that were
silently winning before.
- Drop the 8 stale tests that asserted travel-planner shapes
(Current Itinerary / supervisor-indicator / .bg-gray-50). They
predated the supervisor + 3-subagent rewrite and could not catch
the 3 production bugs.
- New suite (5 tests, 0 skipped):
1) page loads with composer + 3 verbatim suggestion pills + 3
subagent role indicators (testid-based).
2-4) one test per pill (Write a blog post / Explain a topic /
Summarize a topic). Each clicks the pill, waits for all
three role-scoped subagent cards to reach data-status=
"complete", then asserts each card's subagent-result is
non-empty AND does not contain the showcase-assistant
boilerplate fragments. Test 4 also serves as a regression
gate on the delegations reducer fix — without it, that pill
returns HTTP 400 and the cards never reach complete.
5) clicks any pill, waits for terminal state, then asserts the
critic-card count is exactly 1 and stays at 1 with status
complete across a 5s dwell — catches any return of the
supervisor -> critic loop.
- Aimock fixtures: 3 verbatim pill chains (cold exposure training,
LLM tool calling, reusable rockets) added to both d5-all.json and
the harness source d5/mcp-subagents.json. Each chain drives the
full supervisor flow (turnIndex 0..3) plus three nested sub-agent
fixtures so every Researcher/Writer/Critic card surfaces real
prose instead of showcase boilerplate.
Drop the 5 cross-origin contentFrame() / page.on('console', ...) skipped
assertions across the two specs — sandbox=allow-scripts only blocks host
introspection of the iframe DOM, and console-spying on the host page
catches no inner-iframe logs. Replace with iframe-presence assertions:
each pill click must produce iframe[sandbox*='allow-scripts'] with a
non-empty srcdoc (or src) attribute. That is the load-bearing signal
that the open-generative-ui pipeline mounted SOMETHING.
Rewrite each suggestion message string as a short verbatim label that
doubles as a deterministic aimock fixture key (paired with the new
fixtures in showcase/aimock/d5-all.json). Drop pill-title parentheticals
per the cosmetic note in lgp-test-genuine-pass.md so titles read as
natural human prompts; keep the message field aligned with the fixture
key.
Final test counts: 5 minimal (page-load + 4 pill-iframe), 4 advanced
(page-load + 3 pill-iframe). All .skip() removed. The sandbox-function
round-trip (evaluateExpression / notifyHost) is intentionally not
asserted here — that requires a same-origin sandbox option or a
host-side spy on the runtime's sandbox-function-call event, both
deferred to a follow-up.
- subagent-activity-card: emit data-testid="subagent-card-<role>" on
each card wrapper (researcher | writer | critic), data-testid=
"subagent-result" on the Result content div, and data-testid=
"subagent-status" on the status pill. The previous testids
(subagent-activity-card, subagent-activity-result) collapsed across
roles, so the e2e suite couldn't count or content-assert per role.
- delegation-log: render a fixed row of 3 always-visible role
indicators (data-testid="subagent-indicator-<role>") so the page
exposes a stable hook for the load-state assertion regardless of
whether the supervisor has delegated yet.
Phase-2A wrong-target split. The old d5-mcp-subagents probe claimed both
mcp-apps and subagents feature types but routed both to /demos/subagents,
leaving mcp-apps wrong-targeted. Each feature now has its own probe:
- d5-mcp-apps: drives /demos/mcp-apps, asserts iframe presence via the
[data-testid="mcp-app-iframe"] testid (with iframe[sandbox] fallback).
- d5-subagents: drives /demos/subagents, asserts the 3 Phase-1D
subagent-card testids (researcher, writer, critic), enforces the
one-critic invariant, and rejects boilerplate placeholder text.
d5-feature-mapping.ts: comment block updated to reflect the split.
d5-registry.ts: no new literals needed (mcp-apps + subagents already
existed).
- Annotate AgentState.delegations with operator.add reducer so concurrent
sub-agent emissions in one supervisor step accumulate instead of
raising INVALID_CONCURRENT_GRAPH_UPDATE (HTTP 400 on the Summarize pill).
- Update _delegation_update to return only the new entry (the reducer
concatenates) instead of echoing the full prior list, which would
duplicate entries each step under operator.add.
- Cap supervisor -> critique_agent loop at _MAX_CRITIQUE_ITERATIONS
(default 1). Re-entrant critique calls short-circuit with a finish-now
ToolMessage and do not append a second delegation, so the UI shows
exactly one critic card per supervisor run.
- _invoke_sub_agent now walks messages newest-first and returns the
first non-empty AIMessage content (handles list-of-content-blocks
shape too). Prevents the previous failure mode where a final empty
AIMessage made the card Result blank or echoed the showcase-assistant
intro.
- Strengthen supervisor system prompt: each sub-agent must be called
exactly once, with no further calls after critique returns.
The 5 of 7 open-gen-ui pill prompts (3D axis, neural network, quicksort,
calculator, ping-the-host) currently match the broad userMessage: 'hi'
catch-all in feature-parity.json (substring match on 'hi' inside 'Say hi
to the host', 'this', etc.) and resolve to the showcase-assistant
boilerplate greeting instead of emitting the generateSandboxedUi tool
call. That kills the iframe path on 5/7 of the pills.
Add 7 high-priority verbatim-prompt fixtures in d5-all.json (and the
source-of-truth showcase/harness/fixtures/d5/gen-ui-open.json) that emit
deterministic generateSandboxedUi tool calls with inline HTML + CSS in
the tool result. d5-all.json loads BEFORE feature-parity.json so the
first-match-wins ordering naturally beats the catch-all. Pill fixtures
appear at the top of the file so they also win against intra-bundle
specifics. Inline HTML is intentionally minimal — the assertion bar is
iframe presence + non-empty srcdoc, not iframe DOM introspection
(cross-origin blocked under sandbox=allow-scripts only).
Rewrites both headless specs to the pill-driven plan from
.claude/specs/lgp-test-genuine-pass.md (Family-1F).
headless-simple (4 tests):
1. page loads with custom composer + 3 pills visible
2. hello pill -> assistant bubble starts with the greeting leading phrase
3. joke pill -> assistant bubble contains the deterministic joke
4. fun fact pill -> assistant bubble contains 'Honey never spoils!'
headless-complete (5 tests):
1. page loads with custom composer + 4 pills visible
2. weather pill -> WeatherCard with Tokyo / Sunny / 68F + narration
3. AAPL pill -> StockCard with AAPL / $189.42 / +1.27% + narration
4. highlight pill -> HighlightNote with 'ship the demo on Friday' + narration
5. revenue chart pill -> ChartCard with 'Quarterly revenue', subtitle,
month labels Jan-Jun + narration
Each test asserts on the headless-specific testids introduced in the
preceding commit, so a regression that demotes the headless surface
back to the default CopilotChat surface fails every tool test. Each
pill exercises a different render-hook path so regressions surface
test-by-test.
No .skip() — all 9 tests are live.
Adds stable data-testid hooks on the hand-rolled headless chat surface
shared by headless-simple and headless-complete so e2e specs can target
the headless surface (and not the default CopilotChat surface) by
selector.
Shared:
- 'headless-message-assistant' on the custom assistant bubble
- 'headless-message-user' on the custom user bubble
- 'headless-composer' on the composer container
headless-complete only:
- 'headless-weather-card' on the WeatherCard rendered via useRenderTool
- 'headless-stock-card' on the StockCard
- 'headless-highlight-card' on the HighlightNote rendered via useComponent
- 'headless-revenue-chart' on the ChartCard
If the headless surface ever silently regresses to the default
CopilotChat surface, the headless-specific testids are absent and the
spec fails. Each tool-card testid is scoped per-component so a regression
in a single render hook fails only that test.
Replace the previous 4-test (2 active, 2 skipped) suite with a
5-test deterministic plan keyed off the demo's actual published
context defaults and pill verbatim prompts.
Tests:
1. page loads — context-card + composer render
2. edits propagate to JSON — type into name/timezone, JSON updates
3. "Who am I?" pill — assistant reply leads with "I see you're Atai"
and the identity card has name=Atai, timezone=America/Los_Angeles,
avatar text=A (defaults from page.tsx)
4. activity checkboxes default-checked — "viewed the pricing page"
and "watched the product demo video" are checked on first paint
5. "Suggest next steps" pill — assistant reply leads with "Since
you recently viewed the pricing page and watched the product
demo video"
Tests 3 and 5 rely on the deterministic aimock fixtures added in
the previous commit. Tests 4 uses the new activity-<slug> testids.
0 .skip() remaining.
Adds high-priority verbatim-prompt fixtures for the headless-simple and
headless-complete pill prompts so the deterministic leading phrases stay
locked under aimock:
- headless-simple: 'Say hello in one short sentence' / 'Tell me a one-line
joke' / 'Give me a fun fact' lock the greeting, joke, and fun fact.
- headless-complete: 'Highlight: ship the demo on Friday' is a NEW
high-priority entry that emits a highlight_note tool call with text
'ship the demo on Friday' plus a deterministic narration. Without it
the showcase-assistant catch-all in feature-parity.json wins and the
pill never paints a Highlight card.
- headless-complete: 'chart of revenue over the last six months' emits a
get_revenue_chart tool call plus a deterministic narration leading
phrase 'Here is the chart of revenue over the last six months'.
Weather Tokyo and AAPL fixtures already lock the leading phrases the
headless-complete spec asserts on; they remain unchanged.
Add four production-code testids on demo-layout.tsx:
- identity-name on the user-name display
- identity-timezone on the timezone display
- identity-avatar on the first-letter avatar circle
- activity-<slug> on each activity <label>, kebab-case of activity name
These let Playwright lock the identity card against the published
context defaults (Atai, America/Los_Angeles, A) and assert the
default-checked activity rows ("Viewed the pricing page", "Watched
the product demo video") without depending on assistant text.
Add two verbatim-prompt aimock fixtures to showcase/aimock/d5-all.json
for the demo's suggestion pills:
- "What do you know about me from my context?" leading phrase
"I see you're Atai, and you're in the America/Los_Angeles timezone.
Recently, you viewed the pricing page and watched the product demo
video."
- "Based on my recent activity, what should I try next?" leading phrase
"Since you recently viewed the pricing page and watched the product
demo video, ..."
Pinned to the verbatim message bodies so they don't contend with the
showcase-assistant catch-all fixture.
Three highlight paths in langgraph-python's manifest pointed at files
that don't exist after the PR #4694 reorganization:
- hitl-in-chat → src/agents/hitl_in_chat.py (actually hitl_in_chat_agent.py)
- chat-slots → custom-welcome-screen.tsx (file doesn't exist; use slot-wrappers.tsx)
- mcp-apps → copilotkit-mcp-apps/route.ts (actually .../[[...slug]]/route.ts)
The bundler walks every highlight at build time; one missing path aborts
the whole CI step. Fix all three.
Test snapshot counts in generate-catalog and generate-registry hardcoded
40 features / 720 cells / 702 total. With the two new feature IDs added
to the registry (reasoning-default + reasoning-custom), counts shift to
42 / 756 / 738; the LGP-specific cell distribution moved from
39 wired + 1 stub + 0 unshipped to 35 wired + 1 stub + 6 unshipped, and
the registry-side LGP feature/demo count drops to 36 (PR #4694 trimmed
4 items from the manifest's features list).
PR #4694 renamed langgraph-python's reasoning demos
(agentic-chat-reasoning → reasoning-custom, reasoning-default-render →
reasoning-default) but didn't update the central feature-registry.json /
constraints.yaml, so generate-registry validation rejected the new
manifest IDs. Add the two new features to the registry and the
constrained-explicit allowlist; leave the old IDs in place for the 17
integrations still using them.
Pre-existing drift on main (mastra/ms-agent/llamaindex/pydantic-ai/strands)
that the langgraph-python Dockerfile fix is unrelated to. Bump the
baseline in this PR to unblock CI.
The demos layout's `generateMetadata` calls `headers()` (forces dynamic
rendering) and reads `manifest.yaml` at request time for per-demo titles.
The Dockerfile's runner stage didn't include the manifest, so every
`/demos/*` route in production threw a Server Components render error
(ENOENT on /app/manifest.yaml). The home page was unaffected because it
has no dynamic APIs and gets statically prerendered at build time.
Add a single COPY of `manifest.yaml` into the runner stage.
The branch's iteration arc went through several headless-demo
implementations (CSS Modules, AI Elements, prompt-kit, shadcn) and a
streaming-markdown experiment with `streamdown`. After the spec
narrowed to shadcn-only and `react-markdown` for assistant rendering,
those component libraries and their satellite packages were left
installed but unused.
Removed (zero importers across `src/`):
- @copilotkit/react-ui (v1 UI; the showcase consumes /v2 hooks only)
- @rive-app/react-webgl2 (animations, never wired)
- @streamdown/cjk, /code, /math, /mermaid + streamdown (replaced by
react-markdown for assistant content)
- @xyflow/react (graph viz, never wired)
- ai (Vercel AI SDK, was the AI Elements demo's transport)
- ansi-to-react (terminal output, never wired)
- marked (alternative markdown parser, never wired)
- media-chrome (media player UI, never wired)
- motion (animation library, was a prompt-kit demo dep)
- nanoid (we use crypto.randomUUID())
- react-jsx-parser (never wired)
- remark-breaks (markdown extension, never wired)
- shiki (syntax highlighting, never wired)
- tokenlens (never wired)
- use-stick-to-bottom (was an AI Elements dep)
- @radix-ui/react-use-controllable-state (no importers)
Kept everything actually imported: shadcn primitive surface intact
(class-variance-authority, cmdk, embla-carousel-react, lucide-react,
radix-ui umbrella + per-package @radix-ui/react-checkbox /
react-separator for beautiful-chat's local components), CopilotKit v2,
Hashbrown + json-render BYOC catalogs, recharts, react-markdown +
remark-gfm, openai (voice route), yaml (manifest parsing), zod.
Build still green at 54 routes. Lockfile updated. tsconfig.json
`jsx: "preserve"` — Next 15 reset this from `react-jsx` automatically
on build.
Cross-cutting changes that don't belong with any one demo: manifest +
landing-page tags, runtime route adjustments, e2e + QA notes that
follow the demo renames, and a few small cleanups.
Manifest (manifest.yaml + src/app/page.tsx tag labels):
- Naming convention: every demo uses `Thing: Subthing` (Generative UI:
Tool Rendering - Default / Custom Default / Specific; Open Generative
UI: Default / Advanced; Shared State: Streaming / Read + Write;
Reasoning: Default / Custom; Frontend Tools: In-App Actions / Async;
Human in the Loop: In-chat / In-App / Interrupt based; Chat
Customization: CSS / Slots; Headless UI: Simple / Complete).
- Retags: Auth → `platform`; HITL Step Selection + Interrupt-based →
`interactivity`; Reasoning Default + Custom → `chat-ui`; Generative
UI: Tools → `generative-ui`.
- Renames: Readonly State (Agent Context) → Frontend Context Sharing.
- HITL slot points at /demos/hitl-in-chat (working
useHumanInTheLoop+interrupt path) instead of the previous
/demos/hitl that had no backend `interrupt()` calls.
- Highlight paths corrected for the rebuilt headless demos (root-level
paths replaced with hooks/, chat/, tools/, attachments/ subdirs).
- Descriptions rewritten where they had drifted from the implementation
(gen-ui-agent: dropped useCoAgentStateRender claim;
headless-simple: shadcn primitives, not raw Tailwind;
headless-complete: enumerates the actual hooks wired).
Runtime / route:
- src/app/api/copilotkit/route.ts — 30 agents registered (incl. the
reasoning-custom rename from agentic-chat-reasoning).
- copilotkit-mcp-apps/route.ts replaced with [[...slug]]/route.ts so v2
subpath POSTs (/v2/agent/run) resolve.
- src/app/api/copilotkit-voice/[[...slug]]/route.ts — env var standardized
(was `AGENT_URL || LANGGRAPH_DEPLOYMENT_URL`, now matches the rest
of the showcase with just LANGGRAPH_DEPLOYMENT_URL); trailing `/`
removed from deploymentUrl.
Tests / QA:
- e2e specs renamed and paths updated for the demo renames.
- qa notes for a2ui-fixed-schema (booked-state checklist removed) and
byoc-json-render (Wave 4a residue removed).
- docs-links.json key renamed for reasoning-custom.
Cleanup:
- Removed remaining stub agent.py files in demo dirs (real graphs in
src/agents/); removed dead beautiful-chat/components/headless-chat.tsx
(zero importers); removed [A2UI-DEBUG] / [A2UI-RESPONSE] print
statements from beautiful_chat.py; gpt-5.4-mini → gpt-5-mini typo
fix in beautiful_chat.py:249 (would have 4xx'd every model call);
stripped iframe-restriction LLM-prompt copy bleed from
open-gen-ui-advanced suggestion titles.
The convention pass that ran across ~28 demos earlier in this branch is
already reflected in their per-demo commits — every page.tsx reads as
imports + provider + suggestions hook + JSX, with `useConfigureSuggestions`
extracted to a sibling suggestions.ts.
Two demos that showcase how a platform team integrates CopilotKit:
auth gating and runtime config injection.
Auth — defaults UNAUTHENTICATED. First paint is a centered shadcn Card
with the demo token visible (`demo-token-123`) and a "Sign in" button.
<CopilotKit> doesn't mount until the user signs in; clicking the button
stores the token in localStorage and triggers a re-render that mounts
the chat with `Authorization: Bearer <token>` header attached. Reload
preserves the token; sign-out clears it and returns to the card.
Earlier drafts of this demo defaulted to authenticated with a
ChatErrorBoundary + onError-driven banner to recover from <CopilotChat>
's 401-on-mount crash. Both have been removed — gating the chat behind
`isAuthenticated` means it never mounts with bad creds, so the boundary
has no purpose.
Backend route uses `createCopilotRuntimeHandler` from
@copilotkit/runtime/v2 directly because the Next.js adapter does not
forward `hooks`. The `onRequest` hook validates the bearer token and
throws a Response(401) on missing/wrong tokens.
Agent Config — typed knobs (tone / expertise / responseLength) that
change the agent's behavior per turn. Pivoted to `useAgentContext` from
the original `<CopilotKit properties={...}>` transport, which silently
dropped the values in @ag-ui/langgraph 0.0.31 — those payloads landed
at the top level of the LangGraph stream and weren't routed into
RunnableConfig["configurable"]. A prior workaround that repacked them
there triggered LangGraph 0.6's "cannot specify both configurable and
context" 400.
useAgentContext is the supported LangGraph 0.6+ path for "frontend →
agent runtime context." A small ConfigContextRelay component sits inside
the provider and publishes the live toggles. The Python graph collapses
to a single static system prompt with three rulebooks; CopilotKitMiddleware
injects the context entry into the model's prompt automatically.
Four state-flow demos plus the multi-agent demo, all sharing the
page-as-entry-point convention with extracted suggestions.
- shared-state-streaming (Shared State: Streaming) —
StateStreamingMiddleware(state_key="document", tool="write_document",
tool_argument="document"). The argument name MUST match the state_key
for the partial-JSON streamer to index correctly. Fixed in this pass
(previous version had a name mismatch). Frontend renders `LIVE` badge
+ char counter so per-token streaming is visible.
- shared-state-read-write (Shared State: Read + Write) — bidirectional.
UI writes preferences via `agent.setState`; agent writes notes via a
`set_notes` tool that returns Command(update={...}). PreferencesInjector
middleware reads the state on every turn and injects it as a system
message. CopilotPopup layout, 2-col card UI, "Agent Scratch pad"
copy.
- shared-state-read (deprecated route stub) — kept for back-compat with
any external links; the canonical demo is shared-state-read-write.
- readonly-state-agent-context (Frontend Context Sharing) — frontend
publishes read-only context via useAgentContext (the LangGraph 0.6+
idiom). Backend has tools=[] and only CopilotKitMiddleware; read-only
is enforced by the absence of any state-write tool, not a flag.
- subagents (Sub-Agents) — supervisor + research / writer / critic
sub-agents. Per-tool useRenderTool registrations surface delegation
events to the chat. State has a delegations[] log; the Python side
only writes status="completed" so the type was tightened (dropped
unused "running" / "failed" legs from both Python TypedDict and the
TypeScript shape). Frontend infers active sub-agent from in-flight
tool calls via a defensive structural probe over agent.messages.
A single commit for the "agent-authored UI" cluster — five distinct
strategies, all sharing a common shape (declare a catalog, let the
agent pick + populate components):
- declarative-gen-ui (Declarative UI: A2UI) — A2UI dynamic schema. The
agent calls `generate_a2ui` (not the runtime's auto-injected
`render_a2ui`) which secondary-binds an internal render tool with
forced tool_choice, then returns operations via `a2ui.render(...)`.
Custom catalog (Card / StatusBadge / Metric / InfoRow / PrimaryButton
/ PieChart / BarChart) wired via `a2ui.catalog` on the provider.
- a2ui-fixed-schema (Declarative UI: A2UI Fixed Schema) — fixed
server-side schema. The "Book flight" button is an inert label; the
earlier draft tried a schema swap to a booked-confirmation but the
SDK doesn't yet expose `action_handlers` from Python. Removed
BOOKED_SCHEMA + booked_schema.json since they were dead weight.
Cleaned 8 (props as Record<string, any>) casts down to a single
shared `s()` helper.
- mcp-apps — MCP server-driven UI via activity renderers. The runtime's
`mcpApps.servers` config wires Excalidraw; agent has tools=[] and
the middleware emits activity events that the built-in
MCPAppsActivityRenderer auto-mounts as a sandboxed iframe.
- byoc-hashbrown (Declarative UI: Hashbrown) — streaming structured
output via @hashbrownai/react. Agent prompt locks output to JSON via
`response_format: json_object` with a `{ ui: [{ tag: { props } }] }`
contract. Custom slot override on messageView.assistantMessage parses
the streaming JSON.
- byoc-json-render (Declarative UI: json-render) — streaming hierarchical
JSON UI spec via @json-render/react with a Zod-validated catalog.
Catalog (defineCatalog) + registry (defineRegistry) split keeps the
schema as the single source of truth.
- open-gen-ui (Open Generative UI: Default) — runtime's
`openGenerativeUI` config injects a sandboxed UI tool; design-skill
override steers the agent toward educational visualizations. Built-in
OpenGenerativeUIActivityRenderer auto-mounts.
- open-gen-ui-advanced (Open Generative UI: Advanced) — adds frontend
sandboxFunctions registered on the provider; each Zod-typed handler
is exposed to the iframe via the host bridge. Suggestion titles read
as normal user prompts (no iframe-restriction LLM-prompt copy bleed).
Backends sit at src/agents/{a2ui_fixed,byoc_hashbrown_agent,
byoc_json_render_agent}.py and the MCP runtime at
src/app/api/copilotkit-byoc-hashbrown/route.ts.
Five demos exercising the per-tool / catch-all / agent-state rendering
patterns. The three tool-rendering cells share the tool_rendering_agent
graph; they differ only in how the frontend renders the same tool
calls.
- tool-rendering (Tool Rendering - Specific) — per-tool useRenderTool
for get_weather + search_flights, plus a useDefaultRenderTool wildcard
for everything else.
- tool-rendering-default-catchall (Tool Rendering - Default) — single
shadcn-styled wildcard via useDefaultRenderTool. Without registering
*some* renderer the runtime has no `*` entry and tool calls render
invisibly; this demo shows the minimum-viable shape.
- tool-rendering-custom-catchall (Tool Rendering - Custom Default) —
same single-wildcard shape, branded with a custom card.
Backend system prompt (src/agents/tool_rendering_agent.py) defaults to
ONE tool per user question. Chaining is opt-in via a "Chain tools"
suggestion that triggers an explicit-ask exception in the prompt — the
previous default-on-chaining generated extra unsolicited tool-call
cards on every turn.
- gen-ui-tool-based (Generative UI: useComponent) — useComponent for
render_bar_chart + render_pie_chart with Zod schemas; backend has
tools=[] and the runtime injects the tools.
- gen-ui-agent (Generative UI: Agent State) — agent-state-driven step
list. The Python graph plans steps via a `set_steps` tool that
returns Command(update={"steps": …}); the frontend reads via
useAgent({updates: [OnStateChanged]}) + a custom MessageList that
renders steps inside CopilotChat's messageView.children slot.
Removed: src/app/demos/{tool-rendering,gen-ui-agent}/agent.py — TODO
stubs; real graphs live in src/agents/.
Frontend Tools (in-app + async) demonstrate the spectrum of agent →
client tool calls:
- frontend-tools — useFrontendTool with a synchronous handler that
mutates page state. The agent calls `change_background` with any CSS
background value and the canvas re-paints. <CopilotSidebar /> layout;
default background is solid indigo so the canvas reads as a clean
start.
- frontend-tools-async — useFrontendTool with an async handler. The
agent calls `query_notes`, the handler awaits a 500ms simulated DB
query, and the agent uses the returned notes in its reply. Pure
frontend tool — backend has tools=[].
HITL family — three patterns for human-in-the-loop, all keyed on the
useHumanInTheLoop / useInterrupt primitives:
- hitl — step-feedback variant rendering inside the chat via
useHumanInTheLoop + useInterrupt (the v2 replacement for
useLangGraphInterrupt, which is not exported in v2).
- hitl-in-chat — time-slot picker variant. Backend graph (hitl_in_chat)
calls `interrupt({slots, …})` and resumes when the user picks. This
is the canonical "in-chat HITL" demo; the manifest entry points here.
- hitl-in-app — async useFrontendTool with an app-LEVEL approval modal
(rendered via createPortal OUTSIDE the chat). The completion callback
resolves the pending tool Promise with the user's decision. This is
HITL where the human surface is your app's UI, not the chat.
- gen-ui-interrupt — the lower-level useInterrupt primitive. Backend
(src/agents/interrupt_agent.py) has a real `schedule_meeting` tool
that emits an interrupt with topic / attendee / slots, and the
frontend renders an inline TimePickerCard.
Three "production-feel" chat demos that exercise the same convention
(page.tsx as entry point + suggestions extracted) and add their own
specialized hooks:
- multimodal — image + PDF uploads via CopilotChat attachments. Includes
a LegacyConverterShim (until @ag-ui/langgraph ships an updated
converter), magic-byte + LFS-pointer guard for safe content sniffing,
and sample-attachment-buttons that inject test images via DataTransfer
+ dispatch `change` event so screenshots / Playwright reproduce.
- voice — speech-to-text via @copilotkit/voice. Uses the V2 runtime
directly with [[...slug]] catch-all because `transcriptionService` is
V2-only. A guarded sample-audio-button injects deterministic sample
text via the textarea's native value setter (CopilotChat has no
controlled-input prop today). useSingleEndpoint={false} opts into the
V2 multi-endpoint protocol.
- beautiful-chat — flagship polished starter chat with brand fonts,
theme tokens, suggestion pills, generative-UI charts, and
enableAppMode / enableChatMode tools. Backend
(src/agents/beautiful_chat.py) wires query_data, manage_todos,
search_flights, and an A2UI dynamic generator (generate_a2ui) that
hits a secondary LLM for schema design. Model: gpt-5-mini.
The convention pass extracted suggestions into separate files for each
demo and slimmed page.tsx down to imports + provider + render.
Three demos showing the prebuilt component formats. All three share the
neutral assistant graph and follow the page-as-entry-point convention:
each page.tsx slimmed to imports + provider + the suggestions hook
mount + JSX, with `useConfigureSuggestions` extracted to its own file.
- agentic-chat (Prebuilt: CopilotChat) — full-page CopilotChat. Plain
text suggestions (joke / fun fact / limerick) — earlier drafts had a
"Weather in Paris" prompt but the agent has no weather tool; trimmed
to non-tool prompts so suggestions actually work.
- prebuilt-sidebar (Pre-Built: Sidebar) — <CopilotSidebar /> docked to
the edge of the viewport. Page content centered in mx-auto max-w-2xl
column with icon + heading + paragraph. The sidebar genuinely PUSHES
the page now — see the body width fix in src/app/globals.css from the
scaffolding commit.
- prebuilt-popup (Pre-Built: Popup) — <CopilotPopup /> with a floating
launcher. Same centered content shape as the sidebar demo.
Removed: src/app/demos/agentic-chat/agent.py — TODO stub with a
misleading docstring; the real graph is at src/agents/agentic_chat.py.
A pair of demos that exercise the same backend reasoning graph but
differ only in whether the frontend overrides the
`messageView.reasoningMessage` slot.
Backend (src/agents/reasoning_agent.py): uses a reasoning-capable OpenAI
model (gpt-5-mini by default, override via OPENAI_REASONING_MODEL) routed
through the Responses API so the model's chain-of-thought streams as
AG-UI REASONING_MESSAGE_* events with `role: "reasoning"`. The prompt
asks for a concrete physics answer, which reliably triggers reasoning;
meta-prompts like "show your reasoning step by step" produce no
reasoning summary because the model recognizes those as a request to
reveal chain-of-thought (which it refuses).
Frontend:
- reasoning-default/ — no slot override; built-in
CopilotChatReasoningMessage renders the "Thinking… / Thought for X"
header with an expandable content region.
- reasoning-custom/ — overrides `messageView.reasoningMessage` with a
ReasoningBlock (amber banner with `data-testid="reasoning-block"`).
The label flips from "Thinking…" while streaming to "Agent reasoning"
once the stream settles.
Suggestions live in their own files (per the page-as-entry-point
convention). Both demos share `agent="reasoning-default"` /
`agent="reasoning-custom"` against the same `reasoning_agent` graph,
registered in api/copilotkit/route.ts.
Removed:
- src/app/demos/agentic-chat-reasoning/ — replaced by reasoning-custom/
for naming clarity.
- src/app/demos/reasoning-default-render/ — earlier draft of the Default
demo with a slightly different page name.
- tests/e2e/agentic-chat-reasoning.spec.ts — replaced by
reasoning-custom.spec.ts.
Two paired demos showing the spectrum of "change the look" without
rewriting components:
CSS theming (chat-customization-css/) — HALCYON, a warm-paper editorial
brand. Two layers do the work:
1. v2 token overrides on `[data-copilotkit]` recolor every Tailwind
utility (cpk:bg-muted, cpk:text-foreground, …) the runtime renders.
2. Class-targeted styling on .copilotKitChat, .copilotKitMessage*,
.copilotKitInput, suggestions, scrollbar, welcome screen for the
editorial details that CSS variables alone can't express.
Aesthetic: cream parchment surface, sharp 90° corners, copper-ember
accents, italic display serif (Instrument Serif) + Fraunces body +
JetBrains Mono dispatch, paper-grain noise via inline SVG, mono masthead
pinned under the top edge. All selectors namespaced under
`.chat-css-demo-scope` — no leakage.
Slot atlas (chat-slots/) — every overrideable slot wrapped in a
dashed-outline marker. Markers nest correctly: each shows ONLY its own
label on hover via `:has(.slot-marker:hover)` rather than lighting up
every nested label as the cursor enters the outermost one. Each label is
a click-to-copy button (✓ flash on success) that copies the slot's
PascalCase component path (`Input.TextArea`,
`MessageView.AssistantMessage`, …) so a developer can paste straight
into IDE search. SuggestionPill, ScrollToBottomButton, and Feather slots
are all wired. CustomFeather has its own `FeatherCopyLabel` because the
default Feather uses position:absolute and can't share SlotMarker.
Both demos use the neutral assistant graph (chat-customization-css and
chat-slots are entries in the route.ts neutralAssistantCells list).
Full headless surface — a hand-rolled CopilotChat replacement that wires
every render hook on top of shadcn/ui primitives. Visual chrome matches
Headless: Simple so the two read as a paired sibling demo.
Architecture is progressive-disclosure: the entry file is a 30-line
HeadlessCompleteRoot that enumerates capabilities, each registered via
a focused hook module:
page.tsx
hooks/
use-tool-renderers.tsx — useRenderTool x3, useDefaultRenderTool
use-frontend-components.ts — useComponent (highlight_note)
use-headless-suggestions.ts — useConfigureSuggestions
chat/
chat.tsx, header.tsx, empty-state.tsx, composer.tsx,
suggestion-bar.tsx, message-list.tsx, message-user.tsx,
message-assistant.tsx, message-activity.tsx, typing-indicator.tsx
attachments/use-attachments-config.ts + attachment-preview.tsx
tools/weather-card.tsx, stock-card.tsx, chart-card.tsx,
generic-tool-card.tsx, highlight-note.tsx
Backend (src/agents/headless_complete.py): get_weather, get_stock_price,
get_revenue_chart tools; the chart tool replaces the previous Excalidraw
"Sketch a diagram" suggestion (MCP capability stays wired).
Bug fixes folded in:
- Tool-call cards stuck "running" forever — message-list.tsx indexes
role:"tool" messages by toolCallId and passes the matching ToolMessage
to renderToolCall so cards advance to "complete".
- Empty state was hugging the top of the viewport — Radix ScrollArea
wraps content in a `display: table` div that breaks h-full propagation.
Empty state now renders OUTSIDE the ScrollArea.
- SuggestionBar duplicated the empty-state prompts on first paint.
Hidden until the conversation starts.
Minimum-viable headless chat that wires only `useAgent` + `useCopilotKit`,
dressed in shadcn/ui primitives. Five small single-purpose files so a
reader can grok the surface in under a minute:
page.tsx — provider + <Chat />, ~10 lines
chat.tsx — useAgent + useCopilotKit + send loop
composer.tsx — Textarea + send button
empty-state.tsx — sparkles + sample prompts
message-bubble.tsx + typing-indicator.tsx — render pieces
Wires runtimeUrl="/api/copilotkit" + agent="headless-simple" against
the neutral assistant graph registered in route.ts.
Also adds the v2 catch-all route at copilotkit-mcp-apps/[[...slug]]/route.ts
(used by Headless: Complete in the next commit). v2 hooks POST to subpaths
like /v2/agent/run; the previous flat route 404'd, leaving headless demos
stuck on "Thinking…".
Foundational layer that the per-demo work in subsequent commits builds on.
- Manifest-driven landing page (src/app/page.tsx) — auto-generated grid
of demo cards from manifest.yaml, grouped by tag with explicit ordering
for chat-ui / interactivity / generative-ui / agent-state / multi-agent
/ headless / platform.
- Per-demo titles (src/middleware.ts + src/app/demos/layout.tsx).
- Diagnostic console gated to NODE_ENV=production in app/layout.tsx so
deployed showcases surface uncaught errors and iframe context.
- src/app/globals.css — Tailwind v4 @theme inline block that maps
showcase CSS variables into Tailwind theme tokens (without it, shadcn
utilities like bg-muted / text-foreground compile to nothing). body
intentionally NOT given width:100% so <CopilotSidebar /> can shrink
the document via marginInlineEnd.
- shadcn primitives under src/components/ui/ + src/lib/utils.ts.
- tsconfig.json — @/* path alias rooted at src/.
Removed:
- src/app/copilotkit-overrides.css (global override layer, superseded
by per-demo theming)
- src/app/api/copilotkit-mcp-apps/route.ts (replaced with
[[...slug]]/route.ts so v2 subpath POSTs like /v2/agent/run resolve)
Update scripts/docs to write directly into showcase/shell-docs/src/content/reference/
(consumed by /reference/[...slug]) instead of the legacy docs/content/docs tree.
Adds repo-root cwd anchoring, mkdir -p on the destination, allSettled so a single
missing source doesn't abort the run; generates 25 MDX pages (20 V1 + 5 SDK) and
shell-docs build passes.
Switch CLI command to `npx copilotkit@latest create --framework <id>` on all 8
framework quickstarts. Bypasses the unfiltered 18-framework picker and skips
the EIP prompt (which is mutually exclusive with --framework and today
scaffolds langgraph-python-threads regardless of which framework page the
user came from).
For LangGraph: keep an EIP callout since EIP=Yes does scaffold a LangGraph-
Python project today; note that threads support for other frameworks is
coming. For Microsoft Agent Framework and LangGraph (multi-variant): show
both --framework <variant> commands.
Apply the canonical V2 form:
- \`<CopilotKit>\` from \`@copilotkit/react-core\` (root, not /v2).
- styles from \`@copilotkit/react-core/styles.css\` (not react-ui/v2).
- drop \`@copilotkit/react-ui\` from npm install commands.
Add sample-audio-button region markers around the SampleAudioButton
component in the built-in-agent voice demo so the
<Snippet cell="voice" region="sample-audio-button" /> reference in
shell-docs/voice.mdx resolves to real code instead of a missing-snippet
warning.
Comment-only change; no runtime behavior change. Mirrors the canonical
in-place region pattern already used by langgraph-typescript, mastra,
ms-agent-python, claude-sdk-typescript, llamaindex, spring-ai,
pydantic-ai, claude-sdk-python, and crewai-crews per the Mastra Round
Pattern Decisions playbook.
## Summary
Three small pre-cutover cleanup fixes batched into one PR (consolidation
of the prior #4680, #4681, #4682 — same content, fewer review queues).
- `b3ec38720` — Add `<WhenFrameworkHas absent>` fallback to
`programmatic-control.mdx` for the 7 frameworks without
`interrupt_pattern` (ag2, agno, built-in-agent, crewai-crews,
google-adk, mastra, spring-ai), mirroring the canonical pattern from PR
#4496.
- `c080ca062` — Retarget `/migrate/1.10.X` redirect destination from
`/migrate` (which 404s) to `/migrate/v2`. `permanent: false` preserved.
- `e61e8c36d` — Add `mcp-server-setup.mdx` exclusion to docs sync
script. Shell-docs version is intentionally ahead of upstream (HTTP/SSE
Tabs + `mcp-remote` + Tadata callout); without exclusion the next sync
would clobber it.
Supersedes #4680, #4681, #4682.
## Test plan
- [ ] `nx run shell-docs:dev` and visit `/programmatic-control` while
switching the framework selector to non-native fws — verify fallback
callout renders and links to `/human-in-the-loop`.
- [ ] Visit `/migrate/1.10.X` — should redirect to `/migrate/v2` and
render the V2 migration page.
- [ ] Inspect `showcase/scripts/sync-docs-from-main.ts` PATH_EXCLUSIONS
for the `mcp-server-setup` regex.