Replace `as any` cast in notifyLifecycle with type-safe branching,
type the re-entrant test callback params via SubscribeToAgentSubscriber,
and replace `Record<string, any>` on Angular stub's core with a
typed StubCore interface.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add ALLOWED_KEYS filter in guardAll to prevent runtime leakage of
extra properties from JS consumers or `as any` casts
- Check `active` flag between flushPending dispatches so unsubscribing
inside onMessagesChanged prevents onStateChanged from firing
- Use real CopilotKitCore instance in Angular test stub instead of
passthrough that bypasses throttle and safeCall logic
- Reword SubscribeToAgentSubscriber and subscribeToAgent JSDoc to
accurately explain why AG-UI event handlers are excluded (mutation
return values silently discarded) rather than implying a clean
return-type split
- Add leading+trailing pattern summary to useAgent throttleMs JSDoc
- Fix setDefaultThrottleMs JSDoc: "logged as errors and ignored"
instead of ambiguous "rejected by the setter"
- Add tests: re-entrant notifications during flush, multiple
simultaneous subscriptions with independent throttle windows,
unsubscribe isolation, unsubscribe-during-flush prevents sibling
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduce SubscribeToAgentSubscriber, a Pick of AgentSubscriber limited
to the five notification/lifecycle callbacks (onMessagesChanged,
onStateChanged, onRunInitialized, onRunFinalized, onRunFailed). Event
handlers that return AgentStateMutation (e.g. onEvent,
onToolCallStartEvent) should use agent.subscribe() directly so their
mutation and stopPropagation semantics are preserved — safeCall's
error-swallowing would silently discard those return values.
This is a compile-time constraint only; no runtime behavior changes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address code review findings for subscribeToAgent:
- Wrap ALL subscriber callbacks (not just onMessagesChanged/onStateChanged)
with safeCall error protection so a throwing lifecycle or event callback
cannot corrupt the agent's notification loop
- safeCall now returns the result on the success path, preserving
AgentStateMutation return values from subscriber callbacks
- Extract SubscribeToAgentOptions named interface for extensibility
- Remove redundant pendingMessages/pendingState boolean flags — use
latestMessagesParams/latestStateParams !== null instead
- Add guardAll helper used by both throttled and unthrottled paths
- Fix JSDoc: reference public defaultThrottleMs (not private field),
document shared throttle window, use callback names instead of enum
- Add 5 new core-level tests: trailing-edge re-arm, onRunFailed
passthrough, async rejection handling, unthrottled exception safety,
unthrottled lifecycle exception safety
- Extract RUN_INPUT constant and notifyLifecycle helper, reducing test
helper duplication
- Remove ephemeral review-reference from test section header
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract safeCall helper that catches synchronous throws and attaches
.catch() for async (MaybePromise<void>) rejections, preventing
unhandled promise rejections from crashing Node.js (SSR) or producing
cryptic browser errors
- Wrap onMessagesChanged/onStateChanged in the unthrottled path with the
same error protection, so a throwing subscriber cannot corrupt the
agent's notification loop regardless of whether throttling is active
- Simplify flushPending by delegating to safeCall
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address review findings from PR #3734:
- Wrap flushPending callback invocations in try-catch so a thrown
exception in onMessagesChanged/onStateChanged does not permanently
deadlock the throttle state machine or skip the sibling flush
- Add 21 dedicated unit tests for CopilotKitCore.subscribeToAgent
covering leading/trailing edge, shared window (bidirectional),
burst coalescing, run lifecycle passthrough, unsubscribe cleanup,
resolution cascade, invalid values, and exception safety
- Use named CopilotKitCoreSubscription return type
- Fix Angular test stub to accept the options parameter and document
that it bypasses throttle logic
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Moves the leading+trailing throttle algorithm from the React-specific
useAgent hook into a framework-agnostic subscribeToAgent method on
CopilotKitCore. This consolidates throttle cascade resolution
(throttleMs ?? defaultThrottleMs ?? 0), validation, and the throttle
algorithm into a single location.
Key changes:
- Add subscribeToAgent() to CopilotKitCore with shared throttle gate
for onMessagesChanged and onStateChanged (run lifecycle events are
never throttled)
- Fix setDefaultThrottleMs to log errors and preserve previous value
on invalid input instead of silently erasing
- Simplify useAgent by removing ~60 lines of inline throttle logic
- Angular AgentStore now uses subscribeToAgent, getting throttle
support via provider-level defaultThrottleMs for free
- Remove duplicate validation from CopilotKitProvider useEffect
- Add direct unit tests for CopilotKitCore.setDefaultThrottleMs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
debug was only read at construction time. Added setDebug() to
CopilotKitCore and added it to the provider's prop-sync useEffect
so runtime changes to the debug prop take effect.
Debug config was silently dropped because ProxiedCopilotRuntimeAgentConfig
had no debug field. Now stores it in the constructor, resolves it in
AgentRegistry before passing, and clone() preserves it. Added 6 tests
verifying the full threading chain.
- Fix clone() dropping debug config on ProxiedCopilotRuntimeAgent
- Pass raw DebugConfig to agents instead of collapsing to boolean
- Clamp verbose to false when enabled is false
- Fix pino-pretty log format in docs (parentheses, timestamps, levels)
- Add loggedEventCount to doc example
- Fix "Agent run started/finished" → actual log messages in arch docs
- New ThreadStoreRegistry suite (8 tests) covers register/get round-trip,
replacement on duplicate id, unregister no-op, and subscriber events for
both register and unregister
- handle-threads suite gains handleClearThreads (InMemory path + intelligence
path) and handleGetThreadMessages (InMemory, unknown thread, intelligence
delegation, 422 fallback) describe blocks
- in-memory-runner suite: clearThreads() in beforeEach fixes GLOBAL_STORE
isolation; vacuous empty-array test replaced with a meaningful post-clear
assertion
- use-threads suite: new test asserts registerThreadStore is called on mount
and unregisterThreadStore is called on unmount
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CopilotKitCore gains a ThreadStoreRegistry (register/unregister by agentId)
and a new onAgentRunStarted subscriber event so the inspector can subscribe
before agent.runAgent() snapshots the subscriber list
- Runtime gains handleListThreads, handleUpdateThread, handleArchiveThread,
handleDeleteThread, handleSubscribeToThreads, and handleGetThreadMessages
handlers; all mutations are authenticated via identifyUser (request body
userId is ignored)
- InMemoryAgentRunner now stores thread history for the local-dev fallback
path; debug console.log removed; InMemoryThread uses literal types for
constant-value fields (organizationId: "", createdById: "", archived: false)
- useThreads hook registers its store with CopilotKitCore on mount and
unregisters on unmount so the inspector can read thread state
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
useAgent() returns per-thread clones that are not in the agent registry.
The inspector only subscribed to registry agents via onAgentsChanged, so
it never received events from clones — resulting in an empty event panel.
Add onAgentRunStarted subscriber event that fires before connectAgent()
and runAgent(), passing the actual agent instance (which may be a clone).
The inspector subscribes to that agent, matching the existing StateManager
pattern (subscribeAgentToStateManager).
- Restore getCapabilities() return type to Promise<AgentCapabilities>
with ?? {} fallback, honoring AbstractAgent base class contract
- Simplify type annotation in get-runtime-info.ts to explicit
AgentCapabilities | undefined instead of complex Awaited<ReturnType<...>>
- Add console.warn logging to per-agent capabilities error catch block
- Fix useCapabilities JSDoc to accurately describe behavior during
runtime handshake
- Add test for empty capabilities object {} (truthy, included in response)
- Update error isolation test to verify warning is logged
- Add per-agent error isolation in /info handler so a single
getCapabilities() failure doesn't 500 the whole endpoint
- Normalize getCapabilities() to return undefined (not {}) when no
capabilities are set, matching the sync getter
- Replace instanceof ProxiedCopilotRuntimeAgent with duck-type check
in useCapabilities hook for extensibility
- Add JSDoc warning about shallow-merge behavior on capabilities config
- Add useCapabilities hook tests (5 cases covering both branches)
- Add per-agent error isolation test for get-runtime-info
applyCredentialsToAgents/applyCredentialsToAgent were defined but never
called from initialize(), setAgents__unsafe_dev_only(), or
addAgent__unsafe_dev_only(). Only remote agents received credentials
via constructor. Local agents now receive credentials alongside headers.
Verify that:
- State is tracked when a run errors (getStateByRun returns error state)
- A new run after an error works correctly (runFinished flag resets,
second run tracked independently)
When the backend emits RunErrorEvent via the AG-UI protocol, several
components did not handle it:
- ProxiedCopilotRuntimeAgent: isRunning stayed true, causing
data-copilot-running to never transition to false (infinite spinner)
- StateManager: activeRun entries were never cleaned up, runFinished
flag never set (stale state on subsequent runs)
- Angular CopilotKitAgent: same isRunning bug as the proxy agent
- useAgentNodeName: node name stuck at last step instead of "end"
onRunErrorEvent is distinct from onRunFailed — the former handles
protocol-level RUN_ERROR events from the backend, the latter handles
local exceptions (network errors, deserialization failures).
Keep both defaultThrottleMs (our branch) and inspectorDefaultAnchor +
design-skill context registration (main) in CopilotKitProvider.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Core setter rejects invalid values (NaN, Infinity, negative) instead of
storing them, preventing garbage from reaching downstream consumers.
- Provider initializes defaultThrottleMs synchronously during instance
creation so child hooks see the correct value on their first render.
- Remove phantom notificationThrottle JSDoc reference (API does not exist).
- Remove dead "default" branch in error source detection.
- Fix misleading @default comments re: 0-vs-undefined semantics and
cascade direction.
- Replace React-specific "re-renders" language in framework-agnostic core.
- Add test for dynamic provider defaultThrottleMs changes.
- Add tests for core setter validation behavior.
- Add defaultThrottleMs to renderWithCopilotKit test helper.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Addresses Tyler's review comment on #3657 — defaultThrottleMs is configuration
that belongs on the copilotkit instance, not as a sibling field in the React
context value.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reverts the copy-dts.mjs approach from #3612 in favor of typesVersions
which is the standard Node/TS mechanism for resolving subpath types
under legacy moduleResolution: "node".
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
Copies .d.cts → .d.ts after each tsdown build so consumers on
moduleResolution "node" (e.g. DocuSign) can resolve types without
changing their tsconfig. Also points the "types" field back to .d.ts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix fetchRuntimeInfoAutoDetect in agent.ts to use 2xx-only check
(was checking only 404/405, now aligns with agent-registry.ts)
- Remove duplicate JSDoc block on fetchRuntimeInfoAutoDetect
- Add 7 edge case tests: 500/403/405/network-error/both-fail scenarios
- Add 4 tests for useSingleEndpoint->runtimeTransport ternary mapping
- Update Angular test stubs to use "auto" default (was "rest")
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>