Node 25 ships an experimental built-in localStorage global accessor that
shadows jsdom's mock, leaving window.localStorage as an empty stub with
no clear/setItem/removeItem/getItem methods. This broke all 22 telemetry
tests with 'window.localStorage.clear is not a function' and was
blocking pre-commit hooks repo-wide.
Add a vitest setup file that installs a proper in-memory Storage shim
on both globalThis and window before each test, so jsdom-environment
tests behave the same on Node 20 and Node 25.
Packages without repository.url fail npm OIDC provenance verification.
Adds the field to agentcore-runner, core, sqlite-runner, voice, and
web-inspector. Includes a one-shot workflow to publish the 14 remaining
v1.57.4 packages (a2ui-renderer already published via OIDC).
Findings from a self-review pass on this PR:
- code-block.tsx: hoist the seven per-row inline-style objects to module
scope so the file-render hot path stops re-allocating ~3 fresh objects
per line per render. ~500-line demos previously allocated ~1.5k style
objects on every parent re-render.
- page.tsx URL-sync effect: add a same-value guard before
history.replaceState so unrelated re-renders don't write the same
query string back to the address bar.
- page.tsx FileTreeRow: collapse the color ternary chain — both selected
and highlighted leaves resolve to the same primary color, so
`isSelected || isHighlighted ? primary : disabled` reads cleaner than
the nested `?:?:`.
- page.tsx: trim the restated WHAT-comment above the reset-on-demo
effect (the function body already says what the comment said).
- vitest.setup.ts MemoryStorage: coerce keys in getItem/removeItem to
match setItem and the real Storage spec (all key args coerce to string).
Deferred to a follow-up PR (would also touch showcase/shell/* and
showcase/shell-docs/*): hoisting `escapeHtml` (5 copies in the repo
today), the hljs `try { highlight } catch { escapeHtml }` pattern (also
copied), and the file-tree build/sort utilities the dojo duplicates from
showcase/shell/.../code/page.tsx.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Node 22.4+ ships an experimental built-in `localStorage` global that is
installed before vitest's jsdom environment runs, leaving
`window.localStorage` as an uninitialized stub without `getItem` /
`setItem` / `clear`. The telemetry persistence tests all crash with
"window.localStorage.clear is not a function" on Node 25.
Add a vitest setup file that installs a minimal in-memory `Storage`
shim on `globalThis.{localStorage,sessionStorage}` so tests behave the
same on Node 18/20/22/25+ without depending on
`--no-experimental-webstorage`. All 29 web-inspector tests now pass on
Node 25.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Release monorepo v1.57.2
**Scope:** `monorepo` | **Bump:** `patch`
---
### How this release process works
1. **This PR was created automatically** by the "release / create-pr"
workflow.
It bumped the `monorepo` packages to `1.57.2`
and generated AI-enhanced release notes.
2. **CI runs on this PR** — the full test suite (unit tests, lint, type
checks, build)
must pass before merging. This is the review gate.
3. **Review the release notes** in `release-notes.md` in this PR.
If a Notion draft was created, you can edit the release notes there
before merging.
4. **When this PR is merged**, the `release / publish` workflow
automatically:
- Builds all packages
- Publishes the `monorepo` packages to npm at version `1.57.2`
- Creates git tag `monorepo/v1.57.2`
- Creates a GitHub Release with the final release notes
### Before merging
- [ ] CI is green (tests, lint, types, build)
- [ ] Version bumps look correct
- [ ] Release notes are accurate (edit in Notion if a draft was created)
---
> **Do not merge until CI is fully green.** The full test suite runs
automatically on this PR.
- Defer trackBannerViewed until runtime connection is established via
pendingBannerViewed + flushPendingBannerViewed(), preventing the race
where the CDN response beats the /info handshake and fires the event
before core.telemetryDisabled is known
- Remove dead isTelemetryOptedOut() short-circuit from track(); opt-out
is enforced at call sites via core.telemetryDisabled, not localStorage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Don't fire banner_clicked on copy-button clicks (move track call to
the non-copy-button branch of handleAnnouncementContentClick)
- Defer ensureTelemetryDistinctId() to runtime connection so no UUID
is written to localStorage when COPILOTKIT_TELEMETRY_DISABLED is set
- Remove setTelemetryOptOut re-export from telemetry.ts (no production
caller; tests already import directly from persistence.ts)
- Fix docs: replace Privacy tab toggle description with env var opt-out
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Per Ben Taylor: the ingest lambda parses the X-CopilotKit-Telemetry-Id header
as telemetry_id for the distinct ID. Send it as a header in addition to keeping
it in the POST body properties.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add telemetryDisabled to RuntimeInfo from COPILOTKIT_TELEMETRY_DISABLED/DO_NOT_TRACK env vars
- Mirror through AgentRegistry and expose via CopilotKitCore getter
- Guard track calls, URL param appending, and console disclosure on core.telemetryDisabled
- Move maybeShowDisclosure() to onRuntimeConnectionStatusChanged (fires after core attaches)
- Update docs to replace localStorage toggle description with env var approach
- Add telemetryDisabled test suite to get-runtime-info tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove "Privacy" from the nav tab row; move settings access to a cog
button (Settings icon) in the top-right header, between dock controls
and the close button. Clicking the cog toggles the settings panel;
clicking again returns to the previous view.
- Settings panel replaces the privacy panel: shows the same transparency
copy but replaces the checkbox toggle with an "I want to opt out —
show me how →" link to the docs. Opt-out is via COPILOTKIT_TELEMETRY_DISABLED
(env variable, documented at docs.copilotkit.ai/telemetry) rather than
a per-browser localStorage toggle.
- Remove isTelemetryOptedOut / setTelemetryOptOut from index.ts (no longer
used; the toggle UI is gone). Underlying persistence exports remain in
telemetry.ts for future use.
- Fix cpk-tab-icon: add display:inline-flex + flex-shrink:0 + align-items:center
to prevent icon containers from collapsing on the x-axis.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move `package` from properties string to top-level `{ name }` object per
Ben's confirmed IngestPayload schema (telemetry-sink-ingest/index.ts:127-134)
- Add typed per-event helpers trackBannerViewed/trackBannerClicked/trackThreadsTabClicked
to enforce property shapes at call sites and prevent PII leakage under wrong keys
- Add trackBannerClickedOnce guard in index.ts (per-mount Set keyed by
banner_id + cta) to prevent banner_clicked inflation on repeated clicks
- Fix handleTelemetryOptOutToggle: replace ?? true fallback with
instanceof HTMLInputElement guard (wrong fallback was a privacy bug)
- Add threadsTabClicked re-selection guard (skip if already on threads tab)
- Replace getTelemetryDistinctIdForUrl() call on mount with ensureTelemetryDistinctId()
- Add inMemoryFallbackId in persistence.ts for funnel coherence when
localStorage is unavailable (same UUID returned per page load)
- Add _resetTelemetryPersistenceForTesting() for test isolation
- Remove @copilotkit/shared dep from telemetry-disclosure.ts (inline
env-var check; keeps module self-contained and testable in isolation)
- Add clearMocks: true to web-inspector vitest config (fixes spy call
history accumulating across tests)
- Expand telemetry.test.ts to 22 tests covering wire body shape, opt-out,
5 error-resilience paths, typed helpers, distinct ID lifecycle (SSR +
localStorage-throws + funnel coherence), maybeShowDisclosure, and
getTelemetryDistinctIdForUrl
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three V1 funnel events from the inspector — oss.inspector.banner_viewed,
oss.inspector.banner_clicked, oss.inspector.threads_tab_clicked — plus a
privacy panel for opt-out, a first-run console disclosure on inspector
mount and runtime startup, and inspector content added to the canonical
/telemetry docs page on main.
Inspector POSTs directly from the browser to telemetry.copilotkit.ai/ingest
(per ticket: URL is intentionally clearly named for transparency in DevTools).
Inline fetch POST in lib/telemetry.ts — no @copilotkit/shared dep on the
inspector, no dependency on any non-main branch.
Wire body shape (conservative; needs Ben confirmation):
POST https://telemetry.copilotkit.ai/ingest
{ event, properties: { ...caller, distinct_id, package }, ts }
If the lambda expects a richer envelope, update the single JSON.stringify
in lib/telemetry.ts.
Privacy invariants:
- Opt-out toggle short-circuits before any network call (verified by test).
- Properties are scoped to event metadata only — no message content, agent
state, prompts, completions, banner markdown. Negative test pins the wire.
- Anonymous distinct ID (UUID v4 in localStorage) set on inspector load and
propagated onto banner CTA links as ?posthog_distinct_id=<uuid> so the
destination site can posthog.alias() and close the
banner_viewed → banner_clicked → signup_attributed funnel. URL param
suppressed when opted out.
- Console disclosure on first inspector mount and runtime startup. Both
link to https://docs.copilotkit.ai/telemetry.
Plan gaps addressed:
- CTA name on banner_clicked: cta:'body'|'dismiss' (click location) plus
optional cta_label read defensively. Sam: confirm dismiss treatment.
- De-anon opt-out folded into the single toggle. Docs say so explicitly.
- banner_viewed dedup: per-instance Set<string> keyed by timestamp.
- EPIC consent / pixel review: out of scope for this PR; flagged at merge.
Deferred for V1.1:
- Wire body shape (Ben).
- Event-type allowlist for oss.inspector.* (Ben — oss-path-to-production).
- posthog_distinct_id URL-param key name (Ben/Tyler/website team).
Refs https://linear.app/copilotkit/issue/OSS-96
Threads is no longer behind a private-beta access code — drop the gate UI
(early-access card, unlocking card, code submission, cookie persistence)
and render the threads view unconditionally.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The prior commit accidentally re-included a local-only flip of
ANNOUNCEMENT_URL to the draft endpoint while staging the unrelated
copy-button timeout fix. Restore the production URL.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous copyResetTimeout field was a single instance-level number,
shared across every code block in a multi-block announcement. Clicking
Copy on block A then block B cancelled block A's reset timer, leaving
block A stuck on "Copied" forever.
Switch to a WeakMap<HTMLButtonElement, number> so each button manages
its own pending reset and is naturally cleaned up when the announcement
re-renders. Also keep aria-label in sync with the visible text label so
screen-reader users hear "Code copied" while sighted users see "Copied".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CR-loop fixes from a 7-agent unbiased review on the announcement banner
work:
- Replace deprecated unescape/escape with TextEncoder/TextDecoder in
encodeBase64/decodeBase64, dropping the half-decoded fallback path.
- Drop unsafe `as HTMLElement` and `as HTMLButtonElement` casts in
handleAnnouncementContentClick; the .announcement-code__copy selector
does not enforce HTMLButtonElement, so use instanceof guards.
- Coalesce overlapping copy-button resets via a class field timeout id
and a constant "Copy" label; previously a rapid double-click captured
"Copied" as the original label and pinned the button text.
- Pass async: false to marked.parse for a type-honest string return.
- Fold escapeHtmlAttr into the module-level escapeHtml helper.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the duplicate escapeHtmlText method with the existing module-level
escapeHtml helper that has identical behavior.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Differentiate h1/h2/h3 sizes and bump body type for readability
- Add code-block styling with horizontal scroll, trailing spacer, and a
copy button (brand lavender on success)
- Move announcement banner from the non-scrollable header into the main
scroll container so an expanded banner no longer pushes the resize
handle and content off-screen
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds three new memo gating signals to MemoizedCustomMessage so custom
message renderers stay reactive across the structural events that affect
"is this slot still authoritative?" decisions:
- numberOfMessagesInRun — invalidates when peers stream into the same run,
so renderers gating on "last message of the run" stay correct.
- isInLatestRun — invalidates when a newer run starts, so renderers gating
on "is this the latest activity?" can drop their badges on completed runs.
- isRunning (gated on isInLatestRun) — invalidates exactly twice per run on
the latest run's slots (start, end), preserving the perf-test guarantee
that completed runs' messages skip re-renders during streaming.
CopilotChatMessageView computes these per slot via getRunIdForMessage and
passes them down. Helper getNumberOfMessagesInRun lives next to the call
site for clarity.
New e2e test at CopilotKitProvider.intelligenceIndicator.e2e.test.tsx
exercises an "Using CopilotKit Intelligence" renderer that gates on:
position === "after", last-in-run, agent.isRunning, and run-is-latest.
The walkthrough scenario drives Run A then Run B with multiple messages
each, verifying the indicator only appears on the canonical slot at each
phase. Four condition-focused tests pin each gate individually.
Includes IsRunningAccurateMockAgent — a local subclass that makes run()
return a per-run observable terminating on RUN_FINISHED/RUN_ERROR. The
shared MockStepwiseAgent.run() returns the un-terminating subject for
backward compatibility, so emit(RUN_FINISHED) on it doesn't trigger
AbstractAgent's finalize → onRunFinalized → useAgent re-render path. The
subclass scopes the fix to this file without disturbing other tests.
Reverts the cloning design from #3525 (useAgent per-thread clones, getThreadClone,
globalThreadCloneMap, cloneForThread) and #3630 (clone routing in activity renderers),
plus the inspector machinery that existed only to handle clones (onAgentRunStarted
subscriber + run-handler emissions from #3869, the connect-time emission from #3872,
and the agentRunThreadId map that read from it).
State-manager isClone composite-key path and SuggestionEngine consumerAgent param —
both added in #3525 to keep clones visible to bookkeeping — are gone too.
Restores agent.threadId = resolvedThreadId in CopilotChat (pre-#3525 behavior) and
swaps the inspector's agentRunThreadId map for a direct agent.threadId read.
Removes the DemoButtonAgent and /a2ui-demo page from the demo (added by #3630 as a
clone-fix repro).
Re-opens the original issue #2957 (CPK-7155): two CopilotChat instances with the same
agentId and different threadIds will share message state again. The follow-up is a
public registerProxiedAgent API so callers can opt into multiple frontend agents
proxying to the same runtime agent, without implicit per-thread cloning.
Mechanical cleanup of the perf commit's three repeated patterns. No
behavioral change.
- Three `_xxxTplCache` fields collapse into a single
`_panelTplCache: Map<ThreadDetailsTab, { key, tpl }>` with a shared
`cachedPanelTpl(slot, key, build)` helper. Cache key is now a tuple
compared element-wise by reference, so each panel passes everything
the template depends on (conversation passes
`[_conversation, _expandedTools, _expandedMessages]`) without
duplicating the cache-check shape three times.
- Three sibling tab-content `<div>` blocks in render() collapse into one
`TAB_LIST.map(...)` driven by a new `renderTabContent(id)` dispatcher.
- Tab-button click handler extracts to `activateTab(id)` plus
`maybeFetchTabData(id)`, keeping the rAF-and-spinner dance and the
lazy-fetch decision off the inline lambda.
- Two-rAF first-activation collapses to a single rAF: Lit batches the
`_activatedTabs` add and the `_panelInitializing = false` clear into
one update, so the second rAF was redundant.
- `highlightedJsonImpl` was a pass-through layer split out only to host
the WeakMap memo; inline it back into `highlightedJson`.
- `ReturnType<typeof html>` swaps to the canonical `TemplateResult`
type imported from lit.
- Class renames `CpkThreadDetails` → `ɵCpkThreadDetails` (already
exported for tests; the prefix keeps the internal-API hint).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three follow-ups to the perf commit:
1. The conversation TemplateResult cache was keyed only on `_conversation`,
so toggling a tool-call expand or "Show more" on a long message — both
of which mutate `_expandedTools` / `_expandedMessages` without touching
the conversation array — returned the pre-toggle template and the
disclosure appeared broken. Widen the cache key to include both expand
sets; production toggles already replace the Set instance, so reference
equality flips correctly.
2. Wrapping each tab in a panel <div> for the keep-mounted approach broke
the `gap` flow that previously cascaded from `.cpk-td__content > *`.
Add a `.cpk-td__panel` class with `display:flex; flex-direction:column;
gap:12px` so conversation items and event rows have breathing room.
3. Export the `ɵCpkThreadDetails` class so unit tests can pin down the
per-panel cache-invalidation contract. Add four tests in
web-inspector.spec.ts covering: threadId change drops all three caches;
conversation cache invalidates on `_conversation` reassignment;
conversation cache invalidates on expand-state change (regression
guard); state and events caches invalidate on their fetched data
reassignment.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three layered fixes for the tab-switch jank on threads with many AG-UI
events. Symptoms: clicking back to a previously-opened tab took roughly
a second per switch on a thread with several hundred recorded events,
even though the underlying data was already cached.
1. Keep activated tab panels mounted. The render conditional swapped
between renderConversation/renderState/renderEvents based on `_tab`,
so Lit tore down the previous panel's DOM and rebuilt the next one
from scratch on every switch. Now once a tab is activated, its panel
stays mounted and inactive panels are hidden via `display:none`.
Activated set resets on threadId change.
2. Memoize per-panel TemplateResults by data reference. Even with the
panel mounted, render() still re-evaluated the template on every
parent update, allocating fresh nested TemplateResults for every
event row. Each render now returns the cached TemplateResult when
`_conversation` / `_fetchedState` / events array references haven't
changed; Lit then short-circuits the entire diff.
3. Defer layout for off-screen events with `content-visibility: auto`
plus a `contain-intrinsic-size` hint. The cached-data switch back to
the events panel still triggered a full layout pass over every
recorded event, which on a 600-event thread shows up as a seconds-
long freeze when the panel becomes visible. The browser now skips
layout/paint for off-screen rows entirely.
Also adds a WeakMap memo around `highlightedJson` so identical event
payloads don't re-run JSON.stringify + the syntax-highlight regex pass
on every render.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Frontend-rendered generative-UI tools (charts, custom UI) never produce
a `role: tool` result message because they execute client-side, so the
prior `item.result ? DONE : PENDING` rule rendered them as PENDING
forever even after the run finished and the chart was on screen.
The args block being populated is itself the resolution signal for these
tools, so flip the condition: any tool call with parsed arguments shows
DONE. The badge stays PENDING only for the brief window where args have
not yet streamed in.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The threads-detail sub-tabs (Conversation, Agent State, AG-UI Events)
all fetched eagerly on threadId change. Against an Intelligence-backed
runtime, the AG-UI events response can be many MB; the JSON.parse alone
blocks the main thread for several seconds while the user is still on
the conversation tab. Any sub-tab click queued during that window
couldn't fire until the parse finished, so the tab itself appeared
unresponsive — the user perceived 15s of frozen UI before the panel
swapped, and the active-tab highlight didn't paint either.
Two fixes:
1. Defer the events / state fetches to first sub-tab click. Conversation
stays eager because it's the default tab and visible immediately.
When the user clicks an Agent State or AG-UI Events sub-tab the fetch
kicks off then — so the heavy JSON.parse blocks AFTER the click has
registered, not before.
2. Add a `_panelInitializing` flag set on tab change and cleared in the
next animation frame. The render switches to a generic "Loading…"
placeholder while the flag is true, so the active-tab highlight and
spinner paint before the heavy per-tab render runs.
Total wait for events to display is unchanged; perceived responsiveness
of the click is now immediate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The first reactivity fix triggered a `_loadingMessages` flicker between
streaming chunks because every live re-fetch toggled the loading state
and replaced the conversation array wholesale. The silent flag suppresses
both: live re-fetches keep the loading indicator off and preserve the
last-good conversation on transient fetch errors. Initial threadId-change
fetches still show a real loading state.
Adds the same staleness guard the other tab fetches use (skip applying
results if `threadId` changed mid-flight) so a quick thread switch can't
leave the wrong thread's data on screen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The conversation view in cpk-thread-details only re-fetched on threadId
change, so live agent output during streaming wasn't visible until the
user switched threads (or unmounted/remounted the element by clicking
out and back in). Restoring the original conversationOverride channel
isn't viable because the parent's agentMessages map is keyed by agentId
(not threadId) and ConversationItem mapping lives in the child — passing
mapped messages would either leak across threads or duplicate the
runtime's AG-UI → ConversationItem conversion in the parent.
Instead, the parent now tracks each agent's currently-running threadId
(from `onAgentRunStarted`) and ticks a per-thread liveMessageVersion
counter every time `syncAgentMessages` fires for that agent. The counter
is passed to cpk-thread-details, which watches it in `updated()` and
re-fetches `/threads/:id/messages` when it changes for the same threadId.
The runtime endpoint is the single source of truth for conversation
shape, so streaming output flows in without any client-side mapping.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Type quality:
- intelligence/threads.ts handleGetThreadMessages: switch on the Message
discriminant (role) and read narrowed fields directly. Removes
`as Record<string, unknown>` laundering and chained `as` casts on
toolCalls/function/arguments. AssistantMessage's toolCalls always have
`function: { name, arguments }`, so the prior fallbacks (`tc.name`,
`tc.args`) were dead branches.
- in-memory.ts getThreadState: import StateSnapshotEvent and use it
instead of `(event as { snapshot?: unknown }).snapshot`.
Comment / API alignment:
- intelligence/threads.ts handleClearThreads JSDoc no longer claims the
inspector calls this; the actual caller is the demo button.
- in-memory.ts clearThreads JSDoc updated to match.
- in-memory.ts getThreadEvents JSDoc no longer references a SQLite
runner that does not exist; just describes the compaction logic.
- web-inspector lint fix: rename unused `changed` parameter to
`_changed` per oxc no-unused-vars.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>