Pins three integration cases for the thread-store auto-unregister branch
in CopilotKitCore's internal onAgentsChanged subscriber:
1. agent removed from agents → store IS unregistered; subscriber receives
the previous store via prevStore.
2. FIRST onAgentsChanged({ agents: {} }) on a published-style core where
the store was registered before any agents arrived → store SURVIVES.
Reproduces the race that the "previously had" guard exists to
prevent.
3. add → register → remove cycle → store IS unregistered. Complements (2)
by exercising the same code path's positive branch.
Test (1) surfaced a real bug in the seed: agentRegistry.initialize does
NOT emit onAgentsChanged, so the constructor's internal subscriber never
saw the agents__unsafe_dev_only set, and `previousAgentIds` stayed empty.
A later removeAgent__unsafe_dev_only call would then be guarded into a
no-op because the agentId looked "new". Fixed by seeding
`previousAgentIds = new Set(Object.keys(agents__unsafe_dev_only))` right
before wiring the subscriber.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CopilotKitCore subscribes to onAgentsChanged and unregisters thread
stores for any agentId not in the new agents map. For published cores,
core.agents is asynchronously populated, so the FIRST
onAgentsChanged({ agents: {} }) notification fires BEFORE published
agents are merged in. Without a guard, that empty notification rips out
a thread store that a consumer (e.g. useThreads) just registered.
Track previousAgentIds and only unregister an agentId that was present
in the previous snapshot AND missing from the new one. The first
empty-agents notification (where the agentId was never previously
present) becomes a no-op.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- thread-store-registry: makeStore now attaches a __testId via
intersection so callers can distinguish stubs at a glance during
debugging instead of relying on identity-by-allocation alone.
- thread-store-registry subscriber-isolation test: assert the
diagnostic content ("Subscriber onThreadStoreRegistered error") and
Error argument, not just that some error was logged.
- handle-threads identifyUser-throws test: assert
"Error identifying intelligence user" with an Error argument, since
the throw originates inside resolveIntelligenceUser which logs and
returns 500 before subscribeToThreads is reached.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
R1's notify-ordering fix only made the sync subscriber path safe; async
subscribers still race against the next register(). When notifySubscribers
awaits inside Promise.all, control returns to register() which assigns the
new store to the same agentId before the async handler resumes — at which
point registry.get(agentId) returns the new store, not the unregistered one.
Carry the previous store on the onThreadStoreUnregistered payload so
subscribers tearing down state don't need to consult the registry. Same
treatment for unregister(). Doc the "do not call registry.get(agentId) in
this callback" contract on the subscriber type itself.
Also tighten getAll(): cache the snapshot, freeze it (so the Readonly<>
claim is honest at runtime), and return the same reference between
mutations. Stable identity matters for useSyncExternalStore consumers that
compare snapshots to skip re-renders. Tests updated to assert prevStore
delivery, frozen-snapshot mutation throws, and reference stability across
calls. Mock notifySubscribers now uses Promise.all to mirror production
parallel dispatch.
vitest types invocationCallOrder as `number[]`, but TS narrows
indexed access to `number | undefined` under noUncheckedIndexedAccess.
Capture the entries first, assert each is defined, then compare —
keeps the ordering check intact while satisfying tsc.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tests in @copilotkit/react-core need the WebSocket retry budget so they
can validate teardown semantics without hardcoding the threshold
separately from production. Exposing it under the ɵ-prefixed internal
namespace keeps it out of the public API surface while letting the
test bench import a single source of truth.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two-part welcome-screen regression for `available: "always"` suggestion configs
when the chat connects to agents via `runtimeUrl` instead of registering them
locally:
1. SuggestionEngine.reloadSuggestions bailed early when the agent wasn't yet
in the registry. With runtimeUrl, the registry is empty during the initial
/info fetch, so the very first reload (fired by useConfigureSuggestions on
mount) skipped every config — static pills never appeared on the welcome
screen, dynamic pills never started generating. Now: don't bail, default
`messageCount` to 0, run static configs anyway. Dynamic configs still need
a real agent and skip until one arrives.
2. useConfigureSuggestions's global-config path only iterated the current
agents map, which compounded the problem above — the empty map meant zero
reloads. Now: also reload for the chat's resolved consumer agent (covers
the empty-map case), and subscribe to onAgentsChanged for dynamic configs
only, firing exactly once when the target agent first appears (so dynamic
pills catch up after the runtime fetch completes, without piling up
overlapping generations as multiple useConfigureSuggestions hooks mount).
`hasSuggestions` keeps the `!isConnecting && !isRunning` UI gate. `available:
"always"` controls eligibility windows (welcome screen vs. after first
message), not whether to render through connect/replay or through a run —
those still hide and the end-of-run reload regenerates against the new
context.
Tests:
- Engine: added unit coverage for reloadSuggestions when no agent is present.
- React: 4 integration tests covering welcome screen (specific + global
consumerAgentId) and the run lifecycle (hide during run, reappear after) in
default and pin-to-send modes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- register() now snapshots and clears the previous slot before queuing the
unregister notification, then assigns the new store before queuing the
register notification. The unregister microtask is queued first so
subscribers tear down stale subscriptions before receiving the
replacement.
- All notifySubscribers calls now attach .catch handlers so notification
failures surface via console.error instead of being silently swallowed
by `void`.
- getAll() returns a shallow copy so callers cannot mutate the registry's
internal state through the returned reference.
- Test mock now mirrors the real notifySubscribers signature (handler +
errorMessage) and wraps each subscriber call in try/catch, exercising
the same error-isolation behaviour as production.
- Replaced the `as unknown as ɵThreadStore` cast with a satisfies-typed
minimal stub matching the real interface.
- Added tests for ordering (unregister before second register), throwing
subscriber isolation, and getAll() snapshot isolation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Covers the shared-state thread-resume path that was missing coverage:
on resume, the /connect bootstrap plan replays STATE_SNAPSHOT events
captured during the original run, and agent.state (both on the direct
IntelligenceAgent and through the ProxiedCopilotRuntimeAgent bridge)
must reflect the final snapshot so UI reading from agent.state renders
the persisted state.
Both tests pass against the current implementation, ruling out these
layers as the source of the shared-state demo regression.
- hooks.ts: keep both threads/clear and cpk-debug-events in RouteInfo
- use-threads.tsx: keep registerThreadStore effect + adopt main's
runtimeStatus gating for context dispatch
- use-threads.test.tsx: keep both our register/unregister test and
main's new runtimeConnectionStatus=Connected gating test
- scripts/hooks/check-binaries.sh: add shell-docs and shell-dojo
demo-content.json exclusions (main introduced these >1MB files without
updating the exclusion list)
- lefthook.yml, pnpm-lock.yaml: accept main's version
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ThreadStoreRegistry.register: delete old store before notifyUnregistered
so callbacks that call getThreadStore(agentId) see undefined, not the
new store
- ThreadDetailsComponent: reset _expandedMessages on threadId change
alongside _expandedToolCalls (prevents stale expanded state across
thread switches)
- handle-threads.test: assert identifyUser called in getThreadMessages
intelligence path; add identifyUser-throws 500 test
- use-threads.test: add fetchMoreThreads end-to-end test (calls the
function, asserts cursor param on second fetch, asserts 3 threads)
- in-memory-runner.test: call clearThreads() in first describe's
beforeEach for GLOBAL_STORE consistency
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rename sortThreadsByUpdatedAt → sortThreadsByRecency to match the
lastRunAt-preferring sort introduced in the previous commit.
- useThreads: correct the context-dispatch comment to describe what the
code actually does (null only when runtimeUrl is absent; transient
status states leave the previous context in place).
- CopilotChatInput: rewrite the `bottomAnchored` prop doc so the
layout/positioning distinction is self-evident.
- Add changeset calling out the behavior change to suggestions (now
hidden while `isRunning`) and summarizing the ENT-314 fixes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Skip copilotkit.connectAgent when CopilotChat lacks a caller-supplied
threadId — a locally-minted UUID has no backend record, so /connect
would always 404 on the intelligence platform.
- Suppress the welcome screen while a connect is in flight and
unconditionally when the caller has supplied a threadId
(hasExplicitThreadId). Prevents the "How can I help you today?"
flash on thread switch.
- Gate suggestions on !isConnecting && !isRunning to avoid painting
them against a mid-replay message tree.
- Defer the isConnecting release by one animation frame so trailing
bootstrap renders commit before the flag flips.
- Reserve room for the "Powered by CopilotKit" license badge via a
new --copilotkit-license-banner-offset CSS var published by the
banner on mount; chat input consumes it only when bottom-anchored.
- Sort and display threads by lastRunAt (fallback to updatedAt →
createdAt) so metadata-only actions like archive/rename don't
reshuffle the list.
- useThreads waits for runtimeConnectionStatus === Connected before
dispatching the store context, eliminating the speculative /threads
fetch that fired before /info returned wsUrl.
Threads example polish: restore button + tooltips on
archive/restore/delete, segmented Active/All filter, graceful error
state, skeleton rows on initial load, stable scrollbar gutter,
pre-paint dark-mode class, logo position stable across app/chat
modes, drop dynamic-import drawer wrapper that caused null first
paint, archived-row dimming via child colors instead of opacity.
Tests:
- CopilotChat.absentThreadConnect: connect is skipped without a
threadId, fires when supplied via prop or config.
- CopilotChatView.connectingGate: isConnecting suppresses welcome;
hasExplicitThreadId suppresses welcome on empty chat.
- threads (core): lastRunAt sort fallback ordering.
- use-threads: Connecting-state gate defers /threads until Connected.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- ThreadStoreRegistry.register() now fires onThreadStoreUnregistered
before overwriting an existing store so subscribers (web inspector)
don't stay subscribed to stale stores on replacement; test extended
to verify both events fire in order
- Wire handleClearThreads to POST /threads/clear route; add RouteInfo
variant, router pattern, and fetch-handler case so the inspector can
actually call it
- Add handleGetThreadMessages tool-call mapping test using properly
typed Message objects (role as const, type as const) — exercises the
real mapping code path without mocking getThreadMessages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Excludes showcase/shell-docs and showcase/shell-dojo demo-content.json from
the size check in lefthook.yml — these data files were added by main but
weren't in the exemption list, causing the pre-commit hook to reject them.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Consolidate the repeated console.error + emitError + .catch pattern
into a single private logAndEmitError method on CopilotKitCore. All 4
call sites (setDefaultThrottleMs, subscribeToAgentWithOptions validation,
safeCall reportError, unsupported-keys warning) now go through the helper.
## Summary
- `buildFrontendTools` only checked `tool.available !== false` but the
`available` field can also be the string `"disabled"`, which is truthy
- Added `tool.available !== "disabled"` to the filter so disabled tools
are properly excluded
- Added test verifying tools with `available: "disabled"` are filtered
out
## Test plan
- [x] New test: tool with `available: "disabled"` is excluded from
`buildFrontendTools`
- [x] All existing available-filtering tests still pass
- [x] Full core test suite passes (338 tests)
Closes#3141
`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.
Split from #3838.
Core test (core-subscribe-to-agent.test.ts):
- Replace `as any` on RunErrorEvent with proper EventType.RUN_ERROR type
- Consolidate 4 identical lifecycle tests into one `it.each` block
- Extract silenceConsoleError() helper used across 10 error-path tests
- Document the remaining intentional `as any` in guardAll JS-consumer test
Angular test (agent.spec.ts):
- Make MockAgent extend AbstractAgent, removing all 4 `as unknown as AbstractAgent` double casts
- Replace all `any` types with proper AG-UI types (Message, State, AgentSubscriber, etc.)
- Emit helpers now pass correct AgentSubscriberParams instead of empty objects
- Use userMsg() factory for properly typed test data
- Simplify ProxiedCopilotRuntimeAgent assertion with single narrowing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
React's useAgent was missing onRunErrorEvent under OnRunStatusChanged,
causing isRunning to stay true after protocol-level RUN_ERROR events
(infinite spinner). Angular already handled this via PR #3749.
Also updates SubscribeToAgentSubscriber JSDoc to accurately document
onRunErrorEvent as the sixth allowed callback with rationale for its
inclusion despite having stopPropagation in its return type, and adds
a core-level test for onRunErrorEvent firing immediately during
throttle windows.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Restore `let active` guard in useAgent subscription effect (removed
during refactor, causing ReferenceError in batchedForceUpdate)
- Add onRunErrorEvent to SubscribeToAgentSubscriber allowed keys
(added on main for Angular AgentStore)
- Fix throttle tests to use async act() for onStateChanged assertions
(batchedForceUpdate uses queueMicrotask, needs await to flush)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the hand-rolled leading+trailing throttle (scheduleOrFlush,
setTimeout, throttleActive state machine) with TanStack Pacer's
Throttler class. Clean up comments to describe the method generically
rather than only in terms of throttling.
No public API changes — throttleMs / defaultThrottleMs stay as-is.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address review feedback: replace Object.entries loop in guardAll
with explicit per-key wrapping so TypeScript can verify all
callback signatures without any casts. Also replace Promise
duck-typing in safeCall with instanceof Promise, and use
Record<string, unknown> instead of any in the unsupported keys check.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move the unsupported-key scan from guardAll into a pre-scan that runs
before the throttled/unthrottled branch, so JS consumers passing
unsupported callbacks (e.g. onEvent via `as any`) get a console.warn
regardless of whether throttling is active. Previously, guardAll only
ran on lifecycleOnly in the throttled path, silently dropping
unsupported keys without any diagnostic.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add Error state handling in Angular CopilotkitAgentFactory (parity with React)
- Add emitError to guardAll for dropped callbacks (structured monitoring)
- Add emitError to setDefaultThrottleMs for monitoring parity
- Replace empty .catch(() => {}) with logging pattern on emitError
- Add generic typing to safeCall for call-site parameter safety
- Clarify flushPending comment re: sync-only unsubscribe guard
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add `satisfies readonly (keyof AgentSubscriber)[]` to SUBSCRIBE_TO_AGENT_KEYS
so upstream AG-UI renames are caught at compile time
- Fix SubscribeToAgentSubscriber JSDoc: separate AG-UI event handlers (mutation
semantics) from per-item callbacks (void return, excluded for surface area),
note that included lifecycle callbacks also return AgentStateMutation
- Emit invalid throttleMs through emitError so monitoring systems see
misconfiguration, not just console.error
- Add flushPending comment explaining the re-checked `active` flag between
onMessagesChanged and onStateChanged dispatch
- Derive SubscribeToAgentFn from CopilotKitCore['subscribeToAgent'] instead
of manually duplicating the signature (eliminates desync risk)
- Derive StubCore from Pick<CopilotKitCore, ...> in Angular tests
- Add test: async rejection during throttled trailing-edge flush
- Add test: clearing defaultThrottleMs(undefined) makes new subs unthrottled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add .catch() to emitError() in reportError to prevent unhandled
promise rejections if the error reporting infrastructure itself fails
- Add console.warn in guardAll when unsupported callback keys are
dropped, so JS consumers discover they need agent.subscribe() directly
- Hoist ALLOWED_KEYS Set to module scope to avoid per-call allocation
- Fix SubscribeToAgentSubscriber JSDoc: explicitly list the 5 supported
callbacks and mention onNewMessage/onNewToolCall as excluded
- DRY subscribeToAgent() method JSDoc by referencing the type instead
of duplicating the AG-UI exclusion rationale
- Fix safeCall comment: async error path returns Promise<undefined>,
not undefined
- Add JSDoc to setDefaultThrottleMs setter; simplify getter JSDoc
- Fix use-agent.tsx: consistent camelCase callback names, fix @default
tag format, replace unresolvable {@link} with plain text reference
- Add test: emitError/onError integration for SUBSCRIBER_CALLBACK_FAILED
- Add test: unsubscribe + throw combination in onMessagesChanged
- Add test: unsupported callback keys dropped with console.warn
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add SUBSCRIBER_CALLBACK_FAILED error code and call emitError() in
safeCall so subscriber failures are visible to monitoring systems
and onError handlers, not just console.error
- Document that safeCall intentionally discards return values on the
error path (including lifecycle AgentStateMutation)
- Extract SUBSCRIBE_TO_AGENT_KEYS tuple as single source of truth for
both the SubscribeToAgentSubscriber type and the runtime ALLOWED_KEYS
set, eliminating the one-directional sync risk from the satisfies pattern
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 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>
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