Commit Graph

3049 Commits

Author SHA1 Message Date
Maximiliano Korp 1819cc953c fix(runtime): use first user message when thread naming fails 2026-09-09 16:54:40 -07:00
Ben Taylor f8520c9804 fix(skills): audit react-core claims, and stop documenting Cloud keys as the Intelligence path (#6997)
Follow-up to #6993. That PR fixed two `react-core` references; four of
their six citations were stale, so this audits the other 13.

## Method

Three passes, because the first two are cheap and the third is the only
one that finds real defects.

1. **Mechanical** — every `packages/...:NN-MM` citation: does the path
exist, is the span in range? 71 citations, 57 with spans.
2. **Symbol** — every `useXxx(` and `<CopilotXxx` in the docs against
the 389 identifiers actually exported from `v2/src`.
3. **Semantic** — read the cited lines and check they support the claim.
This is where the rot lives: the lines exist, they just say something
else.

## Findings fixed

| File | Defect |
| --- | --- |
| `capabilities.md` | Cited `runtime/src/agent/index.ts:821-829,883-887`
for "shallow-merges capabilities at the category level". Those are
factory-mode config types and sampling params (`frequencyPenalty`,
`stopSequences`). The mechanism is `:940-947` — whose own doc comment
says **shallow-merged** — and `:999-1012`. |
| `provider-setup.md` | Claimed the provider resolves `publicLicenseKey
\|\| publicApiKey`. Only one of four sites does. Citation also pointed
at an unrelated line (`copilotkit.tsx:172` is `source: "agent"`). |
| `custom-message-renderers.md` | Cited lines 73-95 of a 93-line file.
The iterate-and-break it describes is at `:68-91`. |
| `suggestions.md` | `useFeatureFlag("suggestions")`, twice, with no
import and no definition. CopilotKit exports no such hook. |
| `threads.md` | `useThreadSelection()`, same problem. |

### The license-key one is a product finding, not just a docs bug

Precedence when both keys are set is inconsistent in the code:

| Site | Order |
| --- | --- |
| `CopilotKitProvider.tsx:487` | `publicApiKey ?? publicLicenseKey` |
| `copilotkit.tsx:111` | `publicApiKey \|\| publicLicenseKey` |
| `copilotkit.tsx:217` | `publicLicenseKey \|\| publicApiKey` |
| `copilotkit.tsx:883` | `publicApiKey \|\| publicLicenseKey` |

Three prefer `publicApiKey`, one prefers `publicLicenseKey`. So which
wins depends on which path runs. Rather than document one order as if it
were the contract, the doc now says to write the canonical name and not
to set both — true regardless of path. **The underlying inconsistency is
untouched here and probably wants its own issue.**

## Findings I retracted

Recording these because two of my three automated passes produced false
positives, and the ratio matters for anyone repeating this.

- **50 "missing paths."** My regex alternated `(?:ts|tsx)`, so it
matched `ts` first and truncated every `.tsx`. All 50 were my own
artifact. Real count: zero missing paths.
- **41 "unsupported spans."** A heuristic checking whether the claim's
backticked identifiers appear in the cited lines. It attributes each
`Source:` to the nearest paragraph above, which is the wrong one in
multi-paragraph gotchas. Useful as a reading list, worthless as a
finding.
- **"13 uses of the deprecated provider."** Most were the *filename*
`CopilotKitProvider.tsx` inside `Source:` citations, not component
usages. There are 2 real JSX usages, both passing only `runtimeUrl`,
both valid — and `provider-setup.md` already carries a callout
explaining that `CopilotKit` is the v1/v2 bridge and
`CopilotKitProvider` "is a perfectly good choice if you do not need the
v1 bridge". No defect.

## Testing

```
$ python3 audit_citations.py
citations: 71   mechanically broken: 0
with a line span: 57   path-only: 14

$ tsx scripts/sync-plugin-skills.ts --check
plugin skill mirror in sync

$ oxfmt --check skills/react-core/references/*.md
All matched files use the correct format.
```

Symbol pass after the fixes leaves only legitimate non-exports: React's
own hooks, two helpers the docs define inline (`useAvailableAgents`,
`useMyFeatureFlag`), and `useAgents`, which `switching-agents.md`
correctly documents as **not existing**.

Edits were made in `packages/react-core/skills/` and mirrored by the
sync script.

## Coverage, honestly

The mechanical and symbol passes cover all 15 files completely. The
semantic pass does not: I read roughly 15 of the 57 spans closely,
prioritising the files documenting APIs that have moved most. The five
defects above are what that subset produced. A full semantic read of the
remaining ~40 spans would likely find more, and the same audit has not
been run on the `runtime` skill (26 references) or `a2ui-renderer`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Documentation**
- Updated React guidance with current source references across
capabilities, message renderers, attachments, chat components,
client-side tools, debugging, human-in-the-loop rendering, tool calls,
agent access, and agent switching.
- Clarified that production SPAs require `runtimeUrl`, with Intelligence
configured server-side on the runtime using `CPK_INTELLIGENCE_API_KEY`.
- Removed outdated `publicLicenseKey` guidance from setup and API
documentation.
  - Corrected feature-flag examples to use an application-defined hook.
- Updated thread-selection examples to derive the active thread ID from
application state.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-09 16:26:53 -05:00
Benjamin Taylor ec249f81fd fix(skills): correct four defects found reviewing the previous commit
The repo's own guard caught the worst of them. `scripts/validate-intelligence-env-names.ts`
retires `INTELLIGENCE_API_KEY`: the canonical name is `CPK_INTELLIGENCE_API_KEY`,
and the retired one "produced an undefined key for a reader who followed it
with a CLI-provisioned project" (OSS-881). The rewrite reintroduced it at
seven sites. Corrected, and the validator's 22 rule tests pass.

The same file also stops listing `INTELLIGENCE_API_URL` and
`INTELLIGENCE_GATEWAY_WS_URL` as things to set. Both default to the managed
hosts when omitted, so any value a reader supplies can only replace a correct
default with a worse one.

`provider-setup.md` called `agents__unsafe_dev_only` and `selfManagedAgents`
"aliases for the same dev-only mechanism". They are not: the first is the free
local-dev escape hatch, the second is an Enterprise Intelligence tier feature
that warns when used without a license key. I inherited that sentence and made
it more confident while editing it.

`copilotkit-setup/SKILL.md` Step 6 was left incoherent by the previous commit.
Removing the provider example stranded an instruction to "set the public
license key and pass it to the provider" above an example that no longer did.
The step now ends with `copilotkit verify`, its credential table lists the one
server-side credential, and the security note no longer claims a client-side
value exists.

Two claims of my own were also too absolute: the provider throws only when
`runtimeUrl`, a Cloud key, AND dev-only agents are all absent, and
`publicLicenseKey` does have a second advisory role gating
`selfManagedAgents`. Both now say so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 15:57:44 -05:00
Benjamin Taylor 57eb071cda fix(skills): stop documenting Cloud keys as the Intelligence path
`publicApiKey` and `publicLicenseKey` route a runtime-less client at
CopilotKit Cloud -- `api.cloud.copilotkit.ai`, header
`X-CopilotCloud-Public-Api-Key`. Five skills presented them as the way to
connect CopilotKit Intelligence, which they have never done.

Intelligence is configured on the runtime. The CLI writes
`INTELLIGENCE_API_KEY` into the server environment and it never reaches the
browser, so no client-side key is involved at all.

What was wrong, beyond the mislabelling:

- `provider-setup.md` had a section titled "SPA with CopilotKit Intelligence
  (no self-hosted runtime)". There is no client-only path: the provider throws
  in production without `runtimeUrl`, a key, or dev-only local agents. It now
  says a runtime is required and why.
- `telemetry-setup.md` told readers to expose the key with a `NEXT_PUBLIC_` or
  `VITE_` prefix and claimed removing the prop disconnects Intelligence.
  Neither is true. Rewritten around the CLI flow, the server-side variables,
  and `copilotkit verify`.
- `error-patterns.md` attributed `MISSING_PUBLIC_API_KEY_ERROR` to
  Intelligence. It is a Cloud error code.
- `copilotkit-setup/SKILL.md` called it the "CopilotKit Intelligence public
  license key" in its props table and put it in the provider example.
- The `react-core` and `runtime` SKILL.md invariants advertised
  `publicLicenseKey` as canonical, and `react-core` offered it as the SPA
  alternative to `runtimeUrl`.

The naming gotcha comparing the two props is gone rather than corrected:
neither belongs in Intelligence guidance. Three mentions remain and all three
name Cloud -- two migration-table rows in `copilotkit-upgrade`, which were
already right, and one line steering readers away.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 15:45:12 -05:00
Benjamin Taylor 588ea74eb7 fix(skills): re-anchor 24 drifted citations across react-core references
Completes the semantic pass: every one of the 57 line-spanned citations in
the 15 references was read against the tree it points at.

24 pointed at lines that had drifted to unrelated code. The claims were
almost all correct -- the pointers were not. Examples:

- `signal` in a tool handler cited `core/src/types.ts:24-30`, the tail of an
  export list. It is at `:39-41`.
- The `followUp !== false` check cited `run-handler.ts:607`
  (`const agentRunInput = {`). It is at `:970` and `:1119`.
- `FrontendTool.followUp` typed `boolean` cited `types.ts:39`, which is
  `signal`. It is at `:78`.
- `useRenderToolCall` takes no arguments cited `use-render-tool.tsx:37-40`,
  a config type in the wrong file. The hook is
  `use-render-tool-call.tsx:159`.
- Four of five citations in `threads.md` had drifted, one into a JSDoc
  example block.
- `showDevConsole` no longer gating the Inspector cited the component's
  destructuring; the `@deprecated` prop is at `:197-200`.

Verified correct and left alone, including several my own checker flagged:
`v2/index.ts:1` (`"use client"`), `:3` (`import "./index.css"`),
`use-agent-context.tsx:30-35` (the `JSON.stringify` memo, cited twice and
right both times), `use-render-tool.tsx:8-20` (camelCase status union),
`use-configure-suggestions.tsx:59-62` (`available: "disabled"` normalizing
to null).

All 71 citations now resolve to a real path and an in-range span.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 15:27:24 -05:00
Ben Taylor cf191b5525 fix(channels-telegram): emit language- form for tagged code fences (#6622)
## Summary
- Tagged fenced code blocks (```js ... ```) now emit Telegram's
`<pre><code class="language-js">` form instead of leaking the language
token into the code body
- Untagged fences stay plain `<pre>`; single-line ``` ```code``` ```
fences become inline `<code>`
- Fixes #6602

## Test plan
- New vitest cases in `telegram-html.test.ts` (tagged/untagged/inline
fences)
- Verified standalone via `node --experimental-strip-types`
2026-09-09 15:25:22 -05:00
tylerslaton cac7cde862 chore: release monorepo v1.71.0 2026-09-09 22:24:05 +02:00
Benjamin Taylor 26d347cc7b fix(skills): correct claims and citations across react-core references
A claim audit of the 13 react-core references not covered by #6993, prompted
by four of six citations being stale in the two files that were.

Five defects, each verified against the tree:

- `capabilities.md` cited `runtime/src/agent/index.ts:821-829,883-887` for
  "shallow-merges capabilities at the category level". Those lines are
  factory-mode config types and sampling parameters. The mechanism is at
  `:940-947`, whose own doc comment says shallow-merged, and `:999-1012`.

- `provider-setup.md` claimed the provider resolves
  `publicLicenseKey || publicApiKey`. Only one of four sites does that:
  `CopilotKitProvider.tsx:487` and two of the three v1-bridge sites prefer
  `publicApiKey`. The guidance is now "write the canonical name, do not set
  both", which is true regardless of path. Its citation also pointed at an
  unrelated line.

- `custom-message-renderers.md` cited lines 73-95 of a 93-line file. The
  iterate-and-break it describes is at `:68-91`.

- `suggestions.md` used `useFeatureFlag("suggestions")` twice with no import
  and no definition. CopilotKit exports no such hook, so an agent could try to
  import it. Renamed and annotated as the reader's own.

- `threads.md` used `useThreadSelection()`, also fictional. Replaced with
  reader-owned state.

All 71 citations in the 15 references now resolve to a real path and an
in-range span.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 15:17:10 -05:00
Ben Taylor c98b7b0b7d fix(skills): correct react-core agent access against the shipped API (#6993)
Refs #6125.

## Summary

The bundled `react-core` skill documented a pre-`registerProxiedAgent`
architecture. It ships inside the `@copilotkit/react-core` tarball via
`"files": ["dist", "skills"]`, so it reaches every consumer with no
install step and no telemetry.

Three classes of defect, all in the same root cause.

### 1. A compile error, five times over

`useAgent({ agentId, threadId })` appeared five times in
`agent-access.md`, including as the **first example in the file**.
`use-agent.tsx:126-137` states there are exactly two valid shapes and
that this is not one of them; three runtime guards at `:160-198` reject
it for callers who bypass the types.

`runtimeAgentId` — the key that makes thread scoping work — did not
appear once in the file.

Now documented as the two shapes the hook admits, plus the rule that
each surface needs its own local `agentId` because a duplicate throws
`already registered` (`agent-registry.ts:486-491`).

### 2. A caching mechanism that does not exist

Two files claimed per-thread clones are cached "in a module-level
WeakMap keyed by `(registryAgent, threadId)`". There is no such WeakMap.
What actually happens:

- `useAgent` either binds the shared registry instance, or registers a
private `ProxiedCopilotRuntimeAgent` under the local `agentId` and
unregisters it on unmount (`use-agent.tsx:239-254`).
- `CopilotChat` binds the **shared** agent with `useAgent({ agentId,
throttleMs })` (`CopilotChat.tsx:138-141`) and then writes
`agent.threadId = resolvedThreadId` (`:395`). That is why two chats
naming one `(agentId, threadId)` both submit — one instance, one thread,
no clone.

### 3. A CRITICAL gotcha describing a guard that was removed

`agent-access.md` claimed `useAgent` calls `source.clone()` and throws
`clone() must return a new, independent object`. `useAgent` never
clones, and that message exists nowhere in the repo — I searched
`packages/**` source for it.

Returning `this` is still wrong, and worse than the doc said: nothing
validates the result, so it fails silently.
`suggestion-engine.ts:244-249` clones the provider agent and then writes
a suggestion thread id, seeded messages, and seeded state onto the copy
— given `this`, it writes all three onto the live agent the user is
talking to.

Its "Correct" example also constructed from `this.config`.
`AbstractAgent` takes `config` as a constructor parameter and does not
retain it (`agent.ts:136` spreads it), so that property does not exist.
The example now says to keep what the subclass needs on a field of its
own, and points at `ProxiedCopilotRuntimeAgent.clone`
(`agent.ts:448-472`) as the reference implementation.

## On the issue

#6125 asked for two things. The second, "fix `agent-access.md` to match
the shipped API", is this PR.

The first was a public headless thread-switching API, because the
reporter was relying on an unblessed `agent.threadId =
crypto.randomUUID()` mutation. That now exists in supported form as
`useAgent({ agentId, runtimeAgentId, threadId })`, which is what the
corrected doc describes. I have used `Refs` rather than `Closes` so a
maintainer can confirm that satisfies them before closing.

## Testing

Docs-only, so the evidence is the gates plus the verification method.

Gates:

```
$ tsx scripts/sync-plugin-skills.ts
synced 3 package skill(s)

$ tsx scripts/sync-plugin-skills.ts --check
plugin skill mirror in sync

$ oxfmt --check skills/react-core/references/*.md
All matched files use the correct format.
```

Edits were made in `packages/react-core/skills/` (the source of truth)
and mirrored to `skills/` by the sync script, not hand-edited in both.

Verification: every claim and every `Source:` citation in both touched
files was checked against the tree at this commit.

| Citation | Verdict |
| --- | --- |
| `use-agent.tsx:78-119` (WeakMap, ×2 files) | stale — now prop docs.
Replaced. |
| `use-agent.tsx:58-69` (clone guard) | stale — now
`UseAgentThreadScopedProps.agentId`. Replaced. |
| `use-agent.tsx:36-48` (throttling) | correct, unchanged |
| `use-agent.tsx:226-290,465-481` (provisional agent, `isReady`) |
correct, unchanged |

I also swept the whole skill set for the same defect elsewhere:
`useAgent({ ... threadId ... })` without `runtimeAgentId` appears in no
other reference, and the WeakMap claim had exactly the one other home,
in `chat-components.md`, fixed here.

Not done: I did not re-audit citations in the other 13 `react-core`
references. Given four of six were stale in these two files, that sweep
is probably worth its own pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Documentation**
- Clarified supported `useAgent` configurations for shared agents and
private agents scoped to specific threads.
- Updated cloning guidance, including field-by-field construction and
validation behavior.
- Documented compile-time and runtime safeguards for invalid
agent/thread combinations.
- Explained how multiple chat components sharing an agent and thread can
interfere through duplicate connections, shared abort handling, and
competing message updates.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-09 15:13:59 -05:00
Benjamin Taylor e5730de1c2 fix(channels-telegram): only a language token becomes a class name
The info string was passed through to `class="language-..."` unfiltered, so a
fence tagged `<script>` emitted `class="language-&lt;script&gt;"`. It is escaped,
so nothing is injectable, but Telegram has no use for it and it is not a
language. Gate the token on /^[\w+#.-]+$/ and fall back to plain <pre> when it
does not match, which keeps c++, objective-c and asp.net working.

Also refresh the module docstring, which still described the single fence form
after this PR split it into three.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 15:10:14 -05:00
Benjamin Taylor 7265e2af12 fix(skills): correct two mechanism claims I did not verify
Self-review of the previous commit. Both were claims I restated or softened
instead of checking.

The duplicate-chat gotcha said two instances "both submit". The original said
"submit duplicate messages". Neither is established. What actually happens on
one shared instance: each mounts its own connect effect so the thread is
connected twice, each assigns `agent.abortController` so the later mount can
have its request aborted by the other's unmount, and each calls `setMessages`
so the last connect to resolve wins. The bookkeeping that would prevent it,
`lastConnectedThreadId` and `activeConnectCountRef`, is per component.

The clone gotcha said the suggestion engine clones the provider agent, without
qualification. It clones only on the stateful path; with `suggestions: true` on
a multi-route runtime it builds a fresh `HttpAgent` and never clones, so the
fault is configuration dependent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 15:02:02 -05:00
Tyler Slaton 87e5dd031b feat(web-inspector): add Learning setup escape actions 2026-09-09 12:54:02 -07:00
Benjamin Taylor a44fad1d37 fix(skills): correct react-core agent access against the shipped API (#6125)
The bundled `react-core` skill documented a pre-`registerProxiedAgent`
architecture. It ships inside the `@copilotkit/react-core` tarball, so it
reaches every consumer with no install step.

`useAgent({ agentId, threadId })` appeared five times, including as the very
first example in the file. That shape is a compile error, and three runtime
guards reject it for callers who bypass the types. `runtimeAgentId` -- the key
that makes thread scoping work -- did not appear once. The two shapes the hook
admits are now documented, with the local-agentId uniqueness rule.

The per-thread WeakMap the file described in two places does not exist.
`useAgent` either binds the shared registry instance or registers a private
`ProxiedCopilotRuntimeAgent`; `CopilotChat` binds the shared agent and writes
`agent.threadId` onto it, which is why two chats on one (agentId, threadId)
both submit.

The CRITICAL clone gotcha claimed `useAgent` calls `clone()` and throws
`clone() must return a new, independent object`. `useAgent` never clones and
that message exists nowhere in the repo. Returning `this` is still wrong, and
worse than documented: nothing validates the result, so the suggestion engine
seeds a thread id, messages, and state onto the live agent instead of a copy.
Its example also constructed from `this.config`, which `AbstractAgent` takes as
a parameter and does not retain.

Every `Source:` citation in both files re-verified against the tree; four were
stale and are corrected, and the two that were right are unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 14:26:22 -05:00
Maxim edee011099 Merge branch 'main' into refactor/rn-render-tool-hooks 2026-09-09 21:20:48 +02:00
Tyler Slaton bba4113b8e fix(react-core): improve Inspector message shortcuts 2026-09-09 11:49:42 -07:00
Maxim e69a7c08e2 docs(react-native): cite the shim's removal issue in its deprecation notes
@BenTaylorDev noted that "removal in the next minor" appeared 11 times across
5 files with no issue behind it, which is how a shim becomes permanent. Filed
as CopilotKit/CopilotKit#6976 and Linear OSS-1148, and cited in the shim's
JSDoc, the entry-point comment, and the reference page.

The runtime warning text is deliberately unchanged — a test asserts on it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 19:32:46 +02:00
Maxim af0c5628e0 fix(react-native): correct what a tool named * did, and warn on the last silent route
Two things, both in the render-tool shim and its tests.

1. The PR's headline claim was false on current main.

The shim, its tests and `headless.ts` all said the old RN hook "advertised
`*` to the model". Core never has. `buildFrontendTools` filters the name out
of the list it hands the agent (`core/src/core/run-handler.ts`, the
`tool.name !== WILDCARD_TOOL_NAME` clause), with a comment saying that
advertising it would offer the agent a tool named `*`. That filter arrived in
31aa1e2162, hours before this PR was opened; the rebase moved the code forward
and left the prose behind.

Measured by vendoring origin/main's hook verbatim and driving it through this
branch's own recording agent:

  A  old hook, wildcard        in core.tools true   advertised [[]]
  B  old hook, no description  in core.tools true   advertised [["legacyJsShape"]]

Row A is the correction. Isolated further: two `useFrontendTool` registrations
with identical description/parameters/render and only the name differing give
registry `["*","notAWildcard"]` and advertised `[["notAWildcard"]]`, so the
NAME is the sole cause.

The real mechanism is stronger. `*` is core's catch-all HANDLER name: when a
tool call has no matching frontend tool and no result yet, core reaches for
`getWildcardTool()` and runs `executeWildcardTool`, whose tool-result splice
and follow-up return sit OUTSIDE the `if (wildcardTool?.handler)` guard (opens
at :1000, closes at :1089; the splice block is :1091-1120, with
`toolCallResult` initialised to `""`). Driving one turn through it confirms it
rather than only tracing it — an assistant message calling an unregistered
tool yields 2 turns and a spliced `{ toolCallId, content: "" }` through the old
hook, versus 1 turn and no tool result through core's `useRenderTool`. So a
display-only wildcard was auto-answering every otherwise-unanswered tool call
and paying for a follow-up turn. Bounded: a server-side call whose result has
already arrived never reaches that branch.

Two `expect(agent.advertised).toEqual([[]])` assertions were introduced under
comments calling them the ones that catch this "where it would actually hurt".
They cannot: they return the same value on origin/main. Both are kept as
forward guards against someone making `*` advertisable later, with comments
that now say so and point at the registry assertions as the discriminating
ones. Confirmed by mutation: removing rule 1 from `routeFor` kills
`getTool({ toolName: "*" })` and leaves both `advertised` assertions green.

2. The renderer-only route is no longer silent.

`{ name, parameters, render }` is the one shape routing cannot discriminate:
it is both the correct new renderer-only spelling and an old plain-JS call
that registered AND advertised a real tool (`description` was required by the
old types, so only untyped JS could reach it; `FrontendTool.handler` is
optional and `buildFrontendTools` does not filter on it). Rows B and C are
that comparison — `[["legacyJsShape"]]` before, `[[]]` after, zero warnings.
So the one shape the shim could not route was also the one it said nothing
about, which is the exact failure the shim exists to prevent.

`warnRouted` now warns on that route too, deduped per tool name and dev-only
like the others, saying the call registers a renderer only and pointing at
`useFrontendTool` for anyone who relied on the advertisement. Guarded on the
ROUTE rather than merely on the absence of legacy fields, so a frozen
`useFrontendTool` route whose fields were later dropped is not mislabelled.
The pre-existing wildcard-branch message is likewise no longer worded as if
every renderer-only route were the wildcard.

The test that asserted silence on this route is updated rather than left
asserting the old behaviour, and two new tests cover the warning and its
dedup/production gating. All three fail if the unconditional warn is reverted.

Also: dropped the duplicated per-status-spread explanation (it appeared in
both the JSDoc and the body of `toFrontendTool`), and documented that the
renderer-only routes register in `useEffect` where `useFrontendTool` uses
`useLayoutEffect`, so a call that used to land in the layout phase now lands
one phase later. Both hooks' effect types verified in the current source.

react-native vitest 300/300 across 23 files at default settings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015XyonXjic9ZEuPTpzgN2uS
2026-09-09 19:32:46 +02:00
Maxim 1cc28f3243 fix(react-native): replace a literal NUL byte in the render-tool shim's warn key
The route-drift `warnOnce` key in the `useRenderTool` shim separated
`${config.name}` from `route-drift` with an actual 0x00 byte, not the escape
sequence `\x00`. It sat at byte offset 14224.

Two consequences, both verified:

  - ripgrep classified the whole file as binary ("binary file matches (found
    "\0" byte around offset 14224)") and printed NO lines, so an `rg` sweep
    over `packages/` returned nothing from this file — including for the very
    identifiers `headless-entry-surface.test.ts` polices by text. `file(1)`
    reported `data`.
  - `git diff` still rendered it as text, because git's binary heuristic only
    reads the first 8000 bytes and this byte is past that. It looked like an
    ordinary space in every diff view, which is how it survived review.

The byte also reached the emitted bundle as part of the dedup key.

Replaced with `:`. Verified at the byte level rather than visually: a scan for
bytes below 0x20 other than tab/newline/carriage-return now returns none,
`file(1)` reports "UTF-8 text", and `rg` prints 28 matching lines where it
previously printed zero. Every other file in the PR's diff was scanned the same
way; none contained a control byte.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015XyonXjic9ZEuPTpzgN2uS
2026-09-09 19:32:45 +02:00
Maxim 95c9c0a5a0 docs(react-native): make the render-tool docs true of the shim, not of the deletion
The deprecated `useRenderTool` shim landed after these pages were written, so
they described a harder break than the code delivers — "React Native has no
render-tool API of its own", and a migration table presenting the old hook as
simply gone.

reference/react-native/hooks/useRenderTool:

- The overview and the migration narrative now say what actually happens: the
  name still exists, still works this release, routes by shape, warns, and goes
  away in the next minor. The routing table is the shim's three rules verbatim
  (src/hooks/useRenderTool.ts `routeFor`), including the unconditional wildcard
  and why it is unconditional.
- The warning is quoted from the source string rather than paraphrased, and gets
  its own "What the warning does not cover" section: dev-only gate, per-name
  dedup for the module's lifetime, silent when no old field is present, and
  emitted from an effect so an unmounted screen never warns.
- "Three shapes the compiler does not catch" is kept, not deleted. It is now
  framed as what the shim exists to route — and as what to audit by hand anyway,
  because the notice is development-only and because all three go back to
  degrading silently once the shim is removed.
- Two new migration rows for losses the table omitted: bare `RenderToolProps`
  is now `TS2314` (core's `S` has no default where RN's `T` did), and the
  `args` -> `parameters` render-prop rename is still `TS2339` even through the
  shim — verified with tsc, the shim restores the old CONFIG fields only. The
  `RenderToolFunction<T>` row now points somewhere instead of saying "gone".
- `parameters` was documented as `Partial<T> | T`, using a generic this page
  renamed to `S` and explicitly warns is the schema rather than the arguments.
  It is `Partial<InferSchemaOutput<S>> | InferSchemaOutput<S>`
  (react-core/src/v2/hooks/use-render-tool.tsx:9-31).

reference/react-native/hooks/useFrontendTool had zero mentions of `ReactElement`,
`ReactNode` or `FlatList` despite being where the migration sends people. It now
documents that its `render` is a `React.ComponentType` and therefore accepts a
bare string that throws on a device, and points at the opt-in
`FrontendToolRenderFunction<T>`.

docs/frontends/react-native and packages/react-native/USAGE.md (which ships in
the npm tarball — no `files` array, no `.npmignore`) get the same corrections at
their own length.

Every code sample added here was typechecked by pasting it into the package and
running `tsc --noEmit`, including the ones asserted to FAIL. Internal links and
heading anchors were checked mechanically against the content tree.
2026-09-09 19:32:45 +02:00
Maxim 2386b5ab65 feat(react-native): add an opt-in element-only render type for useFrontendTool
The convergence deleted RN's `RenderToolFunction`, which was the only thing
narrowing an RN render function to `ReactElement | null`. The migration points at
`useFrontendTool`, whose `render` is `ReactToolCallRenderer<T>["render"]` — a
`React.ComponentType`, so it returns `ReactNode`. Verified with tsc: a `render`
returning a bare string compiles inline in a `useFrontendTool` call today, and
then throws "Text strings must be rendered within a <Text> component" on a
device. Before the rename that call site was a compile error, so the migration
traded away a real crash-prevention property.

`FrontendToolRenderFunction<T>` gives it back. Named for the hook it annotates
rather than reusing `RenderToolFunction`: that name was just deleted, and
reviving it against a DIFFERENT hook's props would be the same defect this PR
exists to fix — a name whose meaning silently moved.

DERIVED from core's contract, not re-declared: props come from
`React.ComponentProps<ReactToolCallRenderer<T>["render"]>` unchanged and only the
return type is React Native's. That is the precedent the deleted file set, and
the reason is on record — the last time this package declared its own render-prop
shape it drifted to `{ args: T; status: "executing" | "complete"; result?: string }`
(commit ebf0f94fb8^), with no `name`, no `toolCallId`, no in-progress arm and
`args` unconditionally complete. `RenderToolProps` / `UseRenderToolOptions` are
NOT reintroduced.

It is opt-in and the JSDoc says so: it changes no hook signature, so an
unannotated inline renderer is still checked against core's `ReactNode` contract.

The type test is a `.test-d.tsx`, which `tsc --noEmit` compiles (the package
tsconfig has `include: ["src"]`) but vitest's
`src/**/__tests__/**/*.{test,spec}.{ts,tsx}` glob does not collect. Both
directions bite, proven by mutation: widening the return to `ReactNode` fails
with 4x TS2578 (unused directive), and replacing the derived props with `any`
or `unknown` fails too — so "derived, not re-declared" is asserted, not just
asserted about.

Also trims the 21-line comment over the render-tool re-exports down to what a
reader needs at that line: the shim is temporary, and do not reintroduce a local
hook under either name. The rest now lives on the reference page and in the
guard's own comment.
2026-09-09 19:32:44 +02:00
Maxim 29d16e4db3 test(react-native): make the shim's warn-once dedup and frozen route bite
Two gaps found by mutating the shim.

The "fires once per distinct tool name" test passed with the module-level dedup
set DELETED, because it only re-rendered: the warning lives in an effect whose
deps are the tool name and the routed hook, so a re-render never re-runs it and
the effect's dependency array was doing all the work. It now unmounts and mounts
again — a fresh mount runs a fresh effect, which is navigating back to a screen
on a real device — so the dedup set is what the assertion rests on. Deleting it
now fails with 2 calls instead of 1.

The frozen route had no test at all. A config whose shape changes between
renders (`handler: enabled ? fn : undefined`) keeps the route it was first
registered under, because a hook cannot be called conditionally unless the
condition is stable for the component's lifetime. That limitation is now
asserted from both ends: the drift warning fires, and the tool is still not
registered.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015XyonXjic9ZEuPTpzgN2uS
2026-09-09 19:32:44 +02:00
Maxim 2b34d975e1 feat(react-native): reintroduce useRenderTool as a loud, deprecated shim
The convergence removed RN's `useRenderTool` outright. That break ships in a
MINOR — @copilotkit/react-native is in the 16-package lockstep `monorepo`
release scope, so a major is not on the table — which leaves the compiler as
the only signal reaching consumers, and there are three call shapes it cannot
see: a hoisted config object whose `render` ignores its props (excess-property
checking needs a fresh literal), the old fields arriving via a spread into an
otherwise fresh literal, and an untyped or `@ts-nocheck` call site (plain-JS RN
screens are common).

In all three, core's `useRenderTool` silently ignores `description` and
`handler`, and because core's bridge spreads `{ ...props, parameters: props.args }`
the old `render: ({ args }) => …` keeps painting exactly as before. The screen
looks unchanged while the tool stops being registered and advertised and the
handler never runs again. This shim is the softer landing @BenTaylorDev asked
for: it routes those calls the way the old hook did, and says so out loud. It
is deprecated, `@deprecated`-marked on every overload, and scheduled for
removal in the next minor; react-core is untouched.

Routing, exactly:

1. `name === "*"` wins UNCONDITIONALLY -> core's `useRenderTool` (renderer-only,
   schema-less wildcard path), then warns about the old fields it ignored. This
   is deliberately not the obvious reading. The old hook made `description`
   REQUIRED, so every wildcard renderer anyone ever wrote carries the old tool
   fields; routing "has old fields" to `useFrontendTool` would recreate the
   original `*`-named-tool bug for precisely the people who had tried hardest to
   use the wildcard.
2. otherwise `handler` or `description` present -> core's `useFrontendTool`
   (tool AND renderer, which is what the old hook actually did), warning that
   the call should be renamed.
3. otherwise -> core's `useRenderTool`, unchanged.

The route is frozen at first render, because a hook cannot be called
conditionally unless the condition is stable for the component's lifetime; a
config that changes shape mid-life keeps its original route and gets its own
warning rather than being silently re-registered elsewhere. Warnings are
dev-only (`process.env.NODE_ENV`), `[CopilotKit]`-prefixed and deduped through a
module-level Set — once per distinct tool name, never once per render, matching
react-core's `warnedUnknownStatuses` idiom.

Registration is DELEGATED on every path: this package still owns no registry.

The entry-surface suite asserted that RN's `useRenderTool` IS core's binding,
which the shim makes false. Rather than weaken that guard — it closed a blind
spot in which every presence check stayed green through the whole convergence
while verifying nothing — its two halves are replaced at equal strength:
`useFrontendTool`'s identity assertion stays as-is, and `useRenderTool` is now
policed by (a) a delegation test that reads which module the entry exports the
name from, asserting runtime identity when that is core's entry and otherwise
requiring the local module to import AND CALL both core hooks, and (b) a
package-wide deny-list that fails if any module in the headless graph touches
`addTool` / `removeTool` / `addHookRenderToolCall` / `renderToolCalls` or
`createContext` — i.e. if RN ever rebuilds a local registry.

Proven by mutation, with the routing suite reading core's own observable state
(`getTool`, `core.tools`, the tool list core hands an agent on a real run)
rather than a spy's arguments.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015XyonXjic9ZEuPTpzgN2uS
2026-09-09 19:32:43 +02:00
Maxim 62cf3878d7 docs(react-native): document core's useRenderTool as React Native's own
Rewrites the RN reference page for core's hook, points tool-plus-renderer users
at `useFrontendTool`, and carries the migration table. That table is the actual
consumer-facing channel for this break: the release-note collector
(scripts/release/lib/changes.ts) reads `git log --format=%H %s` into a
`{ hash, subject }` type, so a `BREAKING CHANGE:` footer has nowhere to land
(#6479).

The migration's loud-failure guarantee is stated precisely rather than
absolutely, because the absolute form is false. TypeScript's excess-property
check rejects an old call site (`TS2769` on `description`) only when the config
is a fresh object literal with those fields written inline; the renamed render
prop gives `TS2339`. Three shapes escape it, each named on the page: a hoisted
config whose `render` ignores its props, the same fields arriving via a spread,
and an untyped or `@ts-nocheck` call site. The last is the one to worry about —
core's bridge spreads `{ ...props, parameters: props.args }`, so `args` still
arrives at runtime and an old renderer keeps painting correctly while the tool
has silently stopped being registered.

Retires three § Known limitations entries this convergence closes (the wildcard,
`followUp`/`available` forwarding, and `handler`'s missing context argument) and
leaves the unrelated ones intact.

Deletes a false claim the previous docs shipped: that comparing `status` against
a bare string literal does not typecheck. A string-enum member is assignable to
its own literal type, so the comparison compiles and narrows in every direction
— verified with tsc in all four combinations. RN's `status` moves from the
`ToolCallStatus` enum to core's string-literal union, and that is explicitly not
a break; no migration work follows from it.

Also updates packages/react-native/USAGE.md, which still taught the deleted API.
That file ships in the published tarball — package.json declares no `files` array
and there is no .npmignore — so the package was documenting an API the package no
longer has. Its remaining samples were compiled against the shipped overloads.

Corrects the primary guide's claim that a `ReactElement | null` render return is
React-Native-specific: it is identical on the web. What is RN-specific is the
consequence of `useFrontendTool`'s looser render type, where a bare string
typechecks and then throws inside a `FlatList`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 19:32:43 +02:00
Maxim b2b6bfbc3e refactor(react-native)!: converge render-tool hooks onto react-core
React Native's `useRenderTool` was not react-core's `useRenderTool`. Its entire
body forwarded to a different hook, `useFrontendTool`, while wearing the other
one's name. core has two: `useFrontendTool` registers a tool AND its renderer
via `addTool`; `useRenderTool` registers a renderer ONLY via
`addHookRenderToolCall`, and special-cases `"*"` into a schema-less fallback.

Because RN's alias took the `addTool` path, `name: "*"` registered a frontend
tool literally named `*` and advertised it to the model. Eight further symptoms
share that single cause: a render-only registration was advertised and shadowed
a same-named server tool; `addTool` evicted an existing same-named
`useFrontendTool` handler with only a `console.warn`; `description` and
`parameters` were required though both are tool fields; `followUp` and
`available` were accepted upstream but not forwarded; `handler` dropped core's
second (context) argument, leaving `stopAgent()`'s abort signal unreachable;
`agentId` changes never re-registered; two structurally incompatible
`RenderTool*Props` families shipped side by side; and `hooks/index.ts` was a
dead barrel with no build entry, no exports mapping and no importer.

Deletes RN's hook and re-exports core's two instead, so each capability has
exactly one implementation and RN carries no render-tool API of its own.

Deleting rather than re-pointing the name is deliberate. Re-pointing is the
dangerous shape: a `{ name, parameters, render }` call with no handler would
keep compiling and silently stop registering the tool.

Two of the nine are documented rather than fixed, so this is not a clean sweep:
`agentId` is still absent from both core hooks' re-registration check, so the
`deps` workaround stands; and `useDefaultRenderTool` keeps a narrower render
return than the hooks converged here.

Adds the guard the entry-surface suite was missing. It asserted only that
`useRenderTool` was *present* on the headless entry, never which hook it was, so
it would have stayed green through this entire change while verifying nothing
about it — proven by mutation: an RN-local hook re-grown under the name fails the
new identity assertion while 18 other guards in that file stay green.

Also rewrites the RN render-tool suite around what RN still owns. The previous
file tested forwarding into `useFrontendTool` through a double that modelled so
little its own header comment recorded that deleting the `deps`, `handler` and
`agentId` forwarding left it fully green. Assertions now read core's observable
state: `getTool`, `core.tools`, a real `runTool` rejection, painted DOM through
the real `useRenderToolCall`, and the tool list core hands an agent on a real
run.

BREAKING CHANGE: removes `useRenderTool`, `RenderToolProps`,
`RenderToolFunction` and `UseRenderToolOptions` from @copilotkit/react-native.
A tool-plus-renderer registration becomes `useFrontendTool` with an otherwise
identical object; renderer-only registration and the `"*"` wildcard become
core's `useRenderTool`; render props rename `args` to `parameters`. The
migration table, and the analysis of which call shapes fail loudly versus
silently, live in
showcase/shell-docs/src/content/reference/react-native/hooks/useRenderTool.mdx —
this repo's release-note collector reads only commit subjects, so a footer is
not a consumer-facing channel (see #6479) and the docs page is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 19:32:42 +02:00
Maxim dce8ee7578 refactor(react-core): let a tool renderer return null
`ReactToolCallRenderer.render` is a `React.ComponentType`, and a function
component returns `ReactNode` — of which `null` is a member. So `null` already
worked at the point a renderer is actually invoked; only the two build-side
helpers forbade it, leaving core stricter on the way in than on the way out.

Widens the render return to `React.ReactElement | null` at six annotations:
`use-render-tool.tsx` (both overloads plus the implementation config) and
`types/defineToolCallRenderer.ts` (both overloads plus the implementation
signature). Both files are required — widening only the hook yields TS2322,
because it passes `config.render`'s result straight into the helper.

Non-breaking. Widening what a caller-supplied callback may return can only
accept more code than before, so every renderer returning an element still
compiles. Tested in both directions: a renderer returning `null` registers and
paints nothing, and one returning an element behaves as before. Note that
vitest transpiles without type-checking, so `tsc --noEmit` (which CI runs via
static_quality.yml's check-types job) is the only guard against a re-narrowing.

Scope: this widens `useRenderTool` and `defineToolCallRenderer` only. It does
NOT make core uniformly permissive — `useDefaultRenderTool` still requires
`ReactElement`, so core's two wildcard entry points now differ. That divergence
fails loudly (TS2322) rather than silently, and is left to its own change since
that hook carries four narrow annotations plus an existing cast. Note
`defineToolCallRenderer` is re-exported from @copilotkit/react-native, so this
change reaches React Native consumers too — it is not web-internal.

Not purely type-level: oxlint's consistent-type-imports rule converted this
file's `ToolCallStatus` import to `import type`, which removes a runtime
`@copilotkit/core` import from the emitted JS. Harmless, since core is imported
from many other react-core modules and no initialisation order changes — but
worth stating rather than filing under "types".

Also re-homes a coverage pin that @copilotkit/react-native's suite carried:
despite living in RN it tested core's `JSON.stringify(extraDeps)` comparator,
and no react-core test covered the case, so removing RN's copy would have left
it unpinned repo-wide. It records a documented sharp edge, not behaviour worth
preserving.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 19:32:42 +02:00
Ben Taylor 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 -->
2026-09-09 10:20:46 -05:00
Benjamin Taylor 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>
2026-09-09 09:19:59 -05:00
Benjamin Taylor 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>
2026-09-09 09:19:50 -05:00
Benjamin Taylor 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.
2026-09-09 08:31:01 -05:00
Ben Taylor 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 -->
2026-09-08 23:45:22 -05:00
Ben Taylor 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.
2026-09-08 23:42:55 -05:00
Benjamin Taylor 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 f9b306aa4e.

Adds a regression test that mounts the consumer FIRST -- mounting it
second passes with either hook and proves nothing. The test fails on the
unmodified hook with "expected [] to include 'employees'".

Completes the half of mxmzb's #4259 that f9b306aa4e did not cover.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 22:54:51 -05:00
Mike Ryan 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 -->
2026-09-08 20:21:50 -07:00
Benjamin Taylor 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
  33f669ba7b, but there is no v2 page of that name — reference/hooks has no
  headless entry. Restore the v1 page, which exists and is what the published
  page links to today. If v1 readers should be pushed to a v2 page instead,
  that page has to be written first.

Every internal link the regeneration newly publishes now resolves to a content
file.

The three package edits are JSDoc comment lines only; no code changed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 21:58:48 -05:00
Ben Taylor 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` (31d0cda168) | 146 passed | 1639 | 0 |
| This branch | 146 passed | **1646** | 0 |

Exactly +7. Comparing the two runs' JSON reports file by file,
`CopilotKitProvider.test.tsx` (39 → 46) is the only file whose count
moved.

### The 7 new tests

```
✓ CopilotKitProvider > agentId > becomes the default agent for a chat that does not name one
✓ CopilotKitProvider > agentId > lets a nested chat configuration override it
✓ CopilotKitProvider > agentId > leaves the global default in place when the prop is omitted
✓ CopilotKitProvider > agentId > publishes no chat configuration of its own
✓ CopilotKitProvider > agentId > follows a changed agentId
✓ CopilotKitProvider > agentId > thread isolation > gives sibling chats their own thread when agentId is set
✓ CopilotKitProvider > agentId > thread isolation > matches the no-agentId tree
```

### Mutation checks

| Mutation | Result |
| -- | -- |
| drop the `providerAgentId` fallback in
`CopilotChatConfigurationProvider` | `becomes the default agent for a
chat that does not name one` and `follows a changed agentId` fail (2
failed / 44 passed) |
| publish the default through a root `CopilotChatConfigurationProvider`
instead of the bare context | `publishes no chat configuration of its
own` and both `thread isolation` tests fail (3 failed / 43 passed) |

The second mutation is the original branch's implementation, so the
thread-isolation tests fail against the code they were written to catch.

### Build, typecheck, lint, format, MDX

- `nx build @copilotkit/react-core --skip-nx-cache`: succeeds.
`context-singleton-preflight: OK — src/v2/context.ts bundled only into 4
allowed target(s)` — the new `CopilotChatConfigurationProvider` →
`../context` import does not add a bundle target.
- `tsc --noEmit -p packages/react-core/tsconfig.json`: 0 errors.
- `oxlint` on the 7 changed source files: 0 errors, and the same 9
warnings before and after (measured by checking `main`'s copies into the
same tree).
- Formatted with `oxfmt`.
- Both changed `.mdx` files compile through `@mdx-js/mdx` with
`remark-gfm`, and all 9 string assertions in `docs-render.test.ts`
against `runtime-endpoints.mdx` still hold.
- `nx test @copilotkit/react-native --skip-nx-cache`: 26 passed, 0
failed (it consumes `react-core`).

## Unrelated CI fix carried here

`doc-tests` went red on this branch for a reason that has nothing to do
with it. `@ag-ui/mastra@1.1.3` was published on 2026-09-08 at 23:19:48Z
and raised its peer range for `@ag-ui/client`/`@ag-ui/core` to
`>=0.0.58`. The Mastra doctest pins both at `0.0.57` and left the
adapter floating, so npm takes 1.1.3 and the install fails with
`ERESOLVE`. Every other open PR's `doc-tests` run that passed completed
before that timestamp — the latest at 23:13:15Z — and this branch's run
at 23:45:20Z is the first one after it, so the break lands on every PR
from here.

The second commit pins the adapter at `1.1.2`, whose peer range
(`>=0.0.44`) the existing pins already satisfy. Bumping the client and
core instead does not work: `@copilotkit/runtime@1.68.3` hard-depends on
`@ag-ui/client@0.0.57`, so raising the snippet to `0.0.59` puts two
copies of `AbstractAgent` in the tree and the snippet fails `tsc
--noEmit` with `TS2769`. Measured locally both ways — version bump
21/22, adapter pin **22/22**.

🤖 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 provider-level default agent selection through the `agentId`
prop on `<CopilotKitProvider>`.
- Descendant chats, threads, suggestions, and agent hooks now inherit
the provider’s agent unless overridden locally.
  - Preserved separate thread identities for sibling chats.

- **Documentation**
- Updated provider migration guidance, agent naming, and automatic
endpoint detection behavior.
- Added examples for provider, subtree, and per-chat agent
configuration.

- **Chores**
  - Pinned the Mastra integration example dependency to version 1.1.2.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-08 20:40:39 -05:00
Benjamin Taylor 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>
2026-09-08 18:40:40 -05:00
Tyler Slaton 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.
2026-09-09 01:37:03 +02:00
Mike Ryan 3c78b1ad52 fix(runtime): keep nullable support scoped to null branches 2026-09-08 16:31:48 -07:00
Ben Taylor 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 -->
2026-09-08 18:31:15 -05:00
tylerslaton 69a940c70e chore: release monorepo v1.70.3 2026-09-08 23:22:32 +00:00
Tyler Slaton 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
2026-09-09 01:17:31 +02:00
Tyler Slaton fdb6ce0714 fix(inspector): require Learning container configuration for status 2026-09-08 16:05:53 -07:00
Tyler Slaton 78e498bf33 fix(inspector): derive green Learning status from its own endpoint 2026-09-08 15:52:32 -07:00
Tyler Slaton 290a8323ae fix(runtime): expose Inspector Learning without extra flags 2026-09-08 15:51:58 -07:00
Benjamin Taylor 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>
2026-09-08 17:49:22 -05:00
Mike Ryan 8757ef0a80 fix(runtime): accept nullable frontend tool schemas 2026-09-08 15:45:17 -07:00
Benjamin Taylor 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>
2026-09-08 17:42:52 -05:00
Benjamin Taylor 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>
2026-09-08 17:42:52 -05:00
Benjamin Taylor 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>
2026-09-08 17:42:52 -05:00
Benjamin Taylor 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 (a2ef51aaf3), so this documented
prop has never waited on the user. It fails the same way on both transports.

Match the `useHumanInTheLoop` semantics: park the tool call until the render
calls `respond`, expose `respond` only while the status is Executing, and
reject with 'Human-in-the-loop interaction aborted' when the run is aborted so
core records an explicit error tool result (#5554). Pending interactions are
keyed by tool call id, and a wildcard registration keeps the invoked tool's
name rather than "*".

Two existing unit tests asserted the placeholder behavior (the console warning
and render identity). They now assert the waiting contract and that the wrapper
renders the caller's component.

Mirrors the Vue fix in #6527. Related: #4953 (the provider-prop half of that
report) and #4955.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 17:31:28 -05:00