Commit Graph

1601 Commits

Author SHA1 Message Date
Max Korp 8fe276eaa9 chore(deps): bump @copilotkit/license-verifier to 0.4.0
Updates runtime, shared, and root override pin from 0.2.0 to 0.4.0.
2026-05-07 09:20:37 -07:00
Alem Tuzlak ba60df5d33 feat(showcase/langgraph-python): add per-tool testids
Add stable testids and rendering surfaces for the three tool-rendering
cells so the e2e suite can distinguish each cell's strategy:

- tool-rendering: register useRenderTool for get_stock_price and
  roll_d20; new StockCard / D20Card components with testids
  stock-card / d20-card / stock-price / stock-change / d20-value.
  Rename FlightListCard testid flight-list-card -> flights-card.
- tool-rendering-default-catchall: drop the custom shadcn
  useDefaultRenderTool registration so the cell is truly zero
  custom-render-hooks. The framework's built-in
  DefaultToolCallRenderer now paints every tool call, with stable
  data-testid='copilot-tool-render' wrapper plus data-tool-name,
  data-args, and data-result attributes for inspection without
  expanding the card.
- tool-rendering-custom-catchall: rename the wildcard renderer's
  testids from custom-catchall-* to custom-wildcard-* so the cell
  is distinguishable from the (now-OOTB) default-catchall demo.
- packages/react-core: when no per-tool / wildcard renderer is
  registered, useRenderToolCall now falls back to the built-in
  DefaultToolCallRenderer instead of returning null.
