Commit Graph

673 Commits

Author SHA1 Message Date
Ben Taylor 8c629c147b docs(showcase): document frontend-driven activity cards (refs #3388) (#6904)
## What

Issue #3388 asked for a way to put a card into the chat transcript from
frontend code, without a tool call and without adding to the
conversation the model reads.

**That already ships.** A message with `role: "activity"` renders
standalone in the transcript, and `AbstractAgent.prepareRunAgentInput`
strips every activity message from the run payload:

```js
prepareRunAgentInput(e) {
  let t = structuredClone_(this.messages).filter(e => e.role !== `activity`);
  ...
}
```

The gap was documentation. `renderActivityMessages` is only documented
for **backend-emitted** activities (mastra background-tasks, a2a,
mcp-apps), so the frontend-driven path was undiscoverable.

This PR adds the missing guide page and a test that pins the behavior.

## Changes

| File | Why |
| --- | --- |
| `showcase/shell-docs/.../generative-ui/frontend-cards.mdx` | New
"Frontend-Driven Cards" guide |
| `showcase/shell-docs/.../generative-ui/meta.json` | Sidebar entry
(6-line insertion) |
| `packages/react-core/.../CopilotChatFrontendActivityCard.e2e.test.tsx`
| Pins both halves of the contract |

No source changes. Behavior is unchanged; this documents and locks what
already works.

## The non-obvious part

The card must be added via the agent returned by `useAgent()`. An agent
instance constructed and held outside React is **not** the instance the
chat renders, so messages added to it silently never appear. This cost
me a debugging round while verifying, and it is called out as a warning
callout in the docs.

## Testing

**1. New test passes against clean `origin/main`** (run in a worktree at
`96cf7aa55f`, with `@copilotkit/shared` and `@copilotkit/core` rebuilt
from the worktree so the test is not reading a stale dist):

```
✓ src/v2/components/chat/__tests__/CopilotChatFrontendActivityCard.e2e.test.tsx (2 tests) 72ms

 Test Files  1 passed (1)
      Tests  2 passed (2)
```

**2. Mutation-checked, so neither assertion is self-fulfilling.**

Drop the renderer registration → the render test fails:
```
× renders a card added from frontend code, with no tool call 1068ms
      Tests  1 failed | 1 passed (2)
```

Swap the card from `role: "activity"` to `role: "assistant"` → it
reappears in the payload, so the exclusion is real and specific to
`activity`:
```
AssertionError: expected [ 'user', 'assistant' ] to deeply equal [ 'user' ]
```

**3. Neighboring test unaffected on the same base:**

```
✓ src/v2/components/chat/__tests__/CopilotChatMessageView.test.tsx (16 tests) 53ms
      Tests  16 passed (16)
```

**4. Independent probe of the filter** against the pinned
`@ag-ui/client` 0.0.57:

```
agent.messages roles: [ 'user', 'activity' ]
run input roles     : [ 'user' ]
```

**5. `tsc --noEmit`** — zero errors in the new file. Remaining errors in
this workspace are in files this PR does not touch
(`MCPAppsActivityRenderer.tsx`, `CopilotKitInspector.tsx`) and are
artifacts of a hand-assembled local `node_modules`; CI has the real
install.

**6. `oxfmt --check`** — clean.

**7. Docs checks** — `meta.json` validated as JSON; internal link uses
the house `/generative-ui/...` form (no `/docs` prefix); `Callout
type="warn"` matches the dominant existing usage; import paths verified
against the real `@copilotkit/react-core/v2` barrel exports.

## Follow-up

Leaving #3388 open until this lands, then closing it with a pointer to
the new page.

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

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

## Summary by CodeRabbit

- **New Features**
- Added support for frontend-driven activity cards that render in chat
transcripts without being sent to the agent or language model.
- Added documentation covering activity card renderers, schemas,
registration, payload filtering, snapshots, and limitations.
- Added a new “Frontend-Driven” section to the Generative UI
documentation navigation.

- **Tests**
- Added end-to-end coverage for activity card rendering and payload
exclusion.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-04 17:32:15 -05:00
Ben Taylor 5fd08a824e feat(react-core): controlled open/onOpenChange props for CopilotSidebar and CopilotPopup (#6905)
Closes #3334 (OSS-524).

## Problem

v1 `<CopilotSidebar>` exposed `open` and `onSetOpen`. Those props let a
host open and close the chat from its own UI. v2 shipped only
`defaultOpen`. The reporter wanted a button in their own nav bar to
close the sidebar.

The reporter's stated root cause is now stale. `shouldCreateModalState`
no longer exists. Since CPK-7152 the provider syncs both directions:
`setAndSync` upward, and an effect downward. A host that wraps its
layout in `<CopilotChatConfigurationProvider>` and calls `setModalOpen`
therefore does drive the sidebar on current `main`. I verified that
before writing any code.

Two things are genuinely missing. The first is the ergonomic API that v1
had. The second is documentation for the outer-provider pattern that
already works.

Two earlier community attempts (#3729, #6418) were closed unmerged.

## What changed

`open` and `onOpenChange` on `<CopilotSidebar>` and `<CopilotPopup>`:

- `open` pins what the surface renders, from the first frame.
- `onOpenChange` reports every request to open or close: the toggle
button, click-outside, Escape, and the drawer's mobile mutual-exclusion.
It fires with or without `open`, so it also works as a plain
notification on the uncontrolled path.
- `defaultOpen` is unchanged. If both are passed, `open` wins.

Two design choices are worth review.

**1. A context-overriding scope, not a fourth mode in the provider.**
`ControlledModalOpenScope` replaces `isModalOpen` and `setModalOpen` for
the subtree below the provider that owns the state. The resolution chain
inside `CopilotChatConfigurationProvider` stays untouched: own state,
parent sync, drawer mutual-exclusion, and the `ɵregisterModalCloser`
stack. The scope's setter still calls the underlying one, so those side
effects keep running. It also registers itself as the modal closer, so
the drawer's mobile exclusion reaches the host instead of flipping state
that nothing displays. The alternative was a controlled branch threaded
through `resolvedIsModalOpen`, `setAndSync`, and the sync effect. That
adds a fourth interacting mode to the code CPK-7152 just stabilized.

**2. The props reach the views by context, not as props.**
`<CopilotSidebar>` hands its view to `<CopilotChat>` as a memoized
`chatView` component. Adding `open` to that memo's deps mints a new
element type per toggle, and React then remounts the whole chat subtree.
That is the same class of bug #6173 fixed for popup resize. There is a
regression test for it.

Scope note: I included `<CopilotPopup>` because it shares the mechanism
and the same docs page. The issue named only the sidebar.

## Testing

**New suite, 15 tests** (`CopilotSidebar.controlledOpen.test.tsx`). It
covers the controlled contract, the unchanged uncontrolled path, and the
remount guard.

```
✓ src/v2/components/chat/__tests__/CopilotSidebar.controlledOpen.test.tsx (15 tests) 155ms
  Test Files  1 passed (1)
       Tests  15 passed (15)
```

**Mutation-checked.** I broke each mechanism to confirm that the tests
really fail.

| Mutation | Result |
| --- | --- |
| Drop `ControlledModalOpenScope`, keep only the seeded default | 5
failed: both `onOpenChange` reports, both host-driven open/close cases,
the popup report |
| Implement through the memoized override instead (add `open` to the
`useMemo` deps) | 1 failed: the remount guard, `expected 4 to be 1`, one
extra mount per flip |
| Drop the `open ?? defaultOpen` seeding | 1 failed: "stays put when the
host stops controlling open" |

I also mutation-checked the pre-existing two-way sync before I started.
That confirmed the outer-provider workaround really works on `main`,
instead of only appearing to.

**Full `@copilotkit/react-core` suite.** No regressions.

```
Test Files  141 passed | 1 skipped (142)
     Tests  1604 passed | 2 skipped (1606)
EXIT=0
```

**Adjacent suites re-run explicitly**: sidebar position, sidebar and
popup slots, popup resize-remount, drawer launcher, and the provider's
own 43 tests.

```
Test Files  6 passed (6)
     Tests  117 passed (117)
```

**Typecheck.** `tsc --noEmit` in `packages/react-core` gave `exit=0`
with no output. The tsconfig includes `src/**/*`, so the new test file
is typechecked too.

**Format and lint.** `oxfmt --check packages/react-core/src/v2` reported
"All matched files use the correct format." `oxlint` on the touched
files reported 0 errors.

**Pre-commit hooks.** They ran for real on both commits.

```
NX   Successfully ran targets test, publint, attw for 2 projects and 20 tasks they depend on
✔️ test-and-check-packages (15.33 seconds)
```

## Docs

- `prebuilt-components/chat-controls.mdx` now leads with the controlled
pair. Its example drives the sidebar from a nav button outside it, which
is the shape #3334 asked about. The `useCopilotChatConfiguration` route
stays, reframed as the option for callers who prefer not to lift the
state.
- `reference/components/CopilotSidebar.mdx` and `CopilotPopup.mdx` gain
`open` and `onOpenChange`. Both pages documented `defaultOpen` as
`false`, but both surfaces mount open, so I corrected that. A new test
per surface pins the real default.

## Not in this PR

- Vue and Angular parity for the same props.
- The `width` prop of `<CopilotSidebar>` still sits in the memo deps of
the `chatView` override. A live-resized sidebar therefore remounts the
chat subtree, the way the popup did before #6173. That is pre-existing
and out of scope here.


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

## Summary by CodeRabbit

- **New Features**
- Added controlled open-state support for chat popups and sidebars
through `open` and `onOpenChange`.
- Preserved uncontrolled usage with `defaultOpen`, while allowing
externally managed visibility and toggle requests.
  - Improved coordination between modal and mobile drawer behavior.

- **Documentation**
- Added usage guidance and reference details for controlled and
uncontrolled open-state management.

- **Tests**
- Added coverage for initial visibility, toggle callbacks, controlled
updates, default behavior, and preserving the chat subtree.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-04 15:39:28 -05:00
Ben Taylor 603bc16cf3 fix(react-core): restore code block line breaks in packaged CSS (#3330) (#6902)
## What does this PR do?

Fixes #3330 — fenced markdown code blocks render as one collapsed line
in the packaged v2 React UI.

### Root cause

streamdown renders one `<span>` per source line inside
`pre[data-streamdown="code-block-body"] > code`, and leaves **no newline
characters** in the text. The line break comes entirely from the raw
Tailwind utility `block` on that span:

```js
// streamdown 1.6.11, dist/code-block-*.js
var v = cn("block", "before:content-[counter(line)]", ...);
```

CopilotKit builds Tailwind with `@import "tailwindcss" prefix(cpk)`, so
`.block` is never emitted into `dist/v2/index.css` — only `.cpk\:block`
is. Every line therefore renders inline and the block collapses onto one
row.

The line spans carry no `data-streamdown` attribute, so the rule has to
be scoped structurally, the same way the table action controls were in
#5944:

```css
[data-copilotkit] [data-streamdown="code-block-body"] > code > span {
  @apply cpk:block;
}
```

### Why the earlier attempts did not work

Three previous PRs (#3441, #3615, #5387) added `whitespace-pre` to the
`<pre>`. That is a no-op: the UA stylesheet already applies
`white-space: pre` to `<pre>`, nothing in the packaged CSS overrides it,
and there are no newlines in the text for it to preserve.

### Knowingly not fixed here

- **Line-number gutter.** streamdown's `before:content-[counter(line)]
before:w-4 before:mr-4 …` utilities are unprefixed too, so the gutter
never renders. That is cosmetic, and the repo's existing scoped rules do
not port it either.
- **The pre-highlight loading skeleton** (`space-y-4`, `divide-y`,
`animate-spin`) is unprefixed as well — a brief flash of unstyled
skeleton before shiki resolves.
- **The broader class of bug.** Every unprefixed streamdown utility has
to be hand-ported like this. streamdown 2.x adds a `prefix` prop that
would fix the whole surface at once, and #5147 proposes removing the
bundled renderer entirely. Both are larger calls than this bug fix.

## Testing

**1. Live browser verification.** Built `dist/v2/index.css` from
`origin/main` and from this branch, rendered streamdown 1.6.11's actual
code-block DOM against each, and measured layout in Chromium:

| | `white-space` on `<pre>` | line-span `display` | distinct rendered
rows | `<pre>` height |
|---|---|---|---|---|
| main | `pre` | `inline` | **1** | 52px |
| this PR | `pre` | `block` | **5** | 112px |

Indentation is preserved after the fix (`spans[1].textContent` starts
with two spaces).


**2. Compiled CSS.** `tailwindcss -i src/v2/styles/globals.css -o … -m`
emits exactly:

```css
[data-copilotkit] [data-streamdown=code-block-body]>code>span{display:block}
```

**3. Tests** — `pnpm -C packages/react-core exec vitest run
src/v2/styles`

```
 ✓ src/v2/styles/__tests__/streamdown-styles.test.ts (3 tests) 2ms
 ✓ src/v2/styles/__tests__/streamdown-table-controls.test.tsx (1 test) 37ms
 ✓ src/v2/styles/__tests__/streamdown-code-block-lines.test.tsx (1 test) 430ms

 Test Files  3 passed (3)
      Tests  5 passed (5)
```

Two tests, following the split established by #5944 — a source-string
test that the selector exists, and a DOM test that streamdown still
renders the structure that selector assumes (so a streamdown markup
change fails loudly instead of silently un-fixing this).

**4. Mutation-checked both tests.** Removing the CSS rule fails the
string test:

```
   × Streamdown styles > ships a scoped display rule for code block lines (#3330) 3ms
      Tests  1 failed | 2 passed (3)
```

Pointing the DOM test at a selector streamdown does not render fails it:

```
   × Streamdown code block lines DOM (#3330) > renders one line span per source line 428ms
      Tests  1 failed (1)
```

**5. Formatting** — `oxfmt --check` clean on all three files; `git diff
--check` clean.

`tsc --noEmit` in this worktree reports 58 pre-existing errors, all from
a stale cross-package `@copilotkit/core` dist; none are in the changed
files (which are CSS plus tests).

## Related PRs and Issues

Fixes #3330
Supersedes #3441, #3615, #5387, #5996 (all added a no-op
`whitespace-pre`)

## Checklist

- [x] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [x] If the PR changes or adds functionality, I have updated the
relevant documentation (not applicable: scoped visual bug fix)
- [x] "Allow edits by maintainers" is checked


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

## Summary by CodeRabbit

- **Bug Fixes**
- Fixed fenced code blocks collapsing into a single line in the packaged
UI.
- Code lines now render vertically as separate rows with the correct
layout styling.

- **Tests**
- Added regression coverage to verify code-line rendering and scoped
styles for code blocks.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-04 15:38:59 -05:00
Ben Taylor 4da2d7e34e fix(core): hydrate self-hosted threads whose /connect replay contains an errored run (#6528)
Hydrating an existing thread through `/connect` fails on a
**self-hosted** runtime whenever that thread's history contains a run
that ended in `RUN_ERROR`.

## The defect

A `/connect` response is a *replay* of a thread's history, so it can
legitimately carry several past runs back to back — including an errored
run followed by a later `RUN_STARTED`. The base `AbstractAgent` connect
pipeline pushes that stream through `verifyEvents`, which enforces
AG-UI's **single run** lifecycle rules and rejects the sequence
outright:

```
Cannot send event type 'RUN_STARTED': The run has already errored with 'RUN_ERROR'. No further events can be sent.
```

The user-visible effect is the one reported in #4943:
`agent_connect_failed` on reload, and the existing thread never hydrates
its prior messages.

`IntelligenceAgent` already omitted `verifyEvents` from its connect
pipeline for exactly this reason (its JSDoc spells it out). But
`ProxiedCopilotRuntimeAgent.connectAgent` only takes that path in
`RUNTIME_MODE_INTELLIGENCE` — self-hosted (`RUNTIME_MODE_SSE`) fell
through to `super.connectAgent()` and inherited the single-run
verification. So the managed product was fine and self-hosting was not.

## The fix

`ɵconnectWithoutEventVerification`
(`packages/core/src/utils/connect-replay.ts`) holds the
verifyEvents-free pipeline, and **both** paths now use it.
`transformChunks` is still applied — message reassembly is needed either
way.

This is a de-duplication rather than a third copy:
`IntelligenceAgent.connectAgent` drops ~100 lines of hand-replicated
base pipeline (including its private-field `any` escape hatch) and keeps
only its canonical-run-id handling before delegating. Net
`intelligence-agent.ts` change is −101 lines.

### Fidelity to the base implementation

The helper was diffed statement-by-statement against the **real**
`AbstractAgent.connectAgent` in `@ag-ui/client@0.0.57` (recovered from
the shipped source map), not just against `IntelligenceAgent`'s replica.
`verifyEvents` is the only intended difference.

That diff caught a defect in the first push: the base special-cases
`AGUIConnectNotImplementedError` (swallow → `EMPTY`) and the replica did
not. `IntelligenceAgent` never needed it — it always implements
`connect()` — so the gap was invisible there, but on the SSE path it is
load-bearing: `run-handler.ts:447-450` documents that `await
agent.detachActiveRun()` only stopped deadlocking because that error
path still reaches the pipeline's finalize block. Routing it through
`onError` would also fire run-failure callbacks on every subscriber for
a benign condition. Restored, with a regression test.

Also confirmed that dropping `verifyEvents` cannot alter a well-formed
replay: it is a pure gate — 18 `return of(event)` pass-throughs, 42
error paths, and zero `endWith` / `startWith` / `tap` side effects. It
only removes the single-run rejection.

The existing upstream TODO still stands and is carried over:
`@ag-ui/client@0.0.57`'s `connectAgent(parameters?, subscriber?)` takes
no option to skip verification, so this override is still the only way
to express "this stream is a replay, not a run."

## On the second half of #4943

The issue also reports that the legacy chat path doesn't copy the
resolved `threadId` onto the agent before connect/run. **That half is
already fixed on `main`** — the #5041/#4739 fix put `agent.threadId =
resolvedThreadId` in v2 `useAgent`, and `useCopilotChatInternal`
delegates to that same hook. Nothing more was needed.

It was untested, though, and untestable from the suite that looked like
it covered it: `use-copilot-chat-internal-connect.test.tsx` mocks
`useAgent` wholesale, so it cannot observe threadId propagation at all.
This PR adds `legacy-chat-explicit-threadid.test.tsx`, which drives the
legacy hook through the **real** `useAgent` under a real `<CopilotKit>`,
covering both the explicit-threadId case and the "don't adopt a
non-explicit placeholder UUID" case.

It reads the agent off `useCopilotChatInternal()`'s own return value
rather than calling `useAgent` in the probe. That distinction matters:
the first version of this test did call `useAgent`, so the probe itself
performed the assignment under test and the test passed **even with
`useCopilotChatInternal()` removed entirely**. The current version is
mutation-checked — disabling the assignment in v2 `useAgent` fails it
(`expected 'dc051f13-…' to be 'cookie-backed-thread'`).

Contributor PR #4969 proposed a manual assignment for this half; it is
now redundant.

## Testing

Worktree caveat, stated up front: this worktree symlinks the primary
checkout's `node_modules`, so `@copilotkit/shared` and
`@copilotkit/core` resolve to that checkout's **stale `dist`**. That
produces failures unrelated to this change; each is baselined against
clean `main` in the same environment below. CI installs fresh and is the
authoritative gate.

**1. Reproduces the reported failure before the fix.** The new core
test, run on unmodified `origin/main`, fails with the exact error from
the issue:

```
FAIL  src/__tests__/proxied-connect-replay-multi-run.test.ts > hydrates a thread whose replayed history contains an errored run
AssertionError: promise rejected "Error: Cannot send event type 'RUN_STARTE…" instead of resolving
Caused by: Error: Cannot send event type 'RUN_STARTED': The run has already errored with 'RUN_ERROR'. No further events can be sent.
```

**2. Passes after the fix**, hydrating both runs' messages (`["msg-1",
"msg-2"]`):

```
✓ src/__tests__/proxied-connect-replay-multi-run.test.ts (1 test) 11ms
Test Files  1 passed (1)
```

**3. Connect-not-implemented guard, fail-first.** With the guard
removed, the new second test fails exactly as the base contract
predicts:

```
× swallows AGUIConnectNotImplementedError instead of failing the run
AssertionError: promise rejected "Error: Connect not implemented. This meth…" instead of resolving
```

**4. Full `@copilotkit/core` suite** — this is the evidence the
`IntelligenceAgent` extraction is behavior-identical, since
`intelligence-agent.test.ts` exercises that path heavily:

```
Test Files  59 passed (59)
      Tests  635 passed (635)
```

(excludes `core-inspector-metadata.test.ts`; its 20 failures are the
stale-`shared`-dist artifact — verified identical on clean `main`: `20
failed | 2 passed`, missing export `InspectorMetadataV1`)

**5. `@copilotkit/react-core` — new + adjacent existing suites:**

```
✓ src/hooks/__tests__/use-copilot-chat-internal-connect.test.tsx (7 tests)
✓ src/hooks/__tests__/legacy-chat-explicit-threadid.test.tsx (2 tests)
✓ src/components/copilot-provider/__tests__/v1-explicit-threadid-bridge.test.tsx (5 tests)
Test Files  3 passed (3)
      Tests  14 passed (14)
```

Full react-core suite: `8 failed | 1492 passed (1500)`. All 8 are in
`use-interrupt` / `use-pin-to-send` / `CopilotChatView.pinToSend` — none
touch connect replay or threadId, and clean `main` in this worktree
fails the identical 8 (`8 failed | 37 passed (45)` for those three files
alone).

**6. `@copilotkit/vue`** (affected via core): `100 passed (100)` files,
`1072 passed (1072)` tests.

**7. Types, lint, format:**

```
tsc -p packages/core/tsconfig.json --noEmit   → no errors in any changed file
oxlint  <5 changed files>                     → Found 0 warnings and 0 errors
oxfmt --check <5 changed files>               → All matched files use the correct format
```

The only remaining `tsc` errors are 4 pre-existing stale-dist ones in
`agent-registry.ts` / `types.ts` (`InspectorMetadataV1`), untouched by
this PR.

Fixes #4943


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

## Summary by CodeRabbit

* **Bug Fixes**
* Improved thread hydration when reconnecting to histories containing
multiple runs, including runs that previously ended in error.
* Prevented unsupported connection errors from being reported as run
failures.
* Ensured connection state is properly finalized after replaying a
thread.
* Legacy chat components now correctly reuse an explicitly provided
thread ID while preserving generated IDs when none is provided.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-04 15:37:42 -05:00
Benjamin Taylor 91abd959f7 feat(react-core): controlled open/onOpenChange props for sidebar and popup
v1 exposed `open` + `onSetOpen`, so a host could open and close the chat
from its own UI. v2 shipped only `defaultOpen`, leaving the open state
reachable exclusively from inside the chat subtree. Restores the
controlled pair on `<CopilotSidebar>` and `<CopilotPopup>`:

- `open` pins what the surface renders, from the first frame.
- `onOpenChange` reports every request to open or close (toggle button,
  click-outside, Escape, the drawer's mobile mutual-exclusion). It fires
  with or without `open`, so it also works as a plain notification.

Implemented as `ControlledModalOpenScope`, which overrides the chat
configuration context for the subtree, rather than as a fourth mode
inside CopilotChatConfigurationProvider's modal-state resolution. The
provider's own state, parent sync, drawer mutual-exclusion and
modal-closer registry are untouched: the wrapped setter still calls the
underlying one, so those side effects keep running, and it registers
itself as the modal closer so the drawer reaches the host.

The props travel to the views by context, not through the memoized
`chatView` override. Threading a changing `open` through that override
would mint a new element type per toggle and remount the whole chat
subtree, which is the class of bug already fixed for popup resize.

Closes #3334
2026-09-04 15:11:41 -05:00
Benjamin Taylor 3672d007ae docs(showcase): document frontend-driven activity cards, lock the behavior with a test
Activity messages (role: "activity") already render standalone in the
transcript and are stripped from the run payload by
AbstractAgent.prepareRunAgentInput, so frontend code can put a card in the
chat without a tool call and without polluting the conversation. That was
only ever documented for backend-emitted activities, so the frontend-driven
path was undiscoverable — issue #3388 asked for a feature that already ships.

Adds a Generative UI guide page for the pattern and a react-core test that
pins both halves of the contract: the card renders, and it never reaches the
agent.

The non-obvious part, and the reason this needs documenting rather than a
one-line answer: the card must be added via the agent from useAgent(). An
agent instance constructed and held outside React is not the instance the
chat renders, so messages added to it silently never appear.

Refs #3388

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 15:05:30 -05:00
Benjamin Taylor b5a6d05ae9 fix(react-core): restore code block line breaks in packaged CSS (#3330)
Fenced code blocks rendered as a single collapsed line in the packaged v2
UI. streamdown emits one <span> per source line inside
`pre[data-streamdown="code-block-body"] > code` and leaves no newline in
the text, so the line break comes entirely from the raw Tailwind utility
`block` on that span. CopilotKit builds Tailwind with `prefix(cpk)`, so
`.block` never reaches `dist/v2/index.css` and every line ran inline.

Scope the display rule structurally, because the line spans carry no
`data-streamdown` attribute of their own.

Adding `whitespace-pre` to the <pre>, as earlier attempts did, changes
nothing: the UA stylesheet already sets `white-space: pre` there and
there are no newlines left to preserve.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 14:57:50 -05:00
Mike Ryan 2cde6b97f7 fix(runtime): preserve single-route resource context 2026-09-04 10:36:27 -07:00
Mike Ryan 840ad3c14a feat(runtime): support Intelligence over one route 2026-09-04 10:36:27 -07:00
tylerslaton 71b2f481f9 chore: release monorepo v1.70.1 2026-09-03 15:49:49 +00:00
Alem Tuzlak 8a5a976f1a feat(core): expose webmcp-enabled frontend tools to browser agents (#6847)
## What does this PR do?

Hooks can now expose a frontend tool to browser agents through the
WebMCP browser API, next to the normal agent registration. Set `webmcp:
true`, or pass `{ annotations }` for WebMCP hints:

```ts
useFrontendTool({
  name: "searchOrders",
  description: "Search the signed-in user's orders by status",
  parameters: z.object({ status: z.enum(["open", "shipped", "delivered"]) }),
  handler: async ({ status }) => searchOrders(status),
  webmcp: { annotations: { readOnlyHint: true } },
});
```

How it works:

1. `FrontendTool` in `@copilotkit/core` gains the `webmcp` option. A new
`WebMCPRegistry` registers the tool on `document.modelContext` with its
name, description, input schema, and annotations. `execute` runs the
tool's own handler. The handler context has no `agent` there.
2. Every tool registry change in `RunHandler` reconciles the WebMCP
registrations. The same availability rules apply as for the agent tool
list. Removing a tool aborts its registration signal, and the browser
then unregisters it.
3. Each adapter picks the option up from core: v2 `useFrontendTool`
(React, Vue, React Native), the v1 `useCopilotAction` and
`useFrontendTool` wrappers (React, Vue), and Angular's
`registerFrontendTool`. Where WebMCP is not available (SSR, React
Native, browsers without the API), registration is a no-op.

The `webmcp` prop is documented on the React, Vue, and Angular reference
pages in shell-docs.

## Related PRs and Issues

- None.

## Checklist

- [x] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [x] If the PR changes or adds functionality, I have updated the
relevant documentation
- [ ] "Allow edits by maintainers" is checked (lets us help iterate on
your PR directly — faster turnaround for everyone)

## Testing

**Commands run**

- `pnpm nx run-many -t check-types
--projects=@copilotkit/core,@copilotkit/react-core,@copilotkit/vue,@copilotkit/angular`
— all pass.
- Full test suites: core (829 tests), vue (103), and angular pass.
react-core passes standalone (1589 tests). Under the lefthook pre-commit
hook, react-core flakes on pre-existing e2e tests (A2UI, MCP Apps) that
do not touch this code. Those tests pass when run alone.

**Manual test**

Requires Chrome 149+ with the WebMCP origin trial, or the testing flag.

1. Enable `chrome://flags/#enable-webmcp-testing`, then relaunch Chrome.
2. In an app that uses CopilotKit, register a tool with `webmcp: true`.
3. Run `await document.modelContext.getTools()` in DevTools. The tool is
listed with its schema and annotations.
4. Unmount the hook. Run the command again. The tool is gone.

**How this PR makes testing easy**

The behavior has automated tests on this branch:

- `packages/core/src/core/__tests__/run-handler-webmcp.test.ts` — 15
tests with a `document.modelContext` stub: registration, annotations,
unregistration, availability rules, name collisions, stale-rejection
races, and handler execution.
-
`packages/react-core/src/v2/hooks/__tests__/use-frontend-tool-webmcp.test.tsx`
and the mirrored
`packages/vue/src/v2/hooks/__tests__/use-frontend-tool-webmcp.test.ts` —
pass-through, re-registration, and agent-scoped cases at the hook level.
- `packages/vue/src/hooks/__tests__/use-frontend-tool-webmcp.test.ts` —
reactive `webmcp` getters through the v1 Vue API.

## Risk / rollback

Low. The feature is opt-in per tool. Without `webmcp`, no code path
changes. Where WebMCP is unsupported, registration is a no-op. Revert
this PR to roll back.

## Public API change

New optional `webmcp` prop on frontend tool registrations. Existing call
sites do not change.

**Before**

```ts
useFrontendTool({
  name: "searchOrders",
  description: "Search orders by status",
  parameters: z.object({ status: z.string() }),
  handler: async ({ status }) => searchOrders(status),
});
```

**After**

```ts
useFrontendTool({
  name: "searchOrders",
  description: "Search orders by status",
  parameters: z.object({ status: z.string() }),
  handler: async ({ status }) => searchOrders(status),
  webmcp: { annotations: { readOnlyHint: true } },
});
```


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

* **New Features**
  * Tools can now be exposed to browser agents through WebMCP.
* Added support for custom annotations and automatic parameter schema
generation.
* WebMCP registrations stay synchronized as tools are added, removed,
enabled, or updated.
  * Available across Angular, React, and Vue tool APIs.
* WebMCP reuses existing handlers and safely does nothing when
unavailable.

* **Documentation**
* Added usage guidance and examples for configuring WebMCP-enabled
tools.
  * Documented that WebMCP invocations do not include an agent context.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-03 14:15:29 +02:00
github-actions[bot] a31daf3c78 style: auto-fix formatting 2026-09-02 15:21:27 +00:00
Alem Tuzlak 808113b923 fix(core): keep webmcp registrations stable across stale rejections and reactive changes 2026-09-02 17:19:34 +02:00
Alem Tuzlak aeea432cb1 feat(adapters): support the webmcp option in react, vue, and angular tool hooks 2026-09-02 16:26:12 +02:00
Lukas Moschitz 9bd7acc5ce fix(shared): drop the retired "premium" tier name from the console notice
The Headless UI console notice told developers about "premium features" and
pointed at /premium/overview. The tier is called CopilotKit Intelligence now, so
the notice named a product that no longer exists. It now uses the same sentence
the Headless UI docs page uses.

The docs links in react-core, web-inspector and the runtime skill reference move
from /premium/* to /intelligence/*. They worked through the redirects added in
#6818, but each cost a hop and carried the old name.

One of them was broken, not just stale: the "Show me how" button on the missing
public API key error opened /premium/overview#getting-access. That heading was
deleted on 2026-06-16 in 449237af0c, so the button had been landing at the top
of the page for two and a half months. It now points at #plans-and-access, the
section that answers how to get a key.

Tests assert these hrefs, so they move with the strings.

Refs OSS-1085
2026-09-02 12:19:50 +02:00
Maximiliano Korp 25cbb372aa docs(react-core): update Learning Container deprecation guidance 2026-09-01 14:01:28 -07:00
yannj-fr 82c2ffbfc6 test(react-core): fix stale allowlist wording in the open-link scheme test
The comment described an http/https/mailto/tel allowlist, but ui/open-link uses a
denylist (javascript:/data:/vbscript:/blob:/file:). Align the comment with the
actual contract so it does not mislead a future change to the scheme policy.
2026-09-01 16:03:14 +02:00
Yann Jouanin b12ba3e3a5 Merge branch 'main' into feat/migrate-ext-app-package 2026-09-01 15:40:55 +02:00
yannj-fr 2544f1ff4c test(react-core): cover ui/initialize negotiation + address round-three nits
- Add e2e tests pinning the ui/initialize contract (the compile-time tie to the
  spec): a well-formed initialize returns the host context and the negotiated MCP
  Apps protocol version; an initialize missing required fields (e.g.
  appCapabilities) is rejected with -32603; a widget sending a different
  protocol-version string gets the host's MCP Apps version back, not its own
  echoed. (2025-06-18 is a base-MCP-protocol version, independent from the MCP
  Apps protocol 2026-01-26; it is what the old hand-rolled host hardcoded.)
- Nit: load the bridge via `import(...).catch(rethrow)` with inferred types
  instead of `typeof import(...)` annotations, removing three
  consistent-type-imports warnings.
- Nit: restore the "ui/message: No agent available" warning log on the no-agent
  path, for parity with the hand-rolled host and the oncalltool guard.
2026-09-01 15:40:09 +02:00
Mike Ryan f3b1ef345b fix(integrations): standardize Intelligence project key name 2026-08-31 20:23:15 -07:00
maxkorp 3a64564508 chore: release monorepo v1.70.0 2026-08-31 19:34:27 +00:00
Max Korp b07b1320f2 feat(runtime): use managed Intelligence authority (#6098)
## What changed

- Add standalone `CPK_TELEMETRY_ID` support to Runtime v1 and v2.
- Keep telemetry opt-out, sampling, Segment, and legacy license fallback
behavior.
- Fetch structured Intelligence entitlements and map them to current
client status.
- Share concurrent entitlement lookups, retry short-lived failures, and
reject stale grants.
- Make managed React, Angular, and Vue thread UIs use Runtime
entitlement authority.
- Keep assistant feedback stable when unrelated Inspector settings
change.
- Update Runtime, telemetry, self-hosting, and Web Inspector docs.

## Why

Managed Intelligence projects use a project API key for product access
and a non-secret telemetry ID for attribution. Offline license tokens
remain a self-hosted entitlement concern.

Starter-template and AgentCore changes live in #6188.

## Companion PRs

- Starter templates: #6188
- CopilotKit/Intelligence#628
- CopilotKit/oss-path-to-production#226

## Review corrections

- Scope shared entitlement attempts to one API key and endpoint.
- Ignore stale attempts after credentials change.
- Bound retries after short denials and transport failures.
- Accept telemetry IDs only when they match the public identifier
contract.
- Read Inspector context in its button, so unrelated label changes do
not rerender assistant feedback.

## Validation

- React Core full suite: 1,537 Vitest tests and 47 script tests passed.
- Runtime, Core, Shared, Angular, Vue, and Web Inspector focused suites
passed.
- React Core typecheck and build passed after the final rebase.
- Direct builds and type checks passed for Angular, Core, Runtime,
Shared, Vue, and Web Inspector.
- Shell docs typecheck and production build passed.
- Changed Vue files passed ESLint.
- `git diff --check` passed.


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

## Summary by CodeRabbit

- **New Features**
- Added structured runtime entitlement support for managed and
self-hosted deployments.
- Feature access and usage limits now reflect active entitlements, with
legacy license compatibility.
- Added runtime entitlement diagnostics to the Inspector’s Threads view.
- Added runtime-scoped telemetry identities and configurable telemetry
ID support.

- **Bug Fixes**
- Licensing interfaces remain in a loading state during retryable
entitlement outages.
- Improved recovery after runtime connection, target, or transport
changes.
- Prevented stale entitlement data from granting access after refresh
failures.

- **Documentation**
- Documented entitlement statuses, telemetry identity precedence,
sampling, and Inspector telemetry behavior.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-31 11:55:42 -07:00
Ben Taylor 6ae23c0df0 fix(react-core): register frontend tools before sibling effects run (#6794)
Re-derived against current main from **#4259** (mxmzb), which diagnosed
this in April. That branch is 7,386 commits behind and conflicts, so
this ports the mechanism rather than rebasing it.

## The bug

`useFrontendTool` registered its tool inside a `useEffect`. React
flushes passive effects **child-first in tree order**, so a component
mounted *before* the registering component runs its own `useEffect`
against an empty tool list.

That is the cross-page-navigation failure: a page mounts `CopilotChat`
and its tool-registering components in one commit, `CopilotChat`'s
connect effect fires first, and the connect request goes out carrying no
frontend tools.

## The fix

Register in `useLayoutEffect`. Layout effects run during commit, ahead
of every passive effect regardless of tree order, closing the window.

This is not a new pattern here — **`useAgentContext` already registers
via `useLayoutEffect`** (`use-agent-context.tsx:2`). The context half of
this hook family was fixed; the tool half was not. This makes them
consistent.

## Testing

### The original test did not detect the bug

Worth recording. #4259 shipped `use-frontend-tool-timing.test.tsx`,
which mounts the tool registrar **before** the observing component. I
ported it verbatim and ran it against unmodified main:

```
✓ src/v2/hooks/__tests__/use-frontend-tool-timing.test.tsx (1 test) 10ms
  Test Files  1 passed (1)
```

It passes without the fix. React runs the registrar's effect first in
that order, so the observer always sees the tool. The PR's own comment
concedes the point — *"the result depends on component ordering and may
be absent."*

The test here mounts the consumer **first**, which is the shape that
actually breaks, and says so in the file so nobody reorders it back.

### RED → GREEN on the real surface

**RED** (current main, `useEffect`):
```
× registers the tool before an earlier-mounted sibling's useEffect runs
AssertionError: expected [] to include 'timingTestTool'
  Tests  1 failed (1)
```
The empty array is the bug: the consumer's effect saw no tools.

**GREEN** (`useLayoutEffect`):
```
✓ src/v2/hooks/__tests__/use-frontend-tool-timing.test.tsx (1 test) 12ms
  Tests  1 passed (1)
```

### No regressions

Full `src/v2/hooks` suite, same environment, with and without the
change:

| | Test files | Tests |
|---|---|---|
| without fix | 8 failed / 29 passed | **37 failed** / 283 passed |
| with fix | 7 failed / 30 passed | **36 failed** / 284 passed |

The delta is exactly the new test. The remaining 36 failures are
pre-existing in my local worktree (stale cross-package `dist`
resolution), identical on both sides.

## Notes

- **SSR:** `useLayoutEffect` warns during server rendering. These hooks
run inside `CopilotKitProvider`'s client context, and the sibling
`useAgentContext` already uses a bare `useLayoutEffect`, so this follows
the established pattern rather than introducing an isomorphic wrapper.
- **Scope:** #4259 also carried a second, independent mechanism — an
`ensureToolMiddleware` fallback that injects tools/context into direct
`agent.runAgent()` calls bypassing `copilotkit.runAgent()`
(`run-handler.ts` +44, plus `core.ts`, `agent-registry.ts`, Angular's
`agent.ts`, `use-agent.tsx`). That addresses a **different** failure and
deserves its own PR, tests and review. It is deliberately **not**
included here and should not be considered resolved by this.

Credit to @mxmzb for the diagnosis.


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

* **Bug Fixes**
* Frontend tools are now registered earlier, ensuring availability
before the interface is displayed.
* Improved consistency when components access frontend tools during
initial effects.
* Renderer cleanup now occurs at the appropriate stage when components
are removed.

* **Tests**
* Added coverage verifying frontend tools are available to
earlier-mounted components.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-31 13:00:20 -05:00
Ben Taylor 3ee0ec189c fix(core,react-core): stop dropping frontend tools, and let catch-all actions wait for a response (#6524)
Two frontend-tool defects that share a shape: a tool that was registered
correctly still never reached the agent, or reached the render without
the ability to respond.

## `useFrontendTool` tools dropped when the runtime enables
`openGenerativeUI` (#4952)

Tools reached the core registry through two owners that shared one array
— the provider via `setTools()`, hooks via `addTool()` — and `setTools`
replaced the array wholesale. Any provider re-sync after mount therefore
wiped every hook-registered tool.

A runtime with `openGenerativeUI: true` made it reproduce on **every**
mount: `/info` flips the flag asynchronously, the provider re-derives
its tool list to add `generateSandboxedUi`, and the re-sync dropped the
app's own tools. The agent then received only `generateSandboxedUi`,
exactly as reported.

The fix splits the two owners into their own buckets, merged on read
with hook entries winning. This mirrors what `CopilotKitCoreReact`
already does for render tool calls (`react-core.ts`) — tools simply
never got the same treatment. Because the clobber lived in core rather
than in one provider, **Vue had the identical bug and is fixed by the
same change** — verified end to end, not by shape:
`CopilotKitProvider.vue:461` assigns `runtimeOpenGenerativeUIEnabled`
from the core, `:297` derives `openGenerativeUIActive`, `:328`/`:354`
add the built-in to `allTools`, and `:492` re-syncs it through
`setTools` behind the same `didMountRef` skip. Angular is unaffected: it
never calls `setTools`, passes `tools` only through the constructor
(`copilotkit.ts:152`), and registers just the *renderers* from
`config.tools` (`:224`) — no second registration.

`addTool` now shadows a provider tool of the same name instead of
refusing to register, and only warns when another imperative
registration already holds the name. It also no longer pushes onto the
array the provider passed in, and `initialize` copies that array like
`setTools` already did.

**One intentional behavior change worth a reviewer's eye:** `setTools`
now replaces provider-owned tools only, so `setTools([])` no longer
clears tools registered through `addTool`. That narrowing *is* the fix,
but anyone calling `setTools([])` as a "clear everything" would now need
`removeTool` per tool. Nothing in the repo does (core, react-core, vue,
angular suites all pass).

## Catch-all actions could not wait for a response (#1746)

`getActionConfig` short-circuited on `name === "*"` before checking for
a wait-render, so a catch-all declaring `renderAndWaitForResponse` was
silently downgraded to render-only — no `respond`, and in practice a
`render is not a function` throw, since the render-only path reads
`action.render`. Handling N human-in-the-loop tools required N hooks.

A catch-all with a wait-render now routes to the human-in-the-loop path.
Core already had the execution half
(`getWildcardTool`/`executeWildcardTool`), so this is routing, not new
machinery. Two supporting changes make it usable:

- The HITL render props now carry **the name of the tool actually being
called**. It equals the registration name for a normal action, but a
catch-all needs it to tell N tools apart, and `"*"` was being written
over it in both the v1 wrapper and the v2 hook.
- `CatchAllFrontendAction` accepts
`renderAndWaitForResponse`/`renderAndWait` alongside `render`, mutually
exclusive as on `FrontendAction`, with `CatchAllActionRenderPropsWait`
exported.

Also: a wildcard tool is no longer advertised to the agent. It is a
local catch-all handler for calls with no exact match, so offering the
model a tool literally named `*` was never meaningful. Latent before
this PR (nothing in React registered a wildcard *tool*); catch-all HITL
activates it.

## Synced with `main` (2026-08-31)

`main` moved react-core's v1 tree under `src/v1-deprecated/` while this
branch was open, so the merge had exactly two conflicts, both
relocations:

- `use-default-tool.ts`'s `DistributiveOmit` change re-applied on the
moved file, keeping main's deprecation banner.
- the catch-all HITL e2e test moved into
`src/v1-deprecated/hooks/__tests__/`, with its `../../v2/...` imports
re-rooted to `../../../v2/...` to match its sibling
`use-copilot-action.e2e.test.tsx`.

The defect is still live on current `main` —
`CopilotKitProvider.tsx:838` still calls `copilotkit.setTools(allTools)`
— and both regression tests still bite there. The suites, the
before/after checks, `tsc --noEmit`, `oxlint` and `oxfmt` below were all
re-run on the merged tree; the browser walkthrough under **Live
verification** is from the pre-merge branch and was not repeated.

## Testing

**New regression tests**

- `packages/core/src/core/__tests__/run-handler-tool-registry.test.ts` —
13 tests: `addTool` survives `setTools`, hook precedence, agent-scoped
vs global, `removeTool` across both buckets, remount re-registration,
capability toggles surviving a re-sync, provider ordering, caller-array
aliasing in both directions, wildcard never advertised.
-
`packages/react-core/src/v2/providers/__tests__/CopilotKitProvider.openGenerativeUIToolLoss.test.tsx`
— drives the **real** core over a stubbed `/info` that returns
`openGenerativeUIEnabled: true`, asserting the hook tool survives.
-
`packages/react-core/src/v1-deprecated/hooks/__tests__/use-copilot-action-catch-all-hitl.e2e.test.tsx`
— end-to-end through the real provider and core: catch-all gets the real
tool name and a live `respond`, the tool result lands on the original
`toolCallId`, the follow-up run fires, and `*` is absent from
`runInputs[0].tools`.

**Both new tests were confirmed to fail before the fix — re-confirmed
after syncing `main`,** by checking the touched sources out at
`origin/main` and rebuilding core's dist:

```
× keeps the hook tool once the runtime turns openGenerativeUI on
  → expected [ 'generateSandboxedUi' ] to include 'sayHello'
```

and before the routing fix, the catch-all test failed with the exact
defect from the issue:

```
× gives the catch-all render a live respond and the real tool name
  → TypeError: render is not a function
    ❯ render src/hooks/use-render-tool-call.ts:44:22
```

**Suites (all green)**

```
@copilotkit/core        67 files,  799 tests passed
@copilotkit/react-core 137 files, 1558 tests passed
@copilotkit/vue        101 files, 1092 tests passed
@copilotkit/angular     49 files,  317 tests passed (1 skipped)
```

`tsc --noEmit` clean for `core` and `react-core`; `oxlint` 0 errors on
the changed files (16 warnings, all pre-existing); `oxfmt` applied.

**Live verification** — `examples/v2/react/demo` in a browser against
built dists, with a temporary stub AG-UI agent (no LLM key) that echoes
the tool names it receives, a runtime configured `openGenerativeUI:
true`, no `openGenerativeUI` prop on the provider, one
`useCopilotAction` frontend tool and one `useCopilotAction({ name: "*",
renderAndWaitForResponse })`:

```
TOOLS_RECEIVED: ["generateSandboxedUi","sayHello"]     <- #4952: hook tool survived; no "*" leaked
catch-all handling: book_call   status: executing      <- #1746: real tool name, live respond
[click "Pick Tuesday"]
catch-all handling: book_call   status: complete
TOOL_RESULT_RECEIVED: "{"slot":"tuesday"}"             <- follow-up run received the result
```

0 console errors. The stub route and page were scratch and are not in
this branch.

## Notes for reviewers

- Community PR #4967 also targets #4952 by merging in the provider
instead. I took the core-layer fix because the clobber is in core's
registry and every framework provider hits it — patching one provider
leaves Vue broken. Happy to reconcile.
- Pre-existing and deliberately **not** changed here:
`useCopilotAction({ name: "*" })` render props do not infer, because the
hook's parameter is a union TypeScript cannot contextually type. This
already affected plain `render` before this PR (verified), so the new
tests and docs annotate props explicitly. Fixing it needs a
`useCopilotAction` overload — worth a follow-up.
- Deliberate small duplications, flagged rather than abstracted:
`WILDCARD_TOOL_NAME` is a one-line const in both core and react-core's
v2 HITL hook (sharing it would mean a new public export from core), and
the link between "a catch-all render receives `name`" and "the v1 HITL
wrapper supplies it" is a cast rather than a type — making it typed
means adding `name` to the public `ActionRenderPropsWait`, which is
wider than this fix.
- #4759 is left with contributor PR #5308, and #6101 needs its own
design pass since it introduces new public API.



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

## Summary by CodeRabbit

- **New Features**
- Added catch-all human-in-the-loop actions that can handle unregistered
tools and wait for user responses.
- Catch-all action renderers now receive the actual invoked tool name
and arguments.
  - Added support for wait-aware catch-all rendering types.

- **Bug Fixes**
  - Preserved frontend tools when provider tool lists are refreshed.
- Prevented duplicate tools and ensured registered tools take
precedence.
- Hidden wildcard tools from agent-advertised tool lists while keeping
them available for handling requests.


<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-31 13:00:00 -05:00
Maximiliano Korp 57a9229e7d fix(react-core): preserve inspector button props 2026-08-31 10:46:15 -07:00
Maximiliano Korp cd7748f25b fix(runtime): harden entitlement resolution 2026-08-31 10:46:15 -07:00
Mike Ryan 2d26a9de1a fix(react-core): isolate assistant inspector context updates 2026-08-31 10:46:13 -07:00
Mike Ryan f1ac08938a feat(runtime): use managed Intelligence authority 2026-08-31 10:46:12 -07:00
Tyler Slaton d477ca7396 chore: merge main into AG-UI dependency bump 2026-08-31 09:26:33 -07:00
Benjamin Taylor c5107bee53 fix(core): keep the connect-not-implemented escape hatch in the shared pipeline
Self-review catch. The extracted pipeline was faithful to IntelligenceAgent's
copy but not to the base AbstractAgent.connectAgent it replaced on the
self-hosted path, which special-cases one error:

  catchError((error) => {
    this.isRunning = false;
    if (!(error instanceof AGUIConnectNotImplementedError)) {
      return this.onError(input, error, subscribers);
    }
    return EMPTY;
  })

IntelligenceAgent never needed it — it always implements connect() — so the
omission was invisible there. On the SSE path it is load-bearing:
run-handler.ts awaits detachActiveRun() before every run and documents that
this only stops deadlocking because the ConnectNotImplementedError path
reaches the finalize block. Routing it through onError would also fire
run-failure callbacks on every subscriber for a benign condition.

Restores the guard, adds a regression test (verified fail-first: without the
guard connectAgent() rejects with "Connect not implemented"), and matches the
base's `void this.onFinalize(...)`.

Also makes the legacy-chat threadId test actually test its claim. It read
agent.threadId from its own useAgent() call, so the probe performed the very
assignment under test — it passed even with useCopilotChatInternal() removed
entirely. It now reads the agent off the hook's own return value, and is
mutation-checked: disabling the assignment in v2 useAgent fails it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 08:11:21 -05:00
Benjamin Taylor 35616f46a2 fix(core): hydrate self-hosted threads whose replay contains an errored run
A /connect response replays a thread's history, so it can carry several
past runs back to back — including a run that ended in RUN_ERROR followed
by a later RUN_STARTED. The base AbstractAgent connect pipeline runs the
stream through verifyEvents, which enforces single-run lifecycle rules and
rejects that sequence outright:

  Cannot send event type 'RUN_STARTED': The run has already errored with
  'RUN_ERROR'. No further events can be sent.

IntelligenceAgent already omitted verifyEvents from its connect pipeline
for this reason, but self-hosted runtimes (RUNTIME_MODE_SSE) fell through
to super.connectAgent() and so never hydrated such a thread.

Extract that verifyEvents-free pipeline into a shared helper used by both
paths, rather than keeping two copies of a delicate 60-line pipeline.
IntelligenceAgent keeps its canonical-run-id handling and delegates the
rest.

Also adds legacy-chat threadId coverage: the pre-existing connect suite
mocks useAgent wholesale, so nothing exercised the real propagation the
CopilotPopup path depends on.

Fixes #4943

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 08:11:01 -05:00
Benjamin Taylor 37e69105d8 fix(react-core): tear down HITL renderer in the same effect phase it is registered
useFrontendTool now registers the tool renderer in a layout effect, but
useHumanInTheLoop still removed that renderer from a passive effect cleanup.

React runs each phase's cleanups before that phase's effects, but runs the
entire layout phase ahead of the entire passive phase. With the two split
across phases, a keyed remount ordered the outgoing instance's removal after
the incoming instance's registration:

  add (new, layout) -> remove (old, passive)

which deleted the renderer that had just been added and left the HITL tool
unrenderable. Moving the teardown to useLayoutEffect restores the correct
remove-then-add ordering.

Caught by the existing 'should maintain executing state across component
remount' test in use-human-in-the-loop.e2e.test.tsx.
2026-08-31 08:08:48 -05:00
Benjamin Taylor ebbba19101 Merge origin/main into ben1/fe-tool-registry-4952-1746
The v1 react-core tree moved under src/v1-deprecated/, so the two conflicts
were relocations: use-default-tool.ts's DistributiveOmit change re-applied on
the moved file, and the catch-all HITL e2e test moved into
src/v1-deprecated/hooks/__tests__/ with its v2 imports re-rooted.
2026-08-31 07:58:44 -05:00
yannj-fr 661fb1981d fix(react-core): address CodeRabbit findings on the MCP Apps bridge
- Sandbox handshake race: load the ext-apps bridge (dynamic import) BEFORE
  creating and attaching the sandbox iframe. The proxy posts sandbox-proxy-ready
  once during srcdoc execution, and the PostMessageTransport must be listening
  (connect) when it fires. Awaiting the import after the iframe was attached let
  a slow import miss that notification, leaving the widget blank. There is now no
  event-loop yield between attaching the iframe and connecting.
- ui/open-link scheme hardening (XSS): the ext-apps schema validates url as a
  string only, so a widget could pass javascript:/data:/blob: etc. Parse the url
  and refuse a denylist of script-executing / attacker-HTML schemes (javascript,
  data, vbscript, blob, file) before window.open. A denylist is used on purpose so
  custom-scheme deep links (myapp:, whatsapp:, ...) and https universal links keep
  working, since window.open on those hands off to an OS handler rather than
  executing in the page. This matches the Anthropic Software Directory policy
  (https origins + owned custom URI schemes) and the MCP spec's prudent-host
  guidance.
- Tests: reject a disallowed scheme without calling window.open; allow a
  custom-scheme deep link.
2026-08-31 13:30:26 +02:00
yannj-fr f794386528 Merge remote-tracking branch 'upstream/main' into feat/migrate-ext-app-package
# Conflicts:
#	pnpm-lock.yaml
2026-08-31 08:15:25 +02:00
yannj-fr 80d530620c refactor(react-core): make the MCP SDK a hard peer and guard the lazy import
- @modelcontextprotocol/sdk is now a non-optional peerDependency (matching how
  ext-apps declares it) instead of an optional one. ext-apps ships as a direct
  dependency and hard-peers the sdk, so the requirement is already inherited by
  every consumer; the optional flag only hid that and dropped the install-time
  signal. react-core does not use the sdk directly (type import only), so it
  stays out of our dependencies; ext-apps remains the direct dependency.
- Guard the lazy bridge import with a try/catch that rethrows naming the packages
  and the install command, so a missing or version-skewed peer surfaces as an
  actionable error instead of an opaque module-resolution rejection in an effect.
2026-08-31 08:12:12 +02:00
Ben Taylor 69441ccfa4 fix(react-core): render every v1 tool call (#6682)
## What does this PR do?

Fixes the v1 compatibility render path so an assistant message can
render every tool call instead of only `toolCalls[0]`.

The returned lazy renderer now:

- matches each tool call with its corresponding tool result;
- renders all registered tool-call renderers in one fragment;
- removes `null` render results and returns `null` when no tool has a
renderer.

Keeping the fragment behind the existing lazy-renderer callback
preserves the exported `useLazyToolRenderer` return signature. Filtering
before returning also avoids attaching empty generative UI, so
caller-provided subcomponents are not suppressed when no renderer is
registered.

Regression tests cover multiple tool calls, per-call result matching,
the all-unhandled case, and a mixed handled/unhandled message.

## Related PRs and Issues

- Fixes #2946

## Verification

- `pnpm nx run @copilotkit/react-core:test` (133 files, 1,530 Vitest
tests plus 47 script tests)
- `pnpm nx run @copilotkit/react-core:check-types`
- `pnpm exec oxfmt --check
packages/react-core/src/v1-deprecated/hooks/use-lazy-tool-renderer.tsx
packages/react-core/src/v1-deprecated/hooks/__tests__/use-lazy-tool-renderer.test.tsx`

## Checklist

- [x] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [x] Documentation is unchanged because this restores existing v1
behavior without changing the public API
- [x] "Allow edits by maintainers" is checked
2026-08-30 16:59:05 -05:00
Ben Taylor 9909ca44d6 fix(react-core): make agentMetadata.nodeName match the node where the interrupt originates (#6488)
## Problem

`useAgentNodeName` must update React consumers when AG-UI node events
arrive, and `useLangGraphInterrupt.enabled()` must receive the node
where an interrupt actually occurred.

Current `main` includes the basic ref-to-state reactivity fix from
[ffd1580](https://github.com/CopilotKit/CopilotKit/commit/ffd15801d6d),
but that commit explicitly leaves #1426 open: a later `RUN_FINISHED` can
still replace the interrupting node with `"end"`, and v1 consumers can
still be hidden behind `useCoAgent`'s memoized return value.

## What remains in this PR

Rebased onto current `main` (`e9387e0`) after the v1 source migration,
this PR contains only the remaining behavior:

- Preserve the last active node when `RUN_FINISHED` reports `outcome:
"interrupt"`.
- Preserve it for the legacy `on_interrupt` custom-event flow as well.
- Continue transitioning successful and failed runs to `"end"`; reset
new runs and agent switches to `"start"`.
- Add `nodeName` to the `useCoAgent` return-value memo dependencies so
v1 consumers receive the reactive update.
- Share `INTERRUPT_EVENT_NAME` between the hook and interrupt
implementation.

The public hook signatures and AG-UI protocol remain unchanged.

## Preview workflow

- Disabled pkg-pr-new's generated all-package StackBlitz template;
package preview install URLs remain available.

## Changes

- `packages/react-core/src/v1-deprecated/hooks/use-agent-nodename.ts`
- `packages/react-core/src/v1-deprecated/hooks/use-coagent.ts`
-
`packages/react-core/src/v1-deprecated/hooks/__tests__/use-agent-nodename.test.tsx`
- `packages/react-core/src/v2/types/interrupt.ts`
- `packages/react-core/src/v2/hooks/use-interrupt.tsx`
- `.github/workflows/publish-commit.yml`

## Verification

- Full React Core Vitest suite: **131 files, 1520 tests passed**.
- Preview workflow: Nx formatting and YAML parsing passed.
- `nx run @copilotkit/react-core:check-types --skipNxCache`: passed,
including all 33 dependency tasks.
- `git diff --check origin/main...HEAD`: passed.
- The composite React Core test target then reaches the existing
Windows-only script baseline: 8 path-normalization failures plus 2
symlink-permission failures. These are outside this PR's files; the
complete Vitest suite passes before that script stage.

## Scope

This intentionally does not change the AG-UI event protocol, runtime
event ordering, HITL workflow, v1/v2 compatibility layer, or the
separate node tracking in `use-coagent-state-render-bridge.tsx`.

Fixes #1426
2026-08-30 10:05:27 -05:00
Benjamin Taylor f9b306aa4e fix(react-core): register frontend tools before sibling effects run
useFrontendTool registered its tool in a useEffect. React flushes passive
effects child-first in tree order, so a consumer mounted before the
registering component runs its own useEffect against an empty tool list.

That is the cross-page-navigation failure: a page mounts CopilotChat and its
tool-registering components in a single commit, CopilotChat's connect effect
fires first, and the connect request carries no frontend tools.

Register in useLayoutEffect instead. Layout effects run during commit, ahead
of every passive effect regardless of tree order, which closes the window.
This matches useAgentContext, which already registers via useLayoutEffect.

Adds a regression test that mounts the consumer FIRST -- mounting it second
passes with either hook and proves nothing.

Re-derived against current main from mxmzb's #4259, which diagnosed this.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-29 10:32:31 -05:00
Yann Jouanin cc8aeb95ef Merge branch 'main' into feat/migrate-ext-app-package 2026-08-29 08:43:09 +02:00
yannj-fr 8f1059d2fa refactor(react-core): address review on the MCP Apps ext-apps migration
- Load the ext-apps bridge lazily. AppBridge/PostMessageTransport now come from a
  dynamic import() inside Effect 1, with a type-only import at the top. A
  <CopilotKit> app no longer pays the ~40-50 kB gzipped ext-apps cost unless it
  actually renders an MCP App. Verified: the built output has no static ext-apps
  import, only import("@modelcontextprotocol/ext-apps/app-bridge").
- Move @modelcontextprotocol/sdk out of dependencies. It is now an optional
  peerDependency (mirroring how ext-apps declares it) plus a devDependency for
  our own build, so the sdk tree (express, hono, jose, ajv, cross-spawn) is no
  longer an install/audit surface for every React consumer. Nothing in the
  bundle reaches sdk at runtime; the only sdk usage is a type import.
- Raise the zod peer floor to >=3.25. The ext-apps/sdk schema slice imports
  zod/v4 and zod/v4-mini, which only exist in zod >= 3.25; the old >=3.0.0 peer
  let a consumer on zod 3.24 hit an unresolvable import at build time.
- Seed the host context at AppBridge construction (hostContext option) instead of
  calling setHostContext after connect, so it is deterministically in place when
  the widget's ui/initialize is handled (the seam #6689 needs to advertise
  displayMode / availableDisplayModes at initialize).
- Restore the full cross-frontend testid surface-contract comment.
- Split the oncalltool guard so "no server hash" and "no agent" report distinctly.
2026-08-28 22:37:39 +02:00
yannj-fr 21dc441a64 Merge remote-tracking branch 'upstream/main' into feat/migrate-ext-app-package 2026-08-28 21:22:22 +02:00
Benjamin Taylor 4edbbc3f28 fix(react-core): honor chatInputToolbarAddButtonLabel in the add menu tooltip
The add-menu ("+") button's tooltip hardcoded the string "Add attachments",
so `labels.chatInputToolbarAddButtonLabel` only retitled the menu item and
the tooltip stayed English. That blocked full localization of CopilotChat
without replacing the whole add-button slot.

Every other tooltip in the v2 chat surface is already label-driven, and the
Angular implementation already derives this tooltip from the same label, so
this was an oversight rather than a deliberate split.

The "/" shortcut glyph stays hardcoded — it is a key name, not prose.

Fixes #6750

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 13:45:54 -05:00
Markus Ecker 71d9731d45 chore(deps): bump @ag-ui/* to 0.0.59
Moves the published packages from 0.0.57 to the current AG-UI release across
@ag-ui/client, core, encoder and proto — 27 declarations in 18 packages.

0.0.59 is the first release carrying the subagent protocol surface
(SUBAGENT_STARTED/FINISHED/ERROR, subagentRunId) along with the null-omission
cleanup, so this is the dependency CopilotKit's subagent work needs.

Scope is packages/** plus the release script noted below. The examples and
showcases sit on a spread of older pins (0.0.40 through 0.0.58) and are left
alone.

One behavioural change comes with the bump. channels-core ships
sanitizeAgentEventStream because @ag-ui/client used to reject a TOOL_CALL_START
carrying parentMessageId: null — the shape @ag-ui/langgraph emits for an
interrupt-triggering tool call. 0.0.59 accepts that null and treats it as
absent, so the two tests asserting the run dies WITHOUT the sanitizer no longer
hold. They now assert the run survives, and the one at agent level still checks
the tool call actually arrives so it cannot pass vacuously. The sanitizer is
untouched and its coercion tests are unchanged; it is simply no longer the
thing keeping such a run alive.

The bump also broke the packed Angular consumer matrix. That job generates a
smoke app from scripts/release/lib/angular-package.ts, whose manifest restated
"@ag-ui/client": "0.0.57" as a literal while packages/angular moved to 0.0.59.
pnpm then installed both copies and the app failed to compile:

  TS2322: Type 'SmokeAgent' is not assignable to type 'AbstractAgent'.
    Types have separate declarations of a private property '_debug'.

The smoke app imports AbstractAgent directly, so it has to resolve the identical
copy the library ships against. Read that version off the packed manifest --
which verify-angular-package.ts already parses for the Angular support contract
-- instead of restating it, so no future AG-UI bump can desynchronise it.
2026-08-28 16:18:52 +02:00
Alem Tuzlak 5686a0669e Merge branch 'main' into lukas/oss-904-runtime-connection-status 2026-08-28 14:06:40 +02:00
Alem Tuzlak 1dfc5cdafa refactor(core): remove OSS-904 design comments 2026-08-28 13:27:37 +02:00
Alem Tuzlak a7191e2a12 fix(core): bound recovery /info hang and tighten OSS-904 comments 2026-08-28 13:08:44 +02:00
MikeRyanDev 8617f5b76b chore: release monorepo v1.69.3 2026-08-27 16:47:48 +00:00
yannj-fr 0b62880b74 Merge remote-tracking branch 'upstream/main' into feat/migrate-ext-app-package
# Conflicts:
#	packages/react-core/package.json
#	pnpm-lock.yaml
2026-08-27 14:10:44 +02:00
yannj-fr fbe6019399 fix(react-core): satisfy oxlint in MCP Apps renderer (zod record + dead types)
The oxlint CI job failed on the ext-apps migration:
- copilotkit/no-single-arg-zod-record (error): the ui/message _meta field used
  the single-arg z.record(z.any()), which is a compile-time error against Zod 4.
  Use the two-arg form z.record(z.string(), z.any()).
- Remove the dead hand-rolled JSON-RPC message types left over from before the
  AppBridge migration (no longer referenced).

Also normalize type-only imports and formatting in the touched e2e tests.
2026-08-27 14:03:44 +02:00