3119 Commits

Author SHA1 Message Date
Maxim f34b7aa23c ci: measure @copilotkit/react-native bundle size (was absent from the glob)
react-native was missing from static_bundle_size.yml's package glob, so its
dist/ has never been measured despite being the consumer most exposed to the
#4893 regression (Metro does not tree-shake). This adds coverage:

- Extend the compressed-size-action glob to include react-native.
- New scripts/measure-headless.mjs: an esbuild-driven gzip signal for the
  @copilotkit/react-native/headless entry, mirroring react-core's
  measure-copilotchat.mjs (stdin + resolveDir, gzip sum, job-summary output).
- Wire a build + measure step into the copilotchat-import-size CI job.

First baseline: @copilotkit/react-native/headless = 92.8 kB gzip
(esbuild regression signal, not a Metro figure).

No limit fields (Phase 1 policy — see dev-docs/bundle-size.md).

esbuild added as a react-native devDependency (^0.27.0, matching react-core);
the root ">=0.25.4" override keeps the monorepo on a single esbuild (0.27.3).

Two corrections to the drafted script, verified by running it:
- Fed the entry via esbuild stdin with resolveDir=pkgRoot; a temp-dir entry
  cannot resolve @copilotkit/react-native/headless through workspace node_modules.
- Dropped useRenderToolCall from the import list — the RN headless surface
  deliberately does not export it (DOM-dependent; see src/index.ts).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-08 14:51:55 +02:00
