feat(runtime): add defaults and max caps for thread lock TTL and heartbeat
lockTtlSeconds defaults to 20s (max 3600s / 1 hour) and
lockHeartbeatIntervalSeconds defaults to 15s (max 3000s / 50 minutes).
Both values are always set, so the heartbeat timer now starts
unconditionally and the paired-option validation is removed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
- Extract dedup logic into exported deduplicateMessages() pure function so
it can be unit-tested directly
- Wrap with useMemo([messages]) to avoid allocating a new Map on every frame
- Collapse three-way branch to two-way (the two arms were identical)
- Use { ...existing, ...message, content } for a true merge so fields present
only in an earlier occurrence are not silently dropped
- Expand comment to explain why || treats empty string as falsy
- Add deduplicateMessages unit tests: toolCalls assertion, reverse scenario,
non-assistant keep-last behavior
- Rename "keeping the last occurrence" test — no longer accurate for assistant
messages which now merge rather than keep-last
Co-Authored-By: Claude Sonnet 4.6 <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>
- Fingerprint all tool-call args on last message (not just last tool call)
so parallel/non-last tool-call streaming is detected and triggers re-render
- Add dev warning when scroll container has clientHeight=0 so virtualization
silently disabled (e.g. chat inside display:none tab) is surfaced early
- Use callback ref for non-autoScroll path in ScrollView to eliminate
useLayoutEffect + eslint-disable; autoScroll path keeps useLayoutEffect
since StickToBottom manages that ref internally
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a jsdom-compatible test that confirms the virtual code path activates
above VIRTUALIZE_THRESHOLD without requiring a real browser viewport. Mocks
clientHeight and getBoundingClientRect on the scroll element to pass both
CopilotChatMessageView's guard and TanStack Virtual's observeElementRect.
Asserts the virtual container div exists and rendered count < TOTAL.
Drains pending rAF callbacks before unmount to prevent spurious uncaught
exceptions after jsdom teardown.
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>
scrollToIndex was firing on every streaming chunk (deduplicatedMessages.length
dep), forcibly yanking users to the bottom even when scrolled up to read
history. Removed the dep — use-stick-to-bottom handles streaming auto-scroll
via content height growth on the virtualizer's total-size div, same as the
flat path. scrollToIndex now only fires on virtual mode activation and thread
switches.
Also removed a stale comment referencing toolResultMap (removed two commits
ago) and replaced it with an accurate description of the linear scan.
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>
- New docs/(root)/migration-guides/ section with migrate-to-v2, migrate-attachments,
migrate-to-1.10.X, migrate-to-1.8.2
- Troubleshooting now only contains error-debugging, observability-connectors, common-issues
- Update all @deprecated JSDoc URLs from /troubleshooting/ to /migration-guides/
- Update doc cross-links (multimodal-attachments page, snippet import)
- Move snippet to snippets/shared/migration-guides/
- Set minimum version to v1.56.0 (was vX.Y.Z placeholder)
- 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
- Fix stale closure in paste handler by using processFilesRef pattern
- Wrap processFiles calls in try/catch across paste, file upload, and drop handlers
- Add early return for empty/falsy accept string in matchesAcceptFilter
- Block send while attachments are still uploading
- Add id field to Attachment interface, replace _id hack with randomUUID()
- Add 10s timeout to generateVideoThumbnail to prevent infinite hanging
- Show placeholder div for uploading attachments instead of rendering incomplete source
- Include filename context in upload error messages
Wrap all three new URL() call sites in try/catch so a malformed URL string
skips the part with a console.warn rather than crashing message conversion.
Add a default switch case to warn on unrecognized content part types.
Add a test covering the malformed-URL skip behaviour.
Re-export AG-UI multimodal input types (InputContent, TextInputPart, ImageInputPart,
AudioInputPart, VideoInputPart, DocumentInputPart, InputContentSource, etc.) from
@copilotkit/shared. UserMessage is now a plain pass-through of agui.UserMessage,
removing the CopilotKit-specific image?: ImageData extension. ImageData is kept with
@deprecated for backward compatibility with AIMessage.image.