mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
codex/fac-202-shared-state-rendering
3023 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 -->
|
||
|
|
d85fa7277b |
test(web-inspector): give the 34-route Threads lab a real timeout budget
One test drives 34 routes against a real lab server, so its cost is the sum of 34 bounded waits and it lands wherever the runner's load puts it. Measured across `test / unit` shards of the same commit: 29.2s on Node 24 / React 19, 56.1s on Node 22 / React 18, 57.1s on Node 20 / React 19, and, on two runs of one commit on Node 20 / React 18, 41.4s and then a timeout at the old 60s ceiling. A five percent margin on the slowest shard is not a budget, so the new ceiling is 180s, about three times the slowest passing run. The number guards against a hang. It asserts nothing about elapsed time, because this test measures no durations. 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>
|
||
|
|
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. |
||
|
|
ad6d42a74a |
fix(react-core): register v1 readables before sibling effects run (#6968)
## What `useCopilotReadable` (v1) 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. This registers 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. ## Why now This completes the half of #4259 that `f9b306aa4e` did not cover. That commit fixed the v2 `useFrontendTool` the same way; the v1 readable had since moved to `packages/react-core/src/v1-deprecated/hooks/` and was left on `useEffect`. #4259 is now closed as superseded, with this as the named follow-up. The v2 siblings `useAgentContext` and `useFrontendTool` already register in the layout phase, so this aligns the last one. ## Scope React's layout-vs-passive split is what makes this bug possible, so `packages/vue` and `packages/angular` are not the same class and are untouched. `use-render-tool.tsx` is still on `useEffect` but registers only a renderer, so it never reaches the connect payload. ## Testing **1. The race reproduces on unmodified `main`.** Hook reverted to its pre-fix body, new test kept: ``` FAIL src/v1-deprecated/hooks/__tests__/use-copilot-readable.test.tsx > useCopilotReadable > registers the context before an earlier-mounted sibling's useEffect runs AssertionError: expected [] to include 'employees' ❯ src/v1-deprecated/hooks/__tests__/use-copilot-readable.test.tsx:305:25 ``` This is also the mutation check: the test fails when the mechanism is broken, so it is not self-fulfilling. The consumer is mounted **first** on purpose — mounting it second passes with either hook and proves nothing. **2. Suite passes with the fix.** ``` ✓ src/v1-deprecated/hooks/__tests__/use-copilot-readable.test.tsx (13 tests) 17ms Test Files 1 passed (1) Tests 13 passed (13) ``` **3. No regression across the v1 tree.** `vitest run src/v1-deprecated`, compared against a clean-`main` baseline in the same worktree: | | Tests passed | Collection failures | |---|---|---| | clean `main` baseline | 106 | 9 | | this branch | 107 | 9 | The 9 collection failures are identical in both runs (`@modelcontextprotocol/ext-apps/app-bridge` resolution in a symlinked worktree) and are not caused by this change. **4. `tsc --noEmit`** — 64 pre-existing errors in the worktree, **0** on either touched file (`grep -c use-copilot-readable` on the output → 0). Same cross-package dist resolution drift. **5. `oxfmt`** on both files — no changes. ### Committed with `--no-verify` The pre-commit hook cannot complete in this worktree: `@copilotkit/runtime:generate-graphql-schema` dies on `packages/runtime/node_modules/@copilotkit/shared` missing a `./telemetry` export, which is the symlinked-worktree dist drift above and cannot be caused by two files in `react-core/src/v1-deprecated`. The `lint-fix` hook step did pass. Items 1-5 are what I ran in its place. Worth a second look from CI. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Bug Fixes** - Improved the timing of Copilot context registration so context is available earlier during page transitions and component initialization. - Resolved an issue where earlier-mounted components could observe missing readable context. - **Tests** - Added coverage validating that readable context is published before sibling effects run. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
8a16d251da |
fix(vue): align human-in-the-loop lifecycle with React (#5965)
## Problem
Vue v2 `useHumanInTheLoop` had drifted from the React v2 lifecycle
contract in a few connected areas:
- The handler ignored its `AbortSignal`. A stopped run could leave the
interaction promise pending instead of producing the explicit abort
error expected by the tool execution flow.
- Render props did not consistently expose `toolCallId` and the static
registration `agentId`, making it harder for renderers to identify the
exact invocation and registration scope.
- Status routing used permissive string checks. That made `respond`
semantics less explicit and allowed a future status to fall through
without a compile-time failure.
- Scoped renderer disposal was not protected by a test capable of
detecting name-only cleanup.
Together, these differences meant Vue could hang on abort, expose a
weaker renderer contract than React, or silently drift again as
tool-call statuses evolve.
## Fix
Align the Vue v2 hook with the current React v2 behavior:
- Reject already-aborted and in-flight interactions with
`Error("Human-in-the-loop interaction aborted")`.
- Register a one-shot abort listener, clear pending resolver references
when settled, and remove the listener before `respond` resolves. A late
abort therefore cannot settle the interaction twice.
- Supply the complete render contract in every status: registration name
and description, `toolCallId`, static `agentId`, args, and result.
`respond` is available only while executing.
- Route statuses through `ToolCallStatus` with a `never` exhaustiveness
check so new statuses require an intentional implementation.
- Preserve exact `{ name, agentId }` renderer cleanup on Vue scope
disposal while intentionally leaving pending interactions unsettled on
unmount, matching React reconnect/remount behavior.
The framework-specific adaptation is limited to Vue refs, rendering, and
scope-disposal mechanics; the lifecycle and response semantics match
React.
## Verification
Added regression coverage that exercises the behavior rather than
restating the implementation:
- Already-aborted and live-abort paths assert the exact error, one-shot
listener behavior, reference cleanup, cleanup-before-resolve, and no
double settlement.
- A full status matrix asserts the complete React render-prop contract
and executing-only `respond`.
- Scoped disposal registers two same-name renderers under different
agents, disposes one scope, and proves only the exact scoped renderer is
removed through the real core registration path.
- Unmount coverage proves a pending interaction remains unsettled for
reconnect/remount.
- End-to-end chat coverage proves run abort produces an error tool
result and scoped/unscoped attribution reaches the renderer.
|
||
|
|
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 |
||
|
|
547329fe09 |
fix(runtime): accept nullable frontend tool schemas (#6958)
A nullable frontend tool field can reach the built-in agent as `anyOf:
[{type: "string"}, {type: "null"}]`. The converter handles the union but
throws `Invalid JSON schema` for its null branch before the model is
called. This matches R14 in the September 3–8 onboarding friction audit.
Accept explicit null branches when converting frontend tools. Required
nullable fields still require a value; optional fields can be omitted.
Invalid non-null values still fail validation.
Validation:
- RED: both the explicit anyOf input and a real Zod v4 nullable schema
failed with `Invalid JSON schema` before the fix.
- `pnpm nx test @copilotkit/runtime` — 2,293 tests passed, including
HTTP runtime integration tests.
- `pnpm nx test @copilotkit/runtime --
src/agent/__tests__/nullable-tools.test.ts` — 3 focused tests passed
after the final test typing change.
- `pnpm nx run-many -t test,check-types,build -p @copilotkit/runtime`
passed on the revised head (2,293 tests).
- `pnpm exec oxlint packages/runtime/src/agent/index.ts
packages/runtime/src/agent/__tests__/nullable-tools.test.ts` — no
errors; three existing shadowing warnings.
- `pnpm exec oxfmt --check packages/runtime/src/agent/index.ts
packages/runtime/src/agent/__tests__/nullable-tools.test.ts` and `git
diff --check` passed.
No live model request was needed: the regression exercises the actual
AG-UI-to-model-tool conversion and validates accepted and rejected
arguments.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **Bug Fixes**
- Improved handling of nullable tool fields, including nullable unions,
arrays, and fields generated by Zod.
- Invalid values and missing required fields continue to be rejected
during tool schema validation.
- **Compatibility**
- JSON Schema type declarations now use a single type value; arrays of
schema types are no longer converted automatically.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
|
||
|
|
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. |
||
|
|
3c78b1ad52 | fix(runtime): keep nullable support scoped to null branches | ||
|
|
31d0cda168 |
fix(a2ui): report a generative-UI result that renders nothing (closes OSS-1048) (#6802)
A generative-UI result that does not paint reports nothing today. The turn finishes, the input returns to idle, the network calls are all 200, and the console is byte-identical to what it held before the request. The only signal is a human noticing a blank space in a screenshot. Three commits, each breaking one of those silences. Rendering behavior is unchanged throughout — every report is a development-only `console.warn`. ## A tool call with no renderer `use-render-tool-call.tsx` resolves a renderer by name, then by agentId, then by wildcard, then returns `null`. The existing comment defends that choice well: auto-painting a default card would leak internal tool names plus raw args and result JSON into every app's production chat. That argument is about *painting*, and it does not cover *warning*. The warning names the tool the agent called and lists the renderer names that are registered. When the cause is a name that does not match, that is the whole diagnosis. It 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. ## An A2UI surface that gets operations and paints nothing Two reports, both in `A2UIMessageRenderer.tsx`. **Operations arrived and no paint followed.** The renderer already waits 8s for a surface to report its first paint before dropping the loader, so reaching that fallback is itself the signal. No new threshold was invented. `surfaceHasRenderableContent` already knows which half is missing, so the message says which: no `updateComponents` at all, or `"path"`-bound components whose `updateDataModel` never carried a value. **Operations named a surface that was never created.** `A2UIRenderer` renders its `fallback` for an unknown surface id and that defaults to `null` — the card is absent and the log is empty. `processMessages` is synchronous, so a surface still missing after it was never created. This report is also deferred and re-checked, because operations stream and a snapshot can reach the processor before the `createSurface` that gives it somewhere to go. ## The two surface-id resolvers 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. 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 files the operations against a surface `createSurface` never made, and an unknown surface id renders the `null` fallback above. So the old React order could *produce* the silence the second commit teaches the renderer to report — which is what the new test asserts, by requiring that the missing-surface warning stay quiet. ## What this does not cover A surface that exists, holds complete components, and still draws nothing. `onReady` fires exactly when `surfaceHasRenderableContent` is true, so that case is invisible to the paint-fallback path by construction. Filed as OSS-1057 with a concrete mechanism: both renderers hard-code a root component id of `root`, and a components list without one shimmers forever. The other item on OSS-1048 — a turn whose only output is generative UI recording a `tool` message with no assistant parent — is a different repo and a real design decision. Filed as OSS-1056. ## Verification `react-core` 1565/1565, `a2ui-renderer` 24/24, both builds clean, lefthook green on all three commits. Every new negative assertion was mutation-tested against the pre-change code. Two of them are guarded twice over, and removing either guard alone left the test green — so both had to be removed before the test would fail, which is what confirms it is not vacuous. Closes OSS-1048. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Operations now consistently target the correct surface, prioritizing nested surface identifiers and falling back to top-level identifiers when needed. * Improved handling of operations for surfaces that are created later. * **Diagnostics** * Added development-time warnings when surfaces receive operations but render nothing. * Added warnings for operations targeting missing surfaces or unresolved root components, including likely causes. * Added warnings when tool calls have no matching renderer, with registered renderer details where available. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
69a940c70e | chore: release monorepo v1.70.3 | ||
|
|
30f67b18f1 |
fix(inspector): enable Learning without flags and correct status (#6957)
## Problem Inspector Learning required both runtime debug mode and a separate handler opt-in, while Threads did not. The Home and launcher Learning indicators also read Memory availability, so configured Learning could remain off. Enabled launcher toggles were purple rather than green |
||
|
|
fdb6ce0714 | fix(inspector): require Learning container configuration for status | ||
|
|
78e498bf33 | fix(inspector): derive green Learning status from its own endpoint | ||
|
|
290a8323ae | fix(runtime): expose Inspector Learning without extra flags | ||
|
|
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> |
||
|
|
8757ef0a80 | fix(runtime): accept nullable frontend tool schemas | ||
|
|
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 (
|
||
|
|
407249d4fc |
fix(react-core): let a custom catch-all renderer return null (#6942)
## Summary `useDefaultRenderTool`'s `render` was typed to return `React.ReactElement`. A caller who wants to render only *some* tool calls therefore could not return `null` to suppress the built-in default for the rest — the value flowed through correctly at runtime, but the type rejected it. This widens the public `render` return type, and the wrapper local that carries the user's value, to `React.ReactElement | null`. ```diff - render?: (props: DefaultRenderProps) => React.ReactElement; + render?: (props: DefaultRenderProps) => React.ReactElement | null; ``` The reference page hand-writes the same signature, so it is updated to match, with one behavior bullet describing what `null` does. ## Scope, and its relationship to #6533 #6533 already widens the same return type to `React.ReactElement | null` in `defineToolCallRenderer.ts` and `use-render-tool.tsx`. It does **not** touch `use-default-render-tool.tsx`, which is the remaining gap and the whole of this PR. There is **no file overlap**, so the two can land in either order. A structural sweep of `react-core/src/v2` for renders still typed `=> React.ReactElement` with no `| null` confirms this leaves nothing behind on this surface: ``` types/defineToolCallRenderer.ts:40,48,56 <- #6533 hooks/use-render-tool.tsx:41,72,109 <- #6533 hooks/use-default-render-tool.tsx:152 <- the deliberate bridge cast, below hooks/use-interrupt.tsx:89 <- different surface, out of scope components/chat/CopilotChatMessageView.tsx:418 <- different surface, out of scope ``` The `as unknown as` cast into `useRenderTool` is deliberately left in place: `useRenderTool` still requires a `ReactElement` return on `main` (verified again after the rebase — `use-render-tool.tsx:41`). Once #6533 lands, that cast can be tightened. The bridge comment is updated to say so. `DefaultToolCallRenderer`'s own return type stays `React.ReactElement` — the built-in default always renders an element. The Vue counterpart needs no equivalent change: its `render` already returns `VNodeChild`, which admits `null`, and `reference/vue/hooks/useDefaultRenderTool.mdx` already matches. ## Why the guard is a type test, not a runtime test TypeScript types are erased, so a `null`-returning render forwards identically before and after the widening. The runtime test passes against un-widened source, which makes it worthless as a guard for this change. So the real guard is `use-default-render-tool-types.test-d.ts`, using the `expectTypeOf` + `toEqualTypeOf` convention already documented in `v2/__tests__/headless-type-exports.test-d.ts`. `toEqualTypeOf` is required rather than assignability: a function returning `ReactElement` **is** assignable to one returning `ReactElement | null`, so an assignability check would pass against the un-widened type and assert nothing. The `.test-d.ts` basename is outside vitest's `include` globs, so nothing there executes; `tsc --noEmit` (`check-types`) is what reads it. Confirmed on this base: ``` $ vitest list --filesOnly | grep -c "test-d" 0 $ grep -n include -A4 packages/react-core/vitest.config.mjs include: [ "src/**/__tests__/**/*.{test,spec}.{ts,tsx}", "src/**/*.{test,spec}.{ts,tsx}", ], $ grep include packages/react-core/tsconfig.json "include": ["src/**/*"], ``` The runtime test is kept as well, since it still covers prop adaptation and forwarding. ## Testing All numbers below were re-measured after the rebase onto `main` (`42494df`). **Mutation check of the type guard** — revert the widening in the source, confirm the guard goes red: ``` ########## RUN A: rebased HEAD as-is ########## total errors: 63 --- errors in touched files --- none ########## RUN B: MUTATION - widening reverted in source ########## total errors: 65 --- guard file errors (expect FAIL) --- use-default-render-tool-types.test-d.ts(26,3): error TS2344: Type '((props: DefaultRenderProps) => ReactElement<...> | null) | undefined' does not satisfy the constraint '"Expected: undefined, Actual: never" | "Expected: function, Actual: never"'. use-default-render-tool.test.tsx(150,30): error TS2322: Type 'Mock<({ status }: DefaultRenderProps) => null>' is not assignable to type '(props: DefaultRenderProps) => ReactElement<...>'. Type 'null' is not assignable to type 'ReactElement<...>'. ``` The guard fails when the widening is reverted, and the two new errors are exactly the guard plus the runtime test's own use of it. Nothing else moves. **Typecheck** (`tsc -p packages/react-core --noEmit`) — error set byte-identical to pristine `origin/main` in the same worktree, none in the touched files: ``` ########## RUN C: pristine origin/main baseline ########## total errors on pristine main: 63 === diff: pristine-main errors vs HEAD errors === IDENTICAL -> the change introduces no new type errors ``` The 63 are pre-existing worktree noise: `react-core` resolves `@copilotkit/core` and `@copilotkit/shared` from a sibling checkout's `dist`, so unrelated exports read as missing. They are present on pristine `origin/main` in the same worktree, which is what the diff above shows. **Target test file:** ``` ✓ src/v2/hooks/__tests__/use-default-render-tool.test.tsx (13 tests) 38ms Test Files 1 passed (1) Tests 13 passed (13) ``` **Broader `src/v2/hooks` + `src/v2/types`** — failure counts identical to pristine `origin/main` in the same worktree, plus exactly the one new passing test: ``` === BASELINE (pristine origin/main in this worktree) === Test Files 22 failed | 17 passed (39) Tests 9 failed | 201 passed (210) === WITH my change === Test Files 22 failed | 17 passed (39) Tests 9 failed | 202 passed (211) ``` **Lint:** `oxlint packages/react-core/src/v2/hooks/` — `Found 62 warnings and 0 errors.` (all pre-existing exhaustive-deps warnings, none in the touched files). **Formatting:** `oxfmt --check` on the three source/test files — `All matched files use the correct format.` `oxfmt` does not process `.mdx`, so the reference page is out of its scope. **Public-API manifest:** no regeneration needed — `scripts/release/public-api/manifest.v1.json` records no type signatures and does not mention `useDefaultRenderTool` (`grep -c ReactElement` → `0`). ## Provenance Extracted from #5509 (@ataibarkai), which is 3915 commits behind `main` and being closed. That PR also changed `defineToolCallRenderer`'s schema default from `def.name === "*" && !def.args ? z.any() : def.args` to `def.args ?? z.any()`. **That change is deliberately not carried here** — it alters runtime behavior for named renderers declared without `args` (from `args: undefined` to `args: z.any()`) and deserves its own PR and its own verification. 🤖 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** * Custom renderers can now return `null` to suppress output when no UI should be displayed. * **Documentation** * Updated `useDefaultRenderTool` guidance to describe null-return behavior and selectively rendering tool calls. * **Tests** * Added coverage confirming null-render behavior and forwarded renderer properties. * Added compile-time validation for supported renderer return types. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
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> |
||
|
|
1a935861a8 |
fix(runtime): do not treat an unread stream as a pre-parsed body in the express bridge
`hasPreParsedBody` gates on `req.body` being set, then confirms the stream is
gone via `req.readableEnded || req.complete || _readableState.ended ||
_readableState.endEmitted`. The last three are set by the Node HTTP parser once
the socket holds every byte, whether or not anything read them, so they do not
establish that a parser ran.
That matters because `req.body` being set does not establish it either.
body-parser 1.x (Express 4) assigns `req.body = req.body || {}` before its own
`hasBody`/`shouldParse` checks, so a request it declines to parse — multipart
upload, text/plain — reaches the bridge with `req.body === {}` and a full,
unread stream. `req.complete` then satisfied the check, the bridge rebuilt the
request from `{}`, and the real payload was silently dropped.
Gate on `readableEnded` alone, which only becomes true after a parser drains the
stream to its end. Verified on express 4.22.2 / body-parser 1.20.6 that a
multipart POST behind a global `express.json()` arrives with `req.body === {}`,
`readableEnded === false`, `complete === true`, and that the genuinely parsed
JSON case is unaffected.
Same root cause as #6489, which fixed the equivalent check in the node-http
request handler. Kept as a separate local predicate rather than sharing one
helper, to avoid coupling `v2/runtime` to the v1 integration tree.
|
||
|
|
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 --> |
||
|
|
175e9c237c | chore: release angular v0.5.2 | ||
|
|
16514e9424 | chore: release monorepo v1.70.2 | ||
|
|
416e854dc6 |
fix(runtime): encode SSE response chunks as bytes (#6909)
Fixes #6888. Refs #6919 — same root cause, reported as a Cloudflare workerd symptom. Deliberately not closed by this change: that report lists two further workerd blockers it does not address (createRequire(import.meta.url) at module load, and AbstractAgent generating a UUID in global scope). |
||
|
|
aff68853a4 |
test(channels-telegram): cover telegram-html edge cases (#6916)
Adds 10 tests for already-documented telegramHtml behavior (empty input, __bold__, _italic_, headings, inline-code escaping, * / + bullets, multiple fenced blocks, bold+italic coexistence). No source logic changed, so no clash with open issue #6602 (language-tag handling untouched). Verified: expected outputs computed by running the actual implementation in node; oxfmt passes. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Tests** - Added coverage for Telegram HTML formatting, including headings, bold and italic text, bullet lists, inline code escaping, multiple code blocks, empty input, and mixed formatting. <!-- 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> |
||
|
|
1a2f5691b6 | Merge upstream/main into fix/copilotkit-6888-sse-binary | ||
|
|
0d46ef5fe7 | Merge branch 'main' into feat/angular-copilot-activity | ||
|
|
5571eeafda | test(angular): await slot rendering with signal-based fixtures | ||
|
|
8100f08c5f | fix(angular): render slots with createComponent bindings | ||
|
|
43f1e81ce6 | test(channels-telegram): cover telegram-html edge cases (bold/italic/headings/bullets/code) | ||
|
|
1fdca1dc9e | fix(inspector): harden Learning review flows | ||
|
|
cfc4fd8e26 | fix(inspector): refine Learning onboarding flow | ||
|
|
c70502b137 | feat(inspector): add Learning view and workbench | ||
|
|
05fc4e05a4 | feat(runtime): expose Learning snapshots to Inspector | ||
|
|
428fcbd60d |
fix(core): send the whole RunAgentInput in the Intelligence run body (#6890)
Fixes OSS-1132
## Problem
`IntelligenceAgent` hand-built its REST run body by naming fields, and
`resume` was not one of them:
```ts
body: JSON.stringify({ threadId, runId, messages, tools, context, state, forwardedProps })
```
`HttpAgent` posts the whole `RunAgentInput`, so the self-hosted and SSE
paths carry `resume` correctly. Only the Intelligence transport dropped
it.
The two interrupt paths carry their resume payload in different fields:
| Path | Trigger | Resume travels as | Survived the Intelligence body |
| -- | -- | -- | -- |
| Legacy | `on_interrupt` CUSTOM event | `forwardedProps.command.resume`
| Yes |
| Standard | `RUN_FINISHED` with `outcome: "interrupt"` | top-level
`resume[]` | **No** |
So resuming a standard interrupt against an Intelligence runtime failed
silently: no error, no console output, and the graph simply re-entered
its gate. The server side was already correct — `RunAgentInputSchema`
declares `resume` and `parseRunRequest` parses with that schema.
Nothing the CLI scaffolds hits this combination today (it needs the
built-in agent plus Intelligence mode plus HITL), which is why it stayed
quiet.
## Change
Spread the input instead of naming fields, so a future protocol field
cannot be lost the same way:
```ts
body: JSON.stringify({ ...input, ...(mode === "connect" ? { lastSeenEventId } : {}) })
```
## Testing
Verified in a worktree with its own full `pnpm install` and freshly
built workspace `dist` output. The baseline is the same command with the
two changed files checked out from `origin/main`.
### Two new tests in
`packages/core/src/__tests__/intelligence-agent.test.ts`
- `carries the AG-UI resume array in the run body`
- `posts every RunAgentInput field, so no protocol field is dropped` —
iterates the input's own keys, so it fails on any future omission
### Whole-package suite
| | Test files | Tests |
| -- | -- | -- |
| Baseline (`origin/main`) | 69 passed | 830 passed |
| With this change | 69 passed | **832 passed** |
+2, exactly the new tests. No failures either side.
### Mutation check
Reverted the source fix to the hand-built field list and re-ran the
file:
```
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 2 ⎯⎯⎯⎯⎯⎯⎯
Tests 2 failed | 59 passed (61)
```
Both new tests fail without the fix, so neither is self-fulfilling.
### Typecheck, lint, format
- `tsc --noEmit -p packages/core/tsconfig.json`: **0 errors**.
- `oxlint` on both files: 0 errors (2 pre-existing
`consistent-function-scoping` warnings in the test file, unchanged).
- Formatted with `oxfmt`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Fixed agent run requests so all provided run input fields are
transmitted correctly.
* Preserved resume information when starting an agent run.
* Ensured no supported protocol fields are omitted from requests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
|
||
|
|
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 --> |
||
|
|
79fdeb64c2 | fix(runtime): encode SSE response chunks as bytes | ||
|
|
a4adf38683 |
fix(shared): keep Node-only telemetry out of browser build graphs (#6846)
## What does this PR do? `@copilotkit/shared` re-exported `telemetry/telemetry-client.ts` from its root entry. That module imports `@segment/analytics-node`, which imports `node-fetch`, which imports the Node built-ins `stream`, `http`, `https` and `zlib`. Browser bundlers resolve the whole static module graph before they tree-shake, so every browser build of a dependent package printed `Module ... has been externalized for browser compatibility` warnings, even when the consumer never touched telemetry. This PR keeps that edge out of the browser-facing entry: - `isTelemetryDisabled` moves into `src/telemetry/telemetry-disabled.ts`, so the root entry can keep exporting it without reaching the client. - The root entry keeps `isTelemetryDisabled`, the `lambdaClient` surface, the sampling helpers, and the `TelemetryCapture` / `TelemetryIdentity` types. The types are exported with `export type`, so they are erased and add no runtime edge. - `TelemetryClient` is now reachable at `@copilotkit/shared/telemetry`, a new export subpath. - A new test walks the value-level import graph from `src/index.ts` and fails if it reaches a Node-only package. Deferring the import does not fix this, which is what PR #5482 attempted. A dynamic import defers evaluation but keeps the graph edge, so `vite:resolve` still reaches `node-fetch`. The measurement is in https://github.com/CopilotKit/CopilotKit/pull/5482#issuecomment-5509823707. ## Export surface change `TelemetryClient` is no longer on the `@copilotkit/shared` root entry, or on the `CopilotKitShared` UMD global. It is reachable at `@copilotkit/shared/telemetry`. ```diff - import { TelemetryClient } from "@copilotkit/shared"; + import { TelemetryClient } from "@copilotkit/shared/telemetry"; ``` This is a public export in the packaging sense only. `TelemetryClient` is our internal metrics client, so no application code is expected to import it, and nothing that works today is expected to stop working. `packages/runtime/src/v1-deprecated/lib/telemetry-client.ts` is the only in-repo consumer and is updated here. There is no root shim on purpose: a runtime re-export would reintroduce the graph edge and the bug. `typesVersions` carries the subpath for `moduleResolution: "node"` (node10) consumers, which `packages/runtime` still uses. Without it, `tsc` cannot see the subpath's types. `scripts/release/public-api/manifest.v1.json` is regenerated for the new entry point. The manifest tracks entry points rather than symbols, so the change there is the added `./telemetry` record. ## Related PRs and Issues - Fixes #4151 - Supersedes #5482 ## Testing ### The reported symptom, before and after Vite 7.3.2, minimal app whose entry imports only browser-safe symbols from `@copilotkit/shared`, pointed at a real tsdown build of the package. | | `vite build` warnings | modules transformed | | --- | --- | --- | | `main` | 4 (`stream`, `http`, `https`, `zlib`) | 663 | | this branch | **0** | 451 | After, verbatim: ``` vite v7.3.2 building client environment for production... transforming... ✓ 451 modules transformed. rendering chunks... computing gzip size... dist/index.html 0.12 kB │ gzip: 0.12 kB dist/assets/index-EEiKsU3u.js 2.43 kB │ gzip: 1.29 kB ✓ built in 267ms ``` The dev-server dependency scanner is fixed too. `vite optimize --force` before this change pre-bundled `@ag-ui/client, @segment/analytics-node, chalk, graphql, partial-json, uuid, zod`; after it pre-bundles `@ag-ui/client, graphql, partial-json, uuid, zod`. ### The new export surface, exercised in Node ``` === CJS require of subpath === TelemetryClient: function isTelemetryDisabled: function true lambdaClient: object segment instantiated: Analytics === ESM import of subpath === esm TelemetryClient: function disabled: true === root entry === root TelemetryClient: undefined root isTelemetryDisabled: function root lambdaClient: object root computeSamplingMeta: function root firstNonBlankTelemetryId: function ``` ### Subpath type resolution, both resolution modes ``` ### moduleResolution node10 (what packages/runtime uses) ### (clean) ### moduleResolution node16 ### (clean) ``` Before adding `typesVersions`, node10 failed as expected, which is why the field is there: ``` probe.ts(1,33): error TS2307: Cannot find module '@copilotkit/shared/telemetry' or its corresponding type declarations. There are types at '.../dist/telemetry/index.d.mts', but this result could not be resolved under your current 'moduleResolution' setting. ``` ### The regression guard is not self-fulfilling Mutation-checked both ways. Restoring `export * from "./telemetry"` on the root entry: ``` × root entry browser safety (#4151) > does not reach Node-only packages through value imports → expected [ '@segment/analytics-node' ] to deeply equal [] ``` Turning the type-only re-export into a value re-export fails it as well, and restoring the file makes both tests pass again. ### The gate that went red on the first push `scripts/release/lib/public-api-manifest.test.ts` compares the committed public API manifest to a freshly generated one, and a new export subpath has to be recorded there. Regenerated with `pnpm generate:public-api-manifest`; the failing test and its whole suite now pass: ``` scripts/release/generate-public-api-manifest.ts --check scripts/release/public-api/manifest.v1.json is current vitest run scripts/release Test Files 14 passed (14) Tests 162 passed (162) ``` ### Package gates ``` @copilotkit/shared: tsc --noEmit clean @copilotkit/shared: vitest run 18 files, 404 tests passed @copilotkit/shared: tsdown Build complete @copilotkit/shared: verify-cjs-exports exit 0 @copilotkit/shared: es-check es2022 55 files, ES13 compatible @copilotkit/shared: es-check es2018 (umd) 1 file, ES9 compatible @copilotkit/shared: publint only the pre-existing repository.url suggestion @copilotkit/shared: attw --profile node16 all green, including "@copilotkit/shared/telemetry" ``` ### Not run locally `@copilotkit/runtime:build` and the workspace-wide pre-commit gate. My local install is missing `type-graphql@2.0.0-rc.1` from the pnpm store, so the runtime build fails on `Cannot find module 'type-graphql'` on `main` as well, with or without this change. The runtime change here is one import line, and I verified that it resolves under both node10 and node16. CI runs the real gate. This commit was made with `--no-verify` for that reason. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added a dedicated `@copilotkit/shared/telemetry` entry point for server-side telemetry functionality. - Added support for disabling telemetry when `COPILOTKIT_TELEMETRY_DISABLED` or `DO_NOT_TRACK` is set to `true` or `1`. - **Improvements** - Improved browser compatibility by preventing Node-only telemetry dependencies from being included in browser bundles. - Existing browser-safe telemetry utilities remain available from the main shared package entry point. - Full telemetry client functionality is now accessed through the dedicated telemetry entry point. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |