Commit Graph

280 Commits

Author SHA1 Message Date
Alem Tuzlak 65928b9ca3 Merge remote-tracking branch 'origin/main' into worktree-lucky-popping-wren
# Conflicts:
#	package.json
2026-05-20 10:54:04 +02:00
tylerslaton efae3dfb5b chore: release monorepo v1.57.3 2026-05-19 15:59:44 +00:00
Claude ce3084700c fix(release): swallow tanstack/virtual rAF teardown error in perf test
The release-PR workflow's pre-commit hook ran the full test suite and
failed because @tanstack/virtual-core 3.13.18 has a latent bug — its
scrollToIndex schedules a nested rAF that calls
`this.targetWindow.requestAnimationFrame(verify)` with no null-check.
The virtualizer's cleanup nulls `targetWindow` on React unmount, so the
queued rAF fires post-unmount and throws. All 1170 tests passed, but
vitest exited non-zero from the unhandled error.

Wrap rAF on both globalThis and window (separate bindings in
vitest+jsdom; tanstack uses `targetWindow.rAF` which resolves to
`window.rAF`) so callbacks hitting this specific error are swallowed.

Also fix the lint-fix lefthook command — `[ -n "{staged_files}" ]`
broke on multi-file expansion ("[: <path>: unexpected operator")
because lefthook interpolates files as space-separated words, not a
quoted string. Use `set --` to put them in positional args.
2026-05-19 15:28:07 +00:00
Austin Merrick 784b365d4f feat(react-core): forward followUp option through useComponent 2026-05-18 10:53:20 -07:00
Tyler Slaton cf0b032d58 chore: release monorepo v1.57.2 (#4787)
## 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.
2026-05-13 16:43:11 -07:00
Jordan Ritter a187e6b64c fix(react-core): fix flaky CopilotChatPerf e2e test
Harden rAF cleanup and timing assertions in the performance
test to prevent intermittent failures on Node 20.
2026-05-13 15:50:13 -07:00
Jordan Ritter 20184e1324 feat(react-native): export full v2 API surface with attachment support
15+ type re-exports from headless layer. expo-document-picker and
expo-file-system as optional peer deps. InterruptEvent,
ReactFrontendTool, ReactHumanInTheLoop added to headless.ts.
2026-05-13 15:38:55 -07:00
tylerslaton 1b14504788 chore: release monorepo v1.57.2 2026-05-13 00:40:27 +00:00
Tyler Slaton 442d2150c3 feat(react-core): add position prop to CopilotSidebar (left/right) (#4710)
## What does this PR do?

Adds a `position?: \"left\" | \"right\"` prop to the v2 `CopilotSidebar`
(and the underlying `CopilotSidebarView`), letting consumers anchor the
sidebar to either side of the viewport. Defaults to `\"right\"` so
existing usage is unchanged.

```tsx
<CopilotSidebar position=\"left\" />
```

### What changes when `position` flips

- **Anchor:** `cpk:right-0` ↔ `cpk:left-0`
- **Border side:** `cpk:border-l` ↔ `cpk:border-r`
- **Off-screen translate (closed state):** `cpk:translate-x-full` ↔
`cpk:-translate-x-full`
- **Body push margin:** `document.body.style.marginInlineEnd` ↔
`marginInlineStart` (with the matching `transition` CSS property name)
- **Aside element:** picks up a `data-position` attribute for
styling/test hooks

`position` is in the `useLayoutEffect` deps, so toggling it at runtime
cleans up the prior side's body margin before applying the new one.

### Tests

New `CopilotSidebarView.position.test.tsx` (7 cases) —
default/right/left class assertions, off-screen translate direction, and
verification that the wrapper forwards through to the view. All 32
sidebar-area tests pass; full react-core suite (1167 tests) green with
no regressions.

### Storybook

Added `RightPosition` and `LeftPosition` stories under
`UI/CopilotSidebarView` for visual diffing.

## Related PRs and Issues

- N/A

## Checklist

- [x] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [ ] If the PR changes or adds functionality, I have updated the
relevant documentation
- [x] \"Allow edits by maintainers\" is checked

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-05-07 16:39:34 -07:00
github-actions[bot] 08a09ab950 style: auto-fix formatting 2026-05-07 23:01:54 +00:00
Tyler Slaton 3626bf1c53 fix(react-core): mirror sidebar toggle button when position="left"
The toggle button is hardcoded right-anchored (cpk:bottom-6 cpk:right-6).
When the sidebar sits on the left, the button should mirror to the left
so it lives behind/under the chat panel — otherwise it floats on the
opposite side from the sidebar it controls.

CopilotSidebarView now passes a position-aware className override into
the toggle slot (left-6 + right-auto, merged via tailwind-merge so the
default right-6 is dropped). Behavior on the right is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 16:00:24 -07:00
github-actions[bot] 1ed859707e style: auto-fix formatting 2026-05-07 20:52:15 +00:00
Tyler Slaton 68d0885d27 feat(react-core): add position prop to CopilotSidebar (left/right)
Lets consumers anchor the v2 CopilotSidebar to either side of the
viewport instead of the hardcoded right side. The prop flips the fixed
anchor, the border side, the off-screen translate direction, and the
body push margin (marginInlineStart vs marginInlineEnd) so the layout
mirrors correctly. Defaults to "right" for backward compatibility.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 13:49:22 -07:00
Alem Tuzlak 5d95e8e102 Merge branch 'main' into blitz/lgp-genuine-pass/integration 2026-05-07 21:17:15 +02:00
tylerslaton 5164ae303f chore: release monorepo v1.57.1 2026-05-07 16:41:22 +00: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 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
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 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
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 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
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 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 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
Martha Schumann 0721414fbb Merge remote-tracking branch 'origin/main' into feat/CPK-7193-inspector-threads-clean
# Conflicts:
#	examples/integrations/langgraph-python-threads/apps/app/package.json
#	examples/integrations/langgraph-python-threads/apps/bff/package.json
#	examples/integrations/langgraph-python-threads/package-lock.json
#	pnpm-lock.yaml
2026-04-30 18:01:51 -07:00
Tyler Slaton 52f8030f82 Merge branch 'main' into release/publish/monorepo/v1.56.5 2026-04-30 12:42:10 -07:00
Martha Schumann c3b7bb0e92 test(react-core): tighten use-threads phoenix mock fidelity
- MockSocket.disconnect() now flips connected to false to match real
  Phoenix sockets, so reconnect-cycle assertions are not vacuous.
- MockChannel.off(event, ref) guards against the case where a prior
  off(event) without a ref already deleted the entry, preventing a
  TypeError from filter() on undefined.
- Use vi.stubGlobal("fetch", ...) + afterAll(unstubAllGlobals) so the
  fetch mock no longer leaks into sibling test files in the same worker.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 12:12:48 -07:00
Martha Schumann cafd071a08 test(react-core): tighten use-threads phoenix mock and indexing
Drop the dead `MockChannel.channels` field — never read or populated.

Stop auto-firing `onOpen` from `MockSocket.connect()`. Real Phoenix sockets
fire `onOpen` once per upgrade, so tests should drive the transition
explicitly via `triggerOpen()`. The auto-fire would either double-fire
when a test also called `triggerOpen()` or hide cases where production
code forgets to await the open before joining a channel. No tests in this
file relied on the auto-fire.

Convert the archive/delete fetch assertions to filter by URL+method, the
same way the rename test was already written. Hardcoded `mock.calls[2]`
and `[3]` indices broke the moment any startup fetch was added or
reordered; the filter-based form survives that without losing
specificity.

Reset `mockUseCopilotKit` at the start of `beforeEach` before re-priming
via `setupCopilotKit()`. `mockReturnValue` is stable across calls, but a
future test using `mockReturnValueOnce` would otherwise leak un-consumed
queued returns into the next test.
2026-04-30 11:51:41 -07:00
Tyler Slaton 0bdc798854 Merge branch 'main' into tyler/kind-mendeleev-636369 2026-04-30 10:48:28 -07:00
Tyler Slaton 8681522462 fix(suggestions): show available:"always" pills on welcome screen with runtimeUrl (#4462)
## Summary

`available: "always"` suggestion configs (static and dynamic) didn't
render on the welcome screen when the chat used `runtimeUrl` to fetch
agents instead of registering them locally with
`agents__unsafe_dev_only`.

The bug has been latent since v2 first landed (Dec 2025); a per-thread
cloning change masked it for some flows from Mar 31 → Apr 23, and the
Apr 23 revert (#3525 backout) re-exposed it.

This PR is welcome-screen only — the `!isConnecting && !isRunning` UI
gate is unchanged, so suggestions still hide during connect/replay and
during runs as before.

## What was broken

With `runtimeUrl`, the agents registry is empty during the initial
`/info` fetch. Two compounding issues meant `available: "always"`
configs never got off the ground:

1. **`SuggestionEngine.reloadSuggestions`** bailed early when the
consumer agent wasn't in the registry yet. The first reload fires from
`useConfigureSuggestions` on mount — at that moment the registry is
empty, so every config got skipped. Static pills never appeared, and
dynamic pills never even started generating.
2. **`useConfigureSuggestions`'s global-config path** (no
`consumerAgentId` or `"*"`) only iterated the current agents map. Empty
map → zero reload calls → suggestions stuck empty until something else
triggered a reload.

## Fix

Three small changes, scoped to the welcome screen path:

1. `SuggestionEngine.reloadSuggestions` no longer bails when the agent's
missing — defaults `messageCount` to 0 and processes static configs
anyway. Dynamic configs still skip until a real agent arrives.
2. `useConfigureSuggestions`'s global path also calls
`reloadSuggestions(targetAgentId)` directly (covers the empty-map case
where the agent the chat is bound to isn't yet in the registry).
3. `useConfigureSuggestions` subscribes to `onAgentsChanged` *only* for
dynamic configs, *only* when the target agent isn't yet present, and
*unsubscribes after firing once*. Dynamic pills catch up after the
runtime fetch completes, without piling up overlapping generations as
multiple hooks mount.

## What's preserved

- `hasSuggestions` keeps `!isConnecting && !isRunning` — bootstrap
replay and run-in-flight both still hide pills (no mid-replay layout
jump, no stale-context flash mid-run).
- `available: "always"` is the *eligibility window* (welcome screen vs
after first message), not a "render through transitions" override.
- Threading behavior (thread switch, explicit `threadId`, multi-chat) is
unchanged. None of the new code paths fire on thread connects.

## Test plan

- [x] `packages/core` — engine unit tests for `reloadSuggestions` when
no agent is present + when only static "always" config exists. All 59
core-suggestions tests pass.
- [x] `packages/react-core` — 4 integration tests in
`CopilotChat.suggestionsAlways.test.tsx`:
  - shows on welcome screen with explicit `consumerAgentId`
  - shows on welcome screen with global config
  - hides during a run, reappears after (default scroll)
  - hides during a run, reappears after (pin-to-send)
- [x] All 1157 react-core tests pass.
- [ ] Manual smoke in `examples/v2/react/demo` with both static and
dynamic `available: "always"` configs — welcome screen pills, hide
during run, regenerate after.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-04-30 10:48:22 -07:00
Martha Schumann 7438782ab9 test(react-core): tighten use-threads hook tests
- Realtime metadata-deletion test now identity-checks the surviving
  thread (id=t-1) so a regression that drops the wrong thread surfaces.
- Rename test finds the PATCH call by URL+method instead of indexing
  fetchMock.mock.calls[2], which was brittle against any change in
  startup fetch order.
- Register/unregister test uses mockReturnValue (not mockReturnValueOnce)
  so the same spies are returned across all renders, and the test
  explicitly sets runtimeConnectionStatus=Connected to exercise the
  fully-wired flow.
- Connecting-gate test replaces the 20ms wall-clock setTimeout with
  chained microtask flushes inside act(), making the "no fetch while
  Connecting" assertion deterministic on slow runners.
- Socket-teardown test sources the threshold from production
  (ɵMAX_SOCKET_RETRIES) and asserts both the pre-threshold (no
  premature teardown) and post-threshold (teardown fires) states.
- MockSocket.connect() now fires registered onOpen handlers
  synchronously, mirroring real Phoenix sockets so production code
  awaiting onOpen is exercised by the same lifecycle.
- MockChannel.join() now returns a fresh MockPush per call so stale
  ok/error callbacks from a prior join cannot fire against a new
  join's listeners.
- getMockSockets is typed as MockSocketLike[] so socket-API typos
  surface at compile time instead of only at runtime.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 10:21:11 -07:00
github-actions[bot] 9bc2ff9c75 style: auto-fix formatting 2026-04-30 17:16:45 +00:00
Tyler Slaton 7a0e2f532e fix(suggestions): show available:"always" pills on welcome screen with runtimeUrl
Two-part welcome-screen regression for `available: "always"` suggestion configs
when the chat connects to agents via `runtimeUrl` instead of registering them
locally:

1. SuggestionEngine.reloadSuggestions bailed early when the agent wasn't yet
   in the registry. With runtimeUrl, the registry is empty during the initial
   /info fetch, so the very first reload (fired by useConfigureSuggestions on
   mount) skipped every config — static pills never appeared on the welcome
   screen, dynamic pills never started generating.  Now: don't bail, default
   `messageCount` to 0, run static configs anyway. Dynamic configs still need
   a real agent and skip until one arrives.

2. useConfigureSuggestions's global-config path only iterated the current
   agents map, which compounded the problem above — the empty map meant zero
   reloads. Now: also reload for the chat's resolved consumer agent (covers
   the empty-map case), and subscribe to onAgentsChanged for dynamic configs
   only, firing exactly once when the target agent first appears (so dynamic
   pills catch up after the runtime fetch completes, without piling up
   overlapping generations as multiple useConfigureSuggestions hooks mount).

`hasSuggestions` keeps the `!isConnecting && !isRunning` UI gate. `available:
"always"` controls eligibility windows (welcome screen vs. after first
message), not whether to render through connect/replay or through a run —
those still hide and the end-of-run reload regenerates against the new
context.

Tests:
- Engine: added unit coverage for reloadSuggestions when no agent is present.
- React: 4 integration tests covering welcome screen (specific + global
  consumerAgentId) and the run lifecycle (hide during run, reappear after) in
  default and pin-to-send modes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-30 10:14:45 -07:00
Ran Shemtov e8061b707b Merge branch 'main' into release/publish/monorepo/v1.56.5 2026-04-30 17:59:09 +02:00
Alem Tuzlak 917c54a8d1 Merge branch 'main' into tyler/kind-mendeleev-636369 2026-04-30 11:03:19 +02:00
Alem Tuzlak 73c0e5e8eb fix(react-core): re-attach input overlay observer after welcome screen (#4472)
## Summary

Fixes a regression introduced by
[f9eee68](https://github.com/CopilotKit/CopilotKit/commit/f9eee688b)
(overlay chat input on scroll area) where late messages and "always"
suggestions slid underneath the absolute-positioned input pill once the
user submitted their first message.

**Root cause:** the `ResizeObserver` `useEffect` in `CopilotChatView`
had `[]` deps. On a fresh chat it mounted with the welcome-screen branch
active — `inputContainerRef.current` was null, the effect bailed, and it
never re-ran when the chat-view branch attached the overlay element.
`inputContainerHeight` stayed at 0, so the scroll content's reserved
bottom padding sat at 32px instead of ~input height.

**Fix:** hold the overlay element in state via a callback ref and key
the effect on the element. Same pattern already used by
`nonAutoScrollRefCallback` elsewhere in this file. The observer now
attaches and detaches reactively as the overlay mounts/unmounts.

## Test plan

- [x] New regression test in `CopilotChatView.inputOverlay.test.tsx`
mounts on the welcome screen, re-renders with messages, and asserts the
observer attaches to the new overlay element and feeds the correct
`paddingBottom` (88 + 32 = 120px). Reverting the fix makes it fail at
the post-transition padding assertion.
- [x] Existing 4 inputOverlay tests still pass.
- [ ] Verify in the demo: load a fresh chat, submit a message, confirm a
long assistant response leaves a gap above the input pill (no content
sliding under the pill).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-04-30 10:36:36 +02:00
Tyler Slaton cff40fdf20 fix(react-core): re-attach overlay observer when leaving welcome screen
`CopilotChatView` mounts on the welcome-screen branch, where the absolute-
positioned input overlay (and its `inputContainerRef`) does not exist. The
ResizeObserver useEffect ran once with an empty `[]` dep array, found
`ref.current === null`, and bailed. Submitting the first message swapped
to the chat-view branch and attached the overlay element — but the effect
never re-ran, so `inputContainerHeight` stayed at 0 and the scroll
content's reserved bottom padding sat at 32px instead of ~input height.
Late messages and any "always" suggestion strip slid underneath the input
pill, invisible to the user.

Hold the overlay element in state via a callback ref and key the effect
on the element. Same pattern already used by `nonAutoScrollRefCallback`
in this file. Effect now attaches and detaches reactively as the overlay
mounts/unmounts (e.g. clearing messages and falling back to the welcome
screen also resets the measured height instead of holding stale data).

Add a test that mounts on the welcome screen, re-renders with messages,
and asserts the observer attaches to the new overlay element and feeds
the correct paddingBottom. Reverting the fix makes it fail on the
post-transition padding assertion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-29 23:55:26 -07:00