With per-thread agent clones, the registry agent has empty messages.
reloadSuggestions was checking messageCount on the registry agent, so
dynamic suggestions (which require messageCount > 0) never fired after
the first message. Fix by passing the thread clone (with actual messages)
through reloadSuggestions → generateSuggestions.
Also updates the demo to use claude-3-7-sonnet (extended thinking support)
when ANTHROPIC_API_KEY is set.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add afterEach with vi.clearAllTimers() to prevent the 300ms setTimeout in
WebInspectorElement (src/index.ts:2065) from firing after the jsdom environment
tears down, which caused intermittent "document is not defined" errors in CI.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- StateManager now uses composite `agentId:threadId` subscription keys so
per-thread clones get independent subscriptions without stomping the
registry agent's subscription.
- Added `activeRun` map so `handleNewMessage` (called without `input` by
ag-ui's addMessage path) can still associate messages with the correct run.
- Added `revoked` flag + `subRunId`/`runFinished` run-isolation logic to
handle the timing race where a new run's events arrive through the previous
pipeline before `subscribeAgentToStateManager` has set up a new one —
detected via `runFinished && input.runId === subRunId`, resolved by minting
a fresh UUID so both runs get distinct state entries.
- Empty state `{}` is no longer persisted; `getStateByRun` returns `undefined`
so renderers can distinguish "no snapshot received yet" from a real state.
- `useAgent`: per-thread clones are shared via a module-level WeakMap so all
components on the same (agentId, threadId) see the same instance.
- Tests updated to cover sequential-run state isolation and the empty-state
semantics change.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After the V1/V2 consolidation merge, several changesets still referenced
old @copilotkitnext/* package names that no longer exist. Fixed package
references and ran changeset version to apply pending version bumps.
https://claude.ai/code/session_01A5aahBWFTmLPR3xZSeh4SD
Flatten all packages from packages/v1/* and packages/v2/* into packages/* —
every package now lives directly under the @copilotkit/ scope with no v1/v2
subdirectories.
- Move all v1 packages (react-core, react-ui, runtime, shared, etc.) from
packages/v1/* to packages/*
- Absorb v2 react code into packages/react-core/src/v2/ (exported via /v2 subpath)
- Absorb v2 agent code into packages/runtime/src/agent/ (exported via /v2 subpath)
- Move v2 packages (core, angular, demo-agents, etc.) to packages/*
- Replace all @copilotkitnext/* imports with @copilotkit/* equivalents
- Keep @copilotkitnext/angular as the sole exception (angular remains on next)
- Update CI workflows, renovate config, release scripts for flat structure
- No public API surface changes — all exports fields are preserved
Co-authored-by: Alem Tuzlak <t.zlak@hotmail.com>
Signed-off-by: Tyler Slaton <tyler@copilotkit.ai>
- Fix 1: store {source, clone} in threadAgentCache to invalidate stale
clones when registry agent is replaced (reconnect/hot-reload)
- Fix 2: add lastConnectedAgentRef in V1 wrapper to prevent connectAgent
firing on every Connected render; ref reset in cleanup for StrictMode
- Fix 3: throw if clone() returns same instance to prevent silent state
destruction via setMessages([]) / setState({}) on the source agent
- Fix 4: clear bare agentId key from provisionalAgentCache on threadId
transition to eliminate phantom provisionals
- Fix 5: clear threadAgentCache on unmount to release clones holding
listeners, WebSocket connections, or timers
- Fix 6: move HttpAgent headers sync out of useMemo into useEffect to
avoid side effects in concurrent-mode pure computation
Tests: T-1 registry replacement, T-2 threadId switching, T-3 provisional
→ real transition, T-4 V1 threadId passthrough, T-5 non-vacuous isolation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
## Summary
- `{ ...defaultComponents, ...components }` in `Markdown.tsx` created a
new object reference on every render
- `MemoizedReactMarkdown` has a custom memo comparator checking
`prevProps.components === nextProps.components` — referential equality
always failed, so every prior message re-rendered on every streaming
chunk
- Fix: wrap the merge in `useMemo([components])` so the reference is
stable
Fixes CPK-7156 / closes#2543
## Test plan
- [x] Verified locally with the form-filling example: completed messages
no longer log new renders while a new message streams
- [x] `nx build @copilotkit/react-ui` passes
- [x] `nx test @copilotkit/react-ui` passes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
{ ...defaultComponents, ...components } created a new object reference on
every render, causing MemoizedReactMarkdown's custom comparator to always
fail and re-render every prior message on each new streaming chunk.
Wrapping the merge in useMemo([components]) gives a stable reference so
the memo comparator works as intended.
Fixes CPK-7156
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(runtime-v2): wire createLicenseChecker into CopilotRuntime
feat(runtime-v2): wire license checks into route handlers
feat(runtime-v1): pass licenseToken through to v2 runtime
When resolve() is called, the interrupt event value is now included
alongside the user's response in forwardedProps.command.interruptEvent.
This enables agent adapters (e.g. Mastra) to round-trip interrupt
context (toolCallId, runId, type) without storing state between runs.
Uses a ref to avoid changing the resolve callback's dependency array,
preventing double-invocation of the handler effect.
The LangGraph adapter is unaffected — it ignores the extra field.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Multiple CopilotChat components with the same agentId but different
threadIds were sharing a single agent instance, causing message state
to bleed between them. The useAgent hook now creates per-thread clones
keyed by agentId:threadId, keeping messages and state isolated.
Closes#2957
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Summary
`<CopilotSidebar defaultOpen={false} />` was opening anyway because the
prop was not being forwarded through the provider chain. This PR fixes
that - and also addresses the architectural trade-off flagged in review.
### Who is affected
**User A** just uses `<CopilotSidebar />` with no outer provider
Before the fix, their sidebar opened by default. After the fix, it still
opens by default. Nothing changed for them.
**User B** uses `<CopilotSidebar defaultOpen={false} />`
Before: broken (opened anyway). After: works.
**User C** wraps their app in a bare `CopilotChatConfigurationProvider`
and reads `isModalOpen` from it.
Before our fix `isModalOpen` was stuck at true forever. After: it
reflects the sidebar's state.
### What changed
- The `defaultOpen` prop is forwarded correctly through the provider
chain to both `CopilotSidebar` and `CopilotPopup`.
- Outer `useCopilotChatConfiguration()` hooks now reflect sidebar state.
Fixed via bidirectional state sync between nested providers.
### Files changed
- `packages/v2/react/src/components/chat/CopilotChat.tsx` - accept and
forward `isModalDefaultOpen`
- `packages/v2/react/src/components/chat/CopilotSidebar.tsx` - pass
`isModalDefaultOpen={defaultOpen}`
- `packages/v2/react/src/components/chat/CopilotPopup.tsx` - same
- `packages/v2/react/src/providers/CopilotChatConfigurationProvider.tsx`
- fix resolution logic + bidirectional sync
-
`packages/v2/react/src/providers/__tests__/CopilotChatConfigurationProvider.test.tsx`
- add tests
### Test plan
- [x] 843 tests pass (including 4 new bidirectional sync tests)
- [x] New tests cover all three reproduction scenarios from
[tkt-modal-default-open](https://github.com/CopilotKit/deep-agent-cpk-experiments/tree/main/app/client/src/tickets/tkt-modal-default-open)
**Linear:**
[CPK-7152](https://linear.app/copilotkit/issue/CPK-7152/fix-defaultopenfalse-prop-ignored-in-copilotsidebar)
**GitHub Issue:** #3475
**Related PRs:** #3477, #3496 (stalled community PRs addressing the same
issue)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
PR #3513 fixed Behavior A (defaultOpen={false} now respected) but broke
Behavior B: users who read isModalOpen from an outer
CopilotChatConfigurationProvider no longer see state changes originating
in the inner provider (e.g. CopilotSidebarView's provider).
Root cause: with hasExplicitDefault=true, the inner provider used its own
setter exclusively, so open/close events never reached ancestor providers.
Fix: when a provider owns its state (hasExplicitDefault=true), wrap
setModalOpen to also call parentConfig.setModalOpen — propagating changes
upward through the provider chain. Add a useEffect to sync parent→child
when an ancestor's state is changed externally (e.g. via an outer hook).
The mount-skip guard (isMounted ref) ensures the child's own
isModalDefaultOpen is honoured on first render and not overwritten.
Three new tests mirror the reproduction scenarios from:
https://github.com/CopilotKit/deep-agent-cpk-experiments/tree/main/app/client/src/tickets/tkt-modal-default-open
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ag-ui/client 0.0.47 had a bug where connectAgent() hung forever
for agents that don't implement connect(), causing detachActiveRun()
and runAgent() to deadlock. Fixed in 0.0.48.
## Summary
Fixes a race condition where `detachActiveRun()` was called without
`await`, allowing a new run to overwrite pipeline state before cleanup
finished — silently dropping the previous run.
- **`run-handler.ts`**: `void agent.detachActiveRun()` → `await
agent.detachActiveRun()`. The original deadlock that motivated
fire-and-forget (ConnectNotImplementedError cleanup) has been resolved
in the current `@ag-ui/client`. Confirmed via git history: added as
fire-and-forget in `5b3037a` on Mar 13 by Max Korp.
- **`use-copilot-chat_internal.ts`**: Added AbortController cleanup in
the `connectAgent` useEffect, mirroring the V2 `CopilotChat.tsx`
pattern. Guards state updates against aborted signals.
- Updated test mocks to include `detachActiveRun`
**Linear:**
[CPK-7151](https://linear.app/copilotkit/issue/CPK-7151/fix-race-condition-in-runhandlerrunagent-causes-dropped-runs)
**GitHub Issue:** #3482
## Files changed
- `packages/v2/core/src/core/run-handler.ts`
- `packages/v1/react-core/src/hooks/use-copilot-chat_internal.ts`
-
`packages/v1/react-core/src/hooks/__tests__/use-copilot-chat-internal-connect.test.tsx`
-
`packages/v1/react-core/src/hooks/__tests__/use-coagent-state-render.e2e.test.tsx`
## Test plan
- [x] All tests pass (pre-commit hook ran full suite)
- [x] Manual: rapid sequential message sends don't drop runs
- [x] Manual: verify no deadlock on initial connectAgent → immediate
runAgent
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Remove -mt-16 from input container so the scrollbar is not clipped.
Drop FEATHER_HEIGHT and inputContainerHeight from content padding
since the input is now a flex sibling, not an absolute overlay.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The input container was absolutely positioned on top of the scroll area,
causing the custom scrollbar to get clipped at the bottom. Changed to
flex column layout with negative margin to preserve the visual overlap
where messages fade behind the input.
Still needs tuning — bottom spacing varies by viewport. Pushing for
review to get input on the layout approach.
Resolves CPK-7153
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace overflow-y-scroll with overflow-y-auto so the scrollbar only
appears when content overflows. Add minimal custom scrollbar CSS scoped
to [data-copilotkit] for a thin, rounded thumb with no gutter.
Resolves CPK-7153
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a user clicks Stop (or calls agent.abortRun()) while a frontend tool
handler is executing, processAgentResult would unconditionally start a new
runAgent() call — restarting the run the user just stopped.
Changes:
- Add AbortController to RunHandler, created per top-level runAgent() call
- stopAgent() now aborts the controller before calling agent.abortRun()
- agent.abortRun() is intercepted during a run to also abort the controller
- processAgentResult checks the abort signal before starting follow-up runs
- Tool handlers now receive an optional AbortSignal in their context for
cooperative cancellation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Await detachActiveRun() in run-handler.ts instead of fire-and-forget.
The original deadlock that motivated void has been resolved in the
current @ag-ui/client — ConnectNotImplementedError cleanup now
completes reliably.
Add AbortController cleanup in use-copilot-chat_internal.ts to match
the V2 CopilotChat.tsx pattern, preventing stale state updates and
ensuring proper teardown on unmount/re-render.
Resolves CPK-7151
Closes#3482
Co-Authored-By: Claude <noreply@anthropic.com>
Forward isModalDefaultOpen through CopilotChat into the configuration
provider. Fix nested provider resolution so explicit isModalDefaultOpen
overrides the parent's value instead of being ignored.
Resolves CPK-7152
Closes#3475
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When using the children render prop pattern, containerRef was never
passed to the render function. This caused CopilotChatView's
ResizeObserver to never measure the input height, resulting in
insufficient paddingBottom and input overlapping chat messages.
Closes#3500
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add MCPClientProvider interface and mcpClients config field to
BuiltInAgent, allowing users to provide pre-created MCP clients
whose tools are merged into the agent's tool set without per-request
lifecycle management. Users control persistence, auth, and caching.
React StrictMode double-mounts components in development, causing
CopilotChat's connectAgent() useEffect to abort its in-flight fetch
on the first cleanup. This produced noisy "Agent execution failed:
AbortError: signal is aborted without reason" errors in browser
devtools.
Two fixes:
1. withAbortErrorHandling() in agent.ts now catches AbortError
(alongside the existing ZodError handling) and returns EMPTY,
preventing the error from reaching onError() which would log
"Agent execution failed:" to the console.
2. CopilotChat.tsx attaches .catch() to the fire-and-forget
detachActiveRun() call in the cleanup function. The promise
does not actually reject, but without an attached handler V8
flags the promise chain as unhandled when the abort signal
propagates through connected promises internally.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
<!--
Thank you for sending the PR! We appreciate you spending the time to
work on these changes.
Help us understand your motivation by explaining why you decided to make
this change.
**Please PLEASE reach out to us first before starting any significant
work on new or existing features.**
By the time you've gotten here, you're looking at creating a pull
request so hopefully we're not too late.
We love community contributions! That said, we want to make sure we're
all on the same page before you start.
Investing a lot of time and effort just to find out it doesn't align
with the upstream project feels awful, and we don't want that to happen.
It also helps to make sure the work you're planning isn't already in
progress.
As described in our contributing guide, please file an issue first:
https://github.com/ag-ui-protocol/ag-ui/issues
Or, reach out to us on Discord: https://discord.com/invite/6dffbvGU3D
You can learn more about contributing to copilotkit here:
https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md
Happy contributing!
-->
## What does this PR do?
(Describe the changes introduced in this PR)
## Related PRs and Issues
- (Direct link to related PR or issue, if relevant)
## Checklist
- [ ] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [ ] If the PR changes or adds functionality, I have updated the
relevant documentation