2026-05-07 17:55:02 +02:00
Markus Ecker 60db1b7a8e feat(core): registerProxiedAgent — explicit multi-agent mounting against one runtime agent (#4629)
## Summary

Replaces the implicit per-thread agent cloning from #3525 / #3630 with
an explicit registration API. Three commits:

1. **revert** — strips the cloning machinery (`useAgent({ threadId })`
per-thread clones, `getThreadClone`, `globalThreadCloneMap`,
`cloneForThread`), the inspector hooks added only to handle clones
(`onAgentRunStarted` from #3869, the connect-time emission from #3872,
the `agentRunThreadId` map), the state-manager `isClone` composite-key
path, and the `consumerAgent` parameter on `SuggestionEngine`. Restores
`agent.threadId = resolvedThreadId` in `CopilotChat` (pre-#3525
behavior). Re-opens issue #2957 (CPK-7155): two `<CopilotChat>`
instances sharing an `agentId` will share state again.

2. **feat** — adds `CopilotKitCore.registerProxiedAgent({ agentId,
remoteAgentId })` which mints a `ProxiedCopilotRuntimeAgent` under a
local registry id and routes its outbound HTTP requests to the named
runtime agent. Returns `{ agent, unregister }` for React `useEffect`
cleanup. Throws on duplicate `agentId` (collisions with
`agents__unsafe_dev_only` or another `registerProxiedAgent` are loud,
not silent).

`ProxiedCopilotRuntimeAgent` gains a `remoteAgentId` field used only for
outbound routing — URL paths (`/agent/<id>/run`, `/connect`, `/stop`),
single-route envelopes, and the `IntelligenceAgent` delegate's
`agentId`. The local `agentId` remains the registry key and source of
truth for state-manager subscriptions, `useAgent` lookups, and
`onAgentsChanged`. So multiple proxies (`chat-1`, `chat-2`) targeting
the same runtime agent (`default`) don't cross-talk in any subscriber
bookkeeping.

3. **test** — re-adds the isolation coverage that the revert deleted,
rewritten against the explicit-registration model (10 tests total).

## Usage

```tsx
const { copilotkit } = useCopilotKit();
useEffect(() => {
  const { agent, unregister } = copilotkit.registerProxiedAgent({
    agentId: "chat-1",          // local registry id (subscriber bookkeeping)
    remoteAgentId: "default",   // runtime id (URL routing only)
  });
  return unregister;
}, [copilotkit]);
// then <CopilotChat agentId="chat-1" />
```

## Test plan

- [x] `nx run @copilotkit/core:test` — **424 passed** (was 409 + 15 new
across feat & test commits)
- [x] `nx run @copilotkit/react-core:test` — **1151 passed** (was 1150 +
1 new)
- [x] `nx run @copilotkit/web-inspector:test` — **7 passed**
- [x] Build: `core`, `react-core`, `web-inspector` all clean

### New tests cover

- routing — proxy at `agentId` routes outbound to `remoteAgentId`; URL
path encodes the remote id, body envelopes use the remote id
- duplicate-throw — register twice with same `agentId` throws (whether
against another registered proxy or an `agents__unsafe_dev_only` entry)
- idempotent `unregister` — calling twice doesn't throw or re-emit;
stale handles don't strip replacements
- `onAgentsChanged` notification on register and unregister
- header inheritance from core
- two proxies → same `remoteAgentId` are distinct instances with
isolated messages, isolated state, independent threadIds, but shared
outbound URL
- `getAgent` returns the same proxy instance across calls (no per-call
clone)
- registering before runtime connects yields a proxy that's still usable
for in-memory ops
- registering with a `remoteAgentId` the runtime doesn't yet expose
still works for local bookkeeping
- re-register after unregister yields a fresh proxy (no carry-over)
- regression: activity renderers receive the agent under the local
`agentId`, never another agent in the registry (replaces the deleted
clone-vs-registry trap test)

## Caveats

- Issue #2957 (CPK-7155) is re-opened by the revert. The new API is the
supported path forward — callers that previously relied on `useAgent({
threadId })` for isolation should move to `registerProxiedAgent`.
- Runtime `/info` sync still uses last-write-wins, not throw, on
collisions with manually-registered ids. Easy follow-up if we want
strict throw there too.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-05-07 15:27:00 +02:00
github-actions[bot] 657077b4f2 style: auto-fix formatting 2026-05-07 12:35:14 +00:00
Markus Ecker 12f2c0e734 refactor(runtime): rename cki → cpki and use INTELLIGENCE_USER_ID_HEADER constant
Two CR comments addressed:

- Rename the local destructure of forwardedProps.auth.copilotkitIntelligence
  from 'cki' to 'cpki' so it matches the project-wide abbreviation already
  used in metadata fields (cpki_event_id, cpki_event_seq, etc).

- Replace the inline 'X-Cpki-User-Id' string literal with the existing
  INTELLIGENCE_USER_ID_HEADER constant exported from intelligence-platform/client.
  Applies to the runtime auto-attach in agent/index.ts and to the three
  test sites in intelligence-mcp-helper.test.ts so the user-side and
  runtime-side stay in sync.
2026-05-07 14:31:59 +02:00
Markus Ecker 3e7449dc4c Merge remote-tracking branch 'origin/main' into lukas/cpk-7526-copilotkitruntimev2-configurable-mcp-server-on-builtinagent 2026-05-07 14:28:27 +02:00
Markus Ecker 79fb5af452 Merge remote-tracking branch 'origin/main' into mme/register-proxied-agent
# Conflicts:
#	packages/react-core/src/v2/hooks/__tests__/use-agent-thread-isolation.test.tsx
#	packages/react-core/src/v2/hooks/use-agent.tsx
#	packages/web-inspector/src/styles/generated.css
2026-05-07 14:20:55 +02:00
Markus Ecker 6ed400c776 refactor(runtime): nest Intelligence MCP credentials under forwardedProps.auth.copilotkitIntelligence
Move the per-request Intelligence MCP bag from
forwardedProps.copilotkitIntelligence to
forwardedProps.auth.copilotkitIntelligence so the Intelligence-side
redaction policy strips it. The 'auth' namespace is the convention for
credentials; persistence sinks (Postgres, Redis, S3) and FE replay
paths in apps/realtime-gateway already strip everything under it.

Updates:
- Emitter (handlers/intelligence/run.ts): merge the bag into a single
  forwardedProps.auth object alongside any upstream auth keys, and
  only emit the auth namespace when there is something to put in it.
- Reader (agent/index.ts): read from forwardedProps.auth.copilotkitIntelligence
  instead of forwardedProps.copilotkitIntelligence.
- Tests (intelligence-mcp-helper.test.ts): three fixtures rewritten
  to the nested shape.
2026-05-07 10:55:02 +02:00
Jordan Ritter 164897647e feat: add @copilotkit/react-native package (#3633)
## Summary

- Extract `CopilotKitContext` and `useCopilotKit` into standalone
`context.ts` in react-core, enabling cross-platform reuse without web
dependencies
- Add new `@copilotkit/react-native` package with lightweight provider,
polyfills, and streaming fetch
- All hooks (`useAgent`, `useFrontendTool`, `useHumanInTheLoop`, etc.)
are re-exported directly from react-core — no reimplementation

## Motivation

CopilotKit's React hooks are platform-agnostic, but the barrel import in
`@copilotkit/react-core` pulls in web-only dependencies (Radix UI, Lit,
A2UI renderer, react-dom, CSS). This makes the package unusable in React
Native without extensive Metro shimming.

By extracting the React context into a standalone entry point
(`@copilotkit/react-core/v2/context`), the new
`@copilotkit/react-native` package can provide its own lightweight
provider while reusing all existing hooks.

## What's in `@copilotkit/react-native`

| Export | Description |
|--------|-------------|
| `CopilotKitProvider` | Lightweight provider — no DOM, CSS, Radix, Lit,
or A2UI deps |
| `installStreamingFetch()` | XHR-based streaming fetch for
`response.body.getReader()` support |
| `@copilotkit/react-native/polyfills` | All polyfills at once
(ReadableStream, TextEncoder, crypto, DOMException, window.location) |
| `@copilotkit/react-native/polyfills/*` | Granular per-polyfill imports
(`/streams`, `/encoding`, `/crypto`, `/dom`, `/location`) for users who
need to avoid overriding their own shims |
| `useAgent`, `useFrontendTool`, etc. | Re-exported from react-core
(shared context) |

## Usage

```tsx
// index.js (entry point, before other imports)
import "@copilotkit/react-native/polyfills";
import { installStreamingFetch } from "@copilotkit/react-native";
installStreamingFetch();

// App.tsx
import { CopilotKitProvider, useAgent, useCopilotKit } from "@copilotkit/react-native";

function App() {
  return (
    <CopilotKitProvider runtimeUrl="https://your-server/api/copilotkit">
      <ChatScreen />
    </CopilotKitProvider>
  );
}
```

## Test plan

- [x] `nx run react-core:build` passes
- [x] `nx run @copilotkit/react-native:build` passes
- [x] `nx run react-core:test` — all 1153 tests pass
- [x] Manual test in React Native app (tested during development with
bare RN 0.84 project)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-05-06 16:46:38 -07:00
Maxim 9328de4a40 chore: wire @copilotkit/react-native into release system
Add to monorepo scope in release.config.json. Set version to 1.56.5,
correct ESM extensions in exports map, add check-types/publint/attw
scripts, align tsdown to ^0.20.3. Add react-native example glob to
pnpm-workspace.yaml. Regenerate lockfile preserving zod@3 and
langchain dependency versions.
2026-05-06 16:42:43 -07:00
Maxim e9651b0c74 docs: add React Native documentation and package README
Package README with installation, polyfill setup, and quick start.
Docs page covering prerequisites, provider wrapping, granular
polyfills, known limitations, and troubleshooting.
2026-05-06 16:42:34 -07:00
Maxim 31061a1d23 test: add react-native tests and update react-core test imports
93 tests for react-native: streaming-fetch (36), provider (17),
polyfills (20), headless integration (9 provider + error boundary).
Update react-core test vi.mock paths from providers/CopilotKitProvider
to context module. Update useAgent throttle tests for batched
forceUpdate.
2026-05-06 16:42:15 -07:00
Maxim c3c30969e4 refactor: extract react-core context and headless hook exports
Extract CopilotKitContext, useCopilotKit, and LicenseContext into
src/v2/context.ts. Add src/v2/headless.ts barrel export for
platform-agnostic hooks. Add v2/context and v2/headless entry
points to tsdown config and package.json exports. Update all hook
imports to use the new context module. Always subscribe to onError
in web provider (matching RN pattern). Use batchedForceUpdate for
onMessagesChanged. Replace extraDeps spread with JSON.stringify in
useFrontendTool and useRenderTool dependency arrays.
2026-05-06 16:42:04 -07:00
Maxim 94c9c1d4c3 feat: add @copilotkit/react-native package
Headless React Native wrapper for CopilotKit. Provides a lightweight
CopilotKitProvider and re-exports platform-agnostic hooks from
react-core without pulling web dependencies (no DOM, CSS, Radix,
Lit, A2UI). Includes XHR-based streaming fetch polyfill for Hermes
and granular polyfills for streams, encoding, crypto, DOM, and
location APIs.
2026-05-06 16:41:51 -07:00
Markus Ecker a83acc7fb8 fix(react-core): IntelligenceIndicator matches the renamed copilotkit_knowledge_base_shell tool
The Intelligence platform's MCP tool was renamed from `bash` to
`copilotkit_knowledge_base_shell` (intelligence/mme/integrate-sl
4256c13). Update the indicator's `DEFAULT_TOOL_PATTERNS` to match the
new name so the pill keeps rendering on the right assistant slots.

Test fixtures that previously used the bare `bash` name follow the
rename — both the default in `emitAssistantMessageWithToolCalls` and
the explicit `tc_match` entry in the tool-match condition test.
2026-05-06 17:33:45 +02:00
Markus Ecker faa2a556d0 test(runtime): cover Intelligence MCP auto-attach via forwardedProps
Four cases at the agent layer (BuiltInAgent reading forwardedProps):
  * attaches when `copilotkitIntelligence` carries all three strings
    (userId, apiKey, mcpUrl) — outbound headers carry Authorization +
    X-Cpki-User-Id.
  * does NOT attach when the bag is absent (no Intelligence wiring on
    this run).
  * does NOT attach when the bag is partial (e.g. mcpUrl missing).
  * does NOT attach when the user has already configured an MCP server
    pointing at the same URL — explicit user config wins, with the
    user's headers and resolver hitting the wire.
2026-05-06 16:16:14 +02:00
Markus Ecker a46acc3f94 feat(runtime/agent): auto-attach Intelligence MCP server via input.forwardedProps
Runtime side (intelligence/run.ts): when `runtime.intelligence.mcpServer`
is enabled, build a `copilotkitIntelligence` bag carrying the resolved
user-id, project apiKey, and the platform MCP URL, and pass it through
on `RunAgentInput.forwardedProps` to the agent. Skipped when the flag
is off — runs that don't go through this Intelligence path simply
don't see the bag.

Agent side (BuiltInAgent's run code): if `forwardedProps.copilotkitIntelligence`
contains all three string values AND the user's static `config.mcpServers`
doesn't already include the same URL, append a per-request
`MCPClientConfigHTTP`. Its `options.fetch` closes over apiKey + userId
and stamps `Authorization: Bearer <apiKey>` and `X-Cpki-User-Id:
<userId>` on every outbound MCP call. The custom fetch is the MCP
TypeScript SDK's documented extension point for per-request header
injection — no extra wrapper class, no separate framework concept.

The agent class is otherwise untouched: no new fields, no per-request
side channels, no typed reference to `CopilotKitIntelligence`. Other
agents that don't read `forwardedProps.copilotkitIntelligence` ignore
the bag.

Pulls in the AI SDK's stable `createMCPClient` export (rename from
`experimental_createMCPClient`) — the experimental name was deprecated;
`mcp-clients.test.ts`'s mock setup follows.
2026-05-06 16:15:57 +02:00
Markus Ecker 1de2fae369 feat(runtime): CopilotKitIntelligence.mcpServer opt-in flag + ɵ-accessors
Adds `mcpServer?: boolean` to `CopilotKitIntelligenceConfig` (default
`false`). When true, the runtime emits the per-request bag the agent
needs to attach the platform's MCP server.

Internal accessors `ɵisMcpServerEnabled()` and `ɵgetApiKey()` round
out the existing `ɵgetApiUrl()`. Used by the runtime layer in the
forthcoming auto-attach commit; not part of the public surface.
2026-05-06 16:15:21 +02:00
Markus Ecker 44cb538856 feat(runtime/v2): re-export MCPClient and MCPTransport from @ai-sdk/mcp
Convenience re-exports so consumers wiring custom MCP clients (via
`mcpClients`) or custom transports don't need to add `@ai-sdk/mcp`
to their dependencies just to type the values.
2026-05-06 16:15:06 +02:00
Markus Ecker 5b11748065 docs(react-core): refresh IntelligenceIndicator auto-mount comment
The previous comment referenced a "200 ms poll interval" that the
indicator no longer uses (polling was removed when we switched to the
tool-call pending-grace timer). Updates the rationale to mention the
current self-gates (latest matching-assistant slot + pending grace
window).
2026-05-06 12:18:14 +02:00
Markus Ecker ee1856f1ba refactor(react-core): minimize MemoizedCustomMessage delta to just the auto-mount
Reverts the cosmetic changes that crept in alongside the auto-mount
edit — restores `stateSnapshot?` (always passed at runtime, no functional
difference), the original concise comments around the memo's comparison
function, and the original combined value+type imports. The only
remaining framework change in this PR is the new auto-mount block:
when `copilotkit.intelligence !== undefined` and the message is an
assistant message, push an `<IntelligenceIndicator>` after the message
slot.

Indicator e2e suite still green (10/10).
2026-05-06 12:16:32 +02:00
Markus Ecker af9ea8b987 refactor(core,docs): rename ProxiedCopilotRuntimeAgent.remoteAgentId → runtimeAgentId
Renames the proxy-config field, the field on the agent instance, and
all matching references in tests and the useCopilotKit reference page.
"runtime" reads more naturally now that the proxy concept is documented
as "a local agent that delegates to a runtime agent" rather than
"remote agent" — the latter conflates with `remoteAgents` (the
registry of agents fetched from the runtime), which keeps its name.

No behavioral change; the field still controls the outbound REST URL
used by the proxy.
2026-05-06 12:01:37 +02:00
Markus Ecker b63602241d refactor(react-core): drop MemoizedCustomMessage's run-state inputs
Removes the numberOfMessagesInRun, isInLatestRun, and isRunning props on
MemoizedCustomMessage along with the per-render runMetadata derivation
that fed them. Authored renderers observe run state via useAgent's
OnRunStatusChanged / OnMessagesChanged subscriptions, which forceUpdate
the renderer independently of the memo's bail-out — the extra
invalidation inputs added nothing for that canonical path and only
masked staleness for renderers that read run state from closure
without subscribing.

The IntelligenceIndicator itself uses useAgent and remains correct.

Net change: ~95 lines removed; one less O(n) scan through messages per
chat re-render. All chat e2e tests (587) pass, including the indicator
suite (10).
2026-05-06 11:57:44 +02:00
Markus Ecker 8623c4a9b5 fix(react-core): IntelligenceIndicator drops polling, gates on tool-call pending window
Replaces the agent.isRunning-driven phase entry (and its 200 ms polling
fallback) with a 100 ms grace timer on unresolved matching tool calls.
Replay flashes (tool call + result in the same tick during connectAgent
history hydration) no longer cross the threshold, so the pill stops
appearing on completed historical runs.

Spinner exits as soon as either agent.isRunning falls or a "real
follow-up" message arrives — assistant prose, a fresh user turn, or
anything that isn't a tool result / empty-content tool-call wrapper.
Multi-step tool chains stay on a single continuous pill (the
latest-matching-assistant slot still moves between messages without a
fade animation when the next bash assistant lands).

Polling and the snapshot-subscriber comment were a misdiagnosis of a
test artifact: useAgent's OnRunStatusChanged subscription is what the
rest of CopilotKit (CopilotChat stop button, MCPAppsActivityRenderer,
chat suggestions) relies on for isRunning falling-edge re-renders.

Tests cover three new cases: replay-flash suppression, multi-step
continuity across tool-result interleaving, and exit-on-prose-followup.
2026-05-05 17:57:41 +02:00
Lukas Moschitz 7baf4d05e8 fix(react-core): IntelligenceIndicator pill shrinks to content width
The auto-mount in `CopilotChatMessageView` puts the indicator inside
a flex column container (`cpk:flex cpk:flex-col`) whose default
`align-items: stretch` was overriding the pill's intended
`display: inline-flex` shrink-to-content behaviour, leaving the
pill stretched to the full chat width — out of proportion with the
short label.

Add `align-self: flex-start` to opt the pill out of the parent's
stretch. Pill renders at content width, anchored to the chat's
left edge in line with the assistant message bubble it represents.
2026-05-05 17:28:01 +02:00
Lukas Moschitz ddb9e244f6 fix(react-core): IntelligenceIndicator no longer depends on getRunIdForMessage
Two SDK gaps surface in real MCP recall flows that the previous gate
revision still tripped on:

- The bash-issuing assistant message is consistently missing from
  `stateManager.messageToRun` even though it is the message the
  indicator needs to attach to. The first gate
  `if (!messageRunId) return null;` fired before any of the slot
  logic ran, so the pill never rendered.
- The threadId key in `messageToRun` can drift out of sync with the
  chat configuration's threadId — same lookup, same null, same gate.

Drop the run-id dependency entirely. The indicator only needs
`agent.messages` and `message.role` / `message.toolCalls`, both of
which the runtime populates correctly in every observed flow. The
walk just finds the latest assistant-with-matching-tool-call across
`agent.messages`; tool result messages (`role: "tool"`) and prose-
only assistants are skipped without invalidating the slot.

Cross-run isolation moves to the phase machine: once an indicator
reaches `phase === "hidden"` it stays there. A later run on the
same chat does not resurrect a faded pill; the new run mounts fresh
indicator instances on its own assistant messages.

Net behaviour:
- Through a multi-step tool chain the pill stays put on the bash-
  issuing assistant.
- When the run finishes, the existing 500 ms debounce -> 800 ms
  check-hold -> 480 ms fade lifecycle plays out unchanged, then
  hidden becomes terminal.
- Subsequent runs are independent: their first assistant-with-tool-
  call message becomes the new canonical slot.
2026-05-05 17:28:01 +02:00
Lukas Moschitz 960ab91c13 fix(react-core): IntelligenceIndicator stays through tool-result interleaving
The pill's gate "the message must be the last message of its run" was
suppressed every time a `role: "tool"` result arrived between
successive assistant-with-tool-call messages. Real MCP recall flows
always interleave tool results between assistant tool-call messages,
so the assistant message holding the matching tool call lost its
"last in run" claim immediately, the indicator returned `null`, and
the pill flashed off. By the time the run finished, the final
prose-only assistant message was the last in the run and the pill
on the bash-bearing assistant stayed suppressed. Net result: the
user saw no pill at all during a real recall.

Change the gate to "the latest assistant-with-matching-tool-call
message in the run". Tool result messages (`role: "tool"`) and
prose-only assistant messages now skip through the walk without
invalidating an earlier matching-assistant's claim on the slot, so
the pill stays continuously through a multi-step tool chain and
transitions to checkmark on `isRunning` falling (debounced 500 ms,
unchanged) as before.

The existing test suite did not cover this case — none of the
walkthrough scenarios emit `role: "tool"` between successive
assistant messages. A regression test that interleaves a tool result
will land alongside this fix.
2026-05-05 17:28:01 +02:00
Martha Kelly Schumann d97b27c195 Merge branch 'main' into release/publish/monorepo/v1.57.0 2026-05-05 05:04:57 -07:00
Tyler Slaton 102b5a9e1f feat(inspector): remove invite-code gate on Threads
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>
2026-05-04 14:17:34 -07:00
Tyler Slaton aa394bbc9d fix(inspector): restore production announcement URL again
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>
2026-05-04 13:28:29 -07:00
Tyler Slaton ecc54cca9c fix(inspector): scope copy-button reset timeouts per button
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>
2026-05-04 13:26:51 -07:00
Tyler Slaton 55b25629a4 fix(inspector): harden announcement copy-button helpers and renderer
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>
2026-05-04 13:21:37 -07:00
Tyler Slaton d45b04b605 refactor(inspector): reuse module-level escapeHtml in announcement renderer
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>
2026-05-04 13:15:42 -07:00
Tyler Slaton c6f0fbe5dc fix(inspector): restore production announcement URL
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 13:05:39 -07:00
Tyler Slaton cbc1aa6ab8 fix(inspector): polish announcement banner styles and move into scroll area
- 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>
2026-05-04 12:52:38 -07:00
tylerslaton 490440a0e4 chore: release monorepo v1.57.0 2026-05-04 17:33:59 +00:00
Markus Ecker e8192a1d52 fix(react-core): address CR-loop findings on IntelligenceIndicator
Round-1 review on the indicator branch surfaced perf and defensive
hardening items. Tests: react-core 1158 — all green.

- CopilotChatMessageView auto-mount now gates on
  `message.role === "assistant"` in addition to
  `copilotkit.intelligence !== undefined`. Eliminates wasted
  `useAgent` subscriptions, 200 ms polling intervals, and four
  `useEffect`s on every user / activity / reasoning slot — the
  indicator's own role gate would short-circuit anyway, but only
  after a subscribe + interval-set + cleanup cycle on every render.

- IntelligenceIndicator's `toolCalls` access is now defensive:
  `Array.isArray(...)` guard and `tc?.function?.name` chain. A
  malformed agent payload no longer crashes the chat tree at
  `.some(...)`.

Comment fixes:

- CopilotChatMessageView: stale `CopilotKitProvider.intelligenceIndicator.e2e.test.tsx`
  reference updated to the actual path
  `intelligence-indicator/__tests__/IntelligenceIndicator.e2e.test.tsx`.

- IntelligenceIndicator `ISRUNNING_POLL_MS` JSDoc rewritten — the
  prior version claimed `addMessage` iterates subscribers live during
  streaming. In fact AG-UI's `runAgent` snapshots subscribers and
  threads them through the entire pipeline (including
  `processApplyEvents` for streaming events), so a late-mounted
  subscriber misses both `onMessagesChanged` AND `onRunFinalized`
  from the run's pipeline. The poll fallback is the only thing that
  catches the falling edge.

- `globals.css` pill-styles port comment listed `#BEC2FF` as part of
  the palette but that hex doesn't appear anywhere in the rules.
  Updated to the actual swatches: text #5B21B6, icon #7C3AED, border
  #9599E0, gradient #EEE6FE, shadow #5E64AD.
2026-05-04 17:39:06 +02:00
Markus Ecker 678d143b27 feat(react-core): IntelligenceIndicator auto-mounts when intelligence is configured
Adds an official "Using CopilotKit Intelligence" pill, ported from the
visuals of CopilotKit/Intelligence#155. Mounts automatically — the
caller never adds the renderer themselves.

Behavior:

- `CopilotChatMessageView` mounts an `<IntelligenceIndicator>` for
  every message slot whenever `copilotkit.intelligence !== undefined`.
  When intelligence is not configured, no indicator instance is
  mounted at all (no perf cost).

- `IntelligenceIndicator` self-gates so only the canonical message
  renders a pill — last message of the latest in-flight run, with at
  least one tool call whose name matches a pattern from
  `DEFAULT_TOOL_PATTERNS` (currently `[/^bash$/]`, the Intelligence
  MCP server's canonical tool).

- The "exactly one pill at any moment" guarantee is structural: only
  one message ever satisfies (last in run) + (run is latest) +
  (matching tool call), so each renderer invocation decides
  independently and the result is one pill in the DOM.

Phase machine (per-instance, all timers local):
  - `spinner` while `agent.isRunning`
  - → `check` after `agent.isRunning` falls (debounced 500 ms to
    absorb step-boundary `RUN_FINISHED → RUN_STARTED` blips inside
    one user turn)
  - → `fading` after `CHECK_HOLD_MS` (800 ms)
  - → `hidden` after `FADE_OUT_ANIMATION_MS` (480 ms)

A 200 ms `agent.isRunning` poll closes the AG-UI snapshot-subscriber
gap (subscribers added INSIDE a run never see that run's
`onRunFinalized`).

Public surface (via `@copilotkit/react-core/v2`):

- `IntelligenceIndicator` — the pill component, exposed for tests
  and inspection. Most callers don't import it directly; the
  auto-mount in `CopilotChatMessageView` does the work.

There is no factory and no provider — auto-registration eliminates
the prior `createIntelligenceIndicatorRenderer` factory and any
`IntelligenceIndicatorProvider`/coordination store.

Tests (all live next to the component):

- 1 walkthrough (Run A → Run B with multiple messages, asserts the
  pill follows the canonical "last message of latest in-flight run"
  slot through every phase, with no `renderCustomMessages` prop on
  the test setup)
- 4 condition tests (last-in-run / in-flight / latest-run /
  tool-match), each pinning one gate
- 1 intelligence-gate test (no pill when `copilotkit.intelligence`
  is undefined)
- 1 explicit auto-registration assertion (no `renderCustomMessages`
  prop is required for the pill to render)

7 tests, react-core 1157 → 1158.
2026-05-04 17:26:11 +02:00
Markus Ecker 7829191350 test(react-core): intelligence-indicator renderer + MemoizedCustomMessage gating signals
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.
2026-05-04 17:26:11 +02:00
Markus Ecker 8436b29205 fix(core): round-2 cleanup — guard cleanup loops, drop dead code
Round-2 review surfaced two cheap improvements; this commit lands them.
All 7 round-1 findings were confirmed fixed by round 2.

- core.ts onAgentsChanged: each iteration of the unregister loops is
  now wrapped in try/catch. A throw on iteration [0] no longer stalls
  cleanup for [1..n]; both registries' unregister paths are
  idempotent so re-attempts on the next onAgentsChanged are safe.

- agent.ts abortRun: removed dead `if (!routedId) return;` after
  `routedAgentId()` — that method now throws (or returns a non-empty
  string), so the guard is unreachable.

Tests: core 425 — all green.
2026-05-04 17:25:27 +02:00
Markus Ecker 9abce2c9bc fix(core,react-core): address CR-loop findings on registerProxiedAgent + cloning revert
Round-1 review found seven actionable items; this commit lands fixes for
all of them. Tests: core 425, react-core 1151, web-inspector 7 — all green.

Real bugs fixed:

- run-handler.ts: dropped the stale `agent` argument on the
  `reloadSuggestions(agentId, agent)` call. The signature was tightened
  to `(agentId)` when the consumerAgent parameter was removed; the call
  site wasn't updated, leaving a TS-2554 build break.

- agent.ts: tightened `routedAgentId(): string` to throw when both
  `agentId` and `remoteAgentId` are unset, instead of returning
  `string | undefined`. Removes two `!` non-null asserts in
  `#runViaHttp` / `#connectViaHttp` and the silent
  `/agent/undefined/connect` URL path.

- agent.ts: marked `remoteAgentId` `readonly`. The field was publicly
  mutable but `super.url` is baked at construction — mutating
  `remoteAgentId` post-construction silently desyncs the REST run URL
  from the routing decision elsewhere. `readonly` prevents.

- agent-registry.ts: the registerProxiedAgent collision check now uses
  `Object.prototype.hasOwnProperty.call(this._agents, agentId)` instead
  of `agentId in this._agents`. The `in` operator walks the prototype
  chain, so an agentId of `"__proto__"`, `"constructor"`, etc. would
  falsely test as already-registered.

- core.ts: the onAgentsChanged handler now mirrors the thread-store
  unregister loop with a parallel
  `stateManager.unsubscribeFromAgent(agentId)` for any agentId in
  previousAgentIds but absent from the current snapshot. Without this,
  `unregister()`'s state-manager subscription leaked.

Comment / test cleanup:

- CopilotChatView.tsx:92: stale "empty cloned agent" reference in the
  `isConnecting` JSDoc rewritten to "empty agent instance" — clones are
  gone.

- core-register-proxied-agent.test.ts: split the misleading "registering
  before runtime connects yields a proxy in pending runtimeMode" test
  (which exercised the no-runtimeUrl path, never the pending path) into
  two: one for the no-runtimeUrl case, one that actually constructs a
  core with a runtimeUrl and asserts `runtimeMode === "pending"`.
2026-05-04 17:20:37 +02:00
github-actions[bot] 7fe0ffd602 style: auto-fix formatting 2026-05-04 12:08:05 +00:00
Markus Ecker 0332c3c697 test(core,react-core): port isolation regression tests to registerProxiedAgent
Re-adds the isolation coverage that the per-thread cloning revert deleted,
rewritten against the explicit-registration model:

- 9 new core-level tests in core-register-proxied-agent.test.ts cover the
  cases from the deleted use-agent-thread-isolation.test.tsx — distinct
  instances when two proxies target the same remoteAgentId, message and
  state isolation between proxies, independent threadId per proxy, shared
  outbound URL (both route to remoteAgentId), getAgent identity, pending
  registration before runtime connect, registration with a remote id the
  runtime doesn't yet know, and re-register-after-unregister yielding a
  fresh proxy.

- 1 new react-core test in CopilotChatActivityRendering.e2e.test.tsx
  replaces the deleted "passes the per-thread clone to activity message
  renderers" regression test. The clone-vs-registry trap is gone in the
  new model, so the test is reframed: the renderer must receive the agent
  registered under the local agentId, not any other agent in the registry
  (e.g. the runtime-side id a proxy might route to).

Total: +10 tests. Core 415 → 424, react-core 1150 → 1151.
2026-05-04 13:57:58 +02:00
Markus Ecker e576bc16b7 feat(core): add registerProxiedAgent for mounting frontend agents against runtime agents
Adds a public CopilotKitCore.registerProxiedAgent({ agentId, remoteAgentId }) API
that mints a ProxiedCopilotRuntimeAgent under a local registry id and routes its
outbound HTTP requests to the named runtime agent. Returns { agent, unregister }
so React callers can clean up via useEffect.

Throws when agentId is already taken — collisions with agents__unsafe_dev_only or
a previous registerProxiedAgent are loud, not silent.

ProxiedCopilotRuntimeAgent gains a remoteAgentId field used only for outbound
routing — URL paths (/agent/<id>/run, /connect, /stop), single-route envelopes,
and the IntelligenceAgent delegate's agentId. The local agentId remains the
registry key and the source of truth for state-manager subscriptions, useAgent
caching, and onAgentsChanged. So multiple proxies (e.g. chat-1, chat-2) can
target the same runtime agent ("default") without cross-talk in any subscriber
bookkeeping.

Use case: replaces the implicit per-thread cloning previously offered by
useAgent({ threadId }). Callers now opt into multiple frontend agents
explicitly.

Includes 6 unit tests covering routing, duplicate-throw, idempotent unregister,
onAgentsChanged notification, and header inheritance.
2026-05-04 13:37:20 +02:00
Markus Ecker 762370a4e5 refactor: remove per-thread agent cloning, restore single registry agent per id
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.
2026-05-04 13:27:04 +02:00
Tyler Slaton ac75fc36f1 feat(inspector): CPK-7193 thread store registry, runtime handlers, and Threads tab UI (#3869)
## What this PR does

Adds the **Threads tab** to the CopilotKit web inspector. The tab lists
every thread the current agent has run and, when you click one, shows
three per-thread sub-tabs:

- **Conversation** — historical messages
- **Agent State** — state snapshot at the end of the thread
- **AG-UI Events** — full AG-UI event stream for the thread (tool calls,
state deltas, text chunks, etc.)

Data flows through new backend endpoints plus a Lit-based UI inside
`@copilotkit/web-inspector`. No extra package required for consumers.

## Changes by layer

### `@copilotkit/core`
- **`ThreadStoreRegistry`** — new class; keyed by `agentId`, lets
`useThreads()` and the inspector share a reference to the same store
without coupling the two packages directly
- **`onAgentRunStarted` subscriber event** — fires before
`agent.runAgent()` snapshots the subscriber list, so the inspector can
subscribe in time to receive run events
- `CopilotKitCore.getThreadStore()` / `registerThreadStore()` /
`unregisterThreadStore()` / `getThreadStores()` — public surface for
hook + inspector to interact with the registry

### `@copilotkit/runtime`
- **Thread HTTP handlers** — `handleListThreads`, `handleUpdateThread`,
`handleArchiveThread`, `handleDeleteThread`, `handleSubscribeToThreads`,
`handleGetThreadMessages`, plus the two new ones below
- **New: `GET /threads/:id/events`** and **`GET /threads/:id/state`** —
return the thread's AG-UI event stream and last `STATE_SNAPSHOT`
payload. Wired through both the in-memory runner and the Intelligence
platform's `_inspect/threads/:id/{events,state}` endpoints (consumed by
`CopilotKitIntelligence.getThreadEvents()` / `getThreadState()`)
- All mutations authenticate via `identifyUser(request)`; `userId` in
the request body is ignored
- **`InMemoryAgentRunner`** — stores thread history (messages +
compacted events per run); new `getThreadEvents()` and
`getThreadState()` methods; `getThreadState()` walks the compacted
events and returns the payload of the last `STATE_SNAPSHOT`

### `@copilotkit/react-core`
- **`useThreads` hook** — fetches threads, subscribes to a Phoenix
WebSocket channel for real-time metadata events, and
registers/unregisters its thread store with `CopilotKitCore` on
mount/unmount

### `@copilotkit/web-inspector`
- **Full Threads tab UI** — implemented in Lit as two custom elements
(`cpk-thread-list`, `ɵCpkThreadDetails`) living in-file alongside the
main `WebInspectorElement`
- Thread details fetches per-thread history via the new endpoints and
renders:
- Conversation: user/assistant bubbles, tool-call blocks with
expand/collapse, tool-call groups, reasoning/state-update chips,
generative-UI placeholders. Tool-call status is derived from parsed-args
presence — frontend-rendered generative-UI tools (charts, custom UI)
read `DONE` once args have streamed in, since they never produce a
`role: tool` result message
  - Agent State: syntax-highlighted JSON of the last state snapshot
- AG-UI Events: colored event rows (by type family) with timestamped,
highlighted payloads. Off-screen rows use `content-visibility: auto` so
reveal cost is independent of total event count
- Right-side detail panel with thread metadata + activity counts,
toggled from the tab bar
- Tab DOM is mounted once per activation and hidden via `display:none`
when inactive, so switching between Conversation / Agent State / AG-UI
Events is a CSS swap rather than a render. Each panel's TemplateResult
is memoized by tuple of input references (`_conversation` + expand-state
Sets for conversation; `_fetchedState` for agent state; events array for
AG-UI events), so when the underlying data hasn't changed Lit's diff
short-circuits. JSON syntax highlighting is WeakMap-memoized by payload
reference
- `attachToCore()` guards the `core.getThreadStores()` call so consumers
still on an older `@copilotkit/core` don't throw when assigning
`inspector.core`

## Architectural notes

**Events/state via Intelligence:** the Intelligence platform persists
every AG-UI event in `cpki.run_events` keyed by run → thread and exposes
them via `_inspect/threads/:id/{events,state}`. The runtime's
`CopilotKitIntelligence.getThreadEvents()` / `getThreadState()` consume
those, so the same per-thread HTTP endpoints used by the in-memory path
serve Intelligence-backed consumers identically.

## Tests added

| File | What's new |
|---|---|
| `packages/core/src/__tests__/thread-store-registry.test.ts` | New —
register/get, replacement, no-op unregister, subscriber events |
| `packages/runtime/src/v2/runtime/__tests__/handle-threads.test.ts` |
`handleClearThreads`, `handleGetThreadMessages`, plus new
`handleGetThreadEvents` and `handleGetThreadState` describe blocks |
|
`packages/runtime/src/v2/runtime/runner/__tests__/in-memory-runner.test.ts`
| `getThreadMessages`, new `getThreadEvents` (stored events, unknown
thread, multi-run flattening), new `getThreadState` (null without
snapshot, returns last STATE_SNAPSHOT, most-recent across runs) |
| `packages/react-core/src/v2/hooks/__tests__/use-threads.test.tsx` |
Registry lifecycle (register on mount, unregister on unmount) |
| `packages/web-inspector/src/__tests__/web-inspector.spec.ts` | New
`ɵCpkThreadDetails caching` describe — threadId-change drops all panel
caches; conversation cache invalidates on `_conversation` reassignment
and on expand-state change; state and events caches invalidate on their
fetched-data reassignment |

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-05-01 14:37:02 -07:00
Mike Ryan 8648e4a838 chore: remove CopilotKit CLI 2026-05-01 13:16:49 -07:00
github-actions[bot] be5bb0070c style: auto-fix formatting 2026-05-01 03:08:26 +00:00
Martha Schumann 62b5252963 refactor(inspector): consolidate per-tab plumbing and template caches
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>
2026-04-30 19:52:36 -07:00
Martha Schumann 3328ae45f2 fix(inspector): widen conversation cache key, add panel spacing, cover cache invariants
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>
2026-04-30 19:44:49 -07:00