1. consumeAttachments: read from a ref mirror instead of side-effecting
out of a setState updater, avoiding reliance on synchronous updater
execution under React concurrent mode.
2. Codemod: skip declaration positions (variable, function, class, type,
interface) and non-reference positions (object keys, member accesses).
When a local declaration shadows the import name, only rename
unambiguous type-position references to avoid corrupting unrelated code.
The CopyButton setTimeout fires after the jsdom test environment is
torn down, causing an unhandled ReferenceError on Node 20. Track the
timer in a ref and clear it on cleanup.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace `ReactActivityMessageRenderer<unknown>` + `(content as any).resourceUri`
with `ReactActivityMessageRenderer<z.infer<typeof MCPAppsActivityContentSchema>>`
so the renderer prop is properly typed and the `as any` cast is eliminated.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After the useRenderActivityMessage fix, MCPAppsActivityRenderer receives the
per-thread clone instead of the registry agent. The mock's clone() did not share
isRunning state, causing waitForAgentIdle(clone) to never resolve (emit() only
updated registry.isRunning). Also, tests that monkey-patch runAgent before
renderWithCopilotKit need the clone to delegate proxied MCP requests to the
registry (to pick up the monkey-patch) while running user-message flows on the
clone itself (so clone.messages is updated and rendered by CopilotKit).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a minimal demo at /a2ui-demo that uses the demo-button agent to
reproduce and validate the A2UI thread-clone bug fix. The agent renders
an A2UI surface with a Confirm button on first run; clicking it fires a
second run that emits a text confirmation message.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- All callbacks (processFiles, handleFileUpload, handleDragOver, handleDragLeave,
handleDrop, removeAttachment, consumeAttachments) are now wrapped in useCallback
with empty deps — referentially stable across renders
- Config values read from configRef to avoid dep array changes
- consumeAttachments no-ops on empty queue (returns same state reference)
- 9 new tests: referential stability across re-renders, re-render counting
(consumeAttachments on empty queue triggers zero re-renders), initial state,
consumeAttachments behavior, removeAttachment no-op
useRenderActivityMessage was calling copilotkit.getAgent() directly,
always returning the registry agent. When a user clicked an A2UI button,
handleAction → runAgent executed on the registry agent — messages
accumulated there while CopilotChat displayed from the per-thread clone
(created by useAgent), so responses appeared to silently vanish.
Apply the same getThreadClone(registryAgent, threadId) ?? registryAgent
pattern already used in useRenderCustomMessages.
Adds a regression test that asserts the renderer receives the clone,
and confirms the test catches the bug when the fix is reverted.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Restore console.error as safety net in processFiles catch blocks (errors no longer
silently vanish when onUploadFailed callback is not provided)
- Add try/catch to useBlobUrl atob() — gracefully handles malformed base64 instead of
crashing the component tree
- Fix DocumentLightboxContent: use blobUrl instead of undefined src variable
- Add .catch() to View Transition API transition.finished promise
- Codemod: preserve actual imageUploadsEnabled value (false, dynamic expressions)
instead of hardcoding true — adds 3 new test cases (18 total)
- Replace all placeholder @deprecated versions (v1.x.0) with @since 1.56.0
- Add missing @since to ImageRenderer, ImageRendererProps, AIMessage.image tags
Attachment queue & previews:
- Image lightbox with View Transition API morph animation
- Video lightbox with native controls and play button overlay
- Document lightbox (PDF via blob URL, text inline, info card fallback)
- Drop zone overlay with upload icon
- Filename preservation via InputContent metadata
- Proper video thumbnail sizing and play/pause indicator
- Fix attachment queue positioning (max-w-3xl constraint)
- Padding between X button and content for audio/document cards
- Document filenames wrap instead of truncating
Attachments config:
- onUploadFailed callback for validation/upload errors (file-too-large, invalid-type, upload-failed)
- onUpload accepts sync or async returns
- AttachmentUploadResult discriminated union with explicit interfaces
- Metadata field on Attachment and onUpload return type
AG-UI version bump:
- Bump @ag-ui/client, @ag-ui/core, @ag-ui/encoder, @ag-ui/proto to 0.0.51
- Remove process.env Vite workaround (fixed upstream in 0.0.51)
Deprecation lifecycle:
- @deprecated JSDoc on all legacy image upload APIs
- ImageRenderer, ImageRendererProps, ImageUpload type, imageUploadsEnabled prop,
inputFileAccept prop, ImageRenderer prop, AIMessage.image, ImageData
- Codemod at codemods/migrate-attachments.ts (15 tests)
- Migration guide updated with codemod instructions and new type shapes
Docs:
- New guide: docs/(root)/multimodal-attachments.mdx
- Updated migration guide with onUpload return type, metadata, codemod section
- Cross-links from prebuilt-components and migration guide
- Label change: "Add photos or files" → "Add attachments"
Tests:
- CopilotChat.attachments.test.tsx — 5 tests for onUploadFailed
- migrate-attachments codemod — 15 tests
Wire multimodal attachment support into the v2 CopilotChat component:
- Add AttachmentsConfig prop to CopilotChat for enabling file attachments
- Implement processFiles with accept filter, size validation, placeholder/ready lifecycle
- Support custom onUpload handlers and default base64 encoding
- Build InputContent[] when attachments accompany a message
- Add drag-and-drop handlers with visual feedback (dashed outline)
- Add clipboard paste handler for file items
- Render CopilotChatAttachmentQueue between scroll view and input
- Forward onAddFile through CopilotChatView to CopilotChatInput's AddMenuButton
- Omit internal attachment state props from CopilotChatProps public API
When users explicitly set autoFocus={true}, the textarea now focuses
with { preventScroll: true } so the page doesn't jump. Also applies
to the modal-open focus path. Added tests verifying default no-focus
behavior and that opt-in focus uses preventScroll.
https://claude.ai/code/session_01LPiJ8GcUNtWdS2W6MYSE4b
The v2 CopilotChatInput had a useEffect that called scrollIntoView()
whenever the textarea received focus. Combined with autoFocus defaulting
to true, this caused the entire page to scroll to the chat component on
load - the same bug that was previously fixed in v1.
https://claude.ai/code/session_01LPiJ8GcUNtWdS2W6MYSE4b
Replace eslint and prettier with oxlint and oxfmt for faster linting
and formatting across the monorepo. Remove all eslint and prettier
configs, dependencies, and related packages. Add .oxlintrc.json and
.oxfmtrc.json for the new tooling. Update CI workflows and lefthook
hooks accordingly. Reformat codebase with oxfmt.
https://claude.ai/code/session_01GMkSf29p78HuMR1mbXn8He
- Introduced message factory functions to eliminate `as any` type assertions for message literals.
- Updated test cases to utilize the new message factories for improved clarity and type safety.
- Added helper functions for subscriber notifications to streamline test setup.
This refactor improves code readability and maintainability in the test suite for the useAgent hook.
- Replace Date.now()-based throttle with flag-based leading+trailing
pattern that is immune to wall-clock jumps
- Wrap throttleMs validation in useMemo to warn once per value change
instead of on every render
- Remove phantom AbstractAgent.notificationThrottleMs JSDoc reference
- Reorder cleanup to clear timer before unsubscribing for safety
- Add tests for trailing-edge data freshness, timer precision, and
dynamic throttleMs prop changes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix JSDoc to accurately describe behavior for negative/non-finite values
- Remove NODE_ENV guard so console.warn fires in all environments
- Use derived effectiveThrottleMs instead of mutating destructured param
- Simplify handler wrappers: direct references instead of arrow wrappers
- Remove stale comment about content stripping
- Add parameterized tests for invalid throttleMs (NaN, Infinity, -1, -Infinity)
- Fix misleading test comment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Addresses code review findings:
- Merge two separate useEffect hooks into one to eliminate dual-subscription
race window and simplify cleanup
- Add active boolean guard to prevent forceUpdate after unmount/re-run
- Validate throttleMs input (reject NaN, Infinity, negatives with dev warning)
- Expand JSDoc with scope, constraints, and notificationThrottleMs interaction
- Replace JSON.stringify(updateFlags) with direct ref in effect deps
- Strengthen unmount test with render counter instead of not.toThrow()
- Add 5 new test cases: explicit zero, rapid burst, new cycle after trailing,
updates excluding OnMessagesChanged, subscription cleanup verification
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds optional `throttleMs` parameter to `useAgent()` that coalesces
rapid onMessagesChanged notifications (per-token TEXT_MESSAGE_CHUNK
events) into fewer React re-renders using a leading+trailing throttle.
- throttleMs=0 (default): exact current behavior, zero overhead
- throttleMs>0: only onMessagesChanged is throttled; onStateChanged
and onRunStatusChanged always fire immediately
- Leading+trailing: first call immediate, last call guaranteed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CopilotMessages received an array of children (memoizedChildren +
RegisteredActionsRenderer) without keys, causing a React "unique key prop"
warning in dev mode. Wrap memoizedChildren in a keyed Fragment and add a
key to RegisteredActionsRenderer.
https://claude.ai/code/session_01N8sLK9h9p6FdRjyHqzKzVV
Incorporates the V1/V2 flat package consolidation (96885b595). All our
thread-isolation changes auto-merged to the new package paths. Three
content conflicts resolved (import paths updated from @/ aliases to
relative paths and @copilotkitnext/* to @copilotkit/*):
- packages/react-core/src/v2/hooks/use-agent.tsx
- packages/react-core/src/v2/hooks/use-render-custom-messages.tsx
- packages/react-core/src/v2/components/chat/CopilotChatMessageView.tsx
File location conflict resolved: use-agent-thread-isolation.test.tsx
moved from packages/v2/react/... to packages/react-core/src/v2/...
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>