Commit Graph

8977 Commits

Author SHA1 Message Date
Alem Tuzlak f55edad5c1 fix(showcase/langgraph-python): chain reasoning-chain demo pills end-to-end
The tool-rendering-reasoning-chain demo previously promised chained tool
calls in its pill titles but the agent and fixtures only delivered single
tools — clicking "Weather + flights to Tokyo" produced just a WeatherCard,
"Compare two stocks" only fetched AAPL, "Find flights from SFO to JFK"
showed flights but no destination weather. Three changes close the gap.

Agent: replace the soft "call 2+ tools when relevant" system prompt with
concrete per-pill chain examples mirroring the pattern already used by the
langgraph-typescript `tool-rendering` agent (weather→flights, ticker→peer,
roll→contrast die, flights→destination weather).

Pills: drop the redundant Tokyo pill (it was the SFO/JFK chain in reverse)
and reword each remaining pill message to PRE-DISCLOSE the chain so the
model commits to the follow-up call:
  - "Compare AAPL and MSFT stocks for me."
  - "Roll a 20-sided die for me and compare it to a smaller one."
  - "Find flights from SFO to JFK and show me the weather there."

Fixtures: 9 fixtures (3 per pill: final-content → second-leg → first-leg,
ordered by toolCallId specificity for first-match-wins). Each fixture is
scoped by a langgraph-python-UNIQUE userMessage tail ("Compare AAPL and
MSFT stocks", "compare it to a smaller one", "show me the weather there").
Those substrings appear nowhere else across the 14+ integrations sharing
showcase-aimock on Railway, so the new fixtures cannot cross-contaminate
the other reasoning-chain demos that still ship the older prompt set.
A toolName-based gate was considered and rejected because most fleet
agents register `roll_dice` and aimock's `toolName` matcher is a tool-LIST
gate, not a tool-CALL gate — it would NOT have isolated this demo.

Probe: collapse the two-turn flow (Tokyo + SFO/JFK) into one chained turn
(SFO→JFK + JFK weather) that asserts BOTH per-tool renderers
(FlightListCard + WeatherCard) mount in a single response. Same coverage
at half the wall-clock and exercises the actual chained-tool path.
2026-05-12 17:18:46 +02:00
Alem Tuzlak 355681cd3a fix(runtime/langgraph): filter reasoning-role messages before AG-UI converter
`@ag-ui/langgraph`'s message converter handles only user/assistant/system/tool
roles and throws `"message role is not supported."` on anything else. Agents
that stream reasoning summaries (OpenAI Responses API + `reasoning={summary:
"detailed"}`) emit AG-UI messages with `role: "reasoning"` that the AG-UI
client replays in the next turn's `input.messages`; the converter then crashes
before the model is ever called and the second pill click in a multi-turn
thread produces an `INCOMPLETE_STREAM` error.

Strip `role: "reasoning"` from `input.messages` inside CopilotKit's
LangGraphAgent.run subclass before delegating to super. This is the narrowest
fix at the runtime/AG-UI boundary — only the inbound message list is filtered,
the outbound event stream still carries reasoning summaries to the client, so
the `<ReasoningBlock>` slot continues to render on the active turn.
2026-05-12 17:17:13 +02:00
Alem Tuzlak 189eca6872 fix(showcase/ms-agent-python): modernize to gpt-5.2, native reasoning + multimodal, fix multi-turn A2UI
Removes obsolete workarounds and uses MAF's native primitives where the
framework supports them. Surfaces the genuine gaps as targeted shims.

Reasoning
  - delete the no-op `think` tool; configure `reasoning={effort,summary}`
    on the Agent default_options. AG-UI bridge already emits real
    REASONING_MESSAGE_* events from the Responses API.
  - bump `@ag-ui/client` 0.0.43 -> 0.0.52 so the frontend event schema
    includes REASONING_MESSAGE_* (0.0.43 was still on the deprecated
    THINKING_TEXT_MESSAGE_* union).
  - reasoning-block.tsx -> consume ReasoningMessage via the
    `messageView.reasoningMessage` slot; default-render demo strips
    its `useRenderTool(think)` and uses zero-config CopilotChat.

Multimodal
  - delete the frontend LegacyConverterShim that rewrote modern
    `{type:"image"|"document", source:...}` parts to legacy `binary`.
    MAF's AG-UI adapter handles the modern shape natively.
  - delete the pypdf extraction subclass; gpt-5.2 reads PDFs natively
    via OpenAI's `input_file`. Drop `pypdf` from requirements.
  - retain a tiny 30-line `_MultimodalAgent` + adapter monkey-patch
    that copies `metadata.filename` into Content.additional_properties
    so OpenAI's `input_file` requirement is satisfied (upstream gap
    in agent_framework_ag_ui._message_adapters._parse_multimodal_media_part).

