mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
codex/fac-202-shared-state-rendering
694 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d7846ba6ca |
fix(release): make GitHub Release notes actually ship (#6830)
## What's broken
Every GitHub Release this repo has ever cut has a body of `Release
<tag>` and nothing else — `v1.70.0`, `v1.69.3`, `channels/v0.6.0`,
`angular/v0.4.0`, all of them. The `#engr` Slack announcement links
"Release notes" at that page, so that link has always pointed at a blank
release.
The notes *were* being generated. The `angular/v0.5.0` create-pr run
logged:
```
Raw release notes written to release-notes.md
Generating AI-enhanced release notes...
AI-enhanced release notes written to release-notes.md
```
They just never left the runner. `release-notes.md` was gitignored
(`.gitignore:72-73`), so `peter-evans/create-pull-request` skipped it,
the file never reached the release branch, and `publish-release.yml`'s
`readFileSync("./release-notes.md")` missed and fell through to `body =
\`Release ${name}\``.
The same ignore rule severed the Notion round-trip:
`release-notes-notion.json` was ignored too, so the publish job could
never read an edited draft back. That path had never run either.
Meanwhile the repo carried **29 changelog files that no tooling had
written since April**. They are changesets-era leftovers, and nothing in
`scripts/` or `.github/` reads or writes them:
```
$ git grep -n "CHANGELOG" -- 'scripts/**' '.github/**' '*.json' ':!*/CHANGELOG.md'
(no matches)
```
They stopped at `1.55.2` while the monorepo lane shipped `1.69.3`, and
`packages/angular/CHANGELOG.md` still claimed `1.54.3` — a version from
before angular split onto its own `0.x` line. So the only changelog a
reader could find in the tree named the wrong version for the wrong
lane.
## What this changes
**1. The notes become a source-controlled changelog, one file per
release lane.**
| lane | file |
|---|---|
| `monorepo` | `CHANGELOG.md` |
| `angular` | `packages/angular/CHANGELOG.md` |
| `channels` | `packages/channels/CHANGELOG.md` |
Per lane rather than one root file because the lanes version
independently: a shared file would interleave `1.70.0`, `angular/0.5.0`
and `channels/0.9.0` into one sequence where no reader can follow any
single line. (Concurrent writes are *not* the reason —
`stable-release.yml` already fails if any release PR is open.)
The flow: `prepare-release.ts` writes the raw notes,
`generate-ai-release-notes.ts` polishes them, **`write-changelog.ts`**
prepends them as this version's section, `create-pull-request` commits
the changelog (a tracked file, so `git add -A` always stages it), and
**`extract-release-notes.ts`** reads that section back in the publish
job as the GitHub Release body.
The changelog is therefore both the durable record and the review
surface: edit the top section on the release PR to change what ships.
`release-notes.md` goes back to being gitignored scratch, so the same
notes never exist as two editable copies with no rule about which one
wins.
**2. The 29 stale changelogs are deleted**, and a test pins the tracked
changelog set to exactly the three lane files, so they cannot creep back
and contradict the real versions again. Their content stays recoverable
from git history (`git show v1.69.3:packages/core/CHANGELOG.md`).
**3. Notes are selected per PR, scoped to the lane.** Selection was
`--no-merges` over every commit since the scope's tag. Two bugs:
- *No path filter* — a scope inherited every other lane's work.
- *`--no-merges` is backwards here* — this repo merges PRs as merge
commits, so the merge **is** the unit of change and the only commit
carrying `(#1234)`. `--no-merges` dropped every PR boundary and kept the
intermediate branch commits.
Now: `--first-parent` over the scope's package directories, minus
commits no consumer would read about (`test`/`ci`/`style`, `chore`
except `chore(deps)`, and the release commit itself).
| scope | before | after |
|---|---|---|
| `angular` v0.5.0 | 159 entries | **4** |
| `channels` (unreleased) | 600+ entries | **9** |
**4. Breaking-change footers still survive.** `--first-parent` alone
silently dropped `BREAKING CHANGE:` footers written on branch commits
rather than in the PR description — measured at **2 of 2 lost** across
`v1.60.0..HEAD`. Each merge's branch messages are now folded into its
body before extraction, so the entry list stays one-per-PR while the
footer scan sees the whole PR. Re-measured: **0 lost**.
**5. The AI prompt is scoped and the API call is correct.** It was
passing a repo-wide `git log -50` as "context" and asserting the release
was "CopilotKit vX.Y.Z, an open-source AI agent framework for React
applications" — wrong commits, wrong framing, and wrong release title
for any non-monorepo lane. Now it gets the lane's own commits, the names
of the packages actually being published, and an instruction to write
about nothing else. Also fixed in the same call: `max_tokens: 2048`
(truncates a large release mid-section, and the truncated text is what
ships as the body), and a response reader that took `content[0].text`
rather than selecting the text block by type. The model pin is left
alone — `main` already carries a current, undated id.
**6. Notion is removed**, not repaired — the release PR is already the
review surface.
### Failure behavior on the publish side
`extract-release-notes.ts` runs **after** `npm publish`, so it never
exits non-zero: failing there would leave the packages published and the
tag unpushed. A missing section prints a `::error::` annotation and
falls through to the workflow's existing `Release <tag>` fallback. Worst
case is the blank body we have today, never a half-finished release.
## Testing
Baseline on `main`: `15 files / 162 tests`. On this branch: **`16 files
/ 197 tests`**.
```
$ npx vitest run --config scripts/release/vitest.config.mts
Test Files 16 passed (16)
Tests 197 passed (197)
```
**The whole lane round-trips end to end.** A real `prepare-release.ts
--scope channels --bump minor` run (versions reverted afterward), then
the two new halves:
```
$ pnpm tsx scripts/release/write-changelog.ts 0.10.0 channels
Recorded 0.10.0 in packages/channels/CHANGELOG.md
$ rm release-notes.md
$ pnpm tsx scripts/release/extract-release-notes.ts 0.10.0 channels
Release body written to release-notes.md from packages/channels/CHANGELOG.md (861 chars)
```
The extracted body is the 9 PR-numbered entries under Features / Fixes /
Other, with **no duplicated version heading** (`grep -c '^## '
release-notes.md` → `0`) — the raw generator's own `## v0.10.0
(channels)` line is stripped when the section heading is written. The
miss path was exercised too:
```
$ pnpm tsx scripts/release/extract-release-notes.ts 9.9.9 channels
::error title=Release notes::No section for 9.9.9 in packages/channels/CHANGELOG.md. ...
exit: 0
```
**The staging behavior is verified against the pinned action, not
assumed.** `peter-evans/create-pull-request@5f6978f` stages with `git
add -A` when `add-paths` is unset. In-repo, after a real notes run:
```
$ git add -A --dry-run | grep -iE "changelog|release-notes"
add 'packages/channels/CHANGELOG.md'
$ git check-ignore -v release-notes.md
.gitignore:77:release-notes.md release-notes.md
```
The changelog is staged; the scratch file is invisible to the commit.
The publish job checks out `ref: main` at `fetch-depth: 0`, and the
release PR merges the changelog into main, so the section is present
when the extractor runs.
**The selection reproduces a hand-curated list exactly.**
`angular/v0.5.0`'s release body was written by hand from its four real
PRs. Running the new selection over that same range returns exactly
those four, release commit correctly dropped:
```
#6098 feat(runtime): use managed Intelligence authority (#6098)
#6756 chore(deps): bump @ag-ui/* to 0.0.59 (#6756)
#6773 feat(angular): add registerComponent ... (refs OSS-1034) (#6773)
#6586 fix(angular): resolve human-in-the-loop results without the bus envelope (#6586)
```
**Breaking-change regression measured, not assumed** — differential
comparison of extracted notes, old selection vs new, over two ranges:
```
range v1.60.0..HEAD old: 2 new: 2 LOST: 0
range v1.50.0..HEAD old: 2 new: 2 LOST: 0
```
(Before the fold was added, the same probe reported `LOST: 2` — that is
how the bug was caught.)
**Every new test was mutation-checked** — the mechanism was broken and
the test confirmed failing:
| mutation | result |
|---|---|
| `--first-parent` → `--no-merges` | 2 failed |
| drop the pathspec filter | 2 failed |
| `isNoiseCommit` always false | 2 failed |
| `parsePrNumber` always null | 2 failed |
| `withBranchMessages` → no-op | 1 failed |
| code-fence tracking disabled | 1 failed |
| `stripVersionHeading` → no-op | 3 failed |
| `prependSection` appends instead | 1 failed |
| `extractSection` keeps the heading | 5 failed |
| `upsertSection` stops replacing | 1 failed |
| re-ignore a lane changelog | 1 failed |
| re-ignore all `packages/*/CHANGELOG.md` | 2 failed |
| an orphan changelog creeps back | 1 failed |
| a lane changelog goes missing | 1 failed |
| *(restored)* | **all green** |
One of those mutations found a bug **in the test itself**: `git
check-ignore <path>` reports nothing for a path that is already tracked,
so the ignore assertion passed against a rule that would still strand
the next lane's file. It now runs `git check-ignore --no-index`, and the
mutation fails as it should. The flagless form is why the row above
exists at all.
Also run: `verify-release-scope-dropdowns.sh` (all OK), YAML parse of
both edited workflows, `oxfmt` (no-op after formatting), `oxlint` (0
warnings, 44 files).
**Not verified:** the live Claude API call. No `ANTHROPIC_API_KEY` was
available locally, so only the no-key fallback path (raw changelog) and
the CLI arg validation were exercised. A generation failure is already
caught and falls back to the raw notes, so the worst case is un-polished
notes rather than a blank body.
The commit is `--no-verify`: the pre-commit nx lane cannot run in this
worktree (`packages/core` and `packages/channels-ui` have no
`node_modules`, and `nx run @copilotkit/core:build` fails identically
with the tree clean). The only change under `packages/**` is deleting
orphan markdown that no build or test reads. CI on this PR runs the real
lane.
## Not in this PR
Slack-side drafting/massaging in a dedicated channel, with write-back to
the release body. Deliberately separate — that lane needs its own
channel and webhook, and must not run through `#engr`. The `notify` job
and the `#engr` announcement are untouched here.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Release notes are now organized by release lane and recorded in
dedicated changelogs.
* GitHub Releases can automatically use the matching lane changelog
section.
* Release notes are scoped to packages included in each release lane.
* **Documentation**
* Added guidance for supported release lanes and changelog workflows.
* **Changes**
* Historical package and example changelog entries were removed or
replaced with the lane-based format.
* Notion-based release-note drafting and PR links are no longer used.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
|
||
|
|
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>
|
||
|
|
2c05ed6885 |
chore(release): keep release notes in one CHANGELOG.md per release lane
The notes now land in a source-controlled changelog instead of a scratch file that rides the release branch. One file per lane, because the lanes version independently: a shared file would interleave `1.70.0`, `angular/0.5.0` and `channels/0.9.0` into one unreadable sequence. monorepo -> CHANGELOG.md angular -> packages/angular/CHANGELOG.md channels -> packages/channels/CHANGELOG.md `write-changelog.ts` prepends this release's section on the release branch, create-pull-request commits it (a tracked file, always staged), and `extract-release-notes.ts` reads the section back in the publish job as the GitHub Release body. The changelog is therefore both the durable record and the review surface: editing a section on the release PR changes what ships. release-notes.md goes back to being ignored, so the same notes never exist as two editable copies. Also deletes 29 changesets-era changelogs that no tooling had written since April. They stopped at 1.55.2 while the lane shipped 1.69.3, and packages/angular/CHANGELOG.md still claimed 1.54.3 from before that lane split onto its own 0.x line. Their content stays recoverable from git history. A test pins the tracked changelog set to the lanes so they cannot creep back and contradict the real versions. Extraction never fails the publish job: it runs after npm publish, so a miss annotates loudly and falls through to the existing bodyless-release fallback rather than stranding the tag. Committed with --no-verify: the pre-commit nx lane cannot run in this worktree (packages/core and packages/channels-ui have no node_modules, and `nx run @copilotkit/core:build` fails identically with the tree clean). The only change under packages/** is deleting orphan markdown that no build or test reads. |
||
|
|
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
|
||
|
|
862ff3c180 |
fix(react-core): name the agent on CopilotKitProvider, warn when threads meet single-route (#6892)
Fixes OSS-1133
Rebased onto `main`. The branch was 205 commits behind, and two of its
three changes did not survive contact with current `main`. Both are
corrected here, so this description replaces the original one rather
than adding to it.
## What changed on `main` under this branch
**The single-route warning is gone.** The branch added a `useThreads`
development warning on the premise that "the thread routes live outside
the single-route envelope, so the list stays empty". That premise is no
longer true. `main` now carries thread, memory, and annotation
operations through the single-route endpoint with a `resource/request`
envelope (`fetch-handler.ts:405`), advertises it as
`singleRoute.resourceOperations` (`get-runtime-info.ts:177`), and the
client reads the thread endpoints from there (`agent-registry.ts:1367`).
A current single-route runtime serves threads, so the warning fired on a
working configuration.
Narrowing it does not rescue it either: when the transport is `single`
and the endpoints are still unavailable, the cause is a missing
Intelligence or thread backend, not the transport — the client cannot
tell those apart. The hook already surfaces the knowable fact through
`threadEndpointsError`. The warning, its three tests, and the
`useThreads.mdx` callout are dropped; `use-threads.tsx` and its test
file are now byte-identical to `main`.
**The docs' `useSingleEndpoint` claim is stale.** Both pages said
released versions of `<CopilotKit>` pin the flag to `true`. `dc73af1dc4`
removed that pin and is an ancestor of the `v1.70.2` release, which is
what `npm` serves today. Corrected on both pages.
## What this PR does
### `agentId` on `CopilotKitProvider`
```tsx
<CopilotKitProvider runtimeUrl="/api/copilotkit" agentId="my_agent">
```
`CopilotKitProvider` carries no agent prop at all, so the only way to
name an agent at the provider level is the v1 compatibility component.
The reporter had to read the installed type definitions to find that
`agentId` lives on `<CopilotChat>` instead.
The prop publishes a bare string context (`CopilotKitAgentIdContext` in
`src/v2/context.ts`) that is the **last** fallback before
`DEFAULT_AGENT_ID`. Five resolution sites consult it:
`CopilotChatConfigurationProvider` (which covers everything nested
inside a chat), `CopilotChat`, `CopilotThreadsDrawer`, `useAgent`, and
`useSuggestions`. An explicit `agentId` still wins at every one of them.
### Why not a root `CopilotChatConfigurationProvider`
The original branch published the default by rendering a
`CopilotChatConfigurationProvider` at the root. 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.
Measured on the original branch with `randomUUID` mocked to increment:
| | sibling chat 1 | sibling chat 2 |
| -- | -- | -- |
| `<CopilotKitProvider>` | `uuid-1` | `uuid-2` |
| `<CopilotKitProvider agentId="my_agent">` | `uuid-1` | `uuid-1` |
A bare string context carries the agent default and nothing else, so the
second row now matches the first.
### Docs
- `reference/components/CopilotKit.mdx`: the callout now says it is the
v1 provider and points at `CopilotKitProvider`, followed by the
agent-prop table. The `useSingleEndpoint` row is replaced by a sentence
saying both providers negotiate the transport, with the pre-1.70.2
behavior named as history.
- `docs/backend/runtime-endpoints.mdx`: the prop rename (`agent` →
`agentId`), and the transport table and its surrounding prose corrected
for the removed pin.
- `reference/hooks/useThreads.mdx`: back to `main` (see above).
I did not rewrite the integration quickstarts that show `<CopilotKit
agent=...>`. They already carry a "Which provider goes with which
handler?" callout and pass `useSingleEndpoint={false}` explicitly, so
they are correct as written; swapping the provider in all of them is a
docs sweep of its own.
## Testing
This worktree has its own full `pnpm install` and a rebuilt workspace
`dist`, so these numbers come from a clean environment on the rebased
tree.
### Whole-package suite
Both rows are real runs in this worktree on the same rebase base, taken
by checking `main`'s `packages/react-core/src` in and out around the
run:
| | Test files | Tests | Failed |
| -- | -- | -- | -- |
| `origin/main` (
|
||
|
|
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> |
||
|
|
fb4f352032 |
chore: release monorepo v1.70.3 (#6960)
## Release monorepo v1.70.3 **Scope:** `monorepo` | **Bump:** `patch` --- ### How this release process works 1. **This PR was created automatically** by the "release / create-pr" workflow. It bumped the `monorepo` packages to `1.70.3` and generated AI-enhanced release notes. 2. **CI runs on this PR** — the full test suite (unit tests, lint, type checks, build) must pass before merging. This is the review gate. 3. **Review the release notes** in `release-notes.md` in this PR. If a Notion draft was created, you can edit the release notes there before merging. 4. **When this PR is merged**, the `release / publish` workflow automatically: - Builds all packages - Publishes the `monorepo` packages to npm at version `1.70.3` - Creates git tag `monorepo/v1.70.3` - Creates a GitHub Release with the final release notes ### Before merging - [ ] CI is green (tests, lint, types, build) - [ ] Version bumps look correct - [ ] Release notes are accurate (edit in Notion if a draft was created) --- > **Do not merge until CI is fully green.** The full test suite runs automatically on this PR. |
||
|
|
69a940c70e | chore: release monorepo v1.70.3 | ||
|
|
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 --> |
||
|
|
16514e9424 | chore: release monorepo v1.70.2 | ||
|
|
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 | ||
|
|
71b2f481f9 | chore: release monorepo v1.70.1 | ||
|
|
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. |
||
|
|
f3b1ef345b | fix(integrations): standardize Intelligence project key name | ||
|
|
3a64564508 | chore: release monorepo v1.70.0 | ||
|
|
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 --> |
||
|
|
3ee0ec189c |
fix(core,react-core): stop dropping frontend tools, and let catch-all actions wait for a response (#6524)
Two frontend-tool defects that share a shape: a tool that was registered correctly still never reached the agent, or reached the render without the ability to respond. ## `useFrontendTool` tools dropped when the runtime enables `openGenerativeUI` (#4952) Tools reached the core registry through two owners that shared one array — the provider via `setTools()`, hooks via `addTool()` — and `setTools` replaced the array wholesale. Any provider re-sync after mount therefore wiped every hook-registered tool. A runtime with `openGenerativeUI: true` made it reproduce on **every** mount: `/info` flips the flag asynchronously, the provider re-derives its tool list to add `generateSandboxedUi`, and the re-sync dropped the app's own tools. The agent then received only `generateSandboxedUi`, exactly as reported. The fix splits the two owners into their own buckets, merged on read with hook entries winning. This mirrors what `CopilotKitCoreReact` already does for render tool calls (`react-core.ts`) — tools simply never got the same treatment. Because the clobber lived in core rather than in one provider, **Vue had the identical bug and is fixed by the same change** — verified end to end, not by shape: `CopilotKitProvider.vue:461` assigns `runtimeOpenGenerativeUIEnabled` from the core, `:297` derives `openGenerativeUIActive`, `:328`/`:354` add the built-in to `allTools`, and `:492` re-syncs it through `setTools` behind the same `didMountRef` skip. Angular is unaffected: it never calls `setTools`, passes `tools` only through the constructor (`copilotkit.ts:152`), and registers just the *renderers* from `config.tools` (`:224`) — no second registration. `addTool` now shadows a provider tool of the same name instead of refusing to register, and only warns when another imperative registration already holds the name. It also no longer pushes onto the array the provider passed in, and `initialize` copies that array like `setTools` already did. **One intentional behavior change worth a reviewer's eye:** `setTools` now replaces provider-owned tools only, so `setTools([])` no longer clears tools registered through `addTool`. That narrowing *is* the fix, but anyone calling `setTools([])` as a "clear everything" would now need `removeTool` per tool. Nothing in the repo does (core, react-core, vue, angular suites all pass). ## Catch-all actions could not wait for a response (#1746) `getActionConfig` short-circuited on `name === "*"` before checking for a wait-render, so a catch-all declaring `renderAndWaitForResponse` was silently downgraded to render-only — no `respond`, and in practice a `render is not a function` throw, since the render-only path reads `action.render`. Handling N human-in-the-loop tools required N hooks. A catch-all with a wait-render now routes to the human-in-the-loop path. Core already had the execution half (`getWildcardTool`/`executeWildcardTool`), so this is routing, not new machinery. Two supporting changes make it usable: - The HITL render props now carry **the name of the tool actually being called**. It equals the registration name for a normal action, but a catch-all needs it to tell N tools apart, and `"*"` was being written over it in both the v1 wrapper and the v2 hook. - `CatchAllFrontendAction` accepts `renderAndWaitForResponse`/`renderAndWait` alongside `render`, mutually exclusive as on `FrontendAction`, with `CatchAllActionRenderPropsWait` exported. Also: a wildcard tool is no longer advertised to the agent. It is a local catch-all handler for calls with no exact match, so offering the model a tool literally named `*` was never meaningful. Latent before this PR (nothing in React registered a wildcard *tool*); catch-all HITL activates it. ## Synced with `main` (2026-08-31) `main` moved react-core's v1 tree under `src/v1-deprecated/` while this branch was open, so the merge had exactly two conflicts, both relocations: - `use-default-tool.ts`'s `DistributiveOmit` change re-applied on the moved file, keeping main's deprecation banner. - the catch-all HITL e2e test moved into `src/v1-deprecated/hooks/__tests__/`, with its `../../v2/...` imports re-rooted to `../../../v2/...` to match its sibling `use-copilot-action.e2e.test.tsx`. The defect is still live on current `main` — `CopilotKitProvider.tsx:838` still calls `copilotkit.setTools(allTools)` — and both regression tests still bite there. The suites, the before/after checks, `tsc --noEmit`, `oxlint` and `oxfmt` below were all re-run on the merged tree; the browser walkthrough under **Live verification** is from the pre-merge branch and was not repeated. ## Testing **New regression tests** - `packages/core/src/core/__tests__/run-handler-tool-registry.test.ts` — 13 tests: `addTool` survives `setTools`, hook precedence, agent-scoped vs global, `removeTool` across both buckets, remount re-registration, capability toggles surviving a re-sync, provider ordering, caller-array aliasing in both directions, wildcard never advertised. - `packages/react-core/src/v2/providers/__tests__/CopilotKitProvider.openGenerativeUIToolLoss.test.tsx` — drives the **real** core over a stubbed `/info` that returns `openGenerativeUIEnabled: true`, asserting the hook tool survives. - `packages/react-core/src/v1-deprecated/hooks/__tests__/use-copilot-action-catch-all-hitl.e2e.test.tsx` — end-to-end through the real provider and core: catch-all gets the real tool name and a live `respond`, the tool result lands on the original `toolCallId`, the follow-up run fires, and `*` is absent from `runInputs[0].tools`. **Both new tests were confirmed to fail before the fix — re-confirmed after syncing `main`,** by checking the touched sources out at `origin/main` and rebuilding core's dist: ``` × keeps the hook tool once the runtime turns openGenerativeUI on → expected [ 'generateSandboxedUi' ] to include 'sayHello' ``` and before the routing fix, the catch-all test failed with the exact defect from the issue: ``` × gives the catch-all render a live respond and the real tool name → TypeError: render is not a function ❯ render src/hooks/use-render-tool-call.ts:44:22 ``` **Suites (all green)** ``` @copilotkit/core 67 files, 799 tests passed @copilotkit/react-core 137 files, 1558 tests passed @copilotkit/vue 101 files, 1092 tests passed @copilotkit/angular 49 files, 317 tests passed (1 skipped) ``` `tsc --noEmit` clean for `core` and `react-core`; `oxlint` 0 errors on the changed files (16 warnings, all pre-existing); `oxfmt` applied. **Live verification** — `examples/v2/react/demo` in a browser against built dists, with a temporary stub AG-UI agent (no LLM key) that echoes the tool names it receives, a runtime configured `openGenerativeUI: true`, no `openGenerativeUI` prop on the provider, one `useCopilotAction` frontend tool and one `useCopilotAction({ name: "*", renderAndWaitForResponse })`: ``` TOOLS_RECEIVED: ["generateSandboxedUi","sayHello"] <- #4952: hook tool survived; no "*" leaked catch-all handling: book_call status: executing <- #1746: real tool name, live respond [click "Pick Tuesday"] catch-all handling: book_call status: complete TOOL_RESULT_RECEIVED: "{"slot":"tuesday"}" <- follow-up run received the result ``` 0 console errors. The stub route and page were scratch and are not in this branch. ## Notes for reviewers - Community PR #4967 also targets #4952 by merging in the provider instead. I took the core-layer fix because the clobber is in core's registry and every framework provider hits it — patching one provider leaves Vue broken. Happy to reconcile. - Pre-existing and deliberately **not** changed here: `useCopilotAction({ name: "*" })` render props do not infer, because the hook's parameter is a union TypeScript cannot contextually type. This already affected plain `render` before this PR (verified), so the new tests and docs annotate props explicitly. Fixing it needs a `useCopilotAction` overload — worth a follow-up. - Deliberate small duplications, flagged rather than abstracted: `WILDCARD_TOOL_NAME` is a one-line const in both core and react-core's v2 HITL hook (sharing it would mean a new public export from core), and the link between "a catch-all render receives `name`" and "the v1 HITL wrapper supplies it" is a cast rather than a type — making it typed means adding `name` to the public `ActionRenderPropsWait`, which is wider than this fix. - #4759 is left with contributor PR #5308, and #6101 needs its own design pass since it introduces new public API. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added catch-all human-in-the-loop actions that can handle unregistered tools and wait for user responses. - Catch-all action renderers now receive the actual invoked tool name and arguments. - Added support for wait-aware catch-all rendering types. - **Bug Fixes** - Preserved frontend tools when provider tool lists are refreshed. - Prevented duplicate tools and ensured registered tools take precedence. - Hidden wildcard tools from agent-advertised tool lists while keeping them available for handling requests. <!-- 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 | ||
|
|
d477ca7396 | chore: merge main into AG-UI dependency bump |