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>
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>
Five behavioural fixes on cpk-thread-details / WebInspectorElement:
- fetchEvents/fetchState: mirror the AbortController pattern fetchMessages
already uses. Without this, switching threads quickly (A→B) can leave
the user looking at thread B with thread A's events/state when A's
request resolves last.
- mapMessages: when JSON.parse fails on tool-call args or tool result
content, log via console.error and attach __parseError + __raw on the
parsed object instead of silently substituting `{}`. The inspector is a
debugging surface; hiding malformed payloads defeats its purpose.
- fetchAnnouncement: keep the captured error and console.warn it. The
prior `catch {}` swallowed Malformed-payload throws, JSON parse
failures, and convertMarkdownToHtml exceptions silently.
- subscribeToThreadStore: also subscribe to ɵselectThreadsError, store
per-agent in `_threadsErrorByAgent`, and surface in renderThreadsView /
cpk-thread-list as an error state branch alongside empty/no-results.
Previously a thread-store load failure (REST list rejection, Phoenix
subscribe failure, retry exhaustion) left the user with stale data and
no indication of failure.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Picks up ag-ui-protocol/ag-ui#1578 — `import * as jsonpatch from
"fast-json-patch"` produced an empty namespace under Node native ESM
because fast-json-patch@3.x populates exports via Object.assign, which
the CJS→ESM named-export detector cannot see. Result: every STATE_DELTA
and ACTIVITY_DELTA event threw "applyPatch is not a function", and
LangGraph generative UI streams floods the console with the failure on
each patch.
0.0.53 switches to a default import so the emitted bundle works under
both ESM and CJS consumers. Bumped @ag-ui/core and @ag-ui/encoder in
lockstep since they share the release.
Replaces the locked-card UI inside the threads gate with the new
private-beta modal: kicker pill, gradient "Threads" title with body
copy, three checkmark bullets, dark MonoPillButton CTA that opens the
HubSpot waitlist form in a new tab, and a dashed divider over the
invite-code row.
Adds an "unlocking" confirmation card (gradient circle, "Welcome to
Threads", "Loading your conversations…") shown for ~2s after a valid
code before the real Threads UI is revealed.
Invalid-code state now flashes a red input border + "That code isn't
valid. Double-check your invite email." copy that auto-clears after
1600ms, matching the design.
Backdrop (gradient + blurred ellipses) is intentionally preserved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drawer-toggle button used to get pushed off-screen when the drawer was
open because the tab list shared flex space with it. Toggle now stays
visible at all times, anchored at the right edge of the tabs row, and
visually distinct from the tabs (vertical separator, lavender filled
state when active — no bottom-border indicator that would mistake it
for "another tab").
The drawer slides open/closed via a width transition (always rendered)
instead of popping in/out, and the resize divider floats over the
drawer's left edge so the toggle and drawer touch directly with no
flex-gap between them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the Angular-backed cpk-thread-list / cpk-thread-details custom
elements with native Lit implementations inside @copilotkit/web-inspector,
so React, Vanilla, and any other non-Angular consumer of the inspector
gets full functionality without pulling the Angular runtime. The
@copilotkit/web-inspector-angular package is deleted entirely, and the
Angular demo no longer calls defineInspectorElements.
Backend: adds GET /threads/:id/events and GET /threads/:id/state to the
runtime (in-memory runner path). The Intelligence path returns 501 with
a clear "not yet supported on this runtime" empty state — coordination
with the Intelligence team is tracked separately (CPK-7453).
Also guards attachToCore's getThreadStores call so consumers on an older
@copilotkit/core don't throw when assigning inspector.core, and drops
two unrelated showcase-whitelist lines that landed in
scripts/hooks/check-binaries.sh during a prior merge.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Change access code from "earlyaccess" to "easyaccess"
- Point "Request early access" button to HubSpot form
- Open HubSpot form in new window on successful code entry
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>
Remove all demo/scripted animation code (demoMode, demoThreads,
demoConversation, startDemoAnimation, etc.) from the clean PR branch.
Also fix duplicate onAgentRunStarted handler, remove unused
announcementMarkdown field, and remove dead getSelectedMenu method.
Co-Authored-By: Claude Sonnet 4.6 <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>
Extract shared helpers to reduce duplication and centralize unavoidable
casts. Add simulateSetState to mock controller to eliminate double-casts
in the new onStateChanged test. Promote mockClipboard to describe scope
to remove navigator clipboard cast in assertion. Add try/catch error
handling to syncAgentMessages and syncAgentState.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When using selfManagedAgents, calling agent.setState() directly only
fires onStateChanged — the inspector subscribed to onStateSnapshotEvent
(pipeline-only) but not onStateChanged, leaving the "Current State"
panel permanently stale. Add onStateChanged handler mirroring the
existing onMessagesChanged pattern.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wires the new Angular Custom Elements into the web inspector's Threads tab.
The tab is hidden behind an early-access gate (isEarlyAccessUser check);
_demoMode defaults to false. The inspector registers its own per-agentId
thread store unless one is already registered by useThreads(), preventing
accidental overwrites for apps that use both.
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).