## What & why
`@copilotkit/react-core`'s `/v2` entry (and the root entry) re-exports
from a
single **monolithic shared chunk**, so importing *any* symbol — even one
hook —
pulls the built-in chat-message rendering stack (`streamdown` → shiki,
`mermaid`,
`cytoscape`, `katex`) into the consumer's bundle: **~3 MB gzip / ~15 MB
raw**,
with no way to tree-shake it. Consumers who build a fully custom chat UI
and only
use hooks pay the full cost. (Issue #4893.)
A separate lean build entry — `@copilotkit/react-core/v2/headless` —
already
ships those hooks in their own small chunk **without** that stack (it's
how
`@copilotkit/react-native` mounts CopilotKit). This PR makes it usable
for a
custom web UI.
Measured with esbuild (react/react-dom external):
| import | bundled JS |
|---|---|
| hooks from `@copilotkit/react-core/v2` | **~2.96 MB** gzip (≈
importing full `CopilotChat`) |
| same hooks from `@copilotkit/react-core/v2/headless` | **~0.03 MB**
gzip |
> Note on the report: `@copilotkit/a2ui-renderer` doesn't bundle the
rich-text
> stack (its deps are `@a2ui/web_core`, `lit`, `clsx`, `zod`). The
weight is
> entirely `streamdown` (shiki/mermaid/cytoscape) + `katex`, pulled by
the
> built-in `CopilotChat*` message components.
## Changes
- **`headless.ts`** — export `useCopilotKit` + `useRenderToolCall` (both
DOM-free
and rendering-stack-free); fix `UseAgentUpdate` (a runtime `enum`) being
re-exported via `export type`, which stripped its runtime value under
`isolatedModules` (so `useAgent`'s `updates` option was unusable from
headless
— a bug `tsc` can't catch). `useDefaultRenderTool` /
`useRenderCustomMessages`
/ `useRenderActivityMessage` stay in `/v2` (web-only markup or
`a2ui-renderer`).
- **Remove a `tailwind-merge` leak** — extract the tailwind-free ref
helpers
(`shallowEqual` / `useShallowStableRef`) into
`lib/shallow-stable-ref.ts` so the
headless graph no longer pulls `tailwind-merge` via
`CopilotChatConfigurationProvider`.
- **Test** — a small vitest export-surface test guarding the hook
surface and the
`UseAgentUpdate` runtime value.
## Verification
`react-core` typecheck + vitest (1427) and `react-native` typecheck
pass. Shipped
`dist/v2/headless.mjs` imports only `react`, `@ag-ui/client`,
`@copilotkit/core`,
`@copilotkit/shared`, `@copilotkit/react-core/v2/context`, `zod` — no
rendering
stack.
## Notes / follow-ups
- Headless hooks read a **different React context** than the prebuilt
`/v2`
`<CopilotKitProvider>`, so the two can't be mixed — mount a lean
provider over
`/v2/context` (as `@copilotkit/react-native` does). Making the `/v2`
provider
reuse the standalone `/v2/context` singleton would remove that sharp
edge and
is the natural follow-up.
- Existing bundle-size CI (`compressed-size-action`) already tracks
`headless.mjs`,
so no new size tooling is added here.
Addresses #4893.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Fixes#5966. Follow-up to #5812 / #5885.
## Problem
After #5885 stopped the post-terminal `TEXT_MESSAGE_END` crash, pressing
**Stop** no longer breaks the chat — but for an agent that emits a
terminal `RUN_ERROR` (code `abort`) as its cancellation signal (e.g.
pydantic-ai's `AGUIAdapter`), the client still surfaces that as an
**error banner** ("This operation was aborted"). A user-initiated stop
is expected cancellation, not a failure.
Traced path (no abort suppression at any hop):
`RUN_ERROR(code:"abort")` → `RunHandler.onRunErrorEvent`
(`run-handler.ts`) → `emitAgentError(AGENT_RUN_ERROR_EVENT)` → `onError`
→ CopilotChat/react-ui `triggerChatError` → banner. The only existing
abort suppression is for the *local* fetch-abort rejection
(`run-handler.ts:318`), a different path.
## Fix
Suppress the error emission in `onRunErrorEvent` when the run was
user-aborted — mirroring the local-abort suppression already on the
`runAgent`/`connectAgent` paths:
```ts
const runWasAborted = this._runAbortController?.signal.aborted === true;
if (runWasAborted || event?.code === "abort") {
return;
}
```
Prefers the client's own `_runAbortController.signal.aborted` (robust —
the agent-supplied `code` isn't standardized across agents; the
`code:"abort"` in the repro comes from the agent, not CopilotKit) with
`code === "abort"` as a secondary signal. Normal `RUN_ERROR`s are
unaffected.
## Testing
- **TDD** (`core-error-handling.test.ts`): two new tests fail pre-fix
and pass after —
- agent emits `RUN_ERROR` code `"abort"` → no `AGENT_RUN_ERROR_EVENT`
surfaced.
- run user-aborted mid-stream (via `agent.abortRun()`, which RunHandler
intercepts to abort the controller) → a subsequent `RUN_ERROR` with a
*non-abort* code is still suppressed (exercises the `signal.aborted`
path).
- The pre-existing test — normal `RUN_ERROR` (code `"bad_request"`)
still emits `AGENT_RUN_ERROR_EVENT` — continues to pass (control against
over-suppression).
- Full `@copilotkit/core` suite green: **578/578**. `tsc` 0 errors;
`oxlint` 0.
Note: this is a UX/product call (a user Stop shouldn't render as an
error). Suppressing in core fixes it for both the default chat banner
and app-level `onError` handlers; the run lifecycle still reflects the
termination.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
- folds CopilotKit App Context into `ModelRequest.system_message` during
LangGraph model calls
- stops injecting App Context as a separate `SystemMessage` from
`before_agent`, avoiding non-consecutive system messages after LangGraph
reducers run
- updates middleware regression tests for state context, runtime
context, forwarded-header filtering, and message-history no-op behavior
## Root cause
`before_agent` inserted a new App Context `SystemMessage` into
`state["messages"]`, but the LangGraph message reducer can append new
message IDs rather than preserving positional insertion. With an
existing system prompt and prior conversation turns, this can produce
multiple non-consecutive system messages, which `langchain-anthropic`
rejects.
Fixes#5610.
## Validation
- `uv run pytest -q`
## What does this PR do?
Pins `opentelemetry-resourcedetector-gcp==1.12.0a0` in the ADK starter.
`google-adk` accepts prerelease resource-detector versions, but `uv`
skips transitive prereleases by default. The only stable candidate,
`1.13.0`, was yanked because it breaks imports. That left the ADK
starter with no valid dependency set.
The direct pin selects the last working release without allowing
prereleases for every dependency. The ADK starter image can build again.
## Related PRs and Issues
- Failing job:
https://github.com/CopilotKit/CopilotKit/actions/runs/30027772426/job/89285780349
## Validation
- RED: `docker build --progress=plain -f
examples/integrations/adk/Dockerfile examples/integrations/adk` failed
at `uv pip install --system -e .` because the requirements were
unsatisfiable.
- GREEN: the same command resolved 124 Python packages, installed the
pinned detector, built the Next.js app, and exported the image.
- `pnpm nx affected -t lint,check-types,test,build --base=origin/main
--head=HEAD` found no affected Nx tasks because this starter is built
outside the Nx project graph.
- `oxfmt --check examples/integrations/adk/agent/pyproject.toml` passed.
## Checklist
- [x] I have read the Contribution Guide.
- [x] No documentation change is needed; this restores the existing
starter build.
- [x] Allow edits by maintainers is enabled.
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| [docker/login-action](https://redirect.github.com/docker/login-action)
| action | minor | `v4.4.0` → `v4.5.0` |
---
> [!WARNING]
> Some dependencies could not be looked up. Check the [Dependency
Dashboard](../issues/592) for more information.
---
### Release Notes
<details>
<summary>docker/login-action (docker/login-action)</summary>
###
[`v4.5.0`](https://redirect.github.com/docker/login-action/compare/v4.4.0...v4.5.0)
[Compare
Source](https://redirect.github.com/docker/login-action/compare/v4.4.0...v4.5.0)
</details>
---
### Configuration
📅 **Schedule**: (in timezone America/Los_Angeles)
- Branch creation
- "before 9am every weekday"
- Automerge
- At any time (no schedule defined)
🚦 **Automerge**: Enabled.
♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box
---
This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/CopilotKit/CopilotKit).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNzUuMiIsInVwZGF0ZWRJblZlciI6IjQzLjI3NS4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
## Summary
`CopilotChatInput`'s `AddMenuButton` renders its trigger as nested Radix
`asChild` slots:
```tsx
<TooltipTrigger asChild>
<DropdownMenuTrigger asChild>{button}</DropdownMenuTrigger>
</TooltipTrigger>
```
Radix `asChild` slots forward a ref to their child.
`DropdownMenuTrigger` and `TooltipTrigger` were plain function
components, so under React 18.3 the forwarded ref triggers this warning
on every render (reported in #5744):
```
Warning: Function components cannot be given refs. Attempts to access this ref will fail.
Did you mean to use React.forwardRef()?
```
## Change
Wrap `DropdownMenuTrigger` and `TooltipTrigger` in `React.forwardRef`,
forwarding the ref to the underlying Radix primitive — mirroring
`forwardRef` usage already present in this package (e.g. `Button`,
`CopilotChatInput.TextArea`). No behavior change; the ref now reaches a
DOM node and the warning is gone.
## Scope
Limited to the two primitives in the reported warning path. The other
shadcn/ui primitives in `react-core` follow the same React-19
(no-`forwardRef`) style and would warn identically when used as
`asChild` children under React 18.3 — happy to extend this to the rest
if you'd prefer full React 18.3 coverage.
## Verification
- `nx build @copilotkit/react-core` succeeds (compiles source +
generates type declarations).
- Pre-commit `test-and-check-packages` passes.
Fixes#5744
## What changed
- Build the canonical Angular browser bundle once when an integration
image needs it.
- Download and materialize that bundle inside each integration Docker
context.
- Generate the per-integration same-origin runtime config before Depot
builds the image.
- Run the same artifact path in the pre-merge build check.
- Rebuild integration images when the Angular host or its workspace
dependencies change.
## Why
The deployment workflow copied `public/angular` as a symlink whose
target sat outside each integration Docker context. The resulting images
had no `/angular/index.html`, so deployed Angular deep links returned
404.
Staging deploys the GHCR images built here. Production promotion pins
the same tested image digest, so the fix applies to both environments.
## Validation
- RED: `pnpm nx test @copilotkit/showcase-scripts -- --run
__tests__/angular-integration-hosting.test.ts` — 2 new workflow
assertions failed before the fix.
- GREEN: the same focused command — 46 tests passed.
- `pnpm nx test @copilotkit/showcase-scripts` — 2,322 tests passed.
- `pnpm nx build @copilotkit/showcase-angular-host` — passed, including
browser artifact audit and bundle budget.
- Staged the built artifact for `langgraph-python`, `mastra`, and `ag2`;
each contained `index.html` and the correct runtime config.
- `actionlint` passed for both changed workflows after ignoring existing
custom-runner and unrelated shellcheck notices.
- `git diff --check` passed.
## Known check
`pnpm nx exec -p @copilotkit/showcase-scripts -- tsc --noEmit -p
tsconfig.json` still reports six existing errors in
`equivalence-gate.ts`, `generate-search-index.ts`, and
`verify-prod-resweep.ts`. This change does not touch those files.
## What does this PR do?
- Preserve non-tool `AIMessage` fields when frontend tool calls are
intercepted in `afterModel` and restored in `afterAgent`, including
`additional_kwargs`, `response_metadata`, `usage_metadata`,
`invalid_tool_calls`, `id`, and `name`.
- Use a shared rebuild helper with `tool_calls` as the source of truth.
For LangChain v1 content blocks, remove stale `tool_call` and
`tool_call_chunk` blocks before reconstructing the message.
- Add regression coverage for the `reasoning + backend/frontend tool
calls -> afterModel -> afterAgent` flow, including metadata preservation
and v1 content block synchronization.
## Related PRs and Issues
- Closes#6087
- Related to #4759 and #5308
## Checklist
- [x] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [x] Documentation is not required because this is an internal
middleware bug fix with no public API changes
- [x] "Allow edits by maintainers" is checked (lets us help iterate on
your PR directly — faster turnaround for everyone)
## Release angular v0.3.0
**Scope:** `angular` | **Bump:** `minor`
---
### How this release process works
1. **This PR was created automatically** by the "release / create-pr"
workflow.
It bumped the `angular` packages to `0.3.0`
and generated AI-enhanced release notes.
2. **CI runs on this PR** — the full test suite (unit tests, lint, type
checks, build)
must pass before merging. This is the review gate.
3. **Review the release notes** in `release-notes.md` in this PR.
If a Notion draft was created, you can edit the release notes there
before merging.
4. **When this PR is merged**, the `release / publish` workflow
automatically:
- Builds all packages
- Publishes the `angular` packages to npm at version `0.3.0`
- Creates git tag `angular/v0.3.0`
- Creates a GitHub Release with the final release notes
### Before merging
- [ ] CI is green (tests, lint, types, build)
- [ ] Version bumps look correct
- [ ] Release notes are accurate (edit in Notion if a draft was created)
---
> **Do not merge until CI is fully green.** The full test suite runs
automatically on this PR.
## Release monorepo v1.63.2
**Scope:** `monorepo` | **Bump:** `patch`
---
### How this release process works
1. **This PR was created automatically** by the "release / create-pr"
workflow.
It bumped the `monorepo` packages to `1.63.2`
and generated AI-enhanced release notes.
2. **CI runs on this PR** — the full test suite (unit tests, lint, type
checks, build)
must pass before merging. This is the review gate.
3. **Review the release notes** in `release-notes.md` in this PR.
If a Notion draft was created, you can edit the release notes there
before merging.
4. **When this PR is merged**, the `release / publish` workflow
automatically:
- Builds all packages
- Publishes the `monorepo` packages to npm at version `1.63.2`
- Creates git tag `monorepo/v1.63.2`
- Creates a GitHub Release with the final release notes
### Before merging
- [ ] CI is green (tests, lint, types, build)
- [ ] Version bumps look correct
- [ ] Release notes are accurate (edit in Notion if a draft was created)
---
> **Do not merge until CI is fully green.** The full test suite runs
automatically on this PR.
## Summary
Adds first-class Angular Showcase support and the public
`@copilotkit/angular`
package.
- Freezes 41 supported Angular features and keeps runnable cells as
frontend
support intersected with backend support.
- Builds one Angular artifact and stages it into all 20 existing
integration
images at same-origin `/angular/{feature}` routes.
- Keeps React `/demos/{feature}` routes unchanged and adds no Angular
service,
proxy, image, deployment environment, or production activation.
- Matches the React SDK's MCP Apps inline sandbox, permissions, and
resource-domain CSP semantics.
- Removes the temporary broad Angular proof workflow after it produced
the
merge evidence below.
## Evidence
- [Angular proof run
29966964494](https://github.com/CopilotKit/CopilotKit/actions/runs/29966964494):
all 48 jobs passed.
- Final report: 1,296 exact cells, 660 frontend comparisons, 636 Angular
pairs,
and 41 registry-supported Angular features.
- Pairwise policy: zero Angular regressions, zero React regressions,
zero
unowned failures, zero identity mismatches, and zero missing results.
- Chromium, Firefox, and WebKit each passed 16 browser checks and ten
cold
readiness samples. Maximum readiness was 250 ms, 163 ms, and 166 ms
against
the 2,000 ms limit.
- All 20 existing integration images passed Angular hosting checks.
- Angular 20, 21, and 22 packed consumers passed.
- PR #6095 remains linked as prior implementation evidence. This branch
ports
proven work without merging or resetting the old branch.
## Commits
- [x] Checkpoint 1 — baseline and registry (`873cbb8b6`)
- [x] MCP Apps activity renderer (`b3de484be`, Manfred Steyer)
- [x] Checkpoint 2 — core and package (`fec70d086`)
- [x] Checkpoint 3 — shared build and proof pair (`637845bb7`)
- [x] Checkpoint 4 — all supported features and docs (`4d32d941e`)
- [x] Checkpoint 5 — hardening and final exposure (`7ccd34a05`)
- [x] Review fixes (`cd0b5b406`)
- [x] Angular docs (`06df1ea15`, `1c0c76956`)
- [x] Remove the broad proof workflow (`c4919f9f7`)
- [x] Match React MCP Apps sandbox semantics (`053b00e84`)
## Delivery
- Rebased on `main` at `9855a8107`.
- Preserves both Angular docs commits.
- Uses one canonical Angular artifact across every existing integration
image.
- Preserves React routes and frozen-base comparison.
- Keeps accepted baseline failures owned and linked to issues.
- Excludes CLI scaffolding and Hashbrown work.
Adds the opt-in secondary entry point @copilotkit/angular/mcp-apps for
rendering MCP Apps (MCP ext-apps) inline in the chat:
- CopilotMCPAppsActivityRenderer plus a ready-to-register
mcpAppsActivityRendererConfig for activityType mcp-apps
- CopilotMCPAppsWidget loads the app's ui:// resource from the configured
MCP server, embeds it in a sandboxed iframe, and connects an AppBridge
that relays tool input/result, size changes, links, and log messages
- provideMCPApps registers server URLs and the host identity, capabilities,
and context announced to embedded apps
- @modelcontextprotocol/sdk and @modelcontextprotocol/ext-apps are optional
peer dependencies; the main entry point stays free of MCP imports
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## What
Fixes multi-turn chat on every mastra showcase demo, which 400s on the
2nd turn:
> `AI_APICallError: Invalid 'input[3].id': 'msg-92Y7BhMpWBhXt7dm'.
Expected an ID that contains letters, numbers, underscores, or dashes,
but this value contained additional characters.` (`INCOMPLETE_STREAM`)
## Root cause
OpenAI's Responses API actually **rejects dashes** in `input[].id`. Its
400 message misleadingly lists dashes as allowed, but empirically only
`[A-Za-z0-9_]` is accepted:
| `input[].id` | result |
|---|---|
| *(omitted)* | ✅ 200 |
| `msg_92Y7BhMpWBhXt7dm` (underscore) | ✅ 200 |
| `msg-92Y7BhMpWBhXt7dm` (**the failing client id**) | ❌ 400 |
CopilotKit mints message ids like `msg-…`, and `@ag-ui/mastra` + the AI
SDK forward them straight into `input[].id` when replaying prior-turn
history — so turn 1 works (no prior ids) and every turn after dies.
## Fix
The mastra provider already routes every outbound LLM call through
`forwardingFetch` (the header-forwarding shim). This rewrites dashes
(and any other non-`[A-Za-z0-9_]` char) in each outbound `input[].id` to
`_` there.
**Why here, not in the bridge's message conversion:** it touches only
the bytes sent to OpenAI. Mastra's in-memory `CoreMessage.id` — which
drives its upsert-by-id history **dedup** — is untouched, so dedup is
unaffected. OpenAI-issued ids (`msg_…`, `rs_…`, no dashes) pass through
unchanged, preserving server-side conversation-state references. Chat
Completions bodies (no `input[]` array) are untouched.
This **supersedes ag-ui-protocol/ag-ui#2227** — a bridge-layer charset
munge that *kept* dashes (`[^A-Za-z0-9_-] → -`), making it a no-op on
the real failing ids. That PR is being reverted.
## Tests
`tests/vitest/header-forwarding-id-sanitize.test.ts` — 8 cases: the real
failing id, valid-id no-op, full-charset mapping, in-body rewrite,
no-op/passthrough, and chat-completions-untouched. All green.
## Verification status
- ✅ Transform verified against the exact failing id; `msg_…` form
confirmed accepted by the real OpenAI Responses API.
- ✅ 8 unit tests pass in-module.
- ⚠️ Full end-to-end wasn't run locally (the showcase runtime OOMs a 16
GB box), but **every** showcase OpenAI call flows through this wrapper,
so **staging is the final check** — deploy and re-run a two-click
multi-turn on `/demos/agentic-chat`.
## Refs
- Linear **OSS-381** (mastra refresh umbrella).
- Supersedes ag-ui-protocol/ag-ui#2227 (revert incoming).
- Follow-up worth filing upstream: `@ag-ui/mastra` / AI SDK shouldn't
forward non-provider message ids into `input[].id` at all.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## What
Two mastra showcase D6 follow-ups to #5798 (v1 bridge alpha). Both cells
went red the moment #5798 took them out of `not_supported_features` —
they were claimed supported before actually passing. **Neither is a
v1-bridge streaming regression.**
## Commits
**1. gen-ui-agent completes all 3 steps (raise step cap)** — `57afed6dd`
"Generative UI: Agent State" (gen-ui-agent) stalled at 2/3 steps:
`genUiAgent` set no stop condition, so the AI SDK default halted the
agentic loop before the 3rd step completed. Adds
`defaultOptions.stopWhen = stepCountIs(12)`. Verified 6/6 on the Node-22
+ `next start` + aimock replay rig; tool-rendering / gen-ui-tool-based
unaffected.
**2. useComponent (gen-ui-tool-based) — probe took the wrong path** —
`4640d6304`
The D5 `gen-ui-custom` probe omitted `mastra` from `CHART_INTEGRATIONS`,
so it sent the *haiku* prompt and hunted for a haiku card. mastra's demo
is the LGP-style `useComponent` chart demo (`render_pie_chart` /
`render_bar_chart`) with no haiku tool, so the assistant bubble came
back empty (*"haiku card [data-testid=copilot-assistant-message]
rendered but has no text content"*).
- Add `"mastra"` to `CHART_INTEGRATIONS`.
- Add `aimock/d6/mastra/gen-ui-custom.json` (mirrors langgraph-python;
identical pie schema `{title, description, data:[{label,value}]}`) so
the cell is deterministic under replay instead of falling through to the
live upstream.
- Repoint the probe unit test's haiku-empty-card case from `"mastra"` →
`"agno"` (a genuine haiku integration).
## Verification
- gen-ui-agent: 6/6 on the faithful rig.
- useComponent: logic-only harness + fixture changes; harness unit tests
run in CI (sparse local checkout has no vitest).
## Refs
- Follow-on to #5798. Linear **OSS-381** (mastra refresh umbrella).
- Companion `@ag-ui/mastra` PR — multi-turn Responses-API message-id 400
fix: ag-ui-protocol/ag-ui#2227.
- Sibling from the same triage (separate, not fixed here): **PNI-100** —
hitl-in-app follow-up-run gap.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Kills the "📣 Social Copy Generator" bot comment that lands on every PR.
The comment (e.g.
https://github.com/CopilotKit/CopilotKit/pull/6117#issuecomment-5053084113)
is posted by the `post-comment` job in
`.github/workflows/social_copy-generator.yml`, and the
checkbox-triggered `generate` job lives in the same file — the whole
feature was self-contained there, so deleting the one workflow removes
all of it.
After removal, `git grep -i "social copy"`, `git grep -i "social media
copies"`, and `git grep "copy-generator"` all come back empty — no
scripts, prompts, or docs referenced it anywhere else.
Multi-turn chat on every mastra demo fails on the 2nd turn:
AI_APICallError: Invalid 'input[3].id': 'msg-92Y7BhMpWBhXt7dm'.
Expected an ID that contains letters, numbers, underscores, or dashes,
but this value contained additional characters. (code: INCOMPLETE_STREAM)
Root cause: OpenAI's Responses API actually REJECTS dashes in `input[].id`
(its 400 message misleadingly lists dashes as allowed — empirically only
`[A-Za-z0-9_]` is accepted; `msg-92Y7…` 400s, `msg_92Y7…` succeeds).
CopilotKit mints message ids like `msg-…`, and @ag-ui/mastra + the AI SDK
forward them straight into `input[].id` when replaying prior-turn history, so
the whole request fails. Turn 1 works (no prior ids); every turn after dies.
Fix at the HTTP boundary: the mastra provider already routes every outbound
LLM call through `forwardingFetch` (header-forwarding shim). Rewrite dashes
(and any other non-`[A-Za-z0-9_]` char) in each outbound `input[].id` to `_`
there.
Why here and not in the @ag-ui/mastra message conversion: this touches ONLY
the bytes sent to OpenAI. Mastra's in-memory `CoreMessage.id` — which drives
its upsert-by-id history dedup — is left untouched, so dedup is unaffected.
OpenAI-issued ids (`msg_…`, `rs_…`) contain no dashes and pass through
unchanged, preserving server-side conversation-state references. Chat
Completions bodies (no `input[]` array) are untouched.
Supersedes the ineffective ag-ui-protocol/ag-ui#2227 (a bridge-layer charset
munge that kept dashes — a no-op on the real failing ids; being reverted).
Tests: tests/vitest/header-forwarding-id-sanitize.test.ts (8 cases — the real
failing id, valid-id no-op, full-charset mapping, body rewrite, no-op/passthrough,
chat-completions untouched). All green.
--no-verify: sparse showcase checkout has no monorepo lefthook/commitlint binaries.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## What
Adds a **"Show unique"** filter to the Showcase Dashboard feature grid,
mirroring the existing **"Show deprecated"** toggle. A demo is
**common** when ≥ 2 frameworks ship it; single-framework (and
zero-framework) demos are **unique**.
Both toggles default **OFF**, so the default view is **common ∩
non-deprecated** — the cross-framework gold-standard surface. Each
toggle independently widens the set (AND-combined).
This makes room for framework-specific demos (e.g. the planned Mastra
browser-use / observational-memory demos): once added they'll be hidden
by default and revealed via "Show unique".
## Why the "ships a demo" signal
"Framework supports a demo" is defined as `integration.demos.some(d =>
d.id === feature.id)` (the grid's existing `isWired` signal). The two
alternatives were rejected:
- `integration.features[]` — stale in the data (e.g.
`interrupt-headless` has demos in ~19 integrations but 0 `features[]`
declarations).
- `not_supported_features` — a brand-new single-framework demo never
appears in another integration's `not_supported_features`, so it would
be wrongly classified as common. This is exactly the Mastra case that
motivated the feature.
## UI
- `Show unique (N)` checkbox next to `Show deprecated`, rendered only
when `N > 0`. Tooltip: "N demos supported by fewer than two frameworks —
hidden by default…".
- Subtitle shows the **exact distinct hidden-row count** — `(N hidden)`
— rather than an additive per-category breakdown (a feature can be both
deprecated and unique; the per-category badges provide attribution,
faceted-filter style).
## Testing
- 6 new behavioral tests in `feature-grid.test.tsx` (default-hidden,
reveal-on-toggle, count label, accurate tooltip wording, exact distinct
subtitle count, note-drop when both filters enabled). Suite: **37/37
green**.
- `tsc --noEmit` clean; `oxlint` clean (on the changed files); `next
build` succeeds.
## Notes / follow-up
- **Column-header tallies** count the full matrix regardless of
row-visibility filters. This is pre-existing (unchanged by this PR) and
arguably correct-by-design (a coverage metric over the whole matrix, per
the `computeColumnTally` docstring). Flagged in review, deferred.
- The "unique" count uses `frameworkCount < 2`, intentionally grouping
zero-demo features with single-framework ones (approved design
decision); tooltip wording reflects this.
## Scope
Client-side view state only — no registry, catalog, props, or SSE
changes; all edits internal to `FeatureGrid`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Per-category subtitle counts overlapped (a feature can be both deprecated
and unique), overstating hidden rows. Show the distinct total instead;
per-category counts remain on the toggle badges. CR round 1 finding F2.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tooltip said 'only one framework' but uniqueCount is frameworkCount<2,
which includes zero-framework demos. CR round 1 finding F3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Hides single-framework (non-common) demo rows by default, mirroring the
Show deprecated toggle. Common = shipped by >=2 frameworks (demos[]).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Summary
Introduces Rich Threads as the documentation front door for persistent,
resumable agent conversations and guides developers from setup through
UI choice, user scoping, production deployment, and history
synchronization.
## What changed
### Rich Threads overview
- Presents the approved Support Desk screenshot and light/dark
user-journey diagrams.
- Moves cloud-hosted Enterprise Intelligence setup near the top with
both a coding-agent prompt and direct CLI steps.
- Defines Rich Threads around durable AG-UI event history, including
messages, generative UI, multimodal inputs, tool activity, state, and
live-run continuity.
- Explains Threads Drawer versus Headless Threads and links to the
appropriate implementation guide.
- Describes production self-hosting with a direct CopilotKit Engineering
contact path.
- Adds an end-user scoping FAQ linked to detailed `identifyUser`
guidance.
### Implementation and lifecycle
- Documents Threads Drawer for both CLI-created starters and existing
CopilotKit applications.
- Keeps Headless Threads as the custom-UI path and adds user-scoping and
production self-hosting guidance.
- Adds the server-side `identifyUser` contract, separating application
identity from the project Runtime API key.
- Keeps Thread & History Lifecycle and Threads & Persistence
Architecture in the Rich Threads navigation group.
### History synchronization
- Renames the generic guide to **Import & Synchronize Thread History**
and the navigation label to **Synchronize Thread History**.
- Provides dedicated **Synchronize ADK Threads** and **Synchronize
LangGraph Threads** guides.
- Documents one-time historical import followed by future
CopilotKit-mediated persistence.
- Explains that native ADK or LangGraph persistence and analytics can
remain in place when the durable native mechanism stays connected.
- Corrects the importer destination contract: dry runs need no
destination credentials; real imports read flags or exported process
environment values.
### Framework coverage
- Applies shared Rich Threads navigation and content across root,
authored, generated, built-in, and framework-specific routes.
- Keeps existing URLs and implementation identifiers unchanged; no
redirects are required.
- Uses shared sources rather than hand-editing generated framework data.
### Measurement
- Adds conversion events for the coding-agent prompt, CLI setup command,
self-hosting contact, Drawer customization, and history synchronization.
## Validation
- [x] Formatting for all files changed by this PR
- [x] Lint (existing repository warnings only)
- [x] Typecheck
- [x] Tests (33 files, 180 tests)
- [x] Production build
- [x] Desktop/mobile and light/dark browser review across representative
root, authored, generated, built-in, ADK, and LangGraph routes
Two issues found in review after `examples/integrations/adk-angular`
merged (#6097).
## 1. Closed chat stayed keyboard-accessible off-screen
The collapsible chat panel is only translated off-screen (`transform:
translateX(100%)`), so its `copilot-chat` inputs/buttons remained in the
tab order and the accessibility tree while "closed."
**Fix:** mark the `<aside>` `inert` while closed
(`[attr.inert]="chatOpen() ? null : ''"`); removed when open.
## 2. Dev inspector shipped in the production bundle
The `cpk-web-inspector` dev aid (and its `@copilotkit/web-inspector`
dependency, ~660 kB) was statically imported, so it landed in the **4.59
MB** production initial bundle.
**Fix:** gate it behind `@defer (when isDev)` (`isDev = isDevMode()`),
splitting it into a lazy chunk that a production build (`isDev` = false)
never loads.
## Testing (live)
- **Prod build** (`ng build`): initial bundle **4.59 MB → 3.93 MB**;
`@copilotkit/web-inspector` is now a separate lazy chunk (660 kB) that
prod never loads.
- **Dev serve**: the inspector still mounts (`@defer` triggers when
`isDev`).
- **a11y**: with the chat closed, focusing a control inside the panel is
blocked (`document.activeElement` falls back to `body`); open panel
unaffected; the toggle FAB still reopens it.
- `oxlint` 0/0.
Follow-up to OSS-561.
🤖 Generated with [Claude Code](https://claude.com/claude-code)