Lands the Bucket A framework fix and the fixture-correctness changes
needed to flip the remaining D4/D2 cells in the langgraph-python column
to D5. Full local D5 sweep is green (1 passed, 0 failed).
Framework: every Python `ToolMessage` constructed via `Command(update=...)`
now sets `name=` and `id=str(uuid.uuid4())`. Without these, @ag-ui/langgraph
synthesises TOOL_CALL_START events with `toolCallName: null` and
`parentMessageId: null`, which @ag-ui/client@0.0.53's Zod schema rejects;
the rejection is silently swallowed by `withAbortErrorHandling -> EMPTY`,
completing the SSE observable mid-stream so post-tool state never reaches
the consumer. Fix is applied across shared_state_streaming, shared_state_read_write,
gen_ui_agent, beautiful_chat, subagents (2 sites). Single-flag change in
a2ui_dynamic flips the secondary `_design_a2ui_surface` LLM call to
`streaming=True` so aimock's record/replay (SSE-only) sees it.
Fixtures (d5-all.json):
- toolCallId follow-ups for set_steps (3), display_flight, generate_a2ui (4),
schedule_meeting (2), generateSandboxedUi (7), and revenue chart so
multi-turn probes don't recurse into recursion-limit loops
- four hand-crafted secondary `_design_a2ui_surface` fixtures so A2UI
dynamic renders without a real LLM
- mcp-apps fixture rewritten to emit `create_view` tool call with a
minimal Excalidraw element payload; runtime middleware fetches the UI
resource and the iframe mounts
- AAPL and revenue `hasToolResult: true` follow-ups tightened to
`toolCallId` so they don't match cross-turn after prior turns' tool
results
- voice fast-path content-only fixture
- beautiful-chat-schedule-meeting first-turn fixture gains content so
the conversation runner sees an assistant message before the picker
click assertion
Probes: bumped per-card waitForSelector in d5-gen-ui-headless-complete from
15s to 60s — recharts ResponsiveContainer can be slow under 4 sequential
turns.
Shell-dojo: hide CLI Start Command from the dojo navigation via
`HIDDEN_DOJO_FEATURE_IDS`. Registry/manifests untouched so
harness/parity/dashboard still see it.
/langgraph-python/frontend-tools referenced regions
frontend-tool-registration and frontend-tool-handler which were missing
markers on the production demo. Added in-place @region markers on the
existing useFrontendTool block in
showcase/integrations/langgraph-python/src/app/demos/frontend-tools/page.tsx
since the demo is a clean teaching example.
/langgraph-python/custom-look-and-feel/slots referenced regions
register-welcome-slot, register-assistant-message-slot, and
register-disclaimer-slot. The chat-slots production demo registers ~12
slot overrides at once with `as unknown as typeof X` casts that obscure
the per-pattern teaching shape, so added a sibling
slot-overrides.snippet.tsx file (mirrors the llamaindex chat-slots
sibling pattern) with the three minimal teaching regions.
Verified by re-bundling demo-content.json, running shell-docs production
build, and curling both pages on a local server: zero Missing snippet
boxes, and the region code (change_background, CustomWelcomeScreen,
CustomDisclaimer, CustomAssistantMessage) renders on the pages.
Three classes of regression are now pinned:
1. Secondary-LLM tool name doesn't collide with the A2UI middleware's
default intercept list (`render_a2ui`). New
`src/agents/test_a2ui_internal_tools.py` parametrises over
`beautiful_chat._design_a2ui_surface`, `a2ui_dynamic._design_a2ui_surface`,
and `a2ui_fixed.display_flight` and asserts none match the
middleware's `a2uiToolNames` default. Catches accidental rename
reverts that would re-enable the bypass.
2. `generate_a2ui` force-pins the canonical `catalog_id` even when the
secondary LLM hallucinates a wrong one. The new test stubs
`ChatOpenAI` with a fake response carrying a bogus catalogId and
asserts the surface op carries the module's `CUSTOM_CATALOG_ID`.
3. `generate_a2ui` short-circuits with a clean error string when the
LLM emits a root component without a `component` field — never
feeds the renderer the partial tree that surfaced as the "Cannot
create component root without a type" infinite-loop.
7 unit tests, all green locally (`pytest src/agents/test_a2ui_internal_tools.py`).
E2E tests on the same fixes now also assert:
- No `A2UI render error: Catalog not found` banner on the page after
Beautiful Chat → Sales Dashboard, Declarative Gen UI → BarChart, and
A2UI Fixed Schema → Find SFO → JFK round-trips.
- No `Cannot create component … without a type` banner on the same
three pills.
- Exactly ONE flight card on A2UI Fixed Schema (was 6+ on deploy
pre-fix from the `display_flight` loop) — `Flight Details` count
pinned to 1, `Book flight` count pinned to 1.
- At most one ResponsiveContainer on the BarChart pill (loops would
stack multiple).
- At most two ResponsiveContainers on Beautiful Chat → Sales Dashboard
(one pie + one bar = single dashboard render).
Two follow-up fixes layered on the previous internal-tool rename:
(1) `a2ui_fixed.py` — fixed-schema demo infinite loop on deploy. The
`display_flight` tool returns the raw `a2ui.render(...)` JSON descriptor
as its tool result. gpt-4o-mini reads that opaque blob, can't tell the
flight was rendered, and re-calls `display_flight` indefinitely (visible
on the deployed showcase as 6+ duplicate flight cards stacked under
repeated assistant text). Local was just lucky.
Hardened the docstring + system prompt to spell out: the JSON return
value is the surface descriptor, the card is already rendered, do NOT
call again, reply with one short confirmation and stop.
(2) Rename `render_a2ui` → `_design_a2ui_surface` in shared and
langgraph-python parity copies of `tools/generate_a2ui.py` (+
`tools/__init__.py` re-export `RENDER_A2UI_TOOL_SCHEMA` →
`DESIGN_A2UI_SURFACE_TOOL_SCHEMA`), and in `showcase/shared/typescript/
tools/generate-a2ui.ts`. These shared helpers were the source-of-truth
for the secondary-LLM tool name across integrations; renaming here keeps
parity with the langgraph-python agents already renamed in
`beautiful_chat.py` / `a2ui_dynamic.py`. Other framework integrations
keep their own `render_a2ui` for now (separate parity sweep).
(3) `showcase/aimock/feature-parity.json` — added a sibling fixture
matching `toolName: "_design_a2ui_surface"` for the beautiful-chat Sales
Dashboard pill so the langgraph-python e2e suite still hits a
deterministic mock on Railway. The original `render_a2ui` fixture is
kept above it so other integrations whose secondary LLM still requests
`render_a2ui` continue to match.
(4) Comment update in `beautiful-chat.spec.ts` to name the new internal
tool.
The A2UI middleware (`@ag-ui/a2ui-middleware`) defaults `a2uiToolNames` to
`["render_a2ui"]` and synthesises ACTIVITY_SNAPSHOT events from the
streaming tool-call args of any matching call — using the LLM's RAW
catalogId and components verbatim, before the Python tool body has a
chance to validate or normalise.
Both `beautiful_chat.py` and `a2ui_dynamic.py` use a `generate_a2ui` tool
that internally invokes a secondary LLM bound to a structured-output
`render_a2ui` helper. Because the helper's name matched the middleware's
default intercept list, the secondary LLM's hallucinated catalogId
(`declarative-gen-ui-catalog` leaking into the beautiful-chat dashboard)
and malformed root components (no `component` field on KPI dashboard)
were emitted to the frontend bypass, surfacing as:
- "A2UI render error: Catalog not found: declarative-gen-ui-catalog" on
the beautiful-chat Sales Dashboard pill
- "A2UI render error: Cannot create component root without a type"
infinite-loop on the declarative-gen-ui KPI Dashboard pill
The earlier force-pin of `catalog_id` and the defensive component sweep
in `generate_a2ui` were correct but ran too late — they execute on the
tool-result, after the middleware has already fired surface events from
the streaming args.
Fix: rename the internal helper to `_design_a2ui_surface` (and update
`tool_choice`, prompt header, and module docstring) so it falls outside
the middleware's intercept list. The explicit `a2ui.render(...)` ops the
outer `generate_a2ui` returns are then the only path to the frontend,
and our Python validation layer is authoritative.
Verified locally: Beautiful Chat → Sales Dashboard pill renders Total
Revenue / New Customers / etc. with no errors.
## Summary
- Beautiful Chat **Search Flights** pill: restored
`_build_flight_components` so the agent emits a flat literal-children
FlightCard tree (the structural-children template form via
`flight_schema.json` + `update_data_model` doesn't expand correctly
through GenericBinder for our custom catalog — it was working until the
recent flagship-cell pass swapped it back). FlightCards render again
instead of falling through to the default tool card.
- Beautiful Chat **Sales Dashboard** pill: hardened `generate_a2ui` so
the secondary LLM gets explicit catalog-id + component-shape rules and
the resulting `catalog_id` is force-pinned to the registered catalog.
Kills the "Catalog not found: declarative-gen-ui-catalog" sibling-demo
hallucination.
- **Declarative Gen UI** (`a2ui_dynamic.py`): same `generate_a2ui`
hardening, plus a defensive sweep that drops malformed components and
bails clean when the LLM omits a typed root. Kills the "Cannot create
component root without a type" infinite-loop renderer error reported on
the KPI Dashboard pill.
Two files touched (`src/agents/beautiful_chat.py`,
`src/agents/a2ui_dynamic.py`); no frontend / runtime / package changes.
Existing aimock fixtures in `feature-parity.json` already use the
canonical catalogId and well-formed components, so e2e specs should pass
without fixture changes.
## Test plan
- [x] Verified locally: `langgraph_cli dev` + `next dev`, clicked
**Search Flights** on `/demos/beautiful-chat` — Delta DL 405 ($329) and
United UA 120 ($289) FlightCards rendered live with airline, route,
times, status, Select.
- [x] Verified locally: clicked **Show a KPI dashboard** on
`/demos/declarative-gen-ui` — REVENUE / SIGNUPS / CHURN metric tree
rendered cleanly, no "Cannot create component root without a type"
error.
- [ ] CI / `nx run @copilotkit/showcase-langgraph-python:e2e`
(beautiful-chat spec already authored against the literal-children
form).
Three regressions, two files. Without these the Search Flights pill falls
through to the default tool card instead of rendering FlightCards, the
Sales Dashboard pill errors with "Catalog not found:
declarative-gen-ui-catalog", and the Declarative Gen UI cell loops on
"Cannot create component root without a type."
beautiful_chat.py — search_flights:
- Restore _build_flight_components and have search_flights emit the flat
literal-children component tree it produces, instead of the
flight_schema.json structural-children template (Row.children =
{ componentId, path: "/flights" }) plus update_data_model. The
GenericBinder doesn't reliably expand the structural form for our
custom FlightCard catalog — sibling demos avoid the form for the same
reason. The literal form has been the working shape since it was
introduced; the recent flagship-cell pass swapped it back to the
schema/data-model form and broke fixed-schema rendering.
- Make Flight TypedDict permissive (total=False) and drop the required
id / statusIcon fields. langchain rejected calls when the LLM (or
aimock fixture) omitted those, surfacing the validation string as the
tool result and never producing a surface.
beautiful_chat.py + a2ui_dynamic.py — generate_a2ui:
- Prepend a hard-requirements header to the secondary LLM's prompt
pinning the canonical catalogId and the component-shape contract
(every entry — including root — must carry both `id` AND `component`).
With injectA2UITool: false the runtime context alone leaves the LLM
enough room to hallucinate sibling-demo catalog IDs (e.g.
declarative-gen-ui-catalog leaking into the beautiful-chat dashboard)
and to omit `component` on the root entry (the source of the
"Cannot create component root without a type" infinite loop).
- Force catalog_id to the module-level CUSTOM_CATALOG_ID after the tool
call so any residual hallucination still routes to the registered
frontend catalog.
- Drop malformed component entries before constructing the operations
list, and bail with a clean error string if no valid root survives —
fail-soft instead of looping the renderer.
The renderer-side definitions.ts/renderers.tsx changes from the same
flagship pass are kept as-is; their Row/Column children union
(string-array OR { componentId, path }) is forward-compatible and
doesn't hurt the literal-children path.
The voice demo's <CopilotKit> didn't pass enableInspector, so
shouldShowDevConsole(undefined) defaulted to isLocalhost(), which
auto-mounts <cpk-web-inspector> on any local Docker host
(localhost:3100 in showcase compose). The inspector overlay
intercepts pointer events on top of the voice sample-audio button,
so dev/D5 probe runs can't click it through Playwright.
Production isn't localhost, so the inspector never mounts there —
voice is D5 in prod and D4 locally for this reason alone. Set
enableInspector={false} explicitly so the demo behaves the same in
both environments.
Probe result: d5:langgraph-python/voice flips green.
CR Round 3 final: my auth.spec.ts e2e was asserting on
[data-message-role="assistant"] which is the v1 react-ui RenderMessage
attribute. The auth demo uses v2 CopilotChat — its
CopilotChatAssistantMessage only emits data-testid="copilot-assistant-message".
The selector would never have matched and both tests would have
timed out at 30s when actually run.
Verified via grep:
- packages/react-core/src/v2/components/chat/CopilotChatAssistantMessage.tsx:192
emits data-testid="copilot-assistant-message" (no data-message-role)
- packages/react-ui/src/components/chat/messages/RenderMessage.tsx:32,41
emits data-message-role="user"/"assistant" (v1 path)
- All sibling specs in langgraph-python/tests/e2e/ correctly use
data-testid="copilot-assistant-message"
One-character switch from data-message-role to data-testid with the
canonical v2 testid value.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The auth demo refactor flipped its lifecycle: unauthenticated is now
the default state, <CopilotKit> only mounts after sign-in, and
sign-out unmounts the entire chat tree (instead of leaving stale
auth headers in a still-mounted chat). The old probe + e2e spec
chased a 401-error-banner surface that no longer exists in the new
demo, plus a brittle 500ms hardcoded `useEffect` flush wait.
Probe rewrite (`d5-auth.ts` + tests):
- Add `buildAuthPreFill` that clicks the SignInCard's sign-in button
before turn 1, then waits for the chat textarea to mount (proves
<CopilotKit> handshook with the runtime).
- `buildAuthAssertion` now clicks sign-out, then waits for SignInCard
to re-mount. The unmount marker IS the proof — no chat-send-and-401
dance is needed (or possible — there's no chat to send into).
- Drop the hardcoded 500ms setTimeout, the unauth-banner wait, and
the error-surface poll. None apply to the new flow.
E2E rewrite (`tests/e2e/auth.spec.ts`):
- "page loads unauthenticated with SignInCard visible"
- "signing in mounts the chat surface with AuthBanner"
- "authenticated send produces an assistant response"
- "signing out unmounts the chat tree and re-renders SignInCard"
- "signing back in re-mounts a fresh chat surface"
Fixture comment updated to reflect the new flow. The user message
("auth check turn 1") and content response are unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The headless-complete refactor replaced the old 5-chip set
(Weather/AAPL/Highlight/Sketch/Largest continent) with a new 4-chip
set wired through `useConfigureSuggestions`/SuggestionBar:
Weather → "What's the weather in Tokyo?" → WeatherCard
Stock price → "What's the price of AAPL right now?" → StockCard
Highlight a note → "Highlight this note for me: 'ship the demo on Friday'."
→ HighlightNote
Revenue chart → "Show me a chart of revenue over the last six months."
→ ChartCard
Probe rewrite:
- Drop the wrapper-scoped chip selector (`[data-testid=
"headless-suggestions"] >> text=...`) — the new SuggestionBar
doesn't have that container testid. Click via plain `button >>
text="<chip title>"` instead.
- Per-turn assert the matching tool card testid mounts
(headless-weather-card, headless-stock-card, headless-highlight-card,
headless-revenue-chart) plus a distinguishing text token. Driven
off a TURN_EXPECTATIONS table so adding a chip is one entry.
- Drop the readMessagesText helper that scoped to a missing
`[data-testid="headless-complete-messages"]` container; collect
text by concatenating all `headless-message-assistant` bubbles.
Fixture rewrite:
- Drop the Excalidraw + Largest-continent entries (no longer in the
chip set). Weather is already covered by feature-parity.json.
- Add a Revenue chart fixture for `get_revenue_chart` with inline
data so ChartCard renders without backend.
- Stock + Highlight fixtures preserved (still match the new chip
messages via substring).
Also restore `data-message-role` attributes on the headless-complete
UserBubble + AssistantBubble so the runner's settle plateau cascade
can resolve them — matches the runner's documented headless-template
contract.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The headless-simple demo was refactored to a deliberately minimal
"two hooks, one shadcn shell" template — text-in/text-out only, no
gen-UI. The D5 type literal `gen-ui-headless` no longer described
what the probe tests, and the old probe (Profile-card useComponent
+ continent fallback) was asserting against UI that no longer exists.
Three coordinated changes:
1. Rename `gen-ui-headless` D5FeatureType to `headless-simple` so the
slug matches the demo. Updated d5-registry, REGISTRY_TO_D5,
CATALOG_TO_D5_KEY (dashboard), and dependent tests/comments.
`headless-complete` keeps its existing literal because that demo
still drives the full gen-UI surface.
2. Replace d5-gen-ui-headless.{ts,test.ts,fixture} with
d5-headless-simple.ts + headless-simple.json fixture. New probe
clicks the "Say hello in one short sentence." chip and asserts the
`[data-testid="headless-message-assistant"]` bubble mounts with
non-empty content.
3. Restore `data-message-role` attributes on the headless-simple
UserBubble + AssistantBubble. The runner's chat-input cascade
documents these as the headless-template contract; the refactor
dropped them, breaking the runner's settle plateau detection.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The chat-slots refactor replaced the explicit `data-testid="custom-assistant-message"`
attribute with a SlotMarker wrapper component. The probe still asserted
the old testid which no longer exists, so the cell stuck at D4.
Add `data-slot-label={label}` to SlotMarker's outer span — idiomatic
data attribute that mirrors the existing `label` prop and gives the
probe a stable contract without restoring the legacy testid pattern.
The marker was already passing label="MessageView.AssistantMessage";
this just surfaces it in the DOM.
Update d5-chat-slots probe + test to assert
`[data-slot-label="MessageView.AssistantMessage"]`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two related changes targeting the fc=97 'Target page closed' D5 failure
on `beautiful-chat-toggle-theme`:
1. Probe (`_beautiful-chat-shared.ts:assertToggleTheme`) — replace the
manual 200ms `page.evaluate` poll over 30s with Playwright's
`waitForFunction`. The native polling is event-driven inside the
browser context, disconnects cleanly on page-close, and avoids
~150 round-trip evaluates per probe. The catch branch now checks
`page.isClosed()` first and surfaces a diagnostic message naming
the renderer-crash case explicitly, so operators don't have to
guess what 'Target page closed' meant.
2. Demo (`use-generative-ui-examples.tsx`) — drop `[theme, setTheme]`
from the `useFrontendTool` deps array. The handler reads `document`
directly and the setter is stable across renders, so the deps
array forced a re-registration after every theme flip. That race
could collide with an in-flight tool result and surface as a
renderer error during multi-turn sequences. Removing deps keeps
the tool registration stable for the duration of the conversation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
_invoke_sub_agent's last-resort branch silently returned '' or a
Python repr like \"[{'type': 'text', ...}]\" for block-list content,
which the UI would render as a blank/garbled card. Return a stable
SUB_AGENT_EMPTY_SENTINEL ('<sub-agent produced no output>') instead
so the d5-subagents probe can match it against its boilerplate-marker
list and fail the genuine-pass test loudly when a sub-agent produces
no usable output.
The get_stock_price tool returned randint-based price/change every
call, so e2e specs asserting $338.37 / -2.96% would only pass when
aimock short-circuited the entire call. The Python tool body still
runs server-side under aimock — only the LLM call is mocked.
Mirror the deterministic-`value` pattern on roll_d20: accept optional
price_usd / change_pct arguments and echo them back when present.
Defaults to random mock data when the args are omitted, preserving
the legacy live-LLM behaviour.
The HELLO_LEADING phrase was the showcase-assistant catch-all
boilerplate ('I can help you with weather lookups...') that other
tests in this PR explicitly guard AGAINST. The dedicated d5-all.json
fixture for 'Say hello in one short sentence' now returns a distinct
non-boilerplate greeting; the spec asserts that distinct phrase, so a
fixture-priority misroute fails loudly instead of passing by accident.
The blitz removed the explicit useDefaultRenderTool() call expecting a
framework-level fallback to handle zero-hook registrations, but the
integration uses the published @copilotkit/react-core@1.56.5 which does
not yet ship that fallback. Without the call, useRenderToolCall has no
'*' renderer and tool calls render invisibly — the user only sees the
agent's final text summary instead of the OOTB tool card.
Restore the explicit useDefaultRenderTool() invocation. The framework
fallback (committed in this PR but inert until react-core publishes a
release that includes it) becomes a no-op once that ships.
Resolved d5-all.json conflict by appending B6's headless-simple/complete fixtures (Say hello, joke, fun fact, Highlight, chart) before the tool-rendering and open-gen-ui entries already on integration.
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.
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).
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).
- 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.
- 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.
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.
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).
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/.