- Type `handlers` in use-agent.tsx as `SubscribeToAgentSubscriber` instead
of full `AgentSubscriber` to prevent silent runtime stripping of unsupported
callbacks
- Reframe AG-UI exclusion JSDoc: designed for observation, not event mutation
(stopPropagation semantics can't be safely mediated)
- Add `satisfies` constraint to ALLOWED_KEYS so it stays synchronized with
the SubscribeToAgentSubscriber Pick type at compile time
- Use `.then` instead of `.catch` for standard thenable detection in safeCall
- Include agent ID in all safeCall error messages for multi-agent debugging
- Only wrap lifecycle callbacks in guardAll for throttled path (messages/state
wrappers were immediately overwritten — wasted work)
- Fix "pending flags" → "pending params" in inline comment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
copilotkit.headers is already captured by headersKey (a stable memoized
string). Keeping the raw headers object in the dep array causes the context
effect to re-run on every render because mock (and real) copilotkit objects
return new header object references, leading to an unhandled
"threads is not iterable" error in tests when the spurious re-run consumed
the wrong fetchMock slot.
Also adds getThreadStore (singular) to MockCore in web-inspector.spec.ts so
ensureOwnedThreadStore can call core.getThreadStore() without throwing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The docs table and prose claimed `debug: true` sets `verbose: true`,
but the implementation intentionally defaults verbose to false (PII
safety). Fixed the table and explanatory text to match.
Also removed packages/vscode-extension/README.md which was committed
on this branch by mistake — it describes an unrelated VS Code extension
and has nothing to do with debug mode.
Two new test cases covering the core behavioral fix:
- User collapses during streaming → panel stays collapsed after stream ends
- User collapses then re-expands during streaming → panel stays open after stream ends
Without act(), React 18 defers the state update through its scheduler,
which can race with waitFor polling on slow CI runners (Node 20.x/22.x).
Wrapping in act() forces synchronous flush.
The auto-collapse useEffect unconditionally called setIsOpen(false) when
streaming ended, overriding any manual expand/collapse the user had
performed. Add a userToggledRef that tracks explicit clicks so the
effect only auto-collapses when the user hasn't interacted.
The CopyButton in react-core was showing the "copied" checkmark based on
an independent clipboard availability check rather than the actual copy
result. This meant a failed copy (e.g. permission denied) would still
show the success indicator. Now the onClick handler returns the boolean
from copyToClipboard, and handleClick uses that to drive the UI state.
Also removes unsafe type casts of onClick to Promise<void>.
Address review feedback: extract the repeated clipboard availability check +
writeText + error handling pattern into a shared copyToClipboard() utility in
@copilotkit/shared. All 9 call sites across angular, react-core, and react-ui
now use the shared utility instead of duplicating the same code block.
Add null checks for navigator.clipboard across all copy-to-clipboard
calls to prevent TypeError in non-localhost environments where the
Clipboard API is unavailable. The copied indicator now only appears
after a confirmed successful write, preventing false positive UX
feedback when the clipboard API is missing or the write fails.
Extract getErrorSuppression as a pure testable function from the
routeError closure. Replace the mock-only test that only proved mock
wiring with 12 real assertions covering all visibility x isDev
combinations against the actual logic.
The routeError function returned early for ALL errors when
showDevConsole was false, suppressing user-visible errors
(TOAST and BANNER visibility) in production.
Now only DEV_ONLY and SILENT errors are suppressed in production.
TOAST and BANNER errors are always surfaced to the chat UI.
The original Math.random() key caused React to remount the CodeBlock on
every render. The PR's content-based key (language + content prefix) still
changed every streaming token, causing the same flickering. Removing the
key entirely lets React use positional identity, which is stable across
re-renders while content streams in.
Closes#2669
The shared visitedRefs Set was mutated in place, so when two sibling
properties referenced the same $def (e.g. billing and shipping both
referencing Address), the second resolution was incorrectly flagged as
circular. Clone the set before recursing so each branch has its own
ancestry path. Added regression test that fails without this fix.
Recursive JSON schemas that reference themselves via $ref would cause
infinite recursion and stack overflow. This adds a visited set that
tracks which $ref paths have been seen during resolution. When a cycle
is detected, it breaks with z.any() and logs a console.warn so users
get feedback. Also adds console.warn for the generic z.any() fallback
on unsupported schema types.
Adds tests for circular refs, non-circular $ref resolution, anyOf with
$ref variants, integer type, null type, and unsupported type warning.