Standalone OnPush Angular component wrapping the framework-agnostic
copilotkit-drawer Lit element. Binds live thread state from injectThreads onto
the element (imperative viewChild+effect, per the a2ui-activity-renderer
precedent) and routes the element's DOM events to the ambient
CopilotChatConfiguration + thread mutations. Ships a CopilotDrawerRow directive
for per-row slot projection, host onThreadSelect/onNewThread escape-hatch
callbacks, and <ng-content> slot passthrough (launcher-icon/memories). Uses the
filtered listError for the error panel. Always-licensed (no licensed/upsell
wiring); inline-chat-only (no Layer-2 open-state coordination). Exported from
public-api.
Adds a listError signal that returns only genuine list/mutation errors,
excluding developer/config errors (missing runtime URL, runtime without thread
endpoints) so consumer error UIs do not surface dev strings to end users.
Mirrors react-core's useThreads listError; `error` is unchanged (additive).
Adds the workspace dep (consumed by the <CopilotDrawer> wrapper) and allows it
as a non-peer dependency in ng-package.json so ng-packagr packages cleanly.
CopilotChat reads the ambient config when a provideCopilotChatConfiguration
provider is in scope: input-first precedence ([agentId]/[threadId] win over the
config, matching React), seeding the config from a set [threadId], and driving
the connector with loading-cursor hooks. Standalone <copilot-chat [threadId]>
usage is unchanged when no provider is present. Exports CopilotChatConfiguration
from the package entry point.
connectActiveThread reactively pins the resolved thread onto agent.threadId.
On an explicit switch it connects the agent, owning the loading-cursor + abort
+ detach lifecycle of the standalone connectToAgent path (per-run AbortController
on HttpAgents, a single caught connect chain so a rejecting connect never leaks
an unhandled rejection, and a staleness-guarded cursor settle). On a fresh /
non-explicit switch it clears messages only on a genuine new-thread transition,
never on mount or a same-thread agent swap. Injection-context-only; not exported
on the public surface.
Injectable service mirroring React's CopilotChatConfigurationProvider. Owns
agentId/threadId resolution (controlled prop > override > options > minted
fallback) and the hasExplicitThreadId welcome-screen flag, exposes
setActiveThreadId/startNewThread setters that no-op when the config is
host-controlled, and reserves drawerOpen/registerDrawer hooks for a future
popup/sidebar layer. provideCopilotChatConfiguration aliases the token to a
single instance via useExisting.
## What & why
Fixes **ENT-1020**. In a side-by-side layout (the threads drawer rail
next to the chat), at iPad-portrait / tablet widths the chat **message
text sat flush against both pane edges** — no horizontal padding — while
the input stayed correctly inset. Surfaced while manually testing the
Angular `CopilotDrawer` (#5746), but it is **not a drawer bug**: it
reproduces in any layout that puts `CopilotChat` in a pane narrower than
the viewport.
### Root cause
The message column is `max-w-3xl` (768px) centered; its wrappers used
`cpk:px-4 cpk:sm:px-0` — 16px below 640px, then **0 at viewport
≥640px**. There was no `container-type` on the chat root, so the `sm:`
variant keyed on the **viewport**, not the chat's own width. In a
sub-viewport pane (~580px chat on an 820px viewport) `sm:px-0` still
fired (viewport ≥640) while the 768px column overflowed the pane
edge-to-edge → flush text.
## The fix (robust / container-relative)
- Add `cpk:@container` (`container-type: inline-size`) to the chat root
(React ×2 render paths, Angular, Vue).
- Switch every message / input / suggestion wrapper from viewport
`cpk:sm:px-0` to the **container** variant `cpk:@3xl:px-0`.
Padding now tracks the chat's **own** width and drops to 0 only once the
container is at least as wide as the column's `max-w-3xl`, i.e. once the
column has real side gutters. In any narrower pane the `px-4` inner
padding is retained. **React, Angular, and Vue kept in lockstep.**
### Why `@3xl`, not the `@sm` the ticket suggested
Tailwind v4 **container-query** breakpoints are a *different scale* from
viewport breakpoints: `@sm` = **24rem/384px** (viewport `sm` = 640px). A
mechanical `sm:` → `@sm:` swap would still collapse the ~580px repro
pane (580 ≥ 384). `@3xl` = **48rem/768px**, which exactly matches the
column's `max-w-3xl` — the width at which gutters first appear — so it
is the semantically correct breakpoint.
> Vue was not named in the ticket scope but shares the identical
`sm:px-0` pattern; left unfixed it would reproduce the bug there, so it
is included for true framework lockstep. web-components only *hosts* the
chat (no `sm:px-0`), so it is correctly untouched.
## Testing
**Browser behavior — real built CSS, exact DOM
(`copilotKitChat`/`@container` root → `cpk:max-w-3xl cpk:mx-auto` →
`cpk:px-4 cpk:@3xl:px-0` message wrapper),
`getComputedStyle().paddingLeft`:**
| Scenario | Result | Expectation |
|---|---|---|
| **580px narrow pane** (the repro) | `padding-left: 16px` | ✅ `px-4`
retained — text no longer flush |
| **900px wide pane** (full desktop) | `padding-left: 0px` | ✅ `px-0` —
column has gutters, behavior preserved |
| **No `@container` ancestor** (render-prop path) | `padding-left: 16px`
| ✅ graceful `px-4`, no flush |
**Generated CSS confirmed** (Tailwind v4.1.18): root emits
`container-type: inline-size`; wrapper emits `@container (min-width:
48rem) { padding-inline: 0 }` (verified in both react-core and angular
builds — a true container query, not a media query).
**Unit/component tests (pass):**
- react-core — full chat suite: **645 tests / 39 files** green (incl.
`CopilotChatCssClasses`).
- angular — `copilot-chat-view` + `copilot-chat-input` specs: **10**
green.
- vue — `CopilotChatView.connectingGate` +
`CopilotChatSuggestionView.slots.e2e`: **30** green.
- pre-commit `test-and-check-packages` (test + publint + attw across all
4 affected projects) passed.
No tests assert these class strings and no snapshots capture them, so
nothing needed regenerating.
## Acceptance criteria
- [x] Messages retain horizontal padding when the chat is in a narrow
(<~768px) pane at viewports ≥640px.
- [x] Full-width chat behavior preserved (container ≥768px → `px-0`),
verified in React, Angular, Vue.
- [x] No regression to input / disclaimer / suggestion alignment with
the message column (all share the same `@3xl` switch).
Closes ENT-1020
🤖 Generated with [Claude Code](https://claude.com/claude-code)
CopilotChat message wrappers used viewport-keyed `cpk:sm:px-0`, collapsing
horizontal padding to 0 at any viewport >=640px. The message column is
`max-w-3xl` (768px) centered; the design assumes the chat fills the viewport,
so at >=640px the column has side gutters and inner padding can drop to 0.
But when the chat lives in a sub-viewport-width pane (e.g. the threads drawer
rail beside the chat, ~580px on an 820px iPad-portrait viewport), `sm:px-0`
still fires on viewport width while the 768px column overflows the narrow
pane and sits flush against both edges. The input wrapper looked fine because
it is visually inset by its own pill, so only message text appeared broken.
Make the padding container-relative instead of viewport-relative:
- add `cpk:@container` (container-type: inline-size) to the chat root, and
- switch the message/input/suggestion wrappers from `cpk:sm:px-0` to the
container variant `cpk:@3xl:px-0`.
Padding now tracks the chat's own width and drops to 0 only once the container
is at least as wide as the column's own max-width, so the column has real
gutters; in any narrower pane the `px-4` inner padding is retained. React,
Angular, and Vue kept in lockstep.
Note on the breakpoint: Tailwind v4 container-query breakpoints differ from
viewport breakpoints (`@sm` = 24rem/384px, not 640px). A mechanical
`sm:` -> `@sm:` swap would still collapse the ~580px repro pane. `@3xl`
(48rem/768px) is used because it exactly matches the column's `max-w-3xl`,
which is the width at which side gutters first appear.
Verified: full-width desktop chat unchanged (container >=768px -> px-0);
580px pane retains 16px padding; render-prop layouts without a container
ancestor degrade safely to `px-4`; sidebar/popup `data-*` padding overrides
are unaffected.
ENT-1020
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
### What
The Angular `CopilotChatInput.handleKeyDown` submits the message when
Enter is pressed without Shift, but it never checks whether an IME
composition is in progress. When typing CJK text (Japanese, Chinese,
Korean), the Enter that confirms an IME candidate also fires a
`keydown`, so the half-composed text gets sent instead of the candidate
being committed.
The React and Vue bindings of the same v2 `CopilotChatInput` already
guard against this; Angular was the one binding still missing it:
- `packages/react-core/src/v2/components/chat/CopilotChatInput.tsx` —
`handleKeyDown` returns early on `e.nativeEvent.isComposing || e.keyCode
=== 229`
- `packages/vue/src/v2/components/chat/CopilotChatInput.vue` —
`handleKeydown` returns early on `isComposing.value || event.isComposing
|| event.keyCode === 229`, and has a test asserting it does not submit
while composing
### Change
Add the same early return to the Angular `handleKeyDown`, using the
native `KeyboardEvent` (`event.isComposing || event.keyCode === 229`),
which matches the Vue binding's idiom.
### Notes
When composition is not active `isComposing` is `false`, so Enter
submits exactly as before and Shift+Enter still inserts a newline. The
most visible case is Safari with a Japanese IME, where the confirming
Enter reports `key === "Enter"` with `isComposing === true`; the
`keyCode === 229` arm mirrors the sibling guards for browsers that
report the composing key that way.
I verified the handler logic in isolation (Enter while composing no
longer submits; plain Enter and Shift+Enter are unchanged). I did not
run the full Angular suite locally.
Adds /a2ui-catalog page and a runtime endpoint with NO a2ui config, so
A2UI switches on purely from the provider's a2ui.catalog (the #5774
path). Also fixes DemoButtonAgent, which never actually rendered: it
emitted the wrong activity content key (operations -> a2ui_operations)
and a non-canonical operation/component format. Rewritten to the A2UI
v0.9 wire format (createSurface/updateComponents, flat components, root
id "root") so the surface paints and the Confirm round-trip works.
A2UI's catalog-on-provider path needs a catalog to pass to
`a2ui.catalog`, but the library build tree-shook the nested barrel
re-export so vueBasicCatalog was unreachable at runtime (present only in
the .d.ts). Re-export it explicitly from the v2 entry, mirroring React's
`basicCatalog` export from @copilotkit/a2ui-renderer. Add an export test
guarding against the regression.
## Why
The 6 OpenAI drop-in integration examples enabled for the CLI's
**keyless AIMock mock mode** (`langgraph-python`, `langgraph-js`,
`mastra`, `llamaindex`, `agno`, `pydantic-ai`) shipped
`fixtures/default.json` files that **did not cover the prompts each
starter's own UI suggests**. AIMock matches `userMessage` as a
**case-sensitive substring, first-match-wins**, so a keyless first-run
user who clicked the demo's suggestion chips mostly fell through to the
generic catch-all ("I only have scripted replies…") instead of getting a
scripted demo reply.
Two root issues found (audit at `origin/main`):
- **`langgraph-python` was mis-keyed** — fixtures keyed on suggestion
*titles* (`"Pie Chart"`, `"Toggle Theme"`, `"Task Manager"`) while the
UI sends long *message* strings that don't contain those substrings →
all 9 suggestions missed.
- The other 5 shipped only a generic `Hello` (+ a `weather` fixture in
langgraph-js/mastra) → 0–1 of each starter's chips covered.
This PR re-keys/extends each example's fixtures so the surfaced
suggestions return scripted replies. Ordering preserved:
most-specific-first, `{}` catch-all last (unchanged text).
> Tracking: **ENT-1003** (CopilotKit/Intelligence). Sibling to ENT-989
(fixtures for *not-yet-enabled* frameworks). This PR covers the
*already-enabled* 6.
## What changed (per template)
| Template | Suggestions now covered | Tool-call replies | Text replies
|
|---|---|---|---|
| langgraph-python | 9/9 (3 re-keyed) | pie/bar chart
(`query_data`→component), `scheduleTime`, `toggleTheme`, `manage_todos`
| Search Flights, Excalidraw, Calculator, Sales-Dashboard A2UI step |
| langgraph-js | 9/9 | `query_data`, `search_flights`, `generate_a2ui`,
`manage_todos` (schemas verified vs agent) | scheduleTime, Excalidraw,
generateSandboxedUi, toggleTheme |
| mastra | 6/6 | `get-weather`, `setThemeColor`, `go_to_moon` (HITL) | 3
proverb chips (proverbs are agent shared-state, not a tool) |
| llamaindex | 3/3 | — | theme / proverb / weather |
| agno | 4/4 | — | weather / theme / stock / proverb |
| pydantic-ai | n/a (UI surfaces no chips) | — | best-effort free-typer
fixtures: "what can you do" / "proverb" / "weather" |
## Honest caveats (best-effort; draft)
- **Tool-call shapes only where evidenced.** Where a suggestion drives
A2UI streaming, an MCP app (Excalidraw), or a frontend-only tool
(`generateSandboxedUi`, `scheduleTime` in some templates) whose call
shape isn't defined in the template, I used an **on-topic text reply**
rather than fabricating a tool envelope. Those replies beat the
catch-all but won't trigger the live generative UI under mock mode — a
follow-up could record the real shapes.
- **`pydantic-ai` surfaces no suggestion chips** in its UI, so there's
nothing to key on; the real fix is a small UI change (add `suggestions`
to `CopilotSidebar`), which is out of scope for a fixtures-only PR. The
added fixtures are a fallback for free-typing users.
- **Not verified end-to-end here** (authored against `origin/main`, not
run live). Each template's `docker-compose.test.yml` AIMock smoke should
stay green; note its `@chat` test only sends `"Hello"` and asserts a
non-empty reply, so it does **not** validate suggestion-chip coverage —
extending it to assert a non-catch-all reply for a real suggestion would
close that blind spot (also noted in ENT-1003).
## Test plan
- [ ] Per-template `docker-compose.test.yml` AIMock smoke still green.
- [ ] Manual: scaffold/run each keyless, click each suggestion chip,
confirm a scripted reply (not the catch-all).
Same class as the toggle-theme fix: langgraph-js's 'schedule a meeting' chip
returned text, so the meeting picker never rendered, while langgraph-python emits
the scheduleTime tool call. scheduleTime is a registered langgraph-js frontend
tool (reasonForScheduling/meetingDuration) — swap text -> tool call, mirroring
langgraph-python. Because scheduleTime is Human-in-the-Loop (the user's pick
returns as a tool result and re-invokes the LLM), add a terminating
call_schedule_time_001 result fixture in BOTH templates so the turn doesn't
re-match the user message and loop (langgraph-python lacked it too — latent).
Verified both terminate at 2 steps (tool call -> terminating text).
The langgraph-js Toggle Theme chip returned a text response claiming it toggled
the theme, but emitted no tool call -- so the theme never changed, while the same
chip in langgraph-python emits the toggleTheme frontend tool call and works.
Identical chip, different outcome by template. Swap the text response for the
toggleTheme tool call, mirroring langgraph-python (frontend tool, no terminating
result fixture needed -> no loop). Verified aimock now returns the toggleTheme
tool call for the chip.
The Vue CopilotKitProvider gated all A2UI activation on the runtime
signal (core.a2uiEnabled), so passing an a2ui.catalog to the provider
did nothing: surfaces never rendered locally and the runtime was never
told to inject the render tool. React already supports the
catalog-on-provider path (1.61.2).
Mirror React: a provided catalog now activates A2UI locally and forwards
the a2uiCatalogAvailable property so the runtime injects the render tool
without a runtime-side a2ui config. User-provided properties are
preserved alongside the signal.
Fixes#5774
The chart-chain fixtures looped under multi-step tool execution: langgraph-python
emitted a 2nd tool call (pieChart/barChart/dashboard) with no terminating
toolCallId result fixture, so it fell through to the still-matching userMessage
fixture and re-called query_data. langgraph-js had its toolCallId result fixtures
ordered after the userMessage fixtures (first array match wins -> re-call).
Add the missing terminating result fixtures (langgraph-python) and reorder so all
toolCallId fixtures precede userMessage fixtures (langgraph-js). Verified termination
via the 2-3 step multi-turn repro; mastra fixtures already terminated.
The 6 OpenAI drop-in integration examples enabled for the CLI's keyless
AIMock mock mode shipped fixtures that did not cover the prompts each
starter's own UI suggests, so a keyless first-run user clicking the demo
suggestions mostly hit the generic catch-all. Re-key/extend each
example's fixtures/default.json (substring, most-specific-first,
catch-all last) so the surfaced suggestions return scripted replies.
ENT-1003.
## ⚠️ Draft — stacked on #5563
This builds on #5563 (the `oracle-agent-memory` showcase). Since the
showcase isn't on `main` yet, **this PR's diff currently includes
#5563's files** — once #5563 merges, it auto-reduces to just the
checkpointer change below. Keeping it as a **draft** until then; opening
early to stage the work.
## What this adds
An **optional, flag-gated** durable LangGraph checkpointer for the
showcase agent, using Oracle's
[`langgraph-oracledb`](https://github.com/oracle/langchain-oracle/tree/main/libs/langgraph-oracledb)
`AsyncOracleSaver` — so per-thread LangGraph graph state persists in
**Oracle**, **complementing** (not replacing) `oracleagentmemory`. That
rounds out the "whole stack on Oracle" story: durable *memory* **and**
durable *graph checkpoints*.
**Default-safe:** gated behind `LANGGRAPH_CHECKPOINTER` (default
`memory` → behavior unchanged; `oracle` is opt-in), with graceful
fallback to in-memory on any Oracle error — so CI and the default run
path never touch a DB.
## The actual delta (just these agent files)
- `agent/concierge/checkpointer.py` *(new)* — flag-gated resolver:
builds a dedicated async Oracle pool + `AsyncOracleSaver`, runs `await
setup()`, degrades to `MemorySaver` on failure.
- `agent/concierge/server.py` — an import-time monkeypatch injects the
resolved checkpointer past `ag_ui_agentspec`'s hardcoded `MemorySaver`;
the FastAPI lifespan inits/closes it.
- `agent/pyproject.toml` — adds `langgraph-oracledb>=1.0.1`, bumps
`oracledb>=2.2.0`, adds dev `pytest`/`pytest-asyncio`.
- `agent/.env.example` — documents `LANGGRAPH_CHECKPOINTER`.
- `agent/tests/test_oracle_checkpointer.py` *(new)* — a durability
round-trip test (skipped unless `LANGGRAPH_CHECKPOINTER=oracle`).
Mirrors
[jerelvelarde/oracle-cookbook#4](https://github.com/jerelvelarde/oracle-cookbook/pull/4),
where it's reviewed + verified — the durability round-trip passes
against a live Oracle DB, and 5/5 e2e pass with the flag off (no
regression on the default path).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Runnable companion to the #5521 cookbook recipe — a portable Oracle
Agent Spec agent on LangGraph over AG-UI, long-term memory on Oracle AI
Database, and a CopilotKit V2 frontend (generative UI + HITL booking).
Lives beside `daytona-runcode`.
- `agent/` — Python (uv) Agent Spec agent + FastAPI AG-UI server
- `frontend/` — Next.js CopilotKit V2 chat (flight cards, recall chip,
HITL booking)
- `db/` — Oracle AI Database (Free image) + cookbook-user init
- `docker-compose.yml` (local Oracle) + per-service `railway.json`
✅ Live cross-session recall verified working on the hosted demo (a
preference taught in one thread is recalled in a fresh thread).
### Pre-merge follow-ups — resolved ✅
- [x] **Workspace deps** — the frontend builds standalone (`npm ci` +
`next build` ✓ inside the monorepo). It ships its own
`package-lock.json` and is intentionally excluded from the pnpm
workspace (the `daytona-runcode` / `shell-dashboard` convention), so the
published prerelease `@copilotkit/*` resolve and no
`workspace:*`/`catalog:` is needed.
- [x] **Promote SSOT** — the live demo is hosted independently (not part
of CopilotKit's Railway promote fleet), so no
`showcase/scripts/railway-envs.ts` entry is required.
- [x] **`db/build-and-push.sh`** — default image namespace genericized
to an `OWNER` placeholder (still `IMAGE`-overridable).
- [x] **Oracle license** — `db/Dockerfile` and `build-and-push.sh` both
document that the built image must stay in a PRIVATE registry
(re-publishing the Oracle base image violates the license).
> The Vercel preview deploys and `fork-pr-monitor` fail because this is
a cross-fork PR (forks can't access deploy secrets) — not code issues.
`config-allowlist` is fixed.
Docs: CopilotKit#5521.
## Problem
Docs OG image generation was producing unreliable social-preview output
for docs URLs. The route depended on old static OG assets and Inter-era
styling, and the preview did not match the current CopilotKit docs theme
or logo.
## Why
The OG route should render a consistent branded card from page
frontmatter for every docs slug. It also needs local render assets for
request-time reliability: `next/og` does not inherit the app layout
font, and image inputs need to be available as bytes when the route
renders.
## Fix
- Reworked `showcase/shell-docs/src/app/og/[...slug]/route.tsx` to
render a branded 1200x630 card with a tighter layout, CopilotKit theme
colors, Plus Jakarta Sans, and per-page title/description/section
labels.
- Kept `next/font/google` for normal docs pages, and added upstream Plus
Jakarta Sans static TTFs only for the OG renderer. `SOURCE.md` records
the upstream URLs and SHA-256 hashes. The Google Fonts variable TTF was
tested but the bundled `next/og` renderer crashes while parsing its
`fvar` table.
- Added the official CopilotKit full lockup as a real PNG asset. It is
covered by the repo-level `*.png filter=lfs` rule, and the route encodes
the PNG bytes to a data URI only at render time for `ImageResponse`.
- Removed the hardcoded runtime/frontend/agent pills and the yellow
gradient stop from the card.
- Updated the focused OG route test to assert the card dimensions and
bundled Plus Jakarta fonts.
Validation: focused OG test, direct `ImageResponse` render with the
upstream fonts, lint, typecheck, build, and live local OG route checks
passed. Full shell-docs test has unrelated existing failures in public
LFS PNG assets and one docs-render nav expectation.
## What does this PR do?
Follow-up polish on the **OpenBox Governance** cookbook recipe (shipped
in #5686), addressing review feedback from the launch. All changes are
in the single recipe page; no functional/code changes.
| Feedback | Change |
| --- | --- |
| "Make this an actual diagram — hard to read as ASCII" | Replaced the
ASCII flow under **How it works** with a real, color-coded **inline-SVG
architecture diagram** (same self-contained pattern as the Oracle recipe
— no external assets). |
| "Huge, scary warning — condense it" | Cut the wall-of-text
**provisioning** warning down to a few lines while keeping the
essentials (idempotent, which keys it needs, how to verify). |
| "Prompts are unreadable in the chart layout → accordions" | Converted
the **Try it** governance-matrix table into one **accordion per prompt**
(verdict-labeled), so the long prompts are on-demand instead of crammed
into table cells. |
| "Highlight the important stuff — guide the user. Applies to all code."
| Added line highlighting across the bash + TypeScript samples so the
key lines (the runtime wrap, `selfGovernedToolNames`, middleware-first,
the provisioning command, the approval schema) stand out. |
| "Move this to the top top and make it an accordion" | Moved the
**coding-agent prompt** from the bottom to just under the intro,
collapsed in an accordion. |
## Verification
Rendered locally against `shell-docs` (`next dev`) and confirmed
in-browser: the SVG diagram renders, all five accordions expand
correctly, the warning is condensed, and code highlighting shows on both
bash and TS blocks (no leaked `[!code]` markers).
## Related
- Recipe (merged): #5686
- Source-link hotfix (merged): #5767
- Companion showcase (pending): #5685 — once it lands, restore the
direct `tree/main/...` source link in **Get the code**.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Address review feedback on the OpenBox Governance recipe:
- Replace the ASCII flow with a real inline-SVG architecture diagram
- Condense the wall-of-text provisioning warning to a few lines
- Convert the governance-matrix table into per-prompt accordions
- Highlight the key lines across the code samples to guide the reader
- Move the coding-agent prompt to the top in a collapsed accordion
- Revert the v2 useRenderToolCall fallback: when no per-tool or wildcard renderer is
registered, return null instead of auto-painting DefaultToolCallRenderer. Showing the
card is opt-in via useDefaultRenderTool().
- The auto-card default shipped silently in v1.57.2 (commit ba60df5d33, buried in a
showcase per-tool-testids commit, no CHANGELOG entry) and leaked internal tool names +
raw args/result JSON (e.g. generate_a2ui) into every v2 app's chat in production.
- Remove the now-dead defaultToolCallRenderAdapter + __testOnly_ export + unused imports.
- Rewrite the resolver test to assert the 3-state opt-in contract: useDefaultRenderTool()
-> card, useDefaultRenderTool({render}) -> custom, no hook -> nothing.
## What does this PR do?
The **OpenBox Governance** cookbook recipe (#5686) merged to `main`
ahead of its companion showcase (#5685), so the recipe's **"Get the
code"** section linked to:
`https://github.com/CopilotKit/CopilotKit/tree/main/examples/showcases/openbox-governed-copilotkit`
…which **404s** today because that code isn't on `main` yet.
This swaps the broken link for a plain-text **"Full source to follow"**
note (no hyperlink, so nothing 404s) that still describes what the
showcase will contain. The live **upstream reference-repo** link
directly below it is kept.
```diff
- Full source: [`examples/showcases/openbox-governed-copilotkit`](https://github.com/.../tree/main/examples/showcases/openbox-governed-copilotkit) — `agent/` … and `frontend/` …
+ Full source to follow — the runnable showcase (the `agent/` LangGraph service with OpenBox middleware and the `frontend/` CopilotKit V2 chat with the wrapped runtime and approval route) will be published under `examples/showcases/openbox-governed-copilotkit`.
```
**Follow-up:** once the showcase merges to `main`, restore the direct
`tree/main/...` link.
_Note:_ the recipe's run-instructions still `cd` into that path
(expected — the whole "run it yourself" flow assumes the code), so those
steps only work once the showcase lands. Left as-is since they read as
setup instructions, not a clickable link.
## Related PRs and Issues
- Docs recipe (merged): #5686
- Companion showcase (pending): #5685
## 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
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01Fr5HVeDzDyC4S6DjyhAFWZ
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Fr5HVeDzDyC4S6DjyhAFWZ)_
The OpenBox Governance recipe (#5686) merged ahead of its companion
showcase (#5685), so the "Get the code" link pointed at
github.com/.../tree/main/examples/showcases/openbox-governed-copilotkit,
which 404s while that code is not yet on main.
Replace the broken link with a plain "Full source to follow" note (no
hyperlink, so nothing 404s) that still describes what the showcase will
contain. The live upstream reference-repo link is kept. Swap the link
back in once the showcase merges to main.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fr5HVeDzDyC4S6DjyhAFWZ
## What this adds
A new **"OpenBox Governance"** recipe in the cookbook
(`showcase/shell-docs`), documenting how to add **OpenBox runtime
governance** — guardrails, OPA/Rego policy, redaction, human-in-the-loop
approvals, and halt — to a **CopilotKit + LangGraph** agent, with
decisions rendered as generative UI.
Follows the existing cookbook recipe pattern (Oracle / Arcade /
Daytona):
- `src/content/docs/cookbook/openbox-governed-copilotkit.mdx` — the
recipe (how it works, the stack, prerequisites, run + **provisioning**
steps, the four-verdict governance matrix, the key pieces in code, going
further).
- `meta.json` — registers the recipe in cookbook nav.
- `index.mdx` — overview `<Card>`.
- `src/lib/sidebar-icon.tsx` — `custom/openbox` sidebar icon.
- `public/logos/openbox.png` — brand mark (Git LFS).
The recipe documents the companion showcase **at full parity**: the
provisioning step (`npm run openbox:admin:setup`), the Allow / Constrain
/ Approval / Block / Halt matrix using the demo's real suggestion
prompts, and "key pieces in code" snippets pulled verbatim from the
final showcase source (the LLM-driven governed engine, the
OpenBox-middleware-first agent, and the runtime + approval routes).
## Red → green (TDD)
The cookbook-nav test in `src/lib/__tests__/docs-render.test.ts` is the
red/green hook — it hard-asserts the recipe count, titles, slugs, and
URLs (6 entries incl. `["OpenBox Governance",
"cookbook/openbox-governed-copilotkit"]`).
## Verification
- `docs-render` test suite: **15/15** (nav asserts 6 entries).
- `next build`: ✅ (all cookbook routes, no MDX/link errors).
- The run-steps, four-verdict matrix, and code snippets were verified
against the **final** demo source (companion PR #5685), so the docs and
the runnable code stay in sync.
## Companion demo PR
The runnable showcase this recipe documents:
**https://github.com/CopilotKit/CopilotKit/pull/5685**
> **Ships standalone.** This recipe no longer links the hosted live demo
— that link now lives on the companion demo PR (#5685), so the cookbook
recipe can merge independently of the demo deployment.
## Problem
There is currently no way to intercept an A2UI action before it reaches
the agent when using `createA2UIMessageRenderer`. Every dispatched
action is forwarded to the agent unconditionally:
1. `A2UIMessageRendererOptions` exposed only `{ theme, catalog?,
loadingComponent?, recovery? }` with no `onAction`
(`packages/react-core/src/v2/a2ui/A2UIMessageRenderer.tsx`).
2. `ReactSurfaceHost` mounted `<A2UIProvider onAction={handleAction}>`
and `handleAction` always called `copilotkit.runAgent({ agent })`
unconditionally.
3. Per-component interception fails: `web_core@0.9.0`'s `GenericBinder`
classifies any prop whose Zod schema is a union containing `{ event }`
as `ACTION` and replaces the raw value with a zero-arg dispatching
closure (so `props.action` is a function, `props.action.event` is
undefined).
4. `functionCall` actions are dropped, not a workaround:
`surface-model.js` `dispatchAction` only emits when the payload has
`event`, and `createCatalog` provides no way to register client
functions.
Raised in Discord by a user with a custom catalog + custom Button who
needs a `navigate` event handled client-side. DevRel confirmed
`onAction` was not exposed.
## Change
Add an optional `onAction` interceptor to `createA2UIMessageRenderer`:
```ts
onAction?: (
action: A2UIUserAction,
forward: (action?: A2UIUserAction) => Promise<void>,
) => void | A2UIUserAction | null | Promise<void | A2UIUserAction | null>;
```
- Return `null` to handle the action client-side and stop forwarding
(agent is not run).
- Return an `A2UIUserAction` to forward the (possibly modified) action.
- Return `undefined` / `void` to forward unchanged.
The interceptor is threaded from `createA2UIMessageRenderer` options
through `ReactSurfaceHost` into `handleAction`. The forwarding logic is
extracted into a `runA2UIAction` helper so the three behaviors are
unit-testable. The original finally-block property cleanup is preserved,
and default behavior is byte-identical when `onAction` is not supplied.
New `A2UIActionInterceptor` type is exported from the v2 entrypoint.
## Tests
Added to
`packages/react-core/src/v2/__tests__/A2UIMessageRenderer.test.tsx`:
- `onAction` returning `null` does not run the agent.
- `onAction` returning a modified action forwards the modified payload.
- No `onAction` forwards the original message unchanged.
- `onAction` returning `undefined` forwards unchanged.
## Verify
- `nx test react-core` (1374 passed)
- `nx run-many -t lint build --projects=react-core` (clean)
Ports two fixes from the canonical oracle-cookbook demo into the oracle-agent-memory
showcase agent (server.py was byte-identical to the demo's pre-fix version):
1. Dangling tool-calls: booking conversationally calls the book_flight HITL tool,
which interrupts and emits an assistant tool_call awaiting the UI's Confirm/Cancel.
If the traveler sends another chat message instead, the unanswered tool_call made
the next turn 400 ("tool_call_ids did not have response messages"). _repair_dangling_tool_calls
synthesizes a "not completed" tool result for any dangling call before the history
reaches the graph. (Inverse of the duplicate-tool-block issue the history-replace
already handled — documented in docs/known-issues/agentspec-multiturn-toolcall-correlation.md.)
2. HTML-escaped persistence: the agentspec exporter HTML-escapes streamed deltas
(& < > -> & < >); the SSE generator persisted them raw, so assistant
replies were stored in Oracle Agent Memory as e.g. "fares < $700".
_clean_assistant_text html.unescapes the assembled text before persisting.
Both are reproduced + verified in oracle-cookbook (unit tests + in-browser); the ported
functions are byte-identical to the verified demo code.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up to #5720 (merged). That PR added the **A2UI Error Recovery**
demo (`a2ui-recovery`) across google-adk +
langgraph-{python,fastapi,typescript} + strands{,-typescript}, but the
recovery flow was only exercised by the manual on-demand workflow
(`/test-aimock`). It was NOT covered by the per-PR d5/d6 fleet harness,
so a regression in heal/exhaust would not turn any automatic CI cell
red.
## What this does
Adds a `d5-a2ui-recovery` probe and wires it into the harness so
`a2ui-recovery` runs on every PR like `gen-ui-declarative`:
- **New probe**
`showcase/harness/src/probes/scripts/d5-a2ui-recovery.ts` drives both
pills in one session and asserts the stable end-states:
- **HEAL**: the healed surface paints (>= 2 newly-mounted
`declarative-metric` tiles) and NO "Couldn't generate the UI" card.
- **EXHAUST**: the hard-failure "Couldn't generate the UI" card appears
and NO surface paints.
- Assertions are delta-based against a pre-send baseline so the two
mutually-exclusive negatives stay correct across the shared two-turn
session. The transient "Retrying..." label is not asserted
(timing-flaky).
- **Per-slug prompts**: the recovery prompts are unique per integration
slug (the inner `render_a2ui` calls carry no `x-aimock-context`, so
identical prompts would collide in the shared aimock matcher). The probe
sends each slug's exact `suggestions.ts` message as typed input, which
is byte-identical to the pill dispatch and matches the same fixture.
Heal is sent exactly once (its fixture stages invalid->valid via
`sequenceIndex` 0->1).
- **Wiring**: register `a2ui-recovery` in `d5-registry`, map it in
`d5-feature-mapping` (`"a2ui-recovery": ["a2ui-recovery"]`), add its
representative fixture in `d5-representatives`, and mirror the mapping
in the dashboard `CATALOG_TO_D5_KEY` (kept in lock-step by the drift
test).
## Verification
Local fleet harness, both recovery paths green (heal + exhaust):
- `showcase test langgraph-python:a2ui-recovery --d6 --isolate` -> green
(backend-owned `get_a2ui_tools` path)
- `showcase test strands:a2ui-recovery --d6 --isolate` -> green
(auto-inject middleware path)
Harness unit tests pass (`d5-representatives`, `d5-mapping-drift`,
`starter-mapping-drift`); harness typecheck clean.
## Note (separate, pre-existing)
The on-demand workflow `test_e2e-showcase-on-demand.yml` is currently
broken for all slugs: it installs `@copilotkit/aimock@^1.16.4` then
invokes `aimock --fixtures ...`, but the resolved aimock CLI no longer
accepts `--fixtures` (`Error: Unknown option '--fixtures'`, only
`-c/--config`). aimock dies at the "Start aimock" step before any test
runs. This is unrelated to the recovery demo and is tracked separately.