mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
codex/fac-205-fix-empty-cache-control
553 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
edee011099 | Merge branch 'main' into refactor/rn-render-tool-hooks | ||
|
|
bba4113b8e | fix(react-core): improve Inspector message shortcuts | ||
|
|
b2b6bfbc3e |
refactor(react-native)!: converge render-tool hooks onto react-core
React Native's `useRenderTool` was not react-core's `useRenderTool`. Its entire
body forwarded to a different hook, `useFrontendTool`, while wearing the other
one's name. core has two: `useFrontendTool` registers a tool AND its renderer
via `addTool`; `useRenderTool` registers a renderer ONLY via
`addHookRenderToolCall`, and special-cases `"*"` into a schema-less fallback.
Because RN's alias took the `addTool` path, `name: "*"` registered a frontend
tool literally named `*` and advertised it to the model. Eight further symptoms
share that single cause: a render-only registration was advertised and shadowed
a same-named server tool; `addTool` evicted an existing same-named
`useFrontendTool` handler with only a `console.warn`; `description` and
`parameters` were required though both are tool fields; `followUp` and
`available` were accepted upstream but not forwarded; `handler` dropped core's
second (context) argument, leaving `stopAgent()`'s abort signal unreachable;
`agentId` changes never re-registered; two structurally incompatible
`RenderTool*Props` families shipped side by side; and `hooks/index.ts` was a
dead barrel with no build entry, no exports mapping and no importer.
Deletes RN's hook and re-exports core's two instead, so each capability has
exactly one implementation and RN carries no render-tool API of its own.
Deleting rather than re-pointing the name is deliberate. Re-pointing is the
dangerous shape: a `{ name, parameters, render }` call with no handler would
keep compiling and silently stop registering the tool.
Two of the nine are documented rather than fixed, so this is not a clean sweep:
`agentId` is still absent from both core hooks' re-registration check, so the
`deps` workaround stands; and `useDefaultRenderTool` keeps a narrower render
return than the hooks converged here.
Adds the guard the entry-surface suite was missing. It asserted only that
`useRenderTool` was *present* on the headless entry, never which hook it was, so
it would have stayed green through this entire change while verifying nothing
about it — proven by mutation: an RN-local hook re-grown under the name fails the
new identity assertion while 18 other guards in that file stay green.
Also rewrites the RN render-tool suite around what RN still owns. The previous
file tested forwarding into `useFrontendTool` through a double that modelled so
little its own header comment recorded that deleting the `deps`, `handler` and
`agentId` forwarding left it fully green. Assertions now read core's observable
state: `getTool`, `core.tools`, a real `runTool` rejection, painted DOM through
the real `useRenderToolCall`, and the tool list core hands an agent on a real
run.
BREAKING CHANGE: removes `useRenderTool`, `RenderToolProps`,
`RenderToolFunction` and `UseRenderToolOptions` from @copilotkit/react-native.
A tool-plus-renderer registration becomes `useFrontendTool` with an otherwise
identical object; renderer-only registration and the `"*"` wildcard become
core's `useRenderTool`; render props rename `args` to `parameters`. The
migration table, and the analysis of which call shapes fail loudly versus
silently, live in
showcase/shell-docs/src/content/reference/react-native/hooks/useRenderTool.mdx —
this repo's release-note collector reads only commit subjects, so a footer is
not a consumer-facing channel (see #6479) and the docs page is.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
dce8ee7578 |
refactor(react-core): let a tool renderer return null
`ReactToolCallRenderer.render` is a `React.ComponentType`, and a function component returns `ReactNode` — of which `null` is a member. So `null` already worked at the point a renderer is actually invoked; only the two build-side helpers forbade it, leaving core stricter on the way in than on the way out. Widens the render return to `React.ReactElement | null` at six annotations: `use-render-tool.tsx` (both overloads plus the implementation config) and `types/defineToolCallRenderer.ts` (both overloads plus the implementation signature). Both files are required — widening only the hook yields TS2322, because it passes `config.render`'s result straight into the helper. Non-breaking. Widening what a caller-supplied callback may return can only accept more code than before, so every renderer returning an element still compiles. Tested in both directions: a renderer returning `null` registers and paints nothing, and one returning an element behaves as before. Note that vitest transpiles without type-checking, so `tsc --noEmit` (which CI runs via static_quality.yml's check-types job) is the only guard against a re-narrowing. Scope: this widens `useRenderTool` and `defineToolCallRenderer` only. It does NOT make core uniformly permissive — `useDefaultRenderTool` still requires `ReactElement`, so core's two wildcard entry points now differ. That divergence fails loudly (TS2322) rather than silently, and is left to its own change since that hook carries four narrow annotations plus an existing cast. Note `defineToolCallRenderer` is re-exported from @copilotkit/react-native, so this change reaches React Native consumers too — it is not web-internal. Not purely type-level: oxlint's consistent-type-imports rule converted this file's `ToolCallStatus` import to `import type`, which removes a runtime `@copilotkit/core` import from the emitted JS. Harmless, since core is imported from many other react-core modules and no initialisation order changes — but worth stating rather than filing under "types". Also re-homes a coverage pin that @copilotkit/react-native's suite carried: despite living in RN it tested core's `JSON.stringify(extraDeps)` comparator, and no react-core test covered the case, so removing RN's copy would have left it unpinned repo-wide. It records a documented sharp edge, not behaviour worth preserving. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
1422019862 |
test(react-core): import the A2UI renderer once, not inside every test
The first test in A2UIMessageRenderer.test.tsx timed out on the
Node 24 / React 19 unit shard while the same commit passed on every other
shard. The test body is about ten milliseconds of work.
The cost was the `await import("../a2ui/A2UIMessageRenderer.js")` inside
the test. That import pulls in the whole @copilotkit/a2ui-renderer graph,
which vitest.config.mjs inlines, and vitest charges the one-time transform
to whichever test runs first. Measured locally: the first test took 502ms
of the 5000ms default timeout, and the other seventeen took 0 to 15ms
each. Under CI load the same cost reached 4798ms on a passing shard.
All eleven dynamic imports named the same module, and the file calls no
vi.resetModules(), so every one already resolved to a single cached
instance. The laziness bought nothing and cost the first test its budget.
One static import moves the work to collection, which no test timeout
bounds. Measured after the change: the first test takes 53ms, and the
import phase grows from 27ms to 468ms, which is where that work belongs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
a38a3a7e92 |
fix(react-core): register v1 readables before sibling effects run
useCopilotReadable published its context in a useEffect. React flushes passive effects child-first in tree order, so a consumer mounted before the readable runs its own useEffect against an empty context store. That is the cross-page-navigation failure: a page mounts the chat and its readable-publishing components in one commit, the chat's connect effect fires first, and the connect request carries no context. Register in useLayoutEffect instead. Layout effects run during commit, ahead of every passive effect regardless of tree order, which closes the window. Register and cleanup stay in the one effect, so both sides remain in the same phase. This matches the v2 siblings useAgentContext and useFrontendTool, the latter fixed the same way in |
||
|
|
274983d4f8 |
fix(docs): make the redirect suffix-aware and repair dead links the regeneration surfaced
Self-review follow-ups on the reference-docs regeneration.
The LangGraphAgent redirect only covered the bare path. A raw Markdown request
reaches redirects before the .md/.mdx rewrite, so a request for
/reference/v1/sdk/python/LangGraphAgent.md would have 404'd for the LLM routes.
Use permanentRedirectsWithSuffixes, which is what the rest of the redirect
table does.
Refreshing the pages also republishes their JSDoc links, and three of those
pointed at pages that do not exist. They were invisible while the pages were
frozen; regenerating makes them live 404s, so fix them at the source:
- use-coagent-state-render.ts linked to /coagents/videos/perplexity-clone, a
legacy URL with no content, no redirect and no rewrite. Point at
/generative-ui/state-rendering, the canonical guide the published page
already named.
- copilotkit-props.tsx linked to
/coagents/shared/guides/langgraph-platform-authentication, which likewise
does not exist. Point at /auth, which is how the rest of the docs link to
that guide.
- use-copilot-chat.ts was flipped to
/reference/v2/hooks/useCopilotChatHeadless_c by the URL canonicalization in
|
||
|
|
e823409f96 |
fix(react-core): name the agent on CopilotKitProvider
`@copilotkit/react-core/v2` re-exports the v1 `<CopilotKit>` provider, and that was the only provider carrying an agent prop. So a v2 application that wanted to name its agent at the provider level had to reach for the v1 compatibility component, and the reporter had to read the installed type definitions to find that the v2 equivalent lives on `<CopilotChat agentId>` instead. Accept `agentId` on `CopilotKitProvider`. It publishes a bare string context that is the last fallback before `DEFAULT_AGENT_ID`, so `<CopilotChat agentId>`, `<CopilotChatConfigurationProvider agentId>`, and an explicit `agentId` argument to `useAgent`/`useSuggestions` all still win. The default deliberately does NOT arrive through a root `CopilotChatConfigurationProvider`. That provider also owns a thread: it resolves a threadId, minting a UUID when none is given, and the top-most one owns the imperative active-thread override. Wrapping the application in one hands every descendant chat the same inherited threadId, so two sibling chats share a transcript. A test renders two sibling chats under the provider and pins that they keep their own threads. Docs: say plainly on the `CopilotKit` reference page that it is the v1 provider, and note the prop rename on the provider-and-handler-pairs page. Both pages claimed that released versions of `<CopilotKit>` pin `useSingleEndpoint` to `true`; that pin was removed in 1.70.2, so both providers now negotiate the transport when the prop is omitted. Fixes OSS-1133 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
b0f349fcfb |
fix(a2ui): report a surface whose root component never resolves (closes OSS-1057)
Both renderers begin walking a surface at the component with id "root" and treat an id they cannot find as not arrived yet, painting an animated placeholder. That is right while operations stream. Once operations have stopped it is not waiting, it is stuck — and every existing check calls it healthy: the surface exists, processMessages does not throw, the component type is never reached so the "Unknown component" branch cannot fire, and surfaceHasRenderableContent says yes on the strength of components plus a non-empty data model, so onReady fires and the never-painted report is suppressed by its own guard. A complete, accepted payload therefore animates a grey box forever with nothing in the console. Reports it on the existing paint deadline, measured from the last operations to land, so a root still missing when it expires is a root that is not coming. The check reads the live components model rather than scanning the operations for the id, which covers every way a root can fail to resolve — not only a payload that never named one — and the message says which of the two it is. Keeps the fixed root id: A2UI v0.9 dropped v0.8's rootComponentId, and createSurface carries only surfaceId, catalogId and theme, so a payload has no way to declare its own entry point. Deriving one instead would pick silently and wrongly whenever several components are unreferenced. The id moves to a single ROOT_COMPONENT_ID constant so the three sites that hard-coded the string, and the new report, agree by construction. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
77dc28ea2c |
fix(a2ui): resolve a surface id the same way in both renderer paths (refs OSS-1048)
The two paths disagreed. React read a top-level `operation.surfaceId` first and only then the nested v0.9 keys. The web-components path read the nested keys only, via normalizeOperations, and never looked at a top-level id at all. So one payload grouped under its own id in React and under "default" in the Lit and Angular renderers. Nested wins in both now, with a top-level id as the fallback when the payload carries none. Nested is the correct half of that choice, not a coin toss: MessageProcessor creates the surface from the nested id. Grouping by a top-level id instead files the operations against a surface that createSurface never made, and an unknown surface id renders A2UIRenderer's null fallback. That is a card that paints nothing, which is what the previous commit taught the renderer to report. So the old React order could produce the silence, and the missing-surface report is what the new test uses to prove the grouping agrees with what got created. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
0f7ee3501a |
fix(react-core): report an A2UI card that receives operations and paints nothing (refs OSS-1048)
Two ways an A2UI surface can render nothing without saying a word. The operations arrive and no paint follows. The renderer already waits 8s for the surface to report its first paint before dropping the loader, so reaching that fallback is itself the signal that nothing painted. Report it there, and use what surfaceHasRenderableContent already knows to say which half is missing: no updateComponents at all, or bound components whose updateDataModel never carried a value. The operations name a surface that was never created. A2UIRenderer renders its fallback for an unknown surface id and that defaults to null, so the card is absent and the log is empty. processMessages is synchronous, so a surface still missing after it was never created. The second report is deferred a task and re-checked, because operations stream and a snapshot can reach the processor before the createSurface that gives it somewhere to go. Removing both the deferral and the re-check makes the mid-stream test fail. Neither report covers a surface that exists and holds complete components and still draws nothing. That case needs the component catalog, which lives in @a2ui/web_core, and it stays silent for now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
22fa691e80 |
fix(react-core): warn when a tool call has no renderer instead of rendering nothing (refs OSS-1048)
A tool call that matches no registered renderer returns null. Nothing else happens: no console output, no empty-state card, and a finished turn. The only signal is a blank message container in the chat, which a developer has to notice in the DOM and then guess at. Report it in development. The warning names the tool the agent called, lists the renderer names that are registered, and points at useRenderTool and useDefaultRenderTool. When the cause is a name that does not match, that is the whole diagnosis. Rendering behavior is unchanged. Auto-painting a default card would leak tool names and raw args into production chat, which is why the resolver returns null, and that decision stands. The report is deferred one task past the commit that recorded the miss, then re-checks the registry. useRenderTool registers from an effect in the component that renders the chat, and React runs child effects before parent ones, so at effect time the resolver can see an empty registry even though the app did register a renderer. Removing that re-check makes two of the new tests fail on exactly that false positive. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2031643fa1 |
fix(react-core): wait for the user on humanInTheLoop provider tools
The `humanInTheLoop` prop on `CopilotKitProvider` registered a handler that
warned and resolved `undefined` the moment the agent invoked it, and it
registered the renderer unwrapped, so the render never received a working
`respond`. A tool declared that way jumped straight to Complete over dead
controls while the agent was told the tool had succeeded. The placeholder is
unchanged since the first v2 provider commit (
|
||
|
|
82cd7c01f7 |
fix(react-core): let a custom catch-all renderer return null
`useDefaultRenderTool`'s `render` was typed to return `React.ReactElement`, so a caller who wanted to render only some tool calls could not return `null` to suppress the built-in default for the rest. The value already flowed through correctly at runtime; only the type rejected it. Widen the public `render` return type, and the wrapper local that carries the user's value, to `React.ReactElement | null`. The `as unknown as` cast into `useRenderTool` stays, because `useRenderTool` still requires a `ReactElement` return on main; PR #6533 widens that hook, after which the cast can be tightened. Guarded by a `.test-d.ts` assertion rather than a runtime test: types are erased, so a null-returning render forwards identically before and after the widening and a runtime test would assert nothing. Extracted from #5509, which is otherwise stale. Co-authored-by: Atai Barkai <atai.barkai@gmail.com> |
||
|
|
42494df607 |
docs(react-core): stop pointing v1 appendMessage at non-public sendMessage (#6940)
## Summary The v1 `useCopilotChat` JSDoc tells readers to use `sendMessage` instead of `appendMessage`. `sendMessage` is not part of the public v1 return type, so following that advice does not compile. `packages/react-core/src/v1-deprecated/hooks/use-copilot-chat.ts:109` explicitly omits it: ```ts export type UseCopilotChatReturn = Omit< UseCopilotChatReturnInternal, | "messages" | "sendMessage" // <- the JSDoc points readers here ... ``` `sendMessage` exists only on `useCopilotChatInternal`. The public hook returns `appendMessage`, which is the working v1 programmatic-send path. This replaces the misdirection with the v2 migration pointer and states plainly what `appendMessage` is for. Comment-only change — no runtime effect. Found while closing #4215, where a user was told by our own docs to call a method we do not export. ## The published page does not change yet This fixes the source of truth. The generated page cannot be refreshed until #6939 is resolved: running the generator today would also embed the internal v1 deprecation banner ("AI CODING AGENTS: Never copy, suggest, or generate these v1 APIs") into 20 public reference pages. I deliberately excluded the regenerated `.mdx` files from this PR rather than ship that. Once #6939 lands, a regenerate publishes this wording. ## Testing **1. `oxfmt --check` — pass** ``` $ ./node_modules/.bin/oxfmt --check packages/react-core/src/v1-deprecated/hooks/use-copilot-chat.ts Checking formatting... All matched files use the correct format. Finished in 33ms on 1 files using 18 threads. ``` **2. Generator reads this file successfully (26/26)** — confirms the JSDoc edit is picked up, and that the only thing blocking publication is #6939, not this change: ``` $ ./node_modules/.bin/tsx scripts/docs/gen.ts Successfully autogenerated showcase/shell-docs/src/content/reference/v1/hooks/useCopilotChat.mdx from packages/react-core/src/v1-deprecated/hooks/use-copilot-chat.ts All reference docs processed (26/26 succeeded) ``` The regenerated page contained the new wording as expected; I then reverted the generated files per the section above. **3. Diff is a single comment hunk** — verified with `git diff --stat`: `1 file changed, 4 insertions(+), 1 deletion(-)`, all inside a JSDoc block. No exported symbol, type, or runtime line touched, so no typecheck or test surface is affected. **4. Claim verified against `origin/main`,** not a local branch: `git show origin/main:packages/react-core/src/v1-deprecated/hooks/use-copilot-chat.ts` confirms both the misdirecting line (`:70`) and the `Omit` (`:109-121`). ## Notes - No changeset — comment-only. - Branch name is a leftover misnomer (`ben1/oss-docs-generator-v1-paths`); the change is the wording fix only. - @ataibarkai's #6653/#6654/#6655 stack would move this file back to `packages/react-core/src/hooks/`. If that stack lands, this one-line change needs carrying forward into the reapply. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Documentation** - Clarified the deprecated `appendMessage` option in `useCopilotChat`. - Documented its role for programmatic sending in v1. - Clarified the migration path for AG-UI format users moving to v2. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
0217f45d73 |
docs(react-core): stop pointing v1 appendMessage at non-public sendMessage
The v1 `useCopilotChat` JSDoc told readers to use `sendMessage` instead of `appendMessage`, but `UseCopilotChatReturn` omits `sendMessage` from the public return type, so following that advice does not compile. Point at the v2 migration path instead, and state that `appendMessage` is the public v1 programmatic-send path. Reported via #4215. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
62c895fee2 |
fix(react-core): default attachment uploads to one at a time
`maxConcurrentUploads` defaulted to 3, which changed when a public `onUpload` is called with no code change on the app's side: a handler written when uploads were serial could suddenly see the next file start before the previous one finished. Concurrency is now something the app asks for, and `maxConcurrentUploads: 3` restores the pool. Queueing the whole selection up front is kept at every limit — it shows the user what they picked rather than changing a contract. The default test now pins one-at-a-time; a separate test pins that `maxConcurrentUploads: 3` really runs three. Docs, the `AttachmentsConfig` JSDoc and the react-core skill reference say `1`. |
||
|
|
c237f29dbb |
fix(react-core): share the upload pool across processFiles calls
The worker pool was per `processFiles` call, so a paste landing while a dropped selection was still uploading opened its own set of workers — two overlapping selections could run 2× the limit, and `maxConcurrentUploads: 1` gave one upload per call rather than one at a time. Move the queue and the worker count onto the hook: workers are counted, not owned by a call, and a call tops the pool up to the limit instead of starting a fresh one. Each call still resolves when its own files have settled. Also pin `Infinity` as "no limit" with a test, and say in the docs that the limit covers everything in flight rather than each batch. |
||
|
|
62067b76d1 |
feat(react-core): upload attachments concurrently
`processFiles` walked the valid files in a `for` loop and awaited each upload inside it, so `onUpload` was called for one file only after the previous had finished — attaching 8 files to a chat cost 8 sequential round trips to whatever storage the app uploads to. Queue the whole selection first, then drain it with a bounded worker pool: `maxConcurrentUploads` on `AttachmentsConfig` sets the bound and defaults to 3, and `1` restores one-at-a-time uploads for an endpoint that wants them. `onUpload` may now be called concurrently. Queueing up front also means a file waiting for a free slot is already visible as `uploading` rather than appearing once its upload starts. The Vue and Angular bindings read the same config type and still upload serially; they can follow separately. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8c629c147b |
docs(showcase): document frontend-driven activity cards (refs #3388) (#6904)
## What Issue #3388 asked for a way to put a card into the chat transcript from frontend code, without a tool call and without adding to the conversation the model reads. **That already ships.** A message with `role: "activity"` renders standalone in the transcript, and `AbstractAgent.prepareRunAgentInput` strips every activity message from the run payload: ```js prepareRunAgentInput(e) { let t = structuredClone_(this.messages).filter(e => e.role !== `activity`); ... } ``` The gap was documentation. `renderActivityMessages` is only documented for **backend-emitted** activities (mastra background-tasks, a2a, mcp-apps), so the frontend-driven path was undiscoverable. This PR adds the missing guide page and a test that pins the behavior. ## Changes | File | Why | | --- | --- | | `showcase/shell-docs/.../generative-ui/frontend-cards.mdx` | New "Frontend-Driven Cards" guide | | `showcase/shell-docs/.../generative-ui/meta.json` | Sidebar entry (6-line insertion) | | `packages/react-core/.../CopilotChatFrontendActivityCard.e2e.test.tsx` | Pins both halves of the contract | No source changes. Behavior is unchanged; this documents and locks what already works. ## The non-obvious part The card must be added via the agent returned by `useAgent()`. An agent instance constructed and held outside React is **not** the instance the chat renders, so messages added to it silently never appear. This cost me a debugging round while verifying, and it is called out as a warning callout in the docs. ## Testing **1. New test passes against clean `origin/main`** (run in a worktree at `96cf7aa55f`, with `@copilotkit/shared` and `@copilotkit/core` rebuilt from the worktree so the test is not reading a stale dist): ``` ✓ src/v2/components/chat/__tests__/CopilotChatFrontendActivityCard.e2e.test.tsx (2 tests) 72ms Test Files 1 passed (1) Tests 2 passed (2) ``` **2. Mutation-checked, so neither assertion is self-fulfilling.** Drop the renderer registration → the render test fails: ``` × renders a card added from frontend code, with no tool call 1068ms Tests 1 failed | 1 passed (2) ``` Swap the card from `role: "activity"` to `role: "assistant"` → it reappears in the payload, so the exclusion is real and specific to `activity`: ``` AssertionError: expected [ 'user', 'assistant' ] to deeply equal [ 'user' ] ``` **3. Neighboring test unaffected on the same base:** ``` ✓ src/v2/components/chat/__tests__/CopilotChatMessageView.test.tsx (16 tests) 53ms Tests 16 passed (16) ``` **4. Independent probe of the filter** against the pinned `@ag-ui/client` 0.0.57: ``` agent.messages roles: [ 'user', 'activity' ] run input roles : [ 'user' ] ``` **5. `tsc --noEmit`** — zero errors in the new file. Remaining errors in this workspace are in files this PR does not touch (`MCPAppsActivityRenderer.tsx`, `CopilotKitInspector.tsx`) and are artifacts of a hand-assembled local `node_modules`; CI has the real install. **6. `oxfmt --check`** — clean. **7. Docs checks** — `meta.json` validated as JSON; internal link uses the house `/generative-ui/...` form (no `/docs` prefix); `Callout type="warn"` matches the dominant existing usage; import paths verified against the real `@copilotkit/react-core/v2` barrel exports. ## Follow-up Leaving #3388 open until this lands, then closing it with a pointer to the new page. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added support for frontend-driven activity cards that render in chat transcripts without being sent to the agent or language model. - Added documentation covering activity card renderers, schemas, registration, payload filtering, snapshots, and limitations. - Added a new “Frontend-Driven” section to the Generative UI documentation navigation. - **Tests** - Added end-to-end coverage for activity card rendering and payload exclusion. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
5fd08a824e |
feat(react-core): controlled open/onOpenChange props for CopilotSidebar and CopilotPopup (#6905)
Closes #3334 (OSS-524). ## Problem v1 `<CopilotSidebar>` exposed `open` and `onSetOpen`. Those props let a host open and close the chat from its own UI. v2 shipped only `defaultOpen`. The reporter wanted a button in their own nav bar to close the sidebar. The reporter's stated root cause is now stale. `shouldCreateModalState` no longer exists. Since CPK-7152 the provider syncs both directions: `setAndSync` upward, and an effect downward. A host that wraps its layout in `<CopilotChatConfigurationProvider>` and calls `setModalOpen` therefore does drive the sidebar on current `main`. I verified that before writing any code. Two things are genuinely missing. The first is the ergonomic API that v1 had. The second is documentation for the outer-provider pattern that already works. Two earlier community attempts (#3729, #6418) were closed unmerged. ## What changed `open` and `onOpenChange` on `<CopilotSidebar>` and `<CopilotPopup>`: - `open` pins what the surface renders, from the first frame. - `onOpenChange` reports every request to open or close: the toggle button, click-outside, Escape, and the drawer's mobile mutual-exclusion. It fires with or without `open`, so it also works as a plain notification on the uncontrolled path. - `defaultOpen` is unchanged. If both are passed, `open` wins. Two design choices are worth review. **1. A context-overriding scope, not a fourth mode in the provider.** `ControlledModalOpenScope` replaces `isModalOpen` and `setModalOpen` for the subtree below the provider that owns the state. The resolution chain inside `CopilotChatConfigurationProvider` stays untouched: own state, parent sync, drawer mutual-exclusion, and the `ɵregisterModalCloser` stack. The scope's setter still calls the underlying one, so those side effects keep running. It also registers itself as the modal closer, so the drawer's mobile exclusion reaches the host instead of flipping state that nothing displays. The alternative was a controlled branch threaded through `resolvedIsModalOpen`, `setAndSync`, and the sync effect. That adds a fourth interacting mode to the code CPK-7152 just stabilized. **2. The props reach the views by context, not as props.** `<CopilotSidebar>` hands its view to `<CopilotChat>` as a memoized `chatView` component. Adding `open` to that memo's deps mints a new element type per toggle, and React then remounts the whole chat subtree. That is the same class of bug #6173 fixed for popup resize. There is a regression test for it. Scope note: I included `<CopilotPopup>` because it shares the mechanism and the same docs page. The issue named only the sidebar. ## Testing **New suite, 15 tests** (`CopilotSidebar.controlledOpen.test.tsx`). It covers the controlled contract, the unchanged uncontrolled path, and the remount guard. ``` ✓ src/v2/components/chat/__tests__/CopilotSidebar.controlledOpen.test.tsx (15 tests) 155ms Test Files 1 passed (1) Tests 15 passed (15) ``` **Mutation-checked.** I broke each mechanism to confirm that the tests really fail. | Mutation | Result | | --- | --- | | Drop `ControlledModalOpenScope`, keep only the seeded default | 5 failed: both `onOpenChange` reports, both host-driven open/close cases, the popup report | | Implement through the memoized override instead (add `open` to the `useMemo` deps) | 1 failed: the remount guard, `expected 4 to be 1`, one extra mount per flip | | Drop the `open ?? defaultOpen` seeding | 1 failed: "stays put when the host stops controlling open" | I also mutation-checked the pre-existing two-way sync before I started. That confirmed the outer-provider workaround really works on `main`, instead of only appearing to. **Full `@copilotkit/react-core` suite.** No regressions. ``` Test Files 141 passed | 1 skipped (142) Tests 1604 passed | 2 skipped (1606) EXIT=0 ``` **Adjacent suites re-run explicitly**: sidebar position, sidebar and popup slots, popup resize-remount, drawer launcher, and the provider's own 43 tests. ``` Test Files 6 passed (6) Tests 117 passed (117) ``` **Typecheck.** `tsc --noEmit` in `packages/react-core` gave `exit=0` with no output. The tsconfig includes `src/**/*`, so the new test file is typechecked too. **Format and lint.** `oxfmt --check packages/react-core/src/v2` reported "All matched files use the correct format." `oxlint` on the touched files reported 0 errors. **Pre-commit hooks.** They ran for real on both commits. ``` NX Successfully ran targets test, publint, attw for 2 projects and 20 tasks they depend on ✔️ test-and-check-packages (15.33 seconds) ``` ## Docs - `prebuilt-components/chat-controls.mdx` now leads with the controlled pair. Its example drives the sidebar from a nav button outside it, which is the shape #3334 asked about. The `useCopilotChatConfiguration` route stays, reframed as the option for callers who prefer not to lift the state. - `reference/components/CopilotSidebar.mdx` and `CopilotPopup.mdx` gain `open` and `onOpenChange`. Both pages documented `defaultOpen` as `false`, but both surfaces mount open, so I corrected that. A new test per surface pins the real default. ## Not in this PR - Vue and Angular parity for the same props. - The `width` prop of `<CopilotSidebar>` still sits in the memo deps of the `chatView` override. A live-resized sidebar therefore remounts the chat subtree, the way the popup did before #6173. That is pre-existing and out of scope here. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added controlled open-state support for chat popups and sidebars through `open` and `onOpenChange`. - Preserved uncontrolled usage with `defaultOpen`, while allowing externally managed visibility and toggle requests. - Improved coordination between modal and mobile drawer behavior. - **Documentation** - Added usage guidance and reference details for controlled and uncontrolled open-state management. - **Tests** - Added coverage for initial visibility, toggle callbacks, controlled updates, default behavior, and preserving the chat subtree. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
603bc16cf3 |
fix(react-core): restore code block line breaks in packaged CSS (#3330) (#6902)
## What does this PR do? Fixes #3330 — fenced markdown code blocks render as one collapsed line in the packaged v2 React UI. ### Root cause streamdown renders one `<span>` per source line inside `pre[data-streamdown="code-block-body"] > code`, and leaves **no newline characters** in the text. The line break comes entirely from the raw Tailwind utility `block` on that span: ```js // streamdown 1.6.11, dist/code-block-*.js var v = cn("block", "before:content-[counter(line)]", ...); ``` CopilotKit builds Tailwind with `@import "tailwindcss" prefix(cpk)`, so `.block` is never emitted into `dist/v2/index.css` — only `.cpk\:block` is. Every line therefore renders inline and the block collapses onto one row. The line spans carry no `data-streamdown` attribute, so the rule has to be scoped structurally, the same way the table action controls were in #5944: ```css [data-copilotkit] [data-streamdown="code-block-body"] > code > span { @apply cpk:block; } ``` ### Why the earlier attempts did not work Three previous PRs (#3441, #3615, #5387) added `whitespace-pre` to the `<pre>`. That is a no-op: the UA stylesheet already applies `white-space: pre` to `<pre>`, nothing in the packaged CSS overrides it, and there are no newlines in the text for it to preserve. ### Knowingly not fixed here - **Line-number gutter.** streamdown's `before:content-[counter(line)] before:w-4 before:mr-4 …` utilities are unprefixed too, so the gutter never renders. That is cosmetic, and the repo's existing scoped rules do not port it either. - **The pre-highlight loading skeleton** (`space-y-4`, `divide-y`, `animate-spin`) is unprefixed as well — a brief flash of unstyled skeleton before shiki resolves. - **The broader class of bug.** Every unprefixed streamdown utility has to be hand-ported like this. streamdown 2.x adds a `prefix` prop that would fix the whole surface at once, and #5147 proposes removing the bundled renderer entirely. Both are larger calls than this bug fix. ## Testing **1. Live browser verification.** Built `dist/v2/index.css` from `origin/main` and from this branch, rendered streamdown 1.6.11's actual code-block DOM against each, and measured layout in Chromium: | | `white-space` on `<pre>` | line-span `display` | distinct rendered rows | `<pre>` height | |---|---|---|---|---| | main | `pre` | `inline` | **1** | 52px | | this PR | `pre` | `block` | **5** | 112px | Indentation is preserved after the fix (`spans[1].textContent` starts with two spaces). **2. Compiled CSS.** `tailwindcss -i src/v2/styles/globals.css -o … -m` emits exactly: ```css [data-copilotkit] [data-streamdown=code-block-body]>code>span{display:block} ``` **3. Tests** — `pnpm -C packages/react-core exec vitest run src/v2/styles` ``` ✓ src/v2/styles/__tests__/streamdown-styles.test.ts (3 tests) 2ms ✓ src/v2/styles/__tests__/streamdown-table-controls.test.tsx (1 test) 37ms ✓ src/v2/styles/__tests__/streamdown-code-block-lines.test.tsx (1 test) 430ms Test Files 3 passed (3) Tests 5 passed (5) ``` Two tests, following the split established by #5944 — a source-string test that the selector exists, and a DOM test that streamdown still renders the structure that selector assumes (so a streamdown markup change fails loudly instead of silently un-fixing this). **4. Mutation-checked both tests.** Removing the CSS rule fails the string test: ``` × Streamdown styles > ships a scoped display rule for code block lines (#3330) 3ms Tests 1 failed | 2 passed (3) ``` Pointing the DOM test at a selector streamdown does not render fails it: ``` × Streamdown code block lines DOM (#3330) > renders one line span per source line 428ms Tests 1 failed (1) ``` **5. Formatting** — `oxfmt --check` clean on all three files; `git diff --check` clean. `tsc --noEmit` in this worktree reports 58 pre-existing errors, all from a stale cross-package `@copilotkit/core` dist; none are in the changed files (which are CSS plus tests). ## Related PRs and Issues Fixes #3330 Supersedes #3441, #3615, #5387, #5996 (all added a no-op `whitespace-pre`) ## 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 (not applicable: scoped visual bug fix) - [x] "Allow edits by maintainers" is checked <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Bug Fixes** - Fixed fenced code blocks collapsing into a single line in the packaged UI. - Code lines now render vertically as separate rows with the correct layout styling. - **Tests** - Added regression coverage to verify code-line rendering and scoped styles for code blocks. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
4da2d7e34e |
fix(core): hydrate self-hosted threads whose /connect replay contains an errored run (#6528)
Hydrating an existing thread through `/connect` fails on a **self-hosted** runtime whenever that thread's history contains a run that ended in `RUN_ERROR`. ## The defect A `/connect` response is a *replay* of a thread's history, so it can legitimately carry several past runs back to back — including an errored run followed by a later `RUN_STARTED`. The base `AbstractAgent` connect pipeline pushes that stream through `verifyEvents`, which enforces AG-UI's **single run** lifecycle rules and rejects the sequence outright: ``` Cannot send event type 'RUN_STARTED': The run has already errored with 'RUN_ERROR'. No further events can be sent. ``` The user-visible effect is the one reported in #4943: `agent_connect_failed` on reload, and the existing thread never hydrates its prior messages. `IntelligenceAgent` already omitted `verifyEvents` from its connect pipeline for exactly this reason (its JSDoc spells it out). But `ProxiedCopilotRuntimeAgent.connectAgent` only takes that path in `RUNTIME_MODE_INTELLIGENCE` — self-hosted (`RUNTIME_MODE_SSE`) fell through to `super.connectAgent()` and inherited the single-run verification. So the managed product was fine and self-hosting was not. ## The fix `ɵconnectWithoutEventVerification` (`packages/core/src/utils/connect-replay.ts`) holds the verifyEvents-free pipeline, and **both** paths now use it. `transformChunks` is still applied — message reassembly is needed either way. This is a de-duplication rather than a third copy: `IntelligenceAgent.connectAgent` drops ~100 lines of hand-replicated base pipeline (including its private-field `any` escape hatch) and keeps only its canonical-run-id handling before delegating. Net `intelligence-agent.ts` change is −101 lines. ### Fidelity to the base implementation The helper was diffed statement-by-statement against the **real** `AbstractAgent.connectAgent` in `@ag-ui/client@0.0.57` (recovered from the shipped source map), not just against `IntelligenceAgent`'s replica. `verifyEvents` is the only intended difference. That diff caught a defect in the first push: the base special-cases `AGUIConnectNotImplementedError` (swallow → `EMPTY`) and the replica did not. `IntelligenceAgent` never needed it — it always implements `connect()` — so the gap was invisible there, but on the SSE path it is load-bearing: `run-handler.ts:447-450` documents that `await agent.detachActiveRun()` only stopped deadlocking because that error path still reaches the pipeline's finalize block. Routing it through `onError` would also fire run-failure callbacks on every subscriber for a benign condition. Restored, with a regression test. Also confirmed that dropping `verifyEvents` cannot alter a well-formed replay: it is a pure gate — 18 `return of(event)` pass-throughs, 42 error paths, and zero `endWith` / `startWith` / `tap` side effects. It only removes the single-run rejection. The existing upstream TODO still stands and is carried over: `@ag-ui/client@0.0.57`'s `connectAgent(parameters?, subscriber?)` takes no option to skip verification, so this override is still the only way to express "this stream is a replay, not a run." ## On the second half of #4943 The issue also reports that the legacy chat path doesn't copy the resolved `threadId` onto the agent before connect/run. **That half is already fixed on `main`** — the #5041/#4739 fix put `agent.threadId = resolvedThreadId` in v2 `useAgent`, and `useCopilotChatInternal` delegates to that same hook. Nothing more was needed. It was untested, though, and untestable from the suite that looked like it covered it: `use-copilot-chat-internal-connect.test.tsx` mocks `useAgent` wholesale, so it cannot observe threadId propagation at all. This PR adds `legacy-chat-explicit-threadid.test.tsx`, which drives the legacy hook through the **real** `useAgent` under a real `<CopilotKit>`, covering both the explicit-threadId case and the "don't adopt a non-explicit placeholder UUID" case. It reads the agent off `useCopilotChatInternal()`'s own return value rather than calling `useAgent` in the probe. That distinction matters: the first version of this test did call `useAgent`, so the probe itself performed the assignment under test and the test passed **even with `useCopilotChatInternal()` removed entirely**. The current version is mutation-checked — disabling the assignment in v2 `useAgent` fails it (`expected 'dc051f13-…' to be 'cookie-backed-thread'`). Contributor PR #4969 proposed a manual assignment for this half; it is now redundant. ## Testing Worktree caveat, stated up front: this worktree symlinks the primary checkout's `node_modules`, so `@copilotkit/shared` and `@copilotkit/core` resolve to that checkout's **stale `dist`**. That produces failures unrelated to this change; each is baselined against clean `main` in the same environment below. CI installs fresh and is the authoritative gate. **1. Reproduces the reported failure before the fix.** The new core test, run on unmodified `origin/main`, fails with the exact error from the issue: ``` FAIL src/__tests__/proxied-connect-replay-multi-run.test.ts > hydrates a thread whose replayed history contains an errored run AssertionError: promise rejected "Error: Cannot send event type 'RUN_STARTE…" instead of resolving Caused by: Error: Cannot send event type 'RUN_STARTED': The run has already errored with 'RUN_ERROR'. No further events can be sent. ``` **2. Passes after the fix**, hydrating both runs' messages (`["msg-1", "msg-2"]`): ``` ✓ src/__tests__/proxied-connect-replay-multi-run.test.ts (1 test) 11ms Test Files 1 passed (1) ``` **3. Connect-not-implemented guard, fail-first.** With the guard removed, the new second test fails exactly as the base contract predicts: ``` × swallows AGUIConnectNotImplementedError instead of failing the run AssertionError: promise rejected "Error: Connect not implemented. This meth…" instead of resolving ``` **4. Full `@copilotkit/core` suite** — this is the evidence the `IntelligenceAgent` extraction is behavior-identical, since `intelligence-agent.test.ts` exercises that path heavily: ``` Test Files 59 passed (59) Tests 635 passed (635) ``` (excludes `core-inspector-metadata.test.ts`; its 20 failures are the stale-`shared`-dist artifact — verified identical on clean `main`: `20 failed | 2 passed`, missing export `InspectorMetadataV1`) **5. `@copilotkit/react-core` — new + adjacent existing suites:** ``` ✓ src/hooks/__tests__/use-copilot-chat-internal-connect.test.tsx (7 tests) ✓ src/hooks/__tests__/legacy-chat-explicit-threadid.test.tsx (2 tests) ✓ src/components/copilot-provider/__tests__/v1-explicit-threadid-bridge.test.tsx (5 tests) Test Files 3 passed (3) Tests 14 passed (14) ``` Full react-core suite: `8 failed | 1492 passed (1500)`. All 8 are in `use-interrupt` / `use-pin-to-send` / `CopilotChatView.pinToSend` — none touch connect replay or threadId, and clean `main` in this worktree fails the identical 8 (`8 failed | 37 passed (45)` for those three files alone). **6. `@copilotkit/vue`** (affected via core): `100 passed (100)` files, `1072 passed (1072)` tests. **7. Types, lint, format:** ``` tsc -p packages/core/tsconfig.json --noEmit → no errors in any changed file oxlint <5 changed files> → Found 0 warnings and 0 errors oxfmt --check <5 changed files> → All matched files use the correct format ``` The only remaining `tsc` errors are 4 pre-existing stale-dist ones in `agent-registry.ts` / `types.ts` (`InspectorMetadataV1`), untouched by this PR. Fixes #4943 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved thread hydration when reconnecting to histories containing multiple runs, including runs that previously ended in error. * Prevented unsupported connection errors from being reported as run failures. * Ensured connection state is properly finalized after replaying a thread. * Legacy chat components now correctly reuse an explicitly provided thread ID while preserving generated IDs when none is provided. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
91abd959f7 |
feat(react-core): controlled open/onOpenChange props for sidebar and popup
v1 exposed `open` + `onSetOpen`, so a host could open and close the chat from its own UI. v2 shipped only `defaultOpen`, leaving the open state reachable exclusively from inside the chat subtree. Restores the controlled pair on `<CopilotSidebar>` and `<CopilotPopup>`: - `open` pins what the surface renders, from the first frame. - `onOpenChange` reports every request to open or close (toggle button, click-outside, Escape, the drawer's mobile mutual-exclusion). It fires with or without `open`, so it also works as a plain notification. Implemented as `ControlledModalOpenScope`, which overrides the chat configuration context for the subtree, rather than as a fourth mode inside CopilotChatConfigurationProvider's modal-state resolution. The provider's own state, parent sync, drawer mutual-exclusion and modal-closer registry are untouched: the wrapped setter still calls the underlying one, so those side effects keep running, and it registers itself as the modal closer so the drawer reaches the host. The props travel to the views by context, not through the memoized `chatView` override. Threading a changing `open` through that override would mint a new element type per toggle and remount the whole chat subtree, which is the class of bug already fixed for popup resize. Closes #3334 |
||
|
|
3672d007ae |
docs(showcase): document frontend-driven activity cards, lock the behavior with a test
Activity messages (role: "activity") already render standalone in the transcript and are stripped from the run payload by AbstractAgent.prepareRunAgentInput, so frontend code can put a card in the chat without a tool call and without polluting the conversation. That was only ever documented for backend-emitted activities, so the frontend-driven path was undiscoverable — issue #3388 asked for a feature that already ships. Adds a Generative UI guide page for the pattern and a react-core test that pins both halves of the contract: the card renders, and it never reaches the agent. The non-obvious part, and the reason this needs documenting rather than a one-line answer: the card must be added via the agent from useAgent(). An agent instance constructed and held outside React is not the instance the chat renders, so messages added to it silently never appear. Refs #3388 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
b5a6d05ae9 |
fix(react-core): restore code block line breaks in packaged CSS (#3330)
Fenced code blocks rendered as a single collapsed line in the packaged v2 UI. streamdown emits one <span> per source line inside `pre[data-streamdown="code-block-body"] > code` and leaves no newline in the text, so the line break comes entirely from the raw Tailwind utility `block` on that span. CopilotKit builds Tailwind with `prefix(cpk)`, so `.block` never reaches `dist/v2/index.css` and every line ran inline. Scope the display rule structurally, because the line spans carry no `data-streamdown` attribute of their own. Adding `whitespace-pre` to the <pre>, as earlier attempts did, changes nothing: the UA stylesheet already sets `white-space: pre` there and there are no newlines left to preserve. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2cde6b97f7 | fix(runtime): preserve single-route resource context | ||
|
|
840ad3c14a | feat(runtime): support Intelligence over one route | ||
|
|
8a5a976f1a |
feat(core): expose webmcp-enabled frontend tools to browser agents (#6847)
## What does this PR do?
Hooks can now expose a frontend tool to browser agents through the
WebMCP browser API, next to the normal agent registration. Set `webmcp:
true`, or pass `{ annotations }` for WebMCP hints:
```ts
useFrontendTool({
name: "searchOrders",
description: "Search the signed-in user's orders by status",
parameters: z.object({ status: z.enum(["open", "shipped", "delivered"]) }),
handler: async ({ status }) => searchOrders(status),
webmcp: { annotations: { readOnlyHint: true } },
});
```
How it works:
1. `FrontendTool` in `@copilotkit/core` gains the `webmcp` option. A new
`WebMCPRegistry` registers the tool on `document.modelContext` with its
name, description, input schema, and annotations. `execute` runs the
tool's own handler. The handler context has no `agent` there.
2. Every tool registry change in `RunHandler` reconciles the WebMCP
registrations. The same availability rules apply as for the agent tool
list. Removing a tool aborts its registration signal, and the browser
then unregisters it.
3. Each adapter picks the option up from core: v2 `useFrontendTool`
(React, Vue, React Native), the v1 `useCopilotAction` and
`useFrontendTool` wrappers (React, Vue), and Angular's
`registerFrontendTool`. Where WebMCP is not available (SSR, React
Native, browsers without the API), registration is a no-op.
The `webmcp` prop is documented on the React, Vue, and Angular reference
pages in shell-docs.
## Related PRs and Issues
- None.
## 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
- [ ] "Allow edits by maintainers" is checked (lets us help iterate on
your PR directly — faster turnaround for everyone)
## Testing
**Commands run**
- `pnpm nx run-many -t check-types
--projects=@copilotkit/core,@copilotkit/react-core,@copilotkit/vue,@copilotkit/angular`
— all pass.
- Full test suites: core (829 tests), vue (103), and angular pass.
react-core passes standalone (1589 tests). Under the lefthook pre-commit
hook, react-core flakes on pre-existing e2e tests (A2UI, MCP Apps) that
do not touch this code. Those tests pass when run alone.
**Manual test**
Requires Chrome 149+ with the WebMCP origin trial, or the testing flag.
1. Enable `chrome://flags/#enable-webmcp-testing`, then relaunch Chrome.
2. In an app that uses CopilotKit, register a tool with `webmcp: true`.
3. Run `await document.modelContext.getTools()` in DevTools. The tool is
listed with its schema and annotations.
4. Unmount the hook. Run the command again. The tool is gone.
**How this PR makes testing easy**
The behavior has automated tests on this branch:
- `packages/core/src/core/__tests__/run-handler-webmcp.test.ts` — 15
tests with a `document.modelContext` stub: registration, annotations,
unregistration, availability rules, name collisions, stale-rejection
races, and handler execution.
-
`packages/react-core/src/v2/hooks/__tests__/use-frontend-tool-webmcp.test.tsx`
and the mirrored
`packages/vue/src/v2/hooks/__tests__/use-frontend-tool-webmcp.test.ts` —
pass-through, re-registration, and agent-scoped cases at the hook level.
- `packages/vue/src/hooks/__tests__/use-frontend-tool-webmcp.test.ts` —
reactive `webmcp` getters through the v1 Vue API.
## Risk / rollback
Low. The feature is opt-in per tool. Without `webmcp`, no code path
changes. Where WebMCP is unsupported, registration is a no-op. Revert
this PR to roll back.
## Public API change
New optional `webmcp` prop on frontend tool registrations. Existing call
sites do not change.
**Before**
```ts
useFrontendTool({
name: "searchOrders",
description: "Search orders by status",
parameters: z.object({ status: z.string() }),
handler: async ({ status }) => searchOrders(status),
});
```
**After**
```ts
useFrontendTool({
name: "searchOrders",
description: "Search orders by status",
parameters: z.object({ status: z.string() }),
handler: async ({ status }) => searchOrders(status),
webmcp: { annotations: { readOnlyHint: true } },
});
```
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Tools can now be exposed to browser agents through WebMCP.
* Added support for custom annotations and automatic parameter schema
generation.
* WebMCP registrations stay synchronized as tools are added, removed,
enabled, or updated.
* Available across Angular, React, and Vue tool APIs.
* WebMCP reuses existing handlers and safely does nothing when
unavailable.
* **Documentation**
* Added usage guidance and examples for configuring WebMCP-enabled
tools.
* Documented that WebMCP invocations do not include an agent context.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
|
||
|
|
a31daf3c78 | style: auto-fix formatting | ||
|
|
808113b923 | fix(core): keep webmcp registrations stable across stale rejections and reactive changes | ||
|
|
aeea432cb1 | feat(adapters): support the webmcp option in react, vue, and angular tool hooks | ||
|
|
9bd7acc5ce |
fix(shared): drop the retired "premium" tier name from the console notice
The Headless UI console notice told developers about "premium features" and
pointed at /premium/overview. The tier is called CopilotKit Intelligence now, so
the notice named a product that no longer exists. It now uses the same sentence
the Headless UI docs page uses.
The docs links in react-core, web-inspector and the runtime skill reference move
from /premium/* to /intelligence/*. They worked through the redirects added in
#6818, but each cost a hop and carried the old name.
One of them was broken, not just stale: the "Show me how" button on the missing
public API key error opened /premium/overview#getting-access. That heading was
deleted on 2026-06-16 in
|
||
|
|
25cbb372aa | docs(react-core): update Learning Container deprecation guidance | ||
|
|
82c2ffbfc6 |
test(react-core): fix stale allowlist wording in the open-link scheme test
The comment described an http/https/mailto/tel allowlist, but ui/open-link uses a denylist (javascript:/data:/vbscript:/blob:/file:). Align the comment with the actual contract so it does not mislead a future change to the scheme policy. |
||
|
|
b12ba3e3a5 | Merge branch 'main' into feat/migrate-ext-app-package | ||
|
|
2544f1ff4c |
test(react-core): cover ui/initialize negotiation + address round-three nits
- Add e2e tests pinning the ui/initialize contract (the compile-time tie to the spec): a well-formed initialize returns the host context and the negotiated MCP Apps protocol version; an initialize missing required fields (e.g. appCapabilities) is rejected with -32603; a widget sending a different protocol-version string gets the host's MCP Apps version back, not its own echoed. (2025-06-18 is a base-MCP-protocol version, independent from the MCP Apps protocol 2026-01-26; it is what the old hand-rolled host hardcoded.) - Nit: load the bridge via `import(...).catch(rethrow)` with inferred types instead of `typeof import(...)` annotations, removing three consistent-type-imports warnings. - Nit: restore the "ui/message: No agent available" warning log on the no-agent path, for parity with the hand-rolled host and the oncalltool guard. |
||
|
|
b07b1320f2 |
feat(runtime): use managed Intelligence authority (#6098)
## What changed - Add standalone `CPK_TELEMETRY_ID` support to Runtime v1 and v2. - Keep telemetry opt-out, sampling, Segment, and legacy license fallback behavior. - Fetch structured Intelligence entitlements and map them to current client status. - Share concurrent entitlement lookups, retry short-lived failures, and reject stale grants. - Make managed React, Angular, and Vue thread UIs use Runtime entitlement authority. - Keep assistant feedback stable when unrelated Inspector settings change. - Update Runtime, telemetry, self-hosting, and Web Inspector docs. ## Why Managed Intelligence projects use a project API key for product access and a non-secret telemetry ID for attribution. Offline license tokens remain a self-hosted entitlement concern. Starter-template and AgentCore changes live in #6188. ## Companion PRs - Starter templates: #6188 - CopilotKit/Intelligence#628 - CopilotKit/oss-path-to-production#226 ## Review corrections - Scope shared entitlement attempts to one API key and endpoint. - Ignore stale attempts after credentials change. - Bound retries after short denials and transport failures. - Accept telemetry IDs only when they match the public identifier contract. - Read Inspector context in its button, so unrelated label changes do not rerender assistant feedback. ## Validation - React Core full suite: 1,537 Vitest tests and 47 script tests passed. - Runtime, Core, Shared, Angular, Vue, and Web Inspector focused suites passed. - React Core typecheck and build passed after the final rebase. - Direct builds and type checks passed for Angular, Core, Runtime, Shared, Vue, and Web Inspector. - Shell docs typecheck and production build passed. - Changed Vue files passed ESLint. - `git diff --check` passed. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added structured runtime entitlement support for managed and self-hosted deployments. - Feature access and usage limits now reflect active entitlements, with legacy license compatibility. - Added runtime entitlement diagnostics to the Inspector’s Threads view. - Added runtime-scoped telemetry identities and configurable telemetry ID support. - **Bug Fixes** - Licensing interfaces remain in a loading state during retryable entitlement outages. - Improved recovery after runtime connection, target, or transport changes. - Prevented stale entitlement data from granting access after refresh failures. - **Documentation** - Documented entitlement statuses, telemetry identity precedence, sampling, and Inspector telemetry behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
6ae23c0df0 |
fix(react-core): register frontend tools before sibling effects run (#6794)
Re-derived against current main from **#4259** (mxmzb), which diagnosed this in April. That branch is 7,386 commits behind and conflicts, so this ports the mechanism rather than rebasing it. ## The bug `useFrontendTool` registered its tool inside a `useEffect`. React flushes passive effects **child-first in tree order**, so a component mounted *before* the registering component runs its own `useEffect` against an empty tool list. That is the cross-page-navigation failure: a page mounts `CopilotChat` and its tool-registering components in one commit, `CopilotChat`'s connect effect fires first, and the connect request goes out carrying no frontend tools. ## The fix Register in `useLayoutEffect`. Layout effects run during commit, ahead of every passive effect regardless of tree order, closing the window. This is not a new pattern here — **`useAgentContext` already registers via `useLayoutEffect`** (`use-agent-context.tsx:2`). The context half of this hook family was fixed; the tool half was not. This makes them consistent. ## Testing ### The original test did not detect the bug Worth recording. #4259 shipped `use-frontend-tool-timing.test.tsx`, which mounts the tool registrar **before** the observing component. I ported it verbatim and ran it against unmodified main: ``` ✓ src/v2/hooks/__tests__/use-frontend-tool-timing.test.tsx (1 test) 10ms Test Files 1 passed (1) ``` It passes without the fix. React runs the registrar's effect first in that order, so the observer always sees the tool. The PR's own comment concedes the point — *"the result depends on component ordering and may be absent."* The test here mounts the consumer **first**, which is the shape that actually breaks, and says so in the file so nobody reorders it back. ### RED → GREEN on the real surface **RED** (current main, `useEffect`): ``` × registers the tool before an earlier-mounted sibling's useEffect runs AssertionError: expected [] to include 'timingTestTool' Tests 1 failed (1) ``` The empty array is the bug: the consumer's effect saw no tools. **GREEN** (`useLayoutEffect`): ``` ✓ src/v2/hooks/__tests__/use-frontend-tool-timing.test.tsx (1 test) 12ms Tests 1 passed (1) ``` ### No regressions Full `src/v2/hooks` suite, same environment, with and without the change: | | Test files | Tests | |---|---|---| | without fix | 8 failed / 29 passed | **37 failed** / 283 passed | | with fix | 7 failed / 30 passed | **36 failed** / 284 passed | The delta is exactly the new test. The remaining 36 failures are pre-existing in my local worktree (stale cross-package `dist` resolution), identical on both sides. ## Notes - **SSR:** `useLayoutEffect` warns during server rendering. These hooks run inside `CopilotKitProvider`'s client context, and the sibling `useAgentContext` already uses a bare `useLayoutEffect`, so this follows the established pattern rather than introducing an isomorphic wrapper. - **Scope:** #4259 also carried a second, independent mechanism — an `ensureToolMiddleware` fallback that injects tools/context into direct `agent.runAgent()` calls bypassing `copilotkit.runAgent()` (`run-handler.ts` +44, plus `core.ts`, `agent-registry.ts`, Angular's `agent.ts`, `use-agent.tsx`). That addresses a **different** failure and deserves its own PR, tests and review. It is deliberately **not** included here and should not be considered resolved by this. Credit to @mxmzb for the diagnosis. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Frontend tools are now registered earlier, ensuring availability before the interface is displayed. * Improved consistency when components access frontend tools during initial effects. * Renderer cleanup now occurs at the appropriate stage when components are removed. * **Tests** * Added coverage verifying frontend tools are available to earlier-mounted components. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
57a9229e7d | fix(react-core): preserve inspector button props | ||
|
|
cd7748f25b | fix(runtime): harden entitlement resolution | ||
|
|
2d26a9de1a | fix(react-core): isolate assistant inspector context updates | ||
|
|
f1ac08938a | feat(runtime): use managed Intelligence authority | ||
|
|
c5107bee53 |
fix(core): keep the connect-not-implemented escape hatch in the shared pipeline
Self-review catch. The extracted pipeline was faithful to IntelligenceAgent's
copy but not to the base AbstractAgent.connectAgent it replaced on the
self-hosted path, which special-cases one error:
catchError((error) => {
this.isRunning = false;
if (!(error instanceof AGUIConnectNotImplementedError)) {
return this.onError(input, error, subscribers);
}
return EMPTY;
})
IntelligenceAgent never needed it — it always implements connect() — so the
omission was invisible there. On the SSE path it is load-bearing:
run-handler.ts awaits detachActiveRun() before every run and documents that
this only stops deadlocking because the ConnectNotImplementedError path
reaches the finalize block. Routing it through onError would also fire
run-failure callbacks on every subscriber for a benign condition.
Restores the guard, adds a regression test (verified fail-first: without the
guard connectAgent() rejects with "Connect not implemented"), and matches the
base's `void this.onFinalize(...)`.
Also makes the legacy-chat threadId test actually test its claim. It read
agent.threadId from its own useAgent() call, so the probe performed the very
assignment under test — it passed even with useCopilotChatInternal() removed
entirely. It now reads the agent off the hook's own return value, and is
mutation-checked: disabling the assignment in v2 useAgent fails it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
35616f46a2 |
fix(core): hydrate self-hosted threads whose replay contains an errored run
A /connect response replays a thread's history, so it can carry several past runs back to back — including a run that ended in RUN_ERROR followed by a later RUN_STARTED. The base AbstractAgent connect pipeline runs the stream through verifyEvents, which enforces single-run lifecycle rules and rejects that sequence outright: Cannot send event type 'RUN_STARTED': The run has already errored with 'RUN_ERROR'. No further events can be sent. IntelligenceAgent already omitted verifyEvents from its connect pipeline for this reason, but self-hosted runtimes (RUNTIME_MODE_SSE) fell through to super.connectAgent() and so never hydrated such a thread. Extract that verifyEvents-free pipeline into a shared helper used by both paths, rather than keeping two copies of a delicate 60-line pipeline. IntelligenceAgent keeps its canonical-run-id handling and delegates the rest. Also adds legacy-chat threadId coverage: the pre-existing connect suite mocks useAgent wholesale, so nothing exercised the real propagation the CopilotPopup path depends on. Fixes #4943 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
37e69105d8 |
fix(react-core): tear down HITL renderer in the same effect phase it is registered
useFrontendTool now registers the tool renderer in a layout effect, but useHumanInTheLoop still removed that renderer from a passive effect cleanup. React runs each phase's cleanups before that phase's effects, but runs the entire layout phase ahead of the entire passive phase. With the two split across phases, a keyed remount ordered the outgoing instance's removal after the incoming instance's registration: add (new, layout) -> remove (old, passive) which deleted the renderer that had just been added and left the HITL tool unrenderable. Moving the teardown to useLayoutEffect restores the correct remove-then-add ordering. Caught by the existing 'should maintain executing state across component remount' test in use-human-in-the-loop.e2e.test.tsx. |
||
|
|
ebbba19101 |
Merge origin/main into ben1/fe-tool-registry-4952-1746
The v1 react-core tree moved under src/v1-deprecated/, so the two conflicts were relocations: use-default-tool.ts's DistributiveOmit change re-applied on the moved file, and the catch-all HITL e2e test moved into src/v1-deprecated/hooks/__tests__/ with its v2 imports re-rooted. |
||
|
|
661fb1981d |
fix(react-core): address CodeRabbit findings on the MCP Apps bridge
- Sandbox handshake race: load the ext-apps bridge (dynamic import) BEFORE creating and attaching the sandbox iframe. The proxy posts sandbox-proxy-ready once during srcdoc execution, and the PostMessageTransport must be listening (connect) when it fires. Awaiting the import after the iframe was attached let a slow import miss that notification, leaving the widget blank. There is now no event-loop yield between attaching the iframe and connecting. - ui/open-link scheme hardening (XSS): the ext-apps schema validates url as a string only, so a widget could pass javascript:/data:/blob: etc. Parse the url and refuse a denylist of script-executing / attacker-HTML schemes (javascript, data, vbscript, blob, file) before window.open. A denylist is used on purpose so custom-scheme deep links (myapp:, whatsapp:, ...) and https universal links keep working, since window.open on those hands off to an OS handler rather than executing in the page. This matches the Anthropic Software Directory policy (https origins + owned custom URI schemes) and the MCP spec's prudent-host guidance. - Tests: reject a disallowed scheme without calling window.open; allow a custom-scheme deep link. |
||
|
|
f794386528 |
Merge remote-tracking branch 'upstream/main' into feat/migrate-ext-app-package
# Conflicts: # pnpm-lock.yaml |
||
|
|
80d530620c |
refactor(react-core): make the MCP SDK a hard peer and guard the lazy import
- @modelcontextprotocol/sdk is now a non-optional peerDependency (matching how ext-apps declares it) instead of an optional one. ext-apps ships as a direct dependency and hard-peers the sdk, so the requirement is already inherited by every consumer; the optional flag only hid that and dropped the install-time signal. react-core does not use the sdk directly (type import only), so it stays out of our dependencies; ext-apps remains the direct dependency. - Guard the lazy bridge import with a try/catch that rethrows naming the packages and the install command, so a missing or version-skewed peer surfaces as an actionable error instead of an opaque module-resolution rejection in an effect. |