A2UI dynamic
  - fix the flat ops shape bug in `build_a2ui_operations_from_tool_call`:
    middleware expects v0.9 nested `{createSurface:{surfaceId,catalogId}}`,
    not flat `{type:"create_surface",surfaceId,...}`. Flat shape silently
    fell back to surface group "default" and never rendered.
  - secondary structured-output call: use chat_client.client (underlying
    AsyncOpenAI) directly to bypass MAF's function-invocation auto-loop;
    inherits api_key + model from the parent. response_format=PydanticModel
    is unsuitable (strict-mode rejects open dicts); a one-shot raw-args
    primitive doesn't exist in agent_framework today.
  - inject the registered A2UI catalog schema (49KB of Zod types) from
    `input_data.context[]` into the secondary call's system prompt so the
    LLM emits correct prop names. _A2UIDynamicAgent captures the schema
    on each run().
  - switch the OUTER agent to OpenAIChatCompletionClient. Responses API
    + tool calls + multi-turn fails ("No tool output found for function
    call ...") because reasoning items can't be replayed; chat.completions
    replays tool history cleanly.
  - defensive _reorder_tool_messages shim: the CopilotKit frontend message
    store ships [user, tool, assistant(toolCalls), assistant, user] on
    turn 2 (tool BEFORE its parent assistant), which OpenAI rejects.
    Walk inbound messages and re-attach tool messages immediately after
    their matching assistant.toolCalls[].id. (Should be fixed upstream
    in @copilotkit/react-core/v2.)
  - tighten the render_a2ui JSON schema (minItems:1, explicit
    items.properties, strict:false) and the prompt header so gpt-5.x
    actually emits non-empty components with entry-level props.
  - same A2UI bypass replacement in agent.py + beautiful_chat.py (shared
    pattern, three call sites of the original `from openai import OpenAI`).

Model bump
  - default OPENAI_CHAT_MODEL_ID -> gpt-5.2 (Responses-capable reasoning
    model). Scoped clients: reasoning agent on gpt-5.2 + Responses;
    a2ui_dynamic on gpt-5.2 + chat.completions (avoids reasoning replay).

Upstream gaps surfaced (recommend follow-up PRs)
  - agent_framework_ag_ui: propagate part.metadata.filename to
    Content.additional_properties["filename"] in
    _parse_multimodal_media_part. (Removes the PDF subclass.)
  - agent_framework: a one-shot tool-args API
    (e.g. get_response(..., auto_invoke=False)) or a cleanly exported
    RawOpenAIChatClient. (Removes the A2UI raw-SDK bypass.)
  - @copilotkit/react-core/v2: stop reordering role=tool messages
    before their assistant parent in the frontend message store.
    (Removes the A2UI message-reorder shim.)
2026-05-12 15:06:11 +02:00
Tyler Slaton c576716bb1 fix(showcase/aimock): chain tool-rendering follow-ups via toolCallId so multi-pill sessions work (#4770)
## Summary

Multi-pill sessions in the showcase demos were silently breaking because
aimock fixtures gated their tool-emit vs. follow-up branches on global
thread state (`hasToolResult`, `turnIndex`). Once the user clicked a
tool-using pill, the thread already had tool/assistant messages so the
next pill's first-leg fixture got skipped and the matcher fell through
to the wrong branch.

Concrete failures Alem reported:
- **tool-rendering**: Find flights then Roll a d20 rendered only 3 d20
cards instead of 5; Chain tools emitted zero cards and went straight to
"Done — Tokyo is sunny…".
- **frontend-tools-async**: First pill rendered the NOTES DB card;
second and third pills only rendered narration with no card.
- **hitl-in-app**: After approving the first pill the second pill
skipped its approval dialog entirely.

## Fix

- Re-key every follow-up fixture on the prior step's `toolCallId` (the
matcher checks `messages[last].tool_call_id`).
- Drop `hasToolResult: false` from the tool-emitting fixtures so they
fire on any iteration whose last message is the user prompt.
- Reorder so the `toolCallId`-specific fixtures come first under
first-match-wins.
- `userMessage` matchers are unchanged — fixtures still match the same
prompt text they always did.

D20 becomes a linear `toolCallId` graph (`seq_001 → _002 → … → _005`);
Chain tools gets disambiguator fixtures for each of its three parallel
`tool_call_id`s; Weather / AAPL / query_notes / HITL approve+reject
branches all gate on the specific `request_user_approval` /
`get_weather` / `query_notes` / `get_stock_price` id that landed last.

## Regression coverage

Adds a multi-pill Playwright test to each affected spec — clicks every
pill sequentially in one thread and asserts the full card count for
each:
- `tool-rendering-default-catchall`: Find flights → 5 d20 rolls (with 20
last)
- `tool-rendering-custom-catchall`: 1 flights + 5 d20 + 3 chain = 9
wildcard cards
- `frontend-tools-async`: 3 NOTES DB cards with the right keyword per
pill
- `hitl-in-app`: refund approve then escalate, each with its own
approval dialog

## Test plan
- [x] Verified each fix end-to-end via Playwright MCP against local dev
server
- [ ] CI runs the new regression specs against the canonical Docker
stack
- [ ] Existing aimock fixture validation suites still pass (verified
locally: 21/21)
2026-05-11 11:56:25 -07:00
Ran Shemtov 3552bdd482 Docs(Deepagents): Removed Checkpointer in Python code snippet (#4678)
## Summary
In LangGraph API, persistence is handled automatically by the platform,
so providing a custom checkpointer throws an Application Failure error.
Therefore, it had to be removed

## What does this PR do?

- Removed ```checkpointer=MemorySaver()``` from python code snippet in
section (Initialize Your Agent Project, and also removed its relevant
import

## Checklist

- [x] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [x] If the PR changes or adds functionality, I have updated the
relevant documentation
- [x] "Allow edits by maintainers" is checked (lets us help iterate on
your PR directly — faster turnaround for everyone)
2026-05-11 20:29:05 +02:00
Ran Shemtov ba97024604 Merge branch 'main' into docs(deepagents)/removed-checkpointer-in-python 2026-05-11 20:28:24 +02:00
Alem Tuzlak b941298cc1 fix(showcase/aimock): chain tool-rendering follow-ups via toolCallId so multi-pill sessions work
The tool-rendering, frontend-tools-async, and hitl-in-app fixtures all gated
their first-leg (tool-emitting) vs. follow-up (narration) responses on
`hasToolResult: false/true` and/or `turnIndex`. Those constraints count the
*entire* thread, so once a user clicked a tool-using pill the thread already
contained tool messages and assistant turns and subsequent pill clicks fell
through to the wrong branch — d20 dropped from 5 rolls to 3, Chain tools
emitted no cards, query_notes returned narration without the Notes DB card,
and the second HITL pill never raised an approval dialog.

Re-key every follow-up fixture on the prior step's `toolCallId` (the matcher
checks `messages[last].tool_call_id`), drop the global `hasToolResult` gates
from the tool-emitting fixtures, and reorder so the toolCallId-specific
fixtures come first under first-match-wins. The d20 chain becomes a linear
toolCallId graph (`call_tr_d20_seq_001` → `_002` → … → `_005`), Chain tools
gets disambiguators for each of its three parallel tool_call_ids, and
Weather/AAPL/query_notes/HITL approve+reject branches all gate on the
specific request_user_approval / get_weather / query_notes / get_stock_price
id that landed last. userMessage matchers are unchanged.

Adds Playwright multi-pill regression tests to the four affected demos that
click every pill sequentially in one thread and assert the full card counts:
- tool-rendering-default-catchall: Find flights → 5 d20 rolls (with 20 last)
- tool-rendering-custom-catchall: 1 flights + 5 d20 + 3 chain = 9 cards
- frontend-tools-async: 3 NOTES DB cards with the right keyword per pill
- hitl-in-app: refund approve then escalate, each with its own dialog
2026-05-11 20:21:45 +02:00
Alem Tuzlak 42d71ce4b2 fix(showcase): unbreak open-gen-ui-advanced interactive pills + add regression coverage (#4766)
## Summary

- **Fix**: the three interactive Open-Generative-UI (Advanced) pills
(Calculator, Ping the host, Inline expression evaluator) shipped HTML +
CSS only in `showcase/aimock/d5-all.json`. The buttons rendered but
every click was a silent no-op because the fixtures never included
`jsFunctions` to wire up the host-bridge calls
(`Websandbox.connection.remote.evaluateExpression` / `notifyHost`).
Restores the missing JS.
- **Two-layer regression guard** so this can't land silently again:
1. `showcase/scripts/__tests__/open-gen-ui-advanced-fixtures.test.ts`
(vitest, runs on every PR in `showcase_validate`) — asserts each
interactive fixture entry references the right host bridge in its
`jsFunctions`.
2.
`showcase/integrations/langgraph-python/tests/e2e/open-gen-ui-advanced.spec.ts`
(playwright, runs in `test_e2e-showcase-on-demand`) — drives the
in-iframe controls, captures the host handler's `console.log`, and
asserts the iframe output reflects the host response.

The existing e2e smoke tests are kept (still useful) but switched from
pill-click to textarea fill+Enter, following the precedent set by commit
15db0bbf3 (`gen-ui-headless-complete`). Combined with
`[data-testid="copilot-chat-textarea"]`, an explicit click, and
`waitForLoadState("networkidle")` in `beforeEach`, the suite is no
longer flaky against React hydration.

## Why fixture-only fix is sufficient

The agent system prompt (`open_gen_ui_advanced_agent.py:71`) tells a
real LLM to emit `jsFunctions`. A real model does. The fixtures simulate
that contract — they just hadn't been updated to. No agent code or
framework code needed to change; the runtime already injects
`jsFunctions` into the Websandbox correctly
(`packages/react-core/src/v2/components/OpenGenerativeUIRenderer.tsx`
Effect 2).

## Test plan

- [x] `pnpm --filter @copilotkit/showcase-scripts test
open-gen-ui-advanced-fixtures` — 3/3 passing
- [x] Full `open-gen-ui-advanced.spec.ts` playwright suite,
aimock-driven, locally — 7/7 passing (4 smoke + 3 round-trip) in ~28s
- [x] Manual: Calculator builds + evaluates, Ping the host updates
output with `host replied at ...`, Inline evaluator returns `= <value>`
- [ ] `/test-aimock langgraph-python` once this PR is open (validates
against the CI aimock stack)
2026-05-11 17:44:48 +02:00
github-actions[bot] 6823c58832 style: auto-fix formatting 2026-05-11 15:36:12 +00:00
Alem Tuzlak 6426281b74 test(showcase): regression coverage for open-gen-ui-advanced sandbox-function round-trips
The previous fixture regression (HTML+CSS only, no jsFunctions) slipped
past CI because the e2e suite only asserted "iframe mounts with non-empty
srcdoc" — which passes whether or not the iframe is interactive. Adds
two layers of guard so the same regression cannot land silently:

1. showcase/scripts/__tests__/open-gen-ui-advanced-fixtures.test.ts
   (vitest, runs in showcase_validate on every PR): asserts each of the
   three interactive fixture entries in d5-all.json ships jsFunctions
   referencing the matching host bridge (evaluateExpression / notifyHost).
   Catches "someone removed jsFunctions" at PR-time with no
   infrastructure dependencies.

2. showcase/integrations/langgraph-python/tests/e2e/open-gen-ui-advanced.spec.ts
   (playwright, runs in test_e2e-showcase-on-demand): adds three
   round-trip tests that drive the in-iframe controls and assert the
   host-side handler ran by capturing its console.log + verifying the
   iframe output element reflects the host response. Catches "the
   renderer fails to inject jsFunctions into the sandbox" too.

The e2e tests also switch the existing smoke tests off pill-click and
onto a textarea-driven fill+Enter path, following the same precedent as
commit 15db0bbf3 (gen-ui-headless-complete) — chip mounts diverge
between EmptyState and SuggestionBar surfaces, and Playwright's pill
click races React hydration. Using [data-testid="copilot-chat-textarea"]
with an explicit click + waitForLoadState("networkidle") makes the
suite reliable end-to-end (7/7 passing locally against the aimock-driven
stack).
2026-05-11 17:34:21 +02:00
Alem Tuzlak 2db696dbd6 fix(showcase/aimock): wire jsFunctions into open-gen-ui-advanced fixtures
The three interactive Open-Generative-UI (Advanced) pills — Calculator,
Ping the host, and Inline expression evaluator — shipped HTML + CSS
only in d5-all.json. The agent system prompt instructs the LLM to also
emit jsFunctions, but the fixtures didn't, so every in-iframe button
became a silent no-op: the iframe rendered, clicks dispatched no events,
and the sandbox-function bridges to evaluateExpression / notifyHost
were never exercised.

Adds the missing jsFunctions to all three fixtures, using single-quoted
JS so no escaping is needed inside the JSON-stringified tool arguments.
Each handler respects the sandbox="allow-scripts"-only iframe constraints
(no <form>, plain addEventListener) and calls back into the host via
Websandbox.connection.remote.<name>, with the expected return-shape
contract (res.ok + res.value / res.receivedAt).
2026-05-11 17:33:59 +02:00
Alem Tuzlak 30aff2b7e3 fix(showcase/langgraph-python): remove dead "Show reasoning" pill from chat-slots (#4765)
## Summary

- The chat-slots cell is wired to the neutral `sample_agent` graph
(plain `ChatOpenAI`, no Responses API, no reasoning config), so it never
emits AG-UI `REASONING_MESSAGE_*` events. The "Show reasoning"
suggestion pill therefore had no way to light up the wrapped
`messageView.reasoningMessage` slot.
- Its prompt — `"Think out loud step by step about whether 17 is prime,
then answer."` — also wasn't a substring of any `userMessage` in
`showcase/aimock/d5-all.json`, so aimock-backed runs returned `404 "No
fixture matched"` rather than the intended reasoning fixture.
- `qa/chat-slots.md` and `tests/e2e/chat-slots.spec.ts` already only
assert the two remaining pills ("Write a sonnet" / "Tell me a joke"), so
dropping the third pill aligns the demo with what the QA + e2e already
expect.

The `CustomReasoningMessage` slot wrapping is intentionally left in
place — the chat-slots page is a "Slot Atlas" where every overrideable
slot is wrapped, even if dormant for this cell. Live reasoning rendering
already has its own dedicated demos at `/demos/reasoning-default` and
`/demos/reasoning-custom`, which use the `reasoning_agent` graph
(Responses API + `reasoning={"effort":"medium","summary":"detailed"}`).

## Test plan

- [ ] `/demos/chat-slots` renders exactly two suggestion pills: "Write a
sonnet" and "Tell me a joke"
- [ ] Both pills produce an assistant reply wrapped in the
`CustomAssistantMessage` slot marker
- [ ] No console errors or missing-fixture 404s appear during the flow
- [ ] `/demos/reasoning-default` and `/demos/reasoning-custom` still
light up the reasoning slot (unchanged here, but worth a sanity check)
2026-05-11 17:26:35 +02:00
Alem Tuzlak 792ae78799 fix(showcase/langgraph-python): remove dead "Show reasoning" pill from chat-slots
The chat-slots cell is wired to the neutral sample_agent graph (plain
ChatOpenAI, no Responses API, no reasoning config), so it never emits
AG-UI REASONING_MESSAGE_* events. The pill could never light up the
wrapped messageView.reasoningMessage slot, and its prompt didn't match
any fixture in showcase/aimock/d5-all.json — aimock-backed runs hit
"No fixture matched". Drop the pill (the QA doc and the e2e spec
already only expect "Write a sonnet" and "Tell me a joke") and leave a
note pointing reasoning demos at /demos/reasoning-default and
/demos/reasoning-custom where the dedicated reasoning_agent graph lives.
2026-05-11 17:15:24 +02:00
Martha Kelly Schumann 5da2a66815 Merge branch 'main' into docs(deepagents)/removed-checkpointer-in-python 2026-05-11 08:07:54 -07:00
Alem Tuzlak 4b67b4c95b fix(showcase/langgraph-python): widen declarative-gen-ui cards + polish InfoRow (#4763)
## Summary

- Widen the `declarative-gen-ui` demo so generated A2UI cards (KPI
dashboards, charts, status reports) actually have room to breathe. Outer
page wrapper goes `max-w-4xl` → `max-w-6xl`, and a scoped attribute
selector lifts the chat's internal `cpk:max-w-3xl` to 64rem **for this
demo only** — no impact on other demos.
- Fix the `InfoRow` trailing-separator artifact. Each row now draws its
own `border-b` with `last:border-b-0 last:pb-0 first:pt-0`, so the final
row in a Card (most visible on the **Status Report** suggestion pill) no
longer leaves a dangling line. Works regardless of whether the agent
wraps rows in a Column or drops them directly into the Card child slot.
- `Card` gains `w-full overflow-hidden` so it stretches into the wider
column; `InfoRow` value gets `text-right tabular-nums` for cleaner
alignment in stacks.

Scoped to the north-star (`showcase/integrations/langgraph-python`) only
— will propagate to the other integrations via `pnpm parity:sync` in a
follow-up.

## Why

Clicking the suggestion pills (KPI dashboard, pie chart, bar chart,
status report) rendered cards inside a 768px column even though the page
wrapped at 1024px. The bottleneck was the chat's internal
`cpk:max-w-3xl`. Status Report also looked busy because every `InfoRow`
rendered a full-width divider after itself, including the last row.

## Test plan

- [ ] `pnpm --filter @copilotkit/showcase-langgraph-python dev`, open
`/demos/declarative-gen-ui`
- [ ] Click each suggestion pill (KPI dashboard, pie chart, bar chart,
status report); confirm cards have noticeably more horizontal room
- [ ] Confirm the last row in **Status Report** has no trailing divider
- [ ] Confirm other demos in the same integration are visually unchanged
(the override is scoped to `.declarative-gen-ui-wide`)
2026-05-11 16:57:04 +02:00
Alem Tuzlak a9e3eacad3 ci: retrigger vercel deployments after infra flake 2026-05-11 16:35:38 +02:00
Ben Taylor 1defc550bf chore(docs): Remove old low-level threads/persistence pages (#4764)
## Summary
- Delete the legacy `threadId`/`setThreadId` docs under
`docs/content/docs/integrations/langgraph/advanced/persistence/` and
`docs/content/docs/integrations/crewai-flows/persistence/` — superseded
by the `useThreads`-based `/langgraph/threads` and
`/crewai-flows/threads` pages that share
`docs/snippets/shared/threads/threads.mdx`.
- Drop `"persistence"` from the CrewAI Flows sidebar
(`docs/content/docs/integrations/crewai-flows/meta.json`); LangGraph's
`advanced/meta.json` auto-lists, so no sidebar edit needed there.
- Redirect every deleted path — plus the legacy
`/langgraph/persistence/*` aliases and the `/coagents/persistence`
middleware entry — to the corresponding `/{framework}/threads` page so
existing inbound links don't 404. The pre-existing `/coagents/:path*`
and `/crewai-crews/:path*` wildcards chain through to these.

No content in `docs/content`, `docs/snippets`, or `docs/src` still
references the removed paths.

## Test plan
- [ ] `pnpm --filter docs build` succeeds
- [ ] `/langgraph/threads` and `/crewai-flows/threads` still render
- [ ] Hitting `/langgraph/advanced/persistence/loading-message-history`,
`/langgraph/persistence/message-persistence`,
`/crewai-flows/persistence/loading-agent-state`, and
`/coagents/persistence` lands on the appropriate `/{framework}/threads`
page
- [ ] CrewAI Flows sidebar no longer shows "Persistence"

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-05-11 09:25:58 -05:00
Alem Tuzlak 9bbcf876d8 fix(showcase/langgraph-python): drop nested-flex-gap arbitrary variant
The `[&_div[style*='flex-direction:_row']]:gap-4` arbitrary variant
(quotes inside doubly-nested brackets) is the most exotic Tailwind
syntax in this PR and lines up exactly with when the Vercel
form-filling deploy started failing. Tailwind v4's content scanner is
likely choking on the apostrophes in the nested attribute selector.

The Metric `flex-1 min-w-[120px]` and the chart `flex-1 min-w-0`
already give us even distribution inside the basic catalog's gap-less
Row; the auto-injected nested gap was nice-to-have, not load-bearing.
2026-05-11 16:25:17 +02:00
Benjamin Taylor 84c029529d docs: remove old low-level threads/persistence pages superseded by useThreads
The /langgraph/advanced/persistence/* and /crewai-flows/persistence/* pages
documented the older threadId/setThreadId mechanism. They're replaced by the
useThreads-based /langgraph/threads and /crewai-flows/threads pages backed by
the shared threads snippet.

Adds redirects from the deleted paths (and their legacy /langgraph/persistence
aliases) to the new framework-scoped /threads pages, and drops "persistence"
from the crewai-flows sidebar.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 09:04:14 -05:00
Alem Tuzlak cc6dd5c4a9 Merge branch 'main' into fix/showcase-declarative-gen-ui-card-width 2026-05-11 16:03:32 +02:00
Alem Tuzlak ba5369c930 refactor(showcase/langgraph-python): make A2UI renderers fill their slot instead of widening the chat
Drop the inline <style> override that widened the chat's `cpk:max-w-3xl`
column and the outer max-w-6xl bump. The chat keeps its normal width;
the real bug was that the basic catalog's Row/Column primitives are
bare `display: flex` divs with no gap and no min-width control on
children, so when the agent dropped multiple Metrics or charts into a
Row they collapsed to content width and looked glued together.

Four targeted fixes inside our renderers:

- Metric gains `flex-1 min-w-[120px]` so a row of KPI tiles distributes
  the available width evenly inside the gap-less basic Row, instead of
  shrinking to content.
- PieChart and BarChart switch from a hardcoded max-w to `flex-1
  min-w-0` so two charts side-by-side each take half the card column
  with Recharts' ResponsiveContainer doing the rest, instead of one
  chart insisting on 640px and overflowing.
- Card's CardContent picks up a Tailwind arbitrary variant
  `[&_div[style*='flex-direction:_row']]:gap-4` (plus the column
  equivalent) that injects a gap into any nested basic Row/Column the
  agent drops in. Underscores in the arbitrary value compile to literal
  spaces, matching React's serialized inline `flex-direction: row`.
- Card itself drops the old `min-w-[260px]` floor in favour of
  `min-w-0` so it cooperates if the agent ever stacks Cards horizontally
  inside a Row.
2026-05-11 16:01:11 +02:00
Alem Tuzlak bedc53a652 fix(showcase/langgraph-python): widen declarative-gen-ui surface and polish InfoRow
The chat shell caps its scroll column at cpk:max-w-3xl (~768px), which
left A2UI-generated cards (KPI dashboards, charts, status reports)
feeling pinched on the declarative-gen-ui demo. Locally widen that
wrapper to 64rem via a scoped attribute selector on the demo and bump
the outer page wrapper from max-w-4xl to max-w-6xl so the card column
actually has room to grow.

While here, fix the InfoRow trailing-separator artifact: each row now
draws its own border-bottom with last:border-b-0 so the final row in a
Card (e.g. the Status Report demo) no longer leaves a dangling line,
regardless of whether the agent wraps the rows in a Column or drops
them directly into the Card child slot. Right-align the value with
tabular-nums for cleaner stacks. Card itself gains w-full
overflow-hidden so it stretches into the now-wider column instead of
sitting at its min-width.
2026-05-11 15:42:06 +02:00
Alem Tuzlak 5d4dd61556 fix(showcase/mcp-apps): route "Draw a flowchart" pill to create_view fixture (#4762)
## Summary

- The mcp-apps demo's "Draw a flowchart" suggestion pill silently broke:
clicking it produced a generic "Here is my plan..." response with no MCP
tool call, so the sandboxed iframe never mounted.
- Root cause: the verbatim pill prompt `"Use Excalidraw to draw a simple
flowchart with three steps."` had no matching `create_view` fixture in
`d5-all.json`. aimock's first-match-wins substring matcher fell through
to `feature-parity.json`'s `{userMessage: "steps"}` content-only
catch-all (matches "with three **steps**.").
- Fix: add a fixture pair keyed on the distinctive substring `"draw a
simple flowchart"` to `d5-all.json` and its mirror in
`harness/fixtures/d5/mcp-apps.json`. Turn 1 emits `create_view` with a
Start → Process → End flowchart; turn 2 (post tool result) emits the
narration.
- Regression guard: a fast vitest under `showcase/scripts/__tests__/`
loads fixtures in the same order as `docker-compose.local.yml` and uses
aimock's `matchFixture` to assert each mcp-apps pill routes to its
`create_view` fixture on turn 1 and its narration fixture on turn 2. The
test fails loudly with a diagnostic naming the absorbing fixture if the
bug returns (verified by reverting the fix locally).

## Test plan

- [x] `pnpm --filter @copilotkit/showcase-scripts test
__tests__/mcp-apps-suggestion-routing.test.ts` — 3 tests, ~260 ms, all
green.
- [x] `pnpm --filter @copilotkit/showcase-scripts test
__tests__/aimock-fixtures.test.ts` — fixture files still validate
cleanly with the new entries.
- [x] Reverted just the two new fixtures, re-ran the regression test,
confirmed it fails with a diagnostic that explicitly names the wrong
fixture (`feature-parity.json`'s `{userMessage: "steps"}` content
blurb).
- [x] Local docker stack (`bin/showcase up langgraph-python`) + browser:
clicked "Draw a flowchart" at http://localhost:3100/demos/mcp-apps —
agent emits `create_view`, MCP middleware fetches the Excalidraw UI
resource, sandboxed iframe mounts inline, narration arrives.
- [x] aimock direct POST to `/v1/chat/completions` confirms both legs:
- Turn 1 (pill + `create_view` in tools[]) → `tool_calls: [{ name:
"create_view", id: "call_d5_mcp_apps_create_view_flowchart_001", ...
}]`.
- Turn 2 (with tool result appended) → `content: "Drew a three-step
flowchart (Start → Process → End)..."`.
2026-05-11 15:26:43 +02:00
Alem Tuzlak c99bea6670 fix(showcase/mcp-apps): route "Draw a flowchart" pill to create_view
The "Draw a flowchart" suggestion pill in the mcp-apps demo sent
"Use Excalidraw to draw a simple flowchart with three steps." which
had no matching create_view fixture in d5-all.json. aimock walked
through to feature-parity.json's `{userMessage: "steps"}` substring
fixture and returned a generic "Here is my plan..." content blurb
with no MCP tool call, so the runtime never invoked create_view, the
MCP middleware never fetched the UI resource, and the sandboxed
iframe never mounted.

Add a fixture pair in d5-all.json (and its harness mirror) keyed on
"draw a simple flowchart": turn 1 emits create_view with a three-
step Start -> Process -> End flowchart, turn 2 emits the narration
after the tool result. The distinctive substring beats the generic
feature-parity catch-alls under first-match-wins.

Adds a regression test that loads the same fixture files in the same
order as docker-compose.local.yml and asserts via aimock's matchFixture
that each mcp-apps pill routes to its create_view fixture on turn 1 and
its narration fixture on turn 2.
2026-05-11 15:21:18 +02:00
Alem Tuzlak 25f1f921a8 fix(showcase): unbreak multimodal demo end-to-end (auto-send, dedupe, proxy) (#4761)
## Summary

Re-lands the multimodal-attachments fix from #4584 (May 1, never merged)
onto current `main`, ported to the post-refactor file layout where
`page.tsx` was split into `legacy-converter-shim.tsx`,
`multimodal-chat.tsx`, and `file-to-data-attachment.ts`.

Auto-send was the visible regression: clicking **Try with sample image /
Try with sample PDF** only queued the attachment chip instead of sending
the canned prompt. This PR restores the full end-to-end behavior plus
five regression tests so it can't silently break again.

## What was broken and what changed

1. **Random uploads crashed with `Failed to fetch`.** aimock returned
HTTP 404 on no-match, the LangGraph SDK surfaced `NotFoundError`, the
AG-UI stream surfaced a `RUN_ERROR`, the demo crashed. → Added
`--proxy-only` + `--provider-openai https://api.openai.com` to the local
aimock command so unmatched user prompts fall through to real OpenAI
(mirrors Railway).

2. **Bundled-sample fixtures keyed on user-visible canned prompts.**
Auto-prompts are now natural and specific ("can you tell me what is in
this demo image/pdf I just attached") so they render cleanly as the user
message bubble AND can't collide with arbitrary user prompts — random
uploads phrase questions differently and fall through to the proxy.

3. **Sample buttons now auto-send via `useAgent`.** The previous
DataTransfer path queued the attachment via the chat's hidden file input
but required clicking send while the attachment was still uploading —
`CopilotChat.onSubmitInput` rejects submits during upload AND clears the
input regardless, so the canned prompt was eaten. Rewrite calls
`agent.addMessage(...)` + `copilotkit.runAgent({ agent })` directly with
the base64'd content part.

4. **PDF flattened text bled into the rendered user message.**
`_PdfFlattenMiddleware` ran in `before_model` and persisted the rewrite
to agent state. Switched to `wrap_model_call` so the PDF→text rewrite is
scoped to the model request only.

5. **Attachments doubled (and PDFs rendered as broken `<img>`).** The
`@ag-ui/langgraph` round-trip mis-tags PDFs as `image` and re-injects
the user's original modern part, doubling chips. Added
`dedupeUserMessageMedia` subscriber on `onMessagesSnapshotEvent` +
`onRunFinalized` to dedupe by `source.value` and re-key type from
mimeType. Also flipped `onRunInitialized` from REPLACE to APPEND so the
modern part stays for the UI alongside a legacy `binary` sibling for the
converter.

6. **Regression suite (`tests/e2e/multimodal.spec.ts`).** Five focused
tests, all pass against live local stack (15.4s):
   - page loads with all expected affordances
- sample image: auto-sends, EXACTLY ONE `<img>`, assistant references
the logo
- sample PDF: auto-sends, EXACTLY ONE `DocumentAttachment` chip ("PDF"
label), NO `<img>`, no `[Attached document]` text bleed
- image then PDF in the same session: each message keeps its own single
chip
   - PDF then image in the same session: symmetric

## Test plan

- [x] `showcase up langgraph-python` — both sample buttons auto-send;
image renders as `<img>`, PDF renders as PDF chip; random paperclip
uploads go through proxy
- [x] `BASE_URL=http://localhost:3100 CI=1 npx playwright test
multimodal.spec.ts` — **5 / 5 passing**
- [ ] Post-merge: e2e-deep cycle for langgraph-python multimodal cell
stays green

## Closes

Closes #4584.
2026-05-11 15:19:34 +02:00
github-actions[bot] 8968007188 style: auto-fix formatting 2026-05-11 12:57:34 +00:00
Alem Tuzlak 7c3edca2b7 fix(showcase): unbreak multimodal demo end-to-end (sample buttons auto-send, dedupe, proxy)
The langgraph-python multimodal-attachments demo had a stack of bugs
that compounded each other. Fixing them required touching the local
docker-compose, the aimock fixtures, the LangChain middleware, the
client-side AG-UI shim, and the sample-attachment buttons. This
commit lands the full set together because they only make sense as
a unit — verified end-to-end against `showcase up langgraph-python`
in a headed browser. New e2e suite pins each regression.

Supersedes #4584 (the original fix from May 1 that never landed —
this is a fresh port onto the post-refactor file layout where
page.tsx is split into legacy-converter-shim.tsx, multimodal-chat.tsx,
file-to-data-attachment.ts).

What was broken and what changed:

1. Random uploads crashed with `Failed to fetch`. aimock returned
   HTTP 404 on no-match, the LangGraph SDK surfaced `NotFoundError`,
   the AG-UI stream surfaced a `RUN_ERROR`, the demo crashed.
   Added `--proxy-only` + `--provider-openai https://api.openai.com`
   to the local aimock command so unmatched user prompts fall through
   to real OpenAI (mirrors the Railway aimock setup).

2. Bundled-sample fixtures keyed on user-visible canned prompts.
   The auto-prompts are deliberately long, specific, and natural-
   reading ("can you tell me what is in this demo image/pdf I just
   attached") so they (a) render cleanly as the user message bubble,
   and (b) can't collide with arbitrary user prompts — random
   uploads phrase questions differently and fall through to the
   proxy.

3. Sample buttons now auto-send via `useAgent`. The previous
   DataTransfer-based path queued the attachment via the chat's
   hidden file input, then required clicking send while the
   attachment was still uploading — `CopilotChat.onSubmitInput`
   rejects submits during upload AND clears the input regardless,
   so the canned prompt was eaten. Rewrite to call
   `agent.addMessage(...)` + `copilotkit.runAgent({ agent })`
   directly with the base64'd content part, sidestepping the
   upload race entirely.

4. PDF flattened text bled into the rendered user message.
   `_PdfFlattenMiddleware` ran in `before_model` and returned
   `{"messages": rewritten}`, which persisted to agent state. The
   chat UI then rendered the `[Attached document]\n<pdf body>` text
   part inline with the user prompt. Switched to `wrap_model_call`
   so the PDF→text rewrite is scoped to the outgoing model request
   only and never pollutes state.

5. Attachments doubled (and PDFs rendered as broken `<img>`). The
   `@ag-ui/langgraph` round-trip translates outgoing `binary` parts
   to LangChain `image_url` and incoming `image_url` back to `image`
   AG-UI parts — regardless of mimeType, so PDFs came back as
   `type: "image"` with `mimeType: "application/pdf"` and were
   forced into `ImageAttachment`, where the load failed and the
   chat showed two "Failed to load image" boxes. Plus the user's
   original modern part survived alongside the round-tripped one,
   doubling visible chips.

   Added a `dedupeUserMessageMedia` subscriber on both
   `onMessagesSnapshotEvent` and `onRunFinalized` to:
   - dedupe media parts by `source.value` so the local + round-
     tripped copy collapse to one chip
   - re-key part `type` from `mimeType` so PDFs route to
     `DocumentAttachment` (icon + filename) and images to
     `ImageAttachment`.

   Also flipped the `onRunInitialized` shim from REPLACE to APPEND
   — keep the modern part for the UI AND emit a legacy `binary`
   sibling for the converter.

6. Regression suite (`tests/e2e/multimodal.spec.ts`). Replaces the
   pre-rewrite suite with five focused tests:
   - page loads with all expected affordances
   - sample image: auto-sends, EXACTLY ONE `<img>`, assistant
     references the logo
   - sample PDF: auto-sends, EXACTLY ONE `DocumentAttachment` chip
     ("PDF" label), NO `<img>`, no `[Attached document]` text bleed
   - image then PDF in the same session: each message keeps its own
     single chip, no cross-contamination
   - PDF then image in the same session: symmetric

   All 5 pass against the live local stack (15.4s).
2026-05-11 14:54:38 +02:00
Alem Tuzlak b5c00a6192 fix(showcase/voice): use real Whisper for mic transcription (#4760)
## Summary

- The voice route's OpenAI client used to fall through to
`OPENAI_BASE_URL`, which `docker-compose.local.yml` sets to
`http://aimock:4010/v1`. Aimock has a catchall `endpoint:
"transcription"` fixture that returns `"What is the weather in Tokyo?"`
for every audio file, so the mic button always produced that phrase
regardless of what the user actually said into the microphone. Pin
`baseURL` to real OpenAI Whisper (overridable via
`OPENAI_TRANSCRIPTION_BASE_URL`). The sample-audio button stays as
synchronous text injection — that's the documented design and what the
e2e + d5 probe rely on.
- Tidy the sample-audio button label so the affordance matches what it
does: "Try a sample question" → "Try a sample audio".
- Realign `tests/e2e/voice.spec.ts` with the shipped component (the spec
was asserting on a `voice-sample-audio` container testid and a `Sample:
"..."` caption that never existed in the current
`sample-audio-button.tsx`), and add cold-start timeout headroom for the
mic button render (waits on `/info` round trip) and the agent-flow test.
- Add `"env": ".env"` to `langgraph.json` so `langgraph_cli dev` picks
up `OPENAI_API_KEY` for local dev. Docker/Railway paths inject env vars
directly via `env_file`, so this is a no-op there.

Scope was deliberately limited to `langgraph-python` (north-star) per
the user's request — the other 17 integrations still route mic
transcription through aimock. Demo parity sync can propagate this when
desired.

## Test plan

- [x] `pnpm exec playwright test voice.spec.ts` — all 3 tests pass
(`page loads…`, `sample audio button injects the canned phrase…`,
`sending the transcribed text produces a weather tool render`)
- [x] Manual mic verification on `http://localhost:3000/demos/voice`
with a real `OPENAI_API_KEY` — speaking "hello" now returns "Hello."
instead of the Tokyo fixture
- [ ] Visual check the new "Try a sample audio" label in the deployed
preview
- [ ] Confirm Railway/Docker deploys still transcribe through real
Whisper (this path was unaffected — `OPENAI_TRANSCRIPTION_BASE_URL`
stays unset, defaulting to api.openai.com)
2026-05-11 14:54:01 +02:00
Alem Tuzlak 985bebf39c fix(showcase/voice): use real Whisper for mic transcription
The voice route's OpenAI client previously fell through to OPENAI_BASE_URL,
which docker-compose.local.yml sets to http://aimock:4010/v1. Aimock has a
catchall transcription fixture that returns "What is the weather in Tokyo?"
for every audio file, so the mic button always produced that phrase no
matter what the user actually said.

Pin baseURL to real OpenAI (overridable via OPENAI_TRANSCRIPTION_BASE_URL).
The sample-audio button stays as synchronous text injection — that's the
documented design, and what the e2e + d5 probe rely on.

Also:
- Tidy the sample button label ("Try a sample question" -> "Try a sample
  audio") so the affordance matches what it does.
- Realign tests/e2e/voice.spec.ts with the shipped component (the
  voice-sample-audio container testid and Sample: "..." caption it asserted
  on never existed on HEAD) and add cold-start timeout headroom for the
  mic-button render and the agent-flow test.
- Add "env": ".env" to langgraph.json so langgraph_cli dev picks up
  OPENAI_API_KEY locally. Docker/Railway paths inject env vars directly so
  this is a no-op there.
2026-05-11 14:45:10 +02:00
Alem Tuzlak 15db0bbf3d fix(showcase/harness): drive gen-ui-headless-complete via textarea, not chip click (#4731)
## Summary

Follow-up to #4723. The post-merge cycle showed
`gen-ui-headless-complete` still red with `page.click: Timeout` waiting
for the aria-label selector I added in #4723.

Root cause: the demo has TWO chip surfaces with diverging aria-label
shapes:
- **EmptyState** (first paint, before any messages): `aria-label="Try
suggestion: <message>"`, visible text = the message string.
- **SuggestionBar** (after first message lands):
`aria-label="Suggestion: <title>"`, visible text = the short title.

Which surface is mounted depends on `messages.length` —
timing-dependent. Both my prior selectors (`button >> text="Weather"`
and `button[aria-label="Suggestion: Weather"]`) matched only one surface
and timed out on the other.

Fix: drop the chip-click preFill entirely. The runner's normal
fill+press hits the textarea with the verbatim prompt; chip clicks and
textarea-Enter dispatch the same `runAgent`, so the fixture matcher
catches either route — and the textarea is always present.

Also includes a small refresh of `e2e-deep.test.ts` for a stale
assertion (Phase-2A REGISTRY_TO_D5 split renamed `tool-rendering` →
`tool-rendering-default-catchall`).

## Test plan

- [ ] Land + watch the next e2e-deep cycle
- [ ] Confirm `gen-ui-headless-complete` flips green

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-05-11 13:34:41 +02:00
Alem Tuzlak 49fd3d899f fix(showcase/aimock): tool-rendering-reasoning-chain multi-turn (Turn 2 hang) (#4752)
## Summary

The LGP **tool-rendering-reasoning-chain** D5 probe hung on Turn 2 (Find
flights from SFO to JFK) after Turn 1 (weather Tokyo) succeeded.

**Root cause:** the SFO/JFK first-leg fixture matched on `hasToolResult:
false`, but Turn 1's tool result remains in the conversation by Turn 2.
So aimock's matcher skipped the first-leg fixture and fell through to
the second-leg (`hasToolResult: true`), returning narration text without
the `search_flights` tool call. The probe then timed out waiting for the
FlightListCard testid that never mounted.

**Fix:** switch the SFO/JFK second-leg fixture to `toolCallId` matching
(last message must be a tool result with that specific `call_id`) and
drop `hasToolResult` from the first-leg. The second-leg fixture must
come BEFORE the first-leg in file order — the matcher is
first-match-wins, but with `toolCallId` it cannot accidentally swallow
first-leg requests (whose last message is the user prompt, not a tool
result).

Also adds a `content` field to the Tokyo first-leg fixtures
(`get_weather` underscore + `get-weather` Mastra-hyphen variants) so
they route through aimock's `buildContentWithToolCallsStreamEvents`
instead of `buildToolCallStreamEvents`. The latter codepath silently
drops the `reasoning` field (4-arg signature, no reasoning param), which
is why the reasoning-block testid never mounted on Turn 1 either before
this change.

Tokyo (Turn 1) keeps the simpler `hasToolResult` pattern because it has
no prior turns; only the SFO/JFK fixture pair needs the `toolCallId`
shape to survive multi-turn use.

## Test plan

- [x] `pnpm --filter @copilotkit/showcase-scripts test aimock-fixtures`
— 18 passed (load-time schema validation)
- [x] `pnpm --filter @copilotkit/showcase-scripts test` — 574/574 passed
- [x] `pnpm --filter @copilotkit/showcase-harness test` — 1588/1588
passed
- [ ] Railway deploys aimock with new fixtures, then
`tool-rendering-reasoning-chain` cell on the dashboard flips D4 → D5 for
langgraph-python
2026-05-11 13:34:05 +02:00
Alem Tuzlak 32d0237cb5 fix(shell-docs): cutover-blocker fixes from Phase 4 validation (#4741)
## Summary

Phase 4 validation (run today against `docs.showcase.copilotkit.ai`)
surfaced four cutover-blocking issues. This PR fixes all of them in four
focused commits.

## Commits

1. **chore(showcase): close last 2 yellow Missing snippet boxes for
cutover** — adds in-place region markers to
`langgraph-python::frontend-tools` and a sibling
`slot-overrides.snippet.tsx` teaching file for
`langgraph-python::chat-slots`. Both pages now render zero `Missing
snippet` warnings.

2. **fix(shell-docs): correct feature-viewer slug + demo-id translation
for code tab** — adds `getFeatureViewerSlug()` and
`getFeatureViewerDemoId()` helpers in `registry.ts`, with explicit
override maps for the 8 framework-name mismatches (built-in-agent,
google-adk, claude-sdk-{python,typescript}, ms-agent-{python,dotnet},
crewai-crews, llamaindex) and 5 demo-id mismatches (gen_ui_tool_based,
gen_ui_agent, shared_state_streaming, shared_state_read_write,
hitl_in_chat). When the framework or demo has no feature-viewer
equivalent, the helper returns null and the Code tab is hidden on that
page (graceful degradation; Demo tab still renders).

3. **fix(shell-docs): redirect catalog hygiene (self-loops +
framework-scoped gap)** — removes 31 self-redirect entries from
`seo-redirects.ts` (where source === destination caused infinite 301
loops on canonical URLs like /frontend-tools, /faq, /human-in-the-loop).
Reorders middleware logic so the redirect catalog is consulted before
the framework-scoped short-circuit fires, fixing 97 framework-scoped
slug-rename URLs that were soft-404ing instead of redirecting. Adds
defense-in-depth skip-when-equal guard in middleware.

4. **fix(shell-docs): resolve 53 sitemap 500s before cutover** — three
independent root causes in MDX rendering:
- 4 missing \`<Component />\` registrations in \`mdx-registry.tsx\`
(CopilotCloudConfigureCopilotKit,
SelfHostingCopilotRuntimeConfigureCopilotKit, CloudCopilotKit, Content)
— affected ~37 pages.
- 3 langgraph tutorial pages had markdown lists immediately preceding
JSX closing tags, causing remark to bail. Fix = blank line between
bullet and close tag (~9 pages).
- 5 MDX files used escaped JSX comments that Acorn cannot parse —
replaced with proper unescaped form (~7 pages).

## Verification

- Production build clean: 27 static pages generated, 0 errors
- Local probes: all 53 sitemap-500 URLs return 200; all 31 former
self-loop URLs return 200; all 85 framework-scoped non-loop catalog
entries 301 to expected destinations
- Code tab: 47/60 (framework × demo) URLs resolve to real code panels
post-fix; remaining 13 are feature-viewer per-framework deploy-coverage
gaps in the \`ag-ui-protocol/ag-ui\` repo, not this one
- Both langgraph-python pages render 0 \`Missing snippet\` boxes after
re-bundling demo-content

## Test plan

- [ ] CI passes
- [ ] Sitemap probe: every URL in \`/sitemap.xml\` returns 200
- [ ] Redirect probe: spot-check 5 framework-scoped slug renames (e.g.
\`/agno/frontend-actions\`, \`/pydantic-ai/use-agent-hook\`)
- [ ] Self-loop probe: \`/frontend-tools\` returns 200, not infinite 301
- [ ] Code tab: visit \`/built-in-agent/frontend-tools\` (tab should be
hidden), \`/langgraph-python/agentic-chat\` (tab should render code),
\`/llamaindex/agentic-chat\` (tab should render after llama-index slug
rename)
- [ ] Yellow boxes: visit \`/langgraph-python/frontend-tools\` and
\`/langgraph-python/custom-look-and-feel/slots\` — zero Missing snippet
warnings
2026-05-11 13:26:19 +02:00
Alem Tuzlak dd3c142a43 Merge remote-tracking branch 'origin/main' into fix/d5-tool-rendering-reasoning-chain-multi-turn
# Conflicts:
#	showcase/aimock/d5-all.json
#	showcase/harness/fixtures/d5/tool-rendering-reasoning-chain.json
2026-05-11 13:25:32 +02:00
Alem Tuzlak bf8782b1b7 test(showcase/langgraph-python): regression coverage for A2UI middleware-intercept + display_flight loop (#4738)
## Summary

Pins the three classes of regression that #4733 + #4734 just fixed, so a
future revert/refactor trips a test instead of breaking the deployed
showcase silently.

### Python unit tests — `src/agents/test_a2ui_internal_tools.py`

7 tests, ~2s, no LLM calls (secondary LLM is stubbed):

1. **Tool name doesn't collide with the A2UI middleware's intercept
list.** Parametrised over `beautiful_chat._design_a2ui_surface`,
`a2ui_dynamic._design_a2ui_surface`, and `a2ui_fixed.display_flight`.
Asserts none of them appear in `a2uiToolNames` (default
`{"render_a2ui"}`). Catches an accidental rename revert that would
re-enable the streaming-args bypass.
2. **`generate_a2ui` force-pins canonical `catalog_id`.** Stubs
`ChatOpenAI` with a fake response carrying a hallucinated `catalogId`,
calls the tool, parses the returned ops, asserts the surface op carries
the module's `CUSTOM_CATALOG_ID`. Covers both `beautiful_chat` and
`a2ui_dynamic`.
3. **Malformed root drops cleanly.** Stubs the secondary LLM emitting a
root component without a `component` field. Asserts `generate_a2ui`
returns a clean error string instead of feeding the renderer a partial
tree (which surfaces as the deploy-only "Cannot create component root
without a type" infinite-loop).

### E2E regression assertions — added to existing specs

- `tests/e2e/beautiful-chat.spec.ts` (Sales Dashboard test): no `Catalog
not found` / `Cannot create component … without a type` banner; at most
2 `recharts-responsive-container` (one pie + one bar = single
dashboard).
- `tests/e2e/declarative-gen-ui.spec.ts` (BarChart test): no banners; at
most 1 `recharts-responsive-container`.
- `tests/e2e/a2ui-fixed-schema.spec.ts` (Find SFO → JFK test): exactly
one `Flight Details` and one `Book flight` button (was 6+ on deploy
pre-fix from the `display_flight` loop); no banners.

## Test plan

- [x] `pytest src/agents/test_a2ui_internal_tools.py` — 7/7 green
locally.
- [ ] Existing Playwright suites still pass (assertions are additive —
only fail if a regression actually surfaces).
2026-05-11 13:24:47 +02:00
Alem Tuzlak 425bd0a744 Merge remote-tracking branch 'origin/main' into fix/d5-tool-rendering-reasoning-chain-multi-turn 2026-05-11 13:20:49 +02:00
Alem Tuzlak 85eba85df9 Merge remote-tracking branch 'origin/main' into tyler/lgp-d5-probe-realign
# Conflicts:
#	showcase/harness/src/probes/drivers/e2e-deep.test.ts
#	showcase/harness/src/probes/scripts/d5-gen-ui-headless-complete.ts
2026-05-11 13:15:21 +02:00
Alem Tuzlak 5c0a87e18b Merge branch 'main' into test/lgp-a2ui-regression-coverage 2026-05-11 13:12:38 +02:00
Tyler Slaton 4414188910 chore(showcase): deprecate legacy byoc-* feature IDs (#4758)
## What does this PR do?

Marks the legacy `byoc-hashbrown` and `byoc-json-render` registry
entries as `"deprecated": true` so the dashboard's "Show deprecated"
toggle hides them by default.

[#4756](https://github.com/CopilotKit/CopilotKit/pull/4756) renamed
langgraph-python's `byoc-*` slugs to `declarative-*` but kept the legacy
IDs in the registry so the other 17 integrations (whose manifests still
declare `byoc-*`) wouldn't drop out of the catalog. That left the BYOC
(Bring Your Own Components) section showing **both** rows for each of
the two demos — legacy IDs red-X'd for langgraph-python (no manifest
declaration there anymore) and the renamed IDs green.

This PR keeps both ID forms live (the other 17 integrations still need
the legacy entries) but hides the legacy duplicates behind the standard
`deprecated` flag — same pattern already used for
`agentic-chat-reasoning`, `hitl`, `hitl-in-chat-booking`,
`reasoning-default-render`. The dashboard's "Show deprecated" toggle in
`feature-grid.tsx` decides visibility from there.

## Why

The dashboard view post-#4756 was confusing — two identical-looking
"Declarative UI: Hashbrown" / "Declarative UI: json-render" rows where
one is green and one is red. Deprecating the legacy entries gives
readers one canonical row per demo by default while preserving the data
path the other 17 integrations rely on.

## Test plan

- [x] `showcase/scripts` vitest suite green (574 tests)
- [x] `npx tsx generate-registry.ts` regenerates `catalog.json` for all
four shells without errors
- [x] No catalog count changes (deprecated cells stay in the catalog,
only dashboard visibility changes)

## Related

- [#4756](https://github.com/CopilotKit/CopilotKit/pull/4756) — the slug
rename that introduced the duplicate rows

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-05-10 23:12:05 -07:00
Tyler Slaton f367d6ef14 chore(showcase): deprecate legacy byoc-* feature IDs
PR #4756 renamed langgraph-python's `byoc-hashbrown` / `byoc-json-render`
slugs to `declarative-*` but kept the legacy IDs in the registry so the
other 17 integrations (which still declare `byoc-*` in their manifests)
don't drop out of the catalog. The dashboard then showed both rows side
by side under "BYOC (Bring Your Own Components)": the legacy IDs red
for langgraph-python (no manifest declaration) and the renamed IDs
green.

Mark the two legacy IDs `"deprecated": true` so the dashboard's "Show
deprecated" toggle hides them by default. The canonical declarative-*
rows stay visible. Data stays intact for the other 17 integrations
until they migrate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 22:51:47 -07:00
Tyler Slaton 4bf9b69439 showcase(langgraph-python): correctness + cleanliness pass, e2e/QA alignment, cr-loop convergence (#4756)
## What does this PR do?

Multi-phase correctness + cleanliness pass on
`showcase/integrations/langgraph-python` plus the shared
harness/aimock/dashboard surfaces it depends on. The langgraph-python
integration is the reference implementation other showcase integrations
are graded against, so the diff cleans up real demo-time bugs, brings
the e2e + QA contract into line with each demo's actual source, and
updates the D5 harness so probe coverage works against the renamed
slugs.

The 18 commits split cleanly into two parts:

**Phase commits (5 — original audit work, already on the branch when
cr-loop started):**

- **`7a66040c2` Phase A correctness fixes** — `interrupt-headless` now
reads `payload.slots` from the backend instead of rendering stale
hardcoded dates; `package.json#dev` runs `langgraph_cli dev --port 8123`
(matching the API route and the production entrypoint) instead of
pointing at a 3-line `agent_server.py` stub on port 8000; the legacy
stub is deleted; the entrypoint smoke-check for `src/agents/tools.py` (a
file that never existed) is removed; the two reasoning-only agents get a
one-line comment explaining why `CopilotKitMiddleware` is intentionally
absent.
- **`ee429d1b2` Phase B demo cleanup** — `shared-state-read` flattens to
a single source of truth (drops dual-state sync that had the read-only
demo locally mutating recipe state); extracts `useAutoScroll` and
`useTypingIndicator` out of `headless-complete`; extracts
`parseJsonResult` into a shared helper; extracts `NOTES_DB` out of
`frontend-tools-async/page.tsx`; introduces a `makeSlotOverride<T>`
helper that replaces 11 `as unknown as` casts in `chat-slots/page.tsx`.
- **`fc8c0f5e4` Phase C Python agent cleanup** — extracts
`_a2ui_utils.py` (`sanitize_a2ui_components` + `has_root_component`)
consumed by `beautiful_chat.py` and `a2ui_dynamic.py`; collapses three
try/except blocks in `multimodal_agent._extract_pdf_text`; replaces
hardcoded PDT offset with `zoneinfo.ZoneInfo(\"America/Los_Angeles\")`
so DST works.
- **`8b6eca973` Phase D rename** — `byoc-{hashbrown,json-render}`
slugs/folders/routes/specs/QAs become `declarative-*` (the internal
Python graph IDs intentionally stay legacy for now); Tool Rendering trio
renames to Default/Catch-all/Custom progression; showcase index now
sorts demos within each tag by `manifest.features` order.
- **`99ad42e9b` Phase E test contract alignment** — agentic-chat spec
rewrites to match the now-simplified vanilla-chat demo; deletes specs
for demos that no longer exist (`shared-state-write.spec.ts`,
`renderer-selector.spec.ts`); aligns QAs with the demos they describe.

**Cr-loop commits (13 — convergence-driven fixes from a 7-round
multi-agent review):**

The cr-loop ran 7 confirmation rounds with 7 specialized review agents
per round (code-reviewer, silent-failure-hunter, pr-test-analyzer,
comment-analyzer, type-design-analyzer, infra code-reviewer, Python
code-reviewer). Findings each round were partitioned into bucket (a)
subject-scope + load-bearing / (b) subject-scope + trivial / (c)
out-of-subject pre-existing / (d) out-of-this-PR subject. The 13 fix
commits address every confirmed bucket (a):

- **Round 1 (`493a6002e`)** — `agentic-chat.spec.ts` was asserting
`[data-role=\"assistant\"]` which the v2 react-core bundle never emits
(it ships `data-testid=\"copilot-assistant-message\"`); fixture comment
that said \"Empty content is load-bearing\" sat next to a non-empty
content field; harness `d5-feature-mapping.ts` / `live-status.ts` /
`d5-byoc.ts` got `declarative-*` entries so D5 probes still target
langgraph-python after the slug rename; bucket-b polish.
- **Round 2 (`c61dc65f3`)** — `subagents._invoke_sub_agent` had a
regression where the docstring cleanup removed an `isinstance(text,
str)` guard, so `{\"type\":\"text\",\"text\":null}` payloads (a known
provider quirk) would crash `\"\".join(parts)` with `TypeError`;
restored the guard. `shared-state-read.spec.ts` was a complete
false-green (asserted on \"Sales Pipeline\" but the demo is now a Recipe
Editor); rewrote against the recipe-card testids. 11 sibling specs were
sweep-fixed for the same stale `data-role` selector. Restored one log
line at `multimodal_agent._extract_pdf_text` (the three-stage prints
were dropped in cleanup; one outer log keeps Railway triageable). Fixed
false `kind: \"testing\"` claim in `qa/reasoning-default.md`.
- **Round 3 (`328282297`)** — `shared-state-read/page.tsx` had `void
copilotkit.runAgent({agent}).catch(() => {})` which explicitly silenced
every error from \"Improve with AI\"; replaced the empty catch with
`console.error`. Demo's visible h1 still read \"BYOC: Hashbrown\" after
the slug rename; updated page heading, spec, and QA in lockstep.
Manifest description for `agentic-chat` claimed \"Natural conversation
with frontend tool execution\" but Phase E rewrote the demo to vanilla
chat with no tools. `reasoning-default.spec.ts` asserted on
`[data-testid=\"copilot-reasoning-message\"]` which
`CopilotChatReasoningMessage` never emits; switched to the text-based
\"Thinking…/Thought for…\" label the component actually renders.
- **Round 4 (`b905869e9` + `21fb20cf0` + `cff562c0d` + `ae24603bb`)** —
fixture-mirror fixes (the new reasoning-default fixture was added to the
bundled `d5-all.json` but not to the per-demo source-of-truth file);
added 3 declarative-* test cases to `d5-byoc.test.ts`; fixed
`qa/chat-slots.md` to match the slot-wrappers refactor; tightened
comment polish (typo, count drift, ported-from path).
- **Round 5 (`39fadec3f` + `64bee6453`)** — Sunday edge case in
`interrupt_agent._candidate_slots` where `tomorrow` and `next_monday`
collapsed to the same date; fixed both Python and TS sides. Five spec/QA
false-fails: `gen-ui-tool-based.spec.ts` asserted on UI text (\"Use the
sidebar...\", \"Chart Generator\") that doesn't exist;
`agent-config.spec.ts` asserted heading \"Agent Config Object\" but the
demo has \"Agent Config\"; `multimodal.spec.ts` asserted a non-existent
heading; `qa/chat-slots.md` said max-w-4xl but the demo is max-w-5xl;
`qa/shared-state-read.md` had a stale default-instruction string.
- **Round 6 (`70548fe2f`)** — `chat-slots.spec.ts` was asserting
`[data-testid=\"custom-assistant-message\"]` in 4 places (4 of 5 tests
would time out); the actual signal is
`data-slot-label=\"MessageView.AssistantMessage\"` from the SlotMarker
wrapper. `qa/gen-ui-tool-based.md` described a \"Haiku Generator\" demo
that doesn't exist anywhere; rewrote to match the chart-rendering demo.
- **Final cleanup (`2342fbb5a`)** — deleted 4 orphan QA files flagged
across multiple cr-loop rounds (`agentic-chat-reasoning.md`, `hitl.md`,
`hitl-in-chat-booking.md`, `shared-state-write.md`).

## Test posture

| Level | Result |
|---|---|
| **smoke** | green (health + agent endpoints) |
| **D4** (e2e-chat-tools / Tool Reasoning) | **green** — not regressed |
| **D5** (e2e-deep) | 38/40 green; the two reds
(`gen-ui-interrupt-sales-call` and `tool-rendering-reasoning-chain` turn
2) are pre-existing and explicitly out-of-scope per the original audit's
Phase F. **`mcp-apps` and `tool-rendering-reasoning-chain` turn 1 went
from red → green on this branch.** |

## Pre-existing reds (deliberately not addressed; track as follow-ups)

- **`gen-ui-interrupt-sales-call`** — agent reply lands but the
`interrupt(...)` payload never reaches the frontend so the picker never
enters its picked state. This is the **W8-6 LangGraph-Platform interrupt
deployment** ticket from the original audit's Phase F.
- **`tool-rendering-reasoning-chain` turn 2** — turn 1 was fixed in
`a6682ccd6` by adding non-empty `content` so aimock's Responses-API
dispatch routes through `buildContentWithToolCallsResponse` instead of
dropping reasoning. Turn 2 is a deeper aimock+deepagents+Responses-API
multi-turn state-management issue — production with real OpenAI works.

## Out-of-this-PR follow-up seeds

- Cross-codebase `byoc-* → declarative-*` rename across the other 17
integrations + shared registry (one of: complete the rename or roll it
back to keep the codebase coherent).
- e2e specs for `interrupt-headless`, `tool-rendering-reasoning-chain`,
`shared-state-read-write` (manifest demos with no Playwright spec).
- `gpt-4o → gpt-5.4` docstring sweep across `multimodal_agent.py` and
`mcp_apps_agent.py` (pre-existing comment drift).

## Checklist

- [x] I have read the Contribution Guide
- [x] If the PR changes or adds functionality, I have updated the
relevant documentation (e2e specs + QA markdowns)
- [x] \"Allow edits by maintainers\" is checked
2026-05-10 16:38:52 -07:00
github-actions[bot] e831c72a8f style: auto-fix formatting 2026-05-10 22:19:25 +00:00
Tyler Slaton df3e232068 fix(showcase/aimock): add D5 fixtures + restore reasoning emission
D5 probes were red for two demos because the aimock fixtures didn't
exist or were dispatched through the wrong Responses-API path.

- `harness/fixtures/d5/mcp-apps.json` (new) — the MCP Apps probe sent
  "Open Excalidraw and sketch a system diagram" with no matching
  fixture, so aimock 404'd and the agent threw `NotFoundError`.
  Added a two-leg fixture: leg 1 emits a `create_view` MCP tool call
  with Excalidraw element JSON; leg 2 is the `hasToolResult: true`
  content reply. Mirrored verbatim into `aimock/d5-all.json` so the
  Docker-baked aimock has the same matches.
- `harness/fixtures/d5/tool-rendering-reasoning-chain.json` — added
  non-empty `content` fields to the first-leg fixtures (Tokyo weather
  + SFO/JFK flights). aimock's Responses-API dispatch routes
  tool-call-only responses (no `content`) through
  `buildToolCallResponse`, which silently drops the `reasoning`
  payload; with a non-empty `content`, dispatch shifts to
  `buildContentWithToolCallsResponse`, which emits the
  reasoning_summary events the v2 `<ReasoningBlock>` needs to mount.
  Mirrored into `d5-all.json`.
- `harness/fixtures/d5/reasoning-display.json` — added a second
  matcher for the `reasoning-default` e2e pill prompt ("sky appears
  blue"), keyed alongside the existing D5 probe matcher ("show your
  reasoning step by step"). Both responses include `reasoning` fields
  so the built-in `CopilotChatReasoningMessage` "Thinking…/Thought
  for…" header lands.
- `aimock/feature-parity.json` — added a fixture for the agentic-chat
  multi-turn name-recall test ("What name did I just give") replying
  "You said your name is Alice." The matcher is intentionally narrow
  to avoid colliding with the generic showcase-assistant catch-all.

Net D5 effect: `mcp-apps` and `tool-rendering-reasoning-chain` turn 1
go from red → green. The remaining red on `tool-rendering-reasoning-
chain` turn 2 is the deeper aimock+deepagents+Responses-API multi-
turn state-management issue (production with real OpenAI works); out
of scope per the original audit's Phase F.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:17:06 -07:00
Tyler Slaton 59eb245a1c fix(showcase/langgraph-python): align e2e specs + QAs with current demos
The specs and QA markdowns had drifted from the demos they describe.
This commit brings every test contract into line with the actual demo
source — eliminating false-greens, false-fails, and stale assertions.

False-fail spec assertions (would fail every run):
- `agentic-chat.spec.ts` — rewrote from the old `change_background` /
  `weather-card` / `useAgentContext` flow that no longer exists. New
  spec exercises the vanilla `<CopilotChat>` + three suggestion pills
  contract the simplified demo actually exposes.
- `gen-ui-tool-based.spec.ts` — asserted on UI text ("Use the sidebar
  to generate charts", "Chart Generator") that doesn't exist; switched
  to suggestion-pill assertions and scoped the SVG check to inside the
  assistant-message bubble (was matching CopilotChat's send-button
  SVG).
- `agent-config.spec.ts` — asserted heading "Agent Config Object" but
  the demo has "Agent Config".
- `multimodal.spec.ts` — asserted a non-existent "Multimodal
  attachments" heading; switched to the `multimodal-demo-root` testid.
- `chat-slots.spec.ts` — asserted `[data-testid="custom-assistant-
  message"]` and the bare text "slot" — neither exists. The actual
  signal is `data-slot-label="MessageView.AssistantMessage"` from the
  SlotMarker wrapper.
- `reasoning-default.spec.ts` — asserted `[data-testid="copilot-
  reasoning-message"]` and `[data-message-role="reasoning"]`; neither
  is emitted by `CopilotChatReasoningMessage`. Switched to the text-
  based "Thinking…/Thought for…" header label.

False-green spec assertions (passed for the wrong reason):
- `shared-state-read.spec.ts` — was a complete false-green: asserted
  on "Sales Pipeline", "Total Pipeline", "Active Deals" but the demo
  has been a Recipe Editor for some time. Rewrote against the
  recipe-card / ingredients-container / instructions-container testids.
- 11 specs (agent-config, beautiful-chat, frontend-tools-async,
  gen-ui-tool-based, gen-ui-agent, gen-ui-interrupt, hitl-in-chat,
  hitl-in-app, multimodal, readonly-state-agent-context, voice) used
  `[data-role="assistant"]` to gate "agent responded" — but the v2
  react-core bundle never emits that attribute (it ships
  `data-testid="copilot-assistant-message"`). Mechanical sweep to the
  correct testid.
- Deleted `shared-state-write.spec.ts` (route consolidated into
  `shared-state-read-write` earlier on this branch — spec targeted a
  removed demo) and `renderer-selector.spec.ts` (asserted on a radio-
  pill UI that no longer exists; the four "Declarative UI" variants
  are now separate manifest demos).

QA drift:
- `qa/gen-ui-tool-based.md` documented a "Haiku Generator" demo with
  haiku-card / japanese-line / english-line / haiku-image testids — a
  demo that doesn't exist anywhere on this branch. Rewrote to match
  the chart-rendering demo's actual testids and pill prompts.
- `qa/chat-slots.md` referenced "Custom Slot" pill / "Welcome to the
  Slots demo" heading / "This welcome card is rendered via the
  welcomeScreen slot." body text — all of which the slot-wrappers
  refactor on this branch removed. Updated to match the
  `custom-welcome-message` sub-slot that's actually rendered. Also
  fixed max-w-4xl → max-w-5xl to match the page.
- `qa/shared-state-read.md` said default instruction is "Preheat oven
  to 350 F" but the source has "Preheat oven to 350°F (175°C)".
- `qa/agentic-chat.md` rewrote to match the simplified vanilla-chat
  demo (the previous QA documented `change_background` / `WeatherCard`
  flows that no longer exist).
- `qa/reasoning-default.md` cited `kind: "testing"` in feature-
  registry.json for the `reasoning-default` entry; the registry entry
  has no `kind` field. Rewrote without the false cross-file claim.
- Deleted 4 orphan QA files for demos that don't exist:
  `agentic-chat-reasoning.md`, `hitl.md`, `hitl-in-chat-booking.md`,
  `shared-state-write.md`.
- Renamed `qa/reasoning-default-render.md` → `qa/reasoning-default.md`
  to match the manifest cell name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:16:42 -07:00
Tyler Slaton 70e2fb13c8 refactor(showcase): rename byoc-* slugs to declarative-* + sort index by manifest features
User-facing renames so the showcase reads the way a cold visitor would
expect:

- `byoc-hashbrown` → `declarative-hashbrown` (and `byoc-json-render` →
  `declarative-json-render`). The display titles already said
  "Declarative UI: …"; only the URL slugs and folder paths still
  leaked the internal BYOC ("Bring Your Own Components") jargon.
  Renamed:
    /demos/byoc-hashbrown          → /demos/declarative-hashbrown
    /demos/byoc-json-render        → /demos/declarative-json-render
    /api/copilotkit-byoc-*         → /api/copilotkit-declarative-*
    src/app/demos/byoc-*           → src/app/demos/declarative-*
    qa/byoc-*.md                   → qa/declarative-*.md
    tests/e2e/byoc-*.spec.ts       → tests/e2e/declarative-*.spec.ts
  Internal Python module names + langgraph graph IDs stay legacy
  (`byoc_hashbrown_agent.py`, `byoc_hashbrown`) — those are not
  user-facing and renaming them is a separate cross-codebase pass.
- `a2ui-fixed-schema` slug intentionally unchanged.
- Tool Rendering trio parenthetical rename (Default → Catch-all →
  Custom progression reads clearly as "how much do I customize?"):
    Tool Rendering (Default)        — unchanged
    Tool Rendering (Custom default) → Tool Rendering (Catch-all)
    Tool Rendering (Specific)       → Tool Rendering (Custom)
- `tool-rendering-reasoning-chain` cell renamed from
  "Generative UI: Rendering multiple tools" to
  "Generative UI: Tool calls + reasoning" (the demo is about combining
  reasoning + tool rendering, not about quantity of tools).
- `Open Generative UI: Default` / `Open Generative UI: Custom`
  descriptions expanded so a visitor understands how Open Generative UI
  differs from Tool Rendering (agent composes UI from a registered
  library vs. attaching a renderer to a *named* backend tool).
- Showcase index now sorts demos within each tag by `manifest.features`
  order. Previously demos appeared in manifest declaration order, which
  ignored the team's curated "polished flagship → simplest start →
  variants" arc.

Cross-cutting registry / harness / dashboard updates that fall out of
the rename:

- `shared/feature-registry.json` adds the two new IDs alongside the
  legacy `byoc-*` (so the catalog stays valid; the other 17
  integrations still declare `byoc-*` in their manifests).
- `shared/constraints.yaml` adds the new IDs to the
  generative-ui-approach allow-list.
- `scripts/__tests__/generate-catalog.test.ts` updates the cell-count
  expectations (45 features × 18 integrations = 810; 792 after docs-
  only exclusion; 45 LGP cells = 38 wired + 1 stub + 6 unshipped).
- Harness probe `d5-byoc.ts` + `d5-byoc.test.ts` now route both slug
  families through `preNavigateRoute` and exercise the new branches.
- `d5-feature-mapping.ts` and `shell-dashboard/live-status.ts` mirror
  the dual-ID mapping so both legacy and renamed slugs roll up under
  the same `byoc` D5 featureType.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:16:02 -07:00
Tyler Slaton 08ba26590e refactor(showcase/langgraph-python): clean up agents + demos for didactic clarity
Extracts duplicated logic into shared modules so each demo file reads
as the feature it teaches, not the boilerplate around it.

Python agents:
- `_a2ui_utils.py` (new) — `sanitize_a2ui_components` and
  `has_root_component`, consumed by both `a2ui_dynamic.py` and
  `beautiful_chat.py` (these previously duplicated the same defensive
  validator inline).
- `byoc_hashbrown_prompt.py` (new) — the 56-line system prompt extracted
  from `byoc_hashbrown_agent.py` so the agent file stays focused on the
  `create_agent(...)` wiring.
- `beautiful_chat.py` secondary `ChatOpenAI` now passes
  `streaming=True` (matching `a2ui_dynamic.py`) so aimock's SSE-only
  fixture matcher sees the call in replay mode — without it the demo
  surfaced "An internal error occurred" on every load.
- `multimodal_agent.py` — top-level `from pypdf import PdfReader` (was
  lazy with three layers of `# pragma: no cover` exception handling
  for stages that never failed independently); kept a single log line
  at the outer except so Railway logs stay triageable.
- `gen_ui_agent.py` — switched from `deepagents.create_deep_agent` (whose
  planner+sub-agent middleware ate enough supersteps to trip LangGraph's
  default recursion limit on this single-tool ReAct loop) to plain
  `langchain.agents.create_agent`. Comment explains the math.
- `tool_rendering_agent.py` — docstring no longer claims to back the
  `tool-rendering-reasoning-chain` cell (it has its own agent file).

TypeScript / TSX demos:
- `_shared/parse-json-result.ts` (new) — extracted from
  `tool-rendering/parse-json-result.ts`; now consumed by three demos.
- `_shared/slot-override.ts` (new) — `makeSlotOverride<T>` centralizes
  the 11 `as unknown as` casts that `chat-slots/page.tsx` previously
  scattered across the slot-override block.
- `shared-state-read` — extracted `RecipeCard` component + `types.ts`,
  dropped the dual-state-sync pattern that had the read-only demo
  locally mutating recipe state. Page is now a thin shell that publishes
  edits via `agent.setState` and reads back via `agent.state.recipe`.
  Also surfaces `runAgent` rejections via `console.error` instead of
  the previous silent `.catch(() => {})`.
- `headless-complete/hooks/use-auto-scroll.ts`,
  `headless-complete/hooks/use-typing-indicator.ts` (new) — extracted
  from `chat.tsx`. The chat file shrinks by ~40 lines. Same silent-
  rejection fix on `runAgent` as shared-state-read.
- `frontend-tools-async/fake-notes-db.ts` (new) — extracted from
  `page.tsx`; the demo file no longer leads with 60 lines of fake-DB
  scaffolding before `useFrontendTool` (the actual feature) appears.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:15:27 -07:00
Tyler Slaton a3d93266e9 fix(showcase/langgraph-python): correctness fixes for demos
Real demo-time bugs in the langgraph-python integration:

- `interrupt-headless` was rendering hardcoded stale slot dates from a
  `DEFAULT_SLOTS` constant instead of reading `payload.slots` that the
  backend `interrupt(...)` already supplies. Sibling `gen-ui-interrupt`
  does this correctly. Headless variant now matches.
- `_shared/interrupt-fallback-slots.ts` (new) — the JS fallback for
  when the backend returns no `slots` array. Generates relative to
  Date.now() so the picker never shows past dates. Used by both
  `interrupt-headless` and `gen-ui-interrupt`. Deletes the old stale-
  literal `gen-ui-interrupt/fallback-slots.ts` (dates from April that
  had already decayed).
- `interrupt_agent.py` — replaced hardcoded `timezone(timedelta(-7))`
  PDT with `zoneinfo.ZoneInfo("America/Los_Angeles")` so the demo
  doesn't lie about offsets in winter. Also fixed a Sunday edge case
  where `next_monday` collapsed to the same date as `tomorrow` (both
  Python and the JS fallback) — added a `<= 1` skip-a-week guard.
- `package.json#dev` ran `uvicorn agent_server:app --port 8000` but
  `src/agent_server.py` was a 3-line stub with no `app` symbol AND the
  API route pointed at port 8123. Replaced with the canonical
  `langgraph_cli dev --port 8123` invocation; deleted the dead stub.
- `entrypoint.sh:47` smoke-checked `src/agents/tools.py` (a file that
  never existed) so every Railway boot logged a phantom ERROR. Dropped.
- `reasoning_agent.py` and `tool_rendering_reasoning_chain_agent.py`
  intentionally omit `CopilotKitMiddleware` (they exercise only
  reasoning-token streaming, no frontend tools / app context). Added a
  one-line comment so a future maintainer doesn't cargo-cult it back in.
- `subagents._invoke_sub_agent` text-block walker had a regression
  where a `{"type":"text","text":null}` payload (a known provider
  quirk) would crash `"".join(parts)` with `TypeError: sequence item
  N: expected str instance, NoneType found`. Restored the
  `isinstance(block.get("text"), str)` guard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:14:43 -07:00
Tyler Slaton 2f6816fe3f feat(showcase/langgraph-python): align chat surfaces + shadcn recipe overhaul (#4753)
## Summary

Four LangGraph Python showcase demos updated:

- **HITL In-app**: `CopilotChat` → `CopilotPopup`
(`defaultOpen={true}`). Tickets panel fills the viewport; chat is a
popup in the corner. Approval dialog still portaled to `<body>`.
- **Shared State: Streaming**: `CopilotChat` (custom aside) →
`CopilotSidebar`. Document view fills the page; sidebar opens by
default.
- **Shared State: Read + Write**: `CopilotPopup` → `CopilotSidebar`.
Card grid breakpoint bumped from `lg:` to `xl:` so cards stack instead
of pinching when the sidebar consumes ~480px on mid-size laptops;
`overflow-y-auto` on the wrapper keeps the page scrollable.
- **Shared State: Read (Recipe)**: visual overhaul with shadcn
primitives (`Card`, `Input`, `Select`, `Textarea`, `Badge`, `Button`,
`Spinner`, `Separator`). All `data-testid` attributes and
QA-doc-asserted strings preserved verbatim ("Make Your Recipe", "AI
Recipe Assistant", "+ Add Ingredient", "+ Add Step", "Improve with AI",
default ingredients/instructions, all 7 dietary preference labels, all 5
cooking-time labels, all 3 suggestions, etc.). React state-sync logic
preserved verbatim (no behavior change).

## Why

The chat surfaces were inconsistent across demos — HITL used a full-pane
chat where the demo's premise is "approval modals appear *outside* the
chat surface", and the shared-state demos mixed `CopilotChat` and
`CopilotPopup` instead of using the prebuilt `CopilotSidebar`. The
Recipe demo was raw Tailwind while the rest of the suite uses shadcn
primitives.

## Plumbing fixes that fell out of the work

- **`clsx` + `tailwind-merge` added to `package.json`** as direct deps.
They were imported by `src/lib/utils.ts` (the `cn` helper used by all
shadcn primitives) but only transitively resolvable, which broke `next
dev --turbopack`'s stricter module resolution.
- **`globals.css` rule**: `body[data-scroll-locked] { padding-right: 0
!important }`. Radix overlays (Select, Dialog, Popover) use
`react-remove-scroll-bar`, which measures the gap between viewport and
body inner width to detect scrollbar size — and our body uses
`margin-inline-end: 480px` to make room for `<CopilotSidebar />`. The
library mis-reads that 480px as scrollbar width and "compensates" with a
matching `padding-right`, which shrinks the content area and shifts the
centered card every time a dropdown opens. We have no real scrollbar
(`body { overflow: hidden }`), so the compensation is unnecessary —
neutralize it. Comment in the diff explains the rationale.

## Test plan

- [x] `next build` clean across all 54 routes
- [x] `oxlint` reports zero new warnings (only pre-existing patterns the
diff preserved verbatim)
- [x] Manually verified `/demos/hitl-in-app`,
`/demos/shared-state-streaming`, `/demos/shared-state-read-write`,
`/demos/shared-state-read` in browser (Chrome, dev server)
- [x] Verified the Recipe page's Select dropdowns no longer shift the
card when opened (the original motivation for the `globals.css` rule)
- [ ] HITL e2e suite (`tests/e2e/hitl-in-app.spec.ts`) — selectors
preserved (`getByPlaceholder("Type a message")`, suggestion-pill
testids, approval-dialog-* testids); CopilotPopup's `defaultOpen={true}`
mounts the chat input on first paint
- [ ] Streaming and Read e2e suites are stale pre-PR (reference content
that doesn't exist in source — "AI Document Editor", "Sales Pipeline")
and remain in the same state

## CR loop

Ran `cr-loop` (Round 1, 7 agents). Reclassified one finding to bucket
(d) per user direction (npm/pnpm lockfile-convention concern is a
different PR's subject — pre-existing across all
`showcase/integrations/*` and out of scope here). Procedure 3 audit
promoted no items to bucket (a); 24 bucket (c) items confirmed as
pre-existing and routed to follow-up backlog.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-05-10 14:46:57 -07:00
Mike Ryan 19494c0515 ENT-510 - fix(core): only reset agent state on actual thread switch (supersedes #4720) (#4740)
## Summary

Supersedes #4720, which Mike correctly identified as introducing a
blocking regression in the fresh-thread-restore path. This PR takes
Mike's framing as the authoritative restatement of the bug and lands the
proxy/orchestrator-level fix that addresses both halves of Tyler's
symptom without that regression.

## The actual bug, restated

When the chat's connect effect re-fires due to React effect-dep churn,
it calls `copilotkit.connectAgent({ agent })` on the same thread again.
`RunHandler.connectAgent` previously did `agent.setMessages([])` +
`agent.setState({})` and (transitively) called `clearReconnectCursor` on
every invocation, **including the churn re-connects**. That forced the
realtime gateway to replay the topic's full event history every time the
chat re-opened a socket — sending the same persisted `cpki_event_id`s
2-3 times per thread switch — and produced both halves of Tyler's video:

- **Cosmetic surface**: duplicate `cpki_event_id` rows in the inspector
AG-UI Events tab.
- **Functional surface**: `agent.messages` got cleared, the replay
started rebuilding it, then another churn re-connect came along, called
`setMessages([])` again, and the next `runAgent` fired with
`messages.length = 1` while the server had N. `prepareRegenerateStream`
triggered, the new user-message id wasn't in any checkpoint, and the
server threw "Message not found".

PR #4720 only addressed the cosmetic surface and made the functional
surface deterministic. Mike's review caught it.

## What this PR changes

- **`RunHandler.connectAgent`** tracks `_lastConnectedThreadId` across
calls. The `setMessages([])` + `setState({})` + replay-cursor clear are
now gated on the threadId actually changing. Same-thread churn
re-connects preserve local state and let the gateway resume from
`lastSeenEventId` instead of replaying. Thread switches still wipe state
and ask for a full replay.
- **`IntelligenceAgent.connect()`** no longer auto-clears the replay
cursor. Cursor management is the caller's decision now.
`clearReconnectCursor` is made `public` so the orchestrator (and tests)
can call it explicitly.
- **`ProxiedCopilotRuntimeAgent`** exposes a public
`clearReplayCursor(threadId)` method that delegates to the
IntelligenceAgent's `clearReconnectCursor`. Non-Intelligence runtime
modes are a safe no-op.

The dispatcher-level dedup from #4720 is **removed entirely**. The
orchestrator-level gate is sufficient and a much smaller, more obviously
correct surface.

## Why this approach

Mike's two suggestions in the #4720 review were "clear the dedup table
when the cursor is cleared" or "scope dedup to a connection/replay
window." Both alone left a regression in the other direction:
- Coupling dedup to `clearReconnectCursor` (which was unconditional)
reset dedup on every churn re-connect, so the original symptom returned.
- Per-connection-window dedup couldn't catch dups across the multiple
sockets that fire during churn.

The right move is one level up: stop the underlying state-reset cascade
that was forcing the replay storm in the first place. Once same-thread
churn no longer wipes local state, the gateway can resume cleanly, the
wire-level replays don't happen, and both Tyler's symptoms disappear
naturally — no dispatcher dedup needed.

## Tests

- **`intelligence-agent.test.ts`**: 4 existing tests updated to reflect
the new contract. `connect()` preserves the cursor by default;
`clearReconnectCursor()` is the explicit primitive. Includes the
regression test Mike asked for: replay the same `cpki_event_id` after `A
→ B → A` and verify the rehydrate still produces the user message.
- **`core-connect-thread-switch.test.ts`** (new): 4 focused tests for
the orchestrator gate — same-thread churn does not reset, cross-thread
switch does reset, `A → B → A` resets each transition, and a
non-Intelligence runtime path is handled cleanly.

All 430 core tests pass:
```
Test Files  40 passed (40)
     Tests  430 passed (430)
```

## Test plan

- [ ] Pull the canary build into `gen-ui-studio` (a fresh `npx
copilotkit create`).
- [ ] Manually walk Tyler's recipe with the inspector open: open thread
A, switch to B, switch back to A. AG-UI Events tab should show one row
per `cpki_event_id`. No "Message not found" toast.
- [ ] Send a message after the switch-back. Run completes cleanly.
- [ ] (Repro harness) `recipe-rehydrate.spec.ts` from kingston's
transport-dup-repro asserts on user-visible state (DOM messages +
absence of "Message not found"). Should pass against this canary; fails
against `origin/main` and against #4720's commit.

## Why "supersedes #4720" not "build on top of"

The dispatcher dedup in #4720 isn't load-bearing under this fix — once
the orchestrator stops cascading state-resets, there's no replay storm
for the dispatcher to filter. Keeping the dedup would just be unused
machinery with non-trivial memory accounting. Cleaner to drop it
entirely and revisit only if a real overlap scenario surfaces later
(network blips, retries, etc.).
2026-05-09 23:23:29 -07:00
Tyler Slaton da8626d819 fix(showcase/aimock): tool-rendering-reasoning-chain multi-turn (Turn 2 hang)
LGP probe hung on Turn 2 (Find flights from SFO to JFK) after Turn 1
(weather Tokyo) succeeded. Root cause: the SFO/JFK first-leg fixture
matched on `hasToolResult: false`, but Turn 1's tool result remains in
the conversation by Turn 2, so the matcher skipped it and fell through
to the second-leg fixture (`hasToolResult: true`), returning narration
without the search_flights tool call. The probe then timed out waiting
for the FlightListCard testid that never mounted.

Fix: switch the second-leg fixture to `toolCallId` matching (the last
message must be a tool result with the specific call_id) and drop
`hasToolResult` from the first-leg fixture. The second-leg fixture
must come BEFORE the first-leg in file order because the matcher is
first-match-wins; with `toolCallId`, it cannot accidentally swallow
first-leg requests (whose last message is the user prompt, not a
tool result).

Also adds a `content` field to the Tokyo first-leg fixtures
(`get_weather` and `get-weather` Mastra-hyphen variants) so they route
through `buildContentWithToolCallsStreamEvents` instead of
`buildToolCallStreamEvents` — the latter codepath silently drops the
`reasoning` field, which is why the reasoning-block testid never
mounted on Turn 1 either before this change.

Tokyo (Turn 1) keeps the simpler `hasToolResult` pattern because it has
no prior turns; only the SFO/JFK fixture pair needs the toolCallId
shape to survive multi-turn use.
2026-05-09 12:01:44 -07:00
Martha Kelly Schumann 4daf07f5c6 Merge branch 'main' into docs(deepagents)/removed-checkpointer-in-python 2026-05-09 07:41:03 -07:00