Maxim 68a30c2535 test(react-core): hard-fail if the /v2/headless chunk links the render stack (#4893) 2026-08-08 14:36:45 +02:00
Maxim ebf0f94fb8 refactor(react-native): register render tools into core's registry, derive props from react-core 2026-08-08 14:27:02 +02:00
Maxim d5ace8b2ce feat(react-core): export ReactToolCallRenderer from the /v2/headless entry 2026-08-08 14:16:21 +02:00
Maxim 94b8357f3c test(react-native): lock the #4893 fat-entry ban in the import-graph guard 2026-08-08 14:07:43 +02:00
Atai Barkai 271614573d docs(channels): drop the channels-core pointer from adapter READMEs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 23:07:22 -07:00
Atai Barkai 6e0e1de049 docs(channels): state lifecycle positively across READMEs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 22:56:58 -07:00
Atai Barkai 049bd2bdcc docs(channels-core): drop the phantom-negation phrasing
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 22:55:32 -07:00
Atai Barkai adbdf65263 docs(channels): state the free plan plainly
Drop the defensive parentheticals around Intelligence pricing; say
"available on a free plan" and move on.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 22:52:33 -07:00
Atai Barkai cb4439b241 docs(channels): clarify the custom channel runner path
Note in the Channels overview and package READMEs that building your own
channel runner on the open-source SDK primitives is a supported path with
no CopilotKit Intelligence dependency; teams choosing it own their state,
persistence, concurrency, locking, retries, and race-condition handling.
Intelligence remains the managed runner, with analytics, learning, and
governance in addition.

Also updates the production self-hosting note: Enterprise Intelligence can
be fully self-hosted today, onboarding guides are still to come.

Refs FAC-155

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 22:35:13 -07:00
Benjamin Taylor ec06d87fb4 fix(react-core): repair useCopilotReadable effect deps, convert args, and dependencies
Four defects in `useCopilotReadable` were introduced together in 80dffec4e7
(v1.50.0), when the hook was repointed from the v1 context tree onto the v2
flat context store. The pre-1.50 implementation was correct on every count.

- `available` was missing from the effect's dependency array, so toggling it
  between "enabled" and "disabled" after mount did nothing. It is back in the
  deps, alongside the restored `available = "enabled"` default.
- `convert` was invoked as `convert(value)` against its declared
  `(description, value)` signature, so a user's function received the value as
  `description` and `undefined` as `value`. The branches are now split, because
  `JSON.stringify(description, value)` would treat the value as a replacer.
- The `dependencies` positional argument was destructured but never reached the
  deps array, matching the pattern already used by
  `useCopilotAdditionalInstructions`.
- The `found` dedup branch is removed. It compared a raw value against a stored
  entry whose value had already been serialized by `addContext`, so it never
  matched and its early return was unreachable. Repairing the comparison rather
  than deleting it would newly let one component's unmount remove a context
  entry another component still depends on.

`parentId` and `categories` are marked @deprecated: both were dropped from the
hook body by the same commit but left in the options type and the JSDoc, which
advertised a nested-context feature that has been a no-op since v1.50.0. The
JSDoc example is rewritten to document behavior that actually exists. Real
hierarchy support needs parent/child modelling in the v2 context store and is
tracked separately.

Adds the hook's first test file: 12 tests, five of which fail against the
pre-fix implementation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: jwgrsol <wefhio1985@gmail.com>
2026-08-07 16:14:13 -05:00
Austin Merrick b32b5539cc feat: add Inspector navigation, usage, and locked Threads (refs ENT-1173) (#6275)
## What does this PR do?

Adds the CopilotKit consumer side of ENT-1173 across Shared, Runtime,
Core, Web Inspector, and the existing Shell Docs pages.

- Defines and parses optional trusted Inspector metadata for identity,
plan, license, action, usage, and expiry. Runtime proxies it through a
private, failure-isolated route, and Core refreshes it without changing
connection state.
- Groups Inspector navigation into Threads, Agents, and Learning.
Threads renders finite, unlimited, unknown, overage, and expiring usage
states plus matching trusted plan or license actions.
- Keeps explicit `threadEndpoints` as the only authority for Thread
requests. Locked or absent capability states make no list, subscription,
detail, message, event, or state calls.
- Keeps the zero-thread video, three example Threads, detail tabs, and
guided tour in empty and locked states. General Intelligence remains the
default onboarding path; only trusted `team_self_hosted` metadata uses
self-hosted onboarding.
- Gives an active license with missing Runtime routes a short **Finish
setting up Rich Threads** state. Users can copy a safe coding-agent
prompt or open the public Runtime setup guide. The same copy control
appears in that guide, and raw Markdown/LLM views include the full
prompt.
- Keeps finite usage green below 90%, orange from 90% to the limit, and
red at or above the limit. At 90%, a trusted plan action changes from
**Manage Your Plan** to a purple **Upgrade Your Plan** without changing
its trusted URL, action kind, or telemetry contract.
- Adds a deterministic 33-state loopback lab for CopilotKit developers.
It has no production route or export, is absent from public docs and
package metadata, and is excluded from the npm tarball.

`Expiring Soon` is display-only; this PR does not enable the thread
culler. Managed Enterprise receives no manage-plan action, and Team
Self-Hosted receives no hosted plan action. Optional metadata and the
additive expiry field remain compatible across mixed producer, Runtime,
Core, and Inspector versions.

A small Channels test-only change updates fetch mocks for current
TypeScript types. It changes no Slack or Teams docs or runtime behavior.

## Related PRs and issues

- Refs
[ENT-1173](https://linear.app/copilotkit/issue/ENT-1173/ship-plg-ready-inspector-navigation-metadata-and-locked-threads)
- Producer:
[CopilotKit/Intelligence#696](https://github.com/CopilotKit/Intelligence/pull/696)

## Validation

- `@copilotkit/web-inspector`: 20 files and 372 tests passed; typecheck
and production build passed.
- Shell Docs: 57 files and 383 tests passed; lint, typecheck, and
production build passed. The build generated all 222 static pages.
- Browser checks cover the copy-prompt flow, unchanged white **Manage
Your Plan**, purple **Upgrade Your Plan**, orange 4,500/5,000 usage, and
red 5,000/5,000 usage.
- Independent review found no Critical or Important issues.
- The broader Runtime, React Native, Channels, package-quality,
compatibility, and Node-version checks from the prior pushed head remain
green.

## Checklist

- [x] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [x] I updated the relevant documentation
- [ ] "Allow edits by maintainers" is checked
2026-08-07 14:08:23 -07:00
Ben Taylor ea3e3fbfa6 fix(react-ui): let sidebar children fill the viewport height (#6410)
Fixes #261 (open since March 2024). Supersedes #4622 — @ashish4143
diagnosed the same wrappers and is credited as co-author on the commit.

## The bug

`CopilotSidebar` wraps consumer content in two divs:

- `.copilotKitSidebarContentWrapper` (`Sidebar.tsx`) — only sets
`overflow`, `margin-right`, `transition`
- `.copilotKitModalChildrenWrapper` (`Modal.tsx`) — **has no CSS rule
anywhere in the repo**

Both are auto-height blocks, so a child's `height: 100%` has no definite
containing block to resolve against and collapses to content height.

## The fix

An opt-in `fullHeightChildren` prop on `CopilotSidebar` that adds a
modifier class to the content wrapper. Two deliberate choices, both from
the review on #4622:

- **Opt-in, not default.** The content wrapper wraps the *entire*
consumer app. Making it a fixed-height flex column for everyone would
reflow apps that never asked for it.
- **A viewport unit, not `height: 100%`.** `100%` only resolves if every
ancestor (`html`/`body`/`#root`) also declares a height — react-ui
neither sets that nor can guarantee it, so `100%` would silently no-op
in a stock Next.js app. `min-height: 0` on the children wrapper clears
the flex-item `min-height: auto` floor so tall content scrolls inside
the child rather than stretching the wrapper past the viewport.

```tsx
<CopilotSidebar fullHeightChildren>
  <div style={{ height: "100%" }}>...</div>
</CopilotSidebar>
```

## Testing

**Unit** — `packages/react-ui/src/css/sidebar-full-height.test.ts` (4
tests), in the repo's existing CSS-contract style. Guards both halves:
the escape hatch's rules, and that the default wrapper stays
auto-height. Also asserts the height is *not* `100%`, since that's the
regression that would make the whole feature a silent no-op.

```
✓ src/css/sidebar-full-height.test.ts (4 tests)
Test Files  9 passed (9)   Tests  58 passed (58)     # full react-ui suite
```
`npx tsc --noEmit` → exit 0. `oxlint` on changed files → 0 warnings, 0
errors.

**Live in Chrome** — the acceptance criterion from the #4622 review: a
stock app where **nothing** declares a height on `html`/`body`/`#root`,
loading the real built `dist/index.css` (not the source CSS), standards
mode, 762px viewport. DOM per `Sidebar.tsx:92` + `Modal.tsx:143`.

| case | child `height:100%` measures |
|---|---|
| default (no opt-in) | **17px** — collapsed, i.e. behavior unchanged
for existing consumers |
| `fullHeightChildren` | **762px** — exactly the viewport |
| `fullHeightChildren`, content 3000px tall | **762px**, scrolls inside
the child (`min-height: 0` holds) |

Also confirmed on the opt-in path: `.copilotKitSidebar` stays `position:
fixed`, and the expanded push-aside `margin-right` is still `448px`
(28rem), so the sidebar's own layout is untouched.

**Docs** — `CopilotSidebar.mdx` is auto-generated from `Sidebar.tsx`;
regenerated via `scripts/docs/gen.ts` and committed only the new
`fullHeightChildren` entry (the generator also surfaces unrelated
pre-existing drift in other reference pages, left out of this PR).

## Not covered

The issue mentions a "works in Safari, not Chrome" symptom. I verified
in Chromium only — the mechanism above is spec behavior rather than a
Chrome quirk, but I haven't measured WebKit.
2026-08-07 11:36:09 -05:00
tylerslaton b40602e698 chore: release monorepo v1.66.4 2026-08-07 01:25:14 +00:00
tylerslaton cfc5cfe727 chore: release monorepo v1.66.3 2026-08-07 00:31:47 +00:00
tylerslaton 289ae4a539 chore: release channels v0.8.0 2026-08-06 09:26:34 -07:00
Murat Sari df8599c8a3 Merge branch 'CopilotKit:main' into feat/height-measurement-cleanup 2026-08-06 13:11:23 +02:00
Murat Sari 4d5e3da712 feat(chat): implement input height measurement and adjust scroll view styles 2026-08-06 12:04:54 +02:00
Ran Shemtov adb2848db7 Merge branch 'main' into claude/jolly-boyd-38b55c 2026-08-06 08:49:55 +02:00
xiaoqinvar ed559fc4c2 fix(core): preserve fresh event run IDs after completed runs 2026-08-06 14:33:32 +08:00
xiaoqinvar 3fce5ecec4 Merge upstream main into fix/state-manager-run-id 2026-08-06 10:54:23 +08:00
David McKay 2d05114160 fix(core): stop HITL continuations reusing the originating run id on the wire (#6411)
Linear:
[CPK-7786](https://linear.app/copilotkit/issue/CPK-7786/hitl-continuation-reuses-the-originating-run-id-on-the-wire-breaking)

Regression fix. **Nothing from #6296 is reverted** — its goal is kept
and moved one layer up.

## What broke

#6296 (@rodboev) preserved the logical run id across a HITL resolve by
pinning the originating id on the follow-up's agent invocation,
correctly fixing #3456 (external tracing saw one logical run split into
two halves).

Pinning it **on the wire**, though, made the transport treat the
follow-up as a resumption of a run it had already finished:

- it re-delivered that run's already-applied half, duplicating every
tool call on the message — each duplicate carrying **empty arguments**,
because a start event has none and the `TOOL_CALL_ARGS` deltas that
follow are addressed to the first copy; and
- the follow-up's own tool call never reached client state, so its card
never rendered.

In `reskinnable-demo`'s banking skin that killed teach mode: the agent
called `awaitDashboardDemonstration`, the server emitted
`TOOL_CALL_START` for it, and the live "Recording your workflow" card
never appeared — no REC indicator, no step feed, no "I'm done", so a
demonstration could not be finished or saved.

## Evidence

- Intelligence event log shows **two `RUN_STARTED`/`RUN_FINISHED` cycles
under one run id**, with `TOOL_CALL_START [awaitDashboardDemonstration]`
in the second — the server does emit it.
- Instrumented `useRenderToolCall`: **never invoked** for that tool,
despite its renderer being registered.
- Client message state after the click: `assistant
tc:["recall_memory","recall_memory"]`, `assistant
tc:["offerWorkflowRecording","offerWorkflowRecording"]` — prior cycle
re-applied, new call absent.
- Bisect: reverting #6296's four source files makes the card appear;
restoring them breaks it again. No later commit touches those files.
- Ruled out: React StrictMode (disabled in that app),
model/prompt/tool-availability differences (byte-identical between the
working and broken apps), and the view-layer dedupe from #6407.

## The fix

`markNextRunAsContinuation` already accepted an `expectedRunId` that was
never used. It now records it, and the state manager re-stamps the
continuation's events onto that id. So:

- **logical identity is preserved** — state/message association and
external tracing still see one run, which is #6296's whole point;
- **the wire identifies the invocation honestly** — the follow-up no
longer claims to be a run that already finished, so nothing is
re-delivered and the continuation's own tool call lands.

## Test changes — please review this part closely

`core-follow-up`'s run-id test asserted the *mechanism* (both
invocations carry the same wire id), which this deliberately changes. It
now asserts the *goal*: the originating id is pinned on the first
invocation, and the follow-up leaves the id to the transport. Its
sibling assertion — the thread still knows exactly one run — was already
there and passes untouched.

A new `StateManager` test covers the re-stamp directly, verified **red
before green** by dropping the `expectedRunId` lookup (it fails, along
with one of #6296's own tests).

@rodboev — flagging you directly since this touches your change from
today. If the wire-level pinning was load-bearing for something I have
not seen, say so and I will rework it.

## Verification

- `@copilotkit/core` 58 files and `@copilotkit/react-core` 123 files
pass.
- In-browser against a live Intelligence stack: before, the recording
card never rendered; after, it renders with its REC indicator and I'm
done / Cancel controls.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-08-05 16:36:41 -07:00
David McKay 6188404f3a test(react-core): assert the legacy HITL follow-up goal, not the wire id
The sibling of the core-follow-up assertion, missed in the previous commit: it
required the follow-up invocation to repeat the originating run id on the wire,
which is exactly what this change stops doing.

It now asserts the follow-up happened and left the id to the transport. Logical
identity is covered where it now lives — StateManager's re-stamp test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 16:24:28 -07:00
David McKay 696c44244b fix(core): stop HITL continuations reusing the originating run id on the wire
#6296 preserved the logical run id across a HITL resolve by pinning the
originating id on the follow-up's agent invocation. That fixed #3456 (external
tracing saw one logical run split into two halves), but pinning it on the WIRE
made the transport treat the follow-up as a resumption of a run it had already
finished. It re-delivered that run's already-applied half — duplicating every
tool call on the message, each duplicate carrying empty arguments, since a start
event has none and the TOOL_CALL_ARGS deltas that follow are addressed to the
first copy — and the follow-up's own tool call never reached client state, so
its card never rendered.

In the reskinnable-demo banking skin that broke teach mode outright: the agent
called awaitDashboardDemonstration, the server emitted TOOL_CALL_START for it,
and the live "Recording your workflow" card never appeared, leaving no way to
finish or save the demonstration.

#6296's goal is kept, moved one layer up. The continuation is registered against
the originating id (markNextRunAsContinuation already took an expectedRunId
parameter, previously unused) and the state manager re-stamps the continuation's
events onto it. State/message association and external tracing still see ONE
logical run; the wire is simply allowed to identify the invocation honestly.
Nothing from #6296 is reverted.

core-follow-up's run-id test asserted the mechanism (both invocations carry the
same wire id), which this deliberately changes, so it now asserts the goal: the
originating id is pinned on the first invocation and the follow-up leaves it to
the transport. Its sibling assertion — the thread still knows exactly one run —
was already there and still passes untouched. A new StateManager test covers the
re-stamp directly; verified red before green by dropping the expectedRunId
lookup.

Verified in the browser against a live Intelligence stack: before, the recording
card never rendered; after, it renders with its REC indicator and I'm done /
Cancel controls. `@copilotkit/core` 58 files and `@copilotkit/react-core` 123
files pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 16:12:12 -07:00
Tyler Slaton 0cf899095b fix(channels): take the legacy path when a dropped stream start settles as applied
The gateway settles a direct-message slack.stream.start provider failure
as applied with capabilityError and no provider reference. Parse the
start result inside the fallback try so that shape reaches the legacy
create instead of hard-failing the stream body.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 15:44:06 -07:00
Alem Tuzlak 85d30dd327 fix(channels): keep managed Slack DM replies visible 2026-08-05 15:44:06 -07:00
Benjamin Taylor a3c5f079ed fix(react-ui): let sidebar children fill the viewport height
Children of `CopilotSidebar` cannot use `height: 100%`. Both wrappers the
sidebar puts around your app -- `.copilotKitSidebarContentWrapper` and
`.copilotKitModalChildrenWrapper` (which had no CSS rule at all) -- are
auto-height blocks, so a percentage height on a child has no definite
containing block and collapses to content height.

Add an opt-in `fullHeightChildren` prop that gives the content wrapper a
one-viewport height and lets the children wrapper fill it. It is opt-in
because the content wrapper wraps the entire consumer app, and giving
every react-ui sidebar user a flex column with a fixed height would
reflow apps that never asked for it.

The height is a viewport unit, not `100%`: `100%` only resolves when
every ancestor (html/body/#root) also declares a height, which react-ui
neither sets nor can guarantee, so it would silently no-op in a stock
Next.js app. `min-height: 0` on the children wrapper clears the flex-item
`min-height: auto` floor so tall content scrolls inside the child instead
of stretching the wrapper past the viewport.

Fixes #261

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Ashish Shaw <77574570+ashish4143@users.noreply.github.com>
2026-08-05 16:54:47 -05:00
Adrien Pouligny add2a80fd8 Merge branch 'main' into fix/runtime-deprecated-uuid-dependency 2026-08-05 14:39:55 -07:00
Ben Taylor ca9a481efd fix(web-inspector): show step lifecycle events (#6323)
## What does this PR do?

The Web Inspector did not record `STEP_STARTED` or `STEP_FINISHED` from
live agent events.

This PR:

- records both step lifecycle events
- adds both events to the Inspector filter
- adds a regression test

## Related PRs and Issues

- Fixes #6324

## Testing

- `pnpm nx test @copilotkit/web-inspector --skip-nx-cache`
- `pnpm nx run @copilotkit/web-inspector:check-types --skip-nx-cache`
- `pnpm nx run @copilotkit/web-inspector:build --skip-nx-cache`
- repository pre-commit checks

## Checklist

- [x] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [x] No documentation update is needed because this fixes existing
behavior without changing the public API
- [x] "Allow edits by maintainers" is checked
2026-08-05 16:34:06 -05:00
David McKay d4d10409f2 fix(react-core): collapse duplicate tool-call ids in the message view (#6407)
Follow-up to #6404. That PR stopped the *duplicate write* from a
resurrected approval card; this one removes the duplicate card itself.

## Symptom

Approving a HITL card in the banking skin left a **second, blank copy**
of the same card in the transcript — "Open policy exception" with no
transaction id and no code — plus a React warning:

```
Encountered two children with the same key, call_QHa801k5PL32WtPKsPItO1pI
```

## Root cause

`@ag-ui/client`'s `TOOL_CALL_START` handler appends to the parent
assistant message's `toolCalls` with no check for an existing entry with
that id:

```js
message.toolCalls ??= [];
message.toolCalls.push({ id, type: "function", function: { name, arguments: "" } });
```

So when a start event is applied twice — which the HITL flow triggers
when the run syncs after `respond()` — the message carries the same call
twice. The second copy has **empty `arguments`**, because a start event
carries none; the args arrive afterwards as `TOOL_CALL_ARGS` deltas
addressed to the first copy. That empty copy is what rendered as the
blank card, and `CopilotChatToolCallsView` uses the call id as its
render key, hence the warning.

Evidence gathered while diagnosing:

- Instrumented the message view: the offending assistant message ends up
with `toolCalls: ["call_X","call_X"]`, and the React warning fires on
the same id in the same tick.
- Queried a local Intelligence stack: the server emits **exactly one**
`TOOL_CALL_START` for that id (one `START`, one `END`, one `RESULT`). So
this is client-side state, not a stream defect.
- React StrictMode is **not** involved — the demo sets `reactStrictMode:
false`.

## Fix

Extends the existing `deduplicateMessages()` — which already collapses
duplicate *message* ids arriving from streaming re-delivery — to also
collapse duplicate *call* ids within a message, preferring whichever
copy actually carries arguments.

Applied outside the merge branch too, because the duplicate also lands
on a message that was never itself duplicated (observed `rawMsgs=5
dedupMsgs=5` with the duplicate still present). Returns the original
array untouched when there is nothing to collapse, so memoized consumers
don't re-render needlessly.

## Scope / known limitation

This fixes what renders. The underlying agent state still holds the
duplicate entry, so a fully correct fix also wants an idempotency guard
in the AG-UI start handler (upstream). Filed separately — flagging here
so the remaining gap is explicit rather than implied-fixed.

The precise reason a single start event gets applied twice is also still
open; the fix is deliberately robust to re-application whatever the
trigger.

## Verification

- 5 regression tests added, **verified red before green** (neutered the
fix → 3 failed with "Expected 1, Received 2"; restored → pass).
- Full `@copilotkit/react-core` suite: **1480 passed / 123 files**.
- `test`, `publint`, `attw` green for react-core and its dependents.
- In-browser against a live Intelligence stack: before, approving left a
blank second card + 3 key warnings; after, **one card and zero
warnings**.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-08-05 14:22:23 -07:00
Ben Taylor 9a86e21d48 feat(runtime): split Channel status into transport and provider legs (refs OSS-739) (#6360)
**Half 2 of 2 for OSS-739.** Gateway half ships first:
CopilotKit/Intelligence#746.

## Why

`status().overall === "online"` proved only that the runtime reached the
Gateway with a valid project API key. It said nothing about whether a
Slack/Teams app was bound to the Channel, so **a Channel with no
provider at all reported `online`** — and every version of our Channels
onboarding guidance used that value to certify end-to-end success.

`setup_required` had **no producer**. The manager set it only when the
activation engine threw `SETUP_REQUIRED`, and the engine stopped doing
that at the 2026-07-29 realtime-boundary cutover (`8f166577ce`). In
published `@copilotkit/channels-intelligence@0.7.0` the string survives
in exactly one file — a shipped *test*. The 15 doc comments describing
the state outlived the mechanism, which is why nobody noticed for a
week.

## Change

- `connectRealtimeGateway` captures the control join reply (it was
**discarded**) and exposes `providerStates()`. Phoenix's `Push.resend`
preserves `recHooks`, so the hook re-fires on every auto-rejoin — a
Channel provisioned while the runtime was disconnected is picked up with
no extra plumbing.
- The launcher and the manager's handle view delegate it as a
**getter**, not a captured snapshot, for that same reason.
- `status()` gains `detail`, reporting `transport` and `provider`
separately so a caller can assert the leg it cares about:

```ts
status() → {
  overall: "setup_required",
  channels: { support: "setup_required" },
  detail: { support: { status: "setup_required", transport: "online", provider: "not_attached" } },
}
```

`channels` keeps its shape — turning its values into objects would break
the CLI's `channels-report` and the starter channel-host — but its
values are now the fold of the two legs, which is what makes `overall`
honest.

- The stale `setup_required` doc comments are corrected, with a note not
to describe the state again without a path that can emit it.

## Back-compat: `unknown` is load-bearing

An older Gateway, a Gateway whose lookup failed, a handle without the
seam, a Channel the Gateway did not mention, an unrecognised state, and
a throwing getter **all** yield `unknown`, which keeps the
transport-derived status — exactly today's behaviour. Only a *positively
reported* absence downgrades a Channel, so no existing deployment turns
amber on upgrade.

The **41 pre-existing channel-manager tests pass unchanged**, which is
that guarantee.

## Testing

- `channel-manager-provider-leg.test.ts` — 14, incl. the regression test
that never existed ("reports setup_required for a joined Channel with no
provider attached") and one case per degradation path
- `realtime-gateway-provider-states.test.ts` — 8 parser cases
- `realtime-gateway.test.ts` — +2 proving the wiring end-to-end through
real Phoenix framing, not just the parser
- Full suites: runtime **1874/1874**, channels-intelligence **192/192**;
`check-types` and `build` clean for both packages

**I mutation-tested the fold** — neutralising it fails 5 tests including
the linchpin — so these assert behaviour rather than passing vacuously.

Worth noting: two type errors (`ChannelsHandle` in `runtime.ts`, a
session mock) were invisible to vitest, which transpiles without
typechecking. The pre-commit build gate caught them.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-08-05 16:06:40 -05:00
David McKay eee580f6da fix(react-core): collapse duplicate tool-call ids in the message view
AG-UI's TOOL_CALL_START handler appends to the parent assistant message's
`toolCalls` without checking whether an entry with that id is already present,
so whenever a start event is applied twice — which the human-in-the-loop flow
triggers when the run syncs after `respond()` — the message carries the same
call twice. The second copy has EMPTY arguments, because a start event carries
none; the args arrive afterwards as TOOL_CALL_ARGS deltas addressed to the first.

Rendering both produced a phantom duplicate card in the transcript (in the
banking skin: a second "Open policy exception" with no transaction id or code)
plus a React "Encountered two children with the same key" warning, since the
call id is the render key in CopilotChatToolCallsView.

Verified against a local Intelligence stack that the server emits exactly ONE
TOOL_CALL_START for the affected id, so this is client-side state, not a stream
defect. React StrictMode is not involved (the demo disables it).

Extends the existing deduplicateMessages() — which already collapses duplicate
message ids from streaming re-delivery — to also collapse duplicate call ids
within a message, preferring whichever copy actually carries arguments. Applied
outside the merge branch too, because the duplicate also lands on a message that
was never itself duplicated. Returns the original array when there is nothing to
collapse, so memoized consumers do not re-render needlessly.

Does not change the underlying agent state, which still holds the duplicate;
that needs an idempotency guard in the AG-UI start handler.

5 regression tests, verified red before green. Full react-core suite: 1480 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 14:00:54 -07:00
Ben Taylor 743351cb3f fix(runtime): initialize arrays before /- append in AGUISendStateDelta (#6293)
## Summary

`AGUISendStateDelta` can emit an array append against a state where the
target array has not been initialized. The emitted patch then fails
during event compaction with `OPERATION_PATH_CANNOT_ADD`.

This change keeps the authoritative state represented in emitted events,
initializes a missing array immediately before its first `/-` append,
and applies the same contract across the generic, AI SDK, and TanStack
state-delta paths. Existing arrays and valid deltas retain their current
contents and operation order.

Closes https://github.com/CopilotKit/CopilotKit/issues/5998

## Changes

- Emit the input state before the first delta when no earlier state
event represents it
- Initialize a missing array before normalized classic, AI SDK, and
TanStack state deltas append through `/-`; custom raw-event mode remains
outside this normalizer
- Preserve populated arrays and already-valid patch operations
- Preserve caller-owned state when structured cloning falls back
- Cover missing-array reconstruction, sibling converters, failure
guards, and existing-array preservation
- Follow the current `release.config.json` and Nx release convention; no
Changeset file is added.

## Test plan

- [x] `pnpm -C packages/runtime exec vitest run
src/agent/__tests__/state-tools.test.ts
src/agent/__tests__/converter-aisdk.test.ts
src/agent/__tests__/converter-tanstack.test.ts`
- [x] `pnpm -C packages/runtime exec vitest run`
- [x] `pnpm exec nx run @copilotkit/runtime:check-types`
- [x] `pnpm exec oxfmt --check` on changed runtime files
- [x] `pnpm exec oxlint` on changed runtime files, zero errors with two
pre-existing no-shadow warnings
- [x] Verify no `.changeset` file is added because current main uses Nx
release
- [x] Verify the final diff contains only the private helper, runtime
implementation, sibling converters, and focused tests
2026-08-05 15:40:21 -05:00
Ben Taylor a3d0d2bfab fix(runtime): reject unenforceable mcpApps tool policy instead of silently ignoring it (#6292)
## Summary

`mcpApps.servers` entries that carry `includeTools` or `excludeTools`
are currently accepted even though the pinned
`@ag-ui/mcp-apps-middleware` package has no option for them. The runtime
then ignores the keys, so tools an operator intended to restrict remain
available. This change rejects that configuration instead of allowing a
silent no-op.

## What CopilotKit owns

- `mcpApps.servers` configuration and `agentId` scoping.
- Projection of selected servers into `MCPAppsMiddleware`.
- Reporting unsupported configuration before middleware construction.

Discovery, model-emitted tool execution, frontend-proxied execution,
server identity, and tool provenance belong to
`@ag-ui/mcp-apps-middleware`.

## Changes

- Extract the server projection into `resolveMcpAppsServers`, which
scans all configured entries for defined policy keys, filters by
`agentId`, strips only `agentId`, and forwards other fields unchanged.
- Return a configuration error naming the unsupported key, server,
pinned middleware version, owning package, and issue when a policy key
is supplied.
- Add tests for agent scoping, field forwarding, malformed and empty
values, undefined spread values, constructor avoidance, and the existing
HTTP error path.
- Document the ownership boundary and add a runtime changeset.

## Why the filter stays external

The pinned package is version `0.0.3`. It owns the private server maps,
UI-tool discovery, model-emitted execution, and frontend proxy
execution. A CopilotKit middleware could observe only one of those paths
and would have to duplicate private server identity and tool provenance.
The complete `includeTools` and `excludeTools` implementation belongs in
the external package, where one predicate can cover discovery and both
execution paths.

## Current behavior

Plain JavaScript or JSON configuration can supply `excludeTools:
["delete_account"]` without a TypeScript excess-property check. The
runtime currently accepts the configuration, constructs
`MCPAppsMiddleware`, and leaves the tool available. The new behavior
returns an HTTP 500 through the existing runtime error path, names the
unsupported key and dependency, and does not construct the middleware.

## Follow-up

The counterpart change in `@ag-ui/mcp-apps-middleware` should add the
fields to the per-server configuration, preserve absent versus empty
include lists, resolve server identity through its existing maps, and
apply one predicate after UI-resource discovery and before model-emitted
and proxied tool execution. Once that version is released, CopilotKit
can remove the rejection and pass the fields through unchanged.

## Related issue

Refs #5930.

The cross-repository ownership split follows the proposal in
https://github.com/CopilotKit/CopilotKit/issues/5930#issuecomment-5128722524.
This PR does not close the issue.

## Test plan

- [x] `pnpm -C packages/runtime exec vitest run
src/v2/runtime/__tests__/mcp-apps-servers.test.ts
src/v2/runtime/__tests__/mcp-apps-middleware-integration.test.ts`
passed, 2 files and 20 tests
- [x] `pnpm -C packages/runtime exec vitest run` passed, 129 files and
1,836 tests
- [x] `pnpm exec nx run @copilotkit/runtime:check-types` passed
- [x] `pnpm exec oxlint` and `pnpm exec oxfmt --check` passed on changed
TypeScript files
- [x] `pnpm check:plugin-skills` passed
- [ ] `CI green for static / quality and test / unit on Node 20, 22, and
24`
2026-08-05 15:29:37 -05:00
Benjamin Taylor 9280e71346 refactor(channels): trim redundant provider-leg tests and fix a wrong comment
Self-review of the previous commit.

Corrects a factual error I introduced: the `SETUP_REQUIRED` note claimed such a
Channel "has no transport at all (the launcher never returned a handle)". False
for the `hasDirectAdapter` branch, which starts the developer-owned transport and
assigns a synthetic handle — so there IS a running transport there. Dropped the
wrong reasoning and shortened the note to the part that holds: the misattribution
is cosmetic on a path with no producer, and a future producer should report
through the `providerStates` seam.

Removes three tests that did not earn their place:

- "calls the seam ON the session" — redundant. `ProviderStateGateway.providerStates`
  reads `this`, so the two remaining tests already fail if the launcher ever used
  a detached reference. It died on the same mutation as the first test, for the
  same reason.
- "omits providerStates for a session without the seam" — survived the mutation
  that removes the forward, so it guarded nothing.
- the channel-level-error rejoin case — same `Push.resend` hook as the transport
  drop, so it re-proved one mechanism at ~1s extra wall-clock. Kept the drop
  case: it asserts a genuinely fresh socket, which is the "provisioned while the
  runtime was disconnected" story the design claim is about.
- "keeps the last reported states while a rejoin has not yet succeeded" — pinned
  behaviour with no observable consequence, since the transport leg dominates the
  fold while offline.

Also trims the drift-guard comment: why a guard was NOT added belongs in the PR
discussion, not permanently in source.

Re-mutation-tested after trimming: removing the forward kills both remaining seam
tests; making `providerStates` a snapshot kills the rejoin test while the other
41 gateway tests pass.

Verified: channels-intelligence 195/195, runtime 1874/1874, build + oxfmt clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 15:27:46 -05:00
Benjamin Taylor c71d9ac58b fix(channels): forward the provider seam through the exported launcher helper
Addresses review on #6360.

`startChannelsWithGatewayControl` is public so callers can compose over a
session they manage themselves, but it forwarded only `onClose` and
`onStateChange` — not `providerStates`. A handle without the provider seam makes
`ChannelManager.providerLeg` fall back to `unknown`, which keeps the
transport-derived status and reports `online` for a Channel with no provider
bound: the exact false green OSS-739 removes, still reachable through a public
export. The guard is widened too, so a session exposing only `providerStates`
is no longer dropped on the fall-through path.

Tests the reconnect claim that makes `providerStates` a getter rather than a
snapshot. Nothing exercised it: the gateway tests covered only the initial join
reply, and the manager-side rejoin test proves the manager re-reads on each
`status()` call, not that the session's value ever changes. The fake socket's
join reply can now vary per join, so a drop -> rejoin carrying a different
`channels` map asserts the refresh over real Phoenix framing — via both rejoin
paths (channel-level error on a live socket, and a full transport drop onto a
fresh socket), plus the case where a rejoin has not yet succeeded and the last
known states must persist.

Mutation-tested both: removing the forward kills 3 of 4 seam tests, and making
`providerStates` a captured snapshot kills both rejoin tests while all 3
pre-existing provider-state tests still pass — which is the gap itself.

Docs corrected against their real mechanisms:

- `attached`/`unhealthy`/`not_attached` now state the gateway's actual rule
  (adapter `status == "active"` is part of the predicate; a configured adapter in
  `error` is `unhealthy` with no failed health check), plus the best-of adapter
  fold that keeps a Slack-only Channel `attached`.
- `ready()` no longer promises it rejects on `error`. It awaits activation, so
  it can resolve while `status().overall === "error"` from an `unhealthy`
  provider. Says that instead.
- Notes the legacy `SETUP_REQUIRED` path reports a provider condition on the
  transport leg (dead, cosmetic, left rather than guessed at), and why the
  provider-state set is duplicated across the duck-typed package seam.

Verified: channels-intelligence 199/199, runtime 1874/1874, both builds clean,
oxfmt/oxlint clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 15:16:31 -05:00
Ran Shemtov 701b03ab96 Merge branch 'main' into claude/jolly-boyd-38b55c 2026-08-05 21:47:52 +02:00
Rod Boev 3a9eda8441 fix(runtime): redact sensitive headers from runtime error context 2026-08-05 15:17:25 -04:00
Ben Taylor 26d352f898 fix(channels): default welcome agent prompt (#6377)
## What does this PR do?

- Gives `thread.runAgent()` inside `onWelcome` the default prompt
`"Introduce yourself to the channel!"`.
- Keeps an explicit `runAgent({ prompt })` ahead of the default.
- Adds regression coverage for the default and override paths.

Welcome deliveries have no inbound message. Before this change,
`thread.runAgent()` passed an empty message list to the AI SDK, which
rejected the run before it reached the model.

## Related PRs and Issues

- No linked issue.

## Testing

- `pnpm nx test @copilotkit/channels-core -- --run src/welcome.test.ts`
- `pnpm nx run-many -t test,check-types,build
--projects=@copilotkit/channels-core`
- `pnpm run lint`
- `pnpm run check-format`
- `pnpm nx run-many -t publint,attw
--projects=@copilotkit/channels-core`

## 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
- [x] Maintainers can edit this same-repository branch; GitHub only
shows the "Allow edits by maintainers" control for forks
2026-08-05 14:02:50 -05:00
Austin Merrick ab7a50fa10 feat(inspector): add agent-ready Rich Threads setup 2026-08-05 11:55:53 -07:00
Austin Merrick ec101367c7 test(runtime): isolate metadata fetch assertions 2026-08-05 11:55:52 -07:00
Austin Merrick b3e1586330 fix(web-inspector): link Rich Threads setup 2026-08-05 11:55:52 -07:00
Austin Merrick 5ebba2b098 feat(web-inspector): warn before thread limits 2026-08-05 11:55:52 -07:00
Austin Merrick ec510e2521 chore(web-inspector): keep scenario lab internal 2026-08-05 11:55:51 -07:00
Austin Merrick 0982e96358 fix(web-inspector): correct thread empty states 2026-08-05 11:55:51 -07:00
Austin Merrick 9b604378aa test(channels): update fetch mocks for current types 2026-08-05 11:55:50 -07:00
Austin Merrick 7c3d043905 fix(web-inspector): use organization billing lab link 2026-08-05 11:55:50 -07:00
Austin Merrick de3ca81721 docs(inspector): document shipped usage states 2026-08-05 11:55:50 -07:00
Austin Merrick 0f3e2bf8ce docs(inspector): explain the thread state lab 2026-08-05 11:55:49 -07:00
Austin Merrick 98bf62143e test(web-inspector): add thread state lab 2026-08-05 11:55:49 -07:00