Commit Graph

1601 Commits

Author SHA1 Message Date
Benjamin Taylor 225a3fc646 fix(web-components): contain mobile-modal focus including the backdrop
The mobile drawer is a modal dialog, but the Tab trap was bound to .root only,
while the backdrop button renders as a sibling OUTSIDE .root — so Tab from the
backdrop (or any out-of-root node) could escape the modal. Move the trap to the
host keydown listener (keydown is composed, so it catches the backdrop too) and
include the backdrop in the focusable cycle; keep initial focus on the first
real control. Adds a regression test for backdrop/outside-root Tab containment.
2026-06-25 11:28:32 -05:00
Benjamin Taylor d07888c611 fix(web-components): make the UMD lit-inlining CI-safe
The UMD bundle inlines lit for a self-contained CDN <script>; inlining lit also
pulls in its runtime deps (@lit/reactive-element, lit-html, lit-element,
@lit-labs/ssr-dom-shim). tsdown flags that transitive bundling as 'unintended'
and promotes the warning to a fatal error under CI, breaking
@copilotkit/web-components:build (and every dependent). Declare all lit packages
in noExternal and set inlineOnly: false to mark the bundling intentional.
2026-06-25 11:28:32 -05:00
github-actions[bot] ba85b2f0a6 style: auto-fix formatting 2026-06-25 15:45:46 +00:00
Benjamin Taylor 36d0bb636f feat(angular): add injectThreads for the threads SDK
Angular signal wrapper over the core thread store: reactive runtime context
(re-dispatches when wsUrl/threadEndpoints arrive), enabled-gated single-slot
registration, bound result methods, and synthesized error/loading parity with
react-core.
2026-06-25 10:42:47 -05:00
Benjamin Taylor 47b7476702 feat(react-core): add CopilotDrawer wrapper and chat drawer-awareness
Add <CopilotDrawer> (interops with the shadow-DOM element, two-pronged license
gate, scoped chat-input focus return, registration-gated header launcher) and
extend CopilotChatConfigurationProvider with drawerOpen + mobile mutual
exclusion + a non-explicit active-thread setter so a bare drawer connects to the
picked thread and resets on New with no host wiring. useThreads gains an
{enabled} gate and a list-only error channel.
2026-06-25 10:42:46 -05:00
Benjamin Taylor a7a6aa0c29 feat(web-components): add @copilotkit/web-components with the <copilotkit-drawer> element
Shadow-DOM Lit element that renders the threads drawer: self-contained styles
with build-time token sync from react-core's theme, slot projection for custom
rows, license/upsell gating, filtering, and a mobile modal. View-state lives on
the element; domain state stays with the host. Multi-format build (ESM/CJS
externalize lit as a peer dep; UMD inlines lit so the CDN <script> path is
self-contained).
2026-06-25 10:42:46 -05:00
Benjamin Taylor fc1fa82c33 feat(core): thread store with optimistic mutations and realtime, plus threads feature
Add the thread store (optimistic add/rename/archive/delete with delete
rollback-on-reject, session-guarded results, startNewThread/refetchThreads,
getServerState/getServerSnapshot, per-store memoized selectors, and non-fatal
realtime diagnostics for channel-join and metadata-credential failures) and
register the "threads" feature so the SDK can gate thread management.
2026-06-25 10:42:46 -05:00
Alem Tuzlak d85abb781d fix(bot-slack): native setStatus thinking indicator instead of hourglass (#5698)
## What

Replaces the homemade animated `⏳` "thinking"
placeholder (a posted message whose dots cycled via `setInterval`) with
Slack's **native** `assistant.threads.setStatus` "is thinking…"
indicator — and generalizes it from assistant-pane-only to **every
thread-anchored reply**: channel @-mentions, channel threads, DMs, and
the pane.

## Why

The hourglass was an extra post-then-delete message that looked nothing
like the native loading state other Slack agents show. Slack has since
relaxed `assistant.threads.setStatus` to accept the ordinary
**`chat:write`** scope (not just `assistant:write`), specifically so
channel-based apps can show AI loading states in channels and DMs
([docs](https://docs.slack.dev/reference/methods/assistant.threads.setStatus/)).
The adapter already used `setStatus` for the pane — this just extends it
everywhere and deletes the placeholder.

## How

- **`event-renderer.ts`** — delete the hourglass machinery
(`startThinking`/`claimThinking`/`clearThinking`/`setInterval`);
generalize `setPaneStatus`/`clearPaneStatus` →
`setStatus`/`clearStatus`. New `status?: { threadTs, isPane, config }`
arg: `statusMode` drives the thinking indicator; `isPane` only selects
tool-progress surface (pane composer status vs `task_update` timeline /
`🔧` rows — both unchanged).
- **`adapter.ts`** — `createRunRenderer` builds `status` for any target
with a thread anchor (`threadTs ?? statusTs`); `assistant: false` opts
out everywhere.
- **`types.ts` / `slack-listener.ts`** — flat DMs (no `thread_ts`) carry
the inbound message `ts` as the status anchor; replies still post flat.
- **README** — document the broadened status + the `chat:write`
relaxation.

## Testing

- `nx run-many -t test build` for `bot-slack` — **248 tests pass**,
build clean, oxlint 0 errors, oxfmt clean.
- New tests cover non-pane channel-thread status, the `🔧` tool
path staying on non-pane, and the DM `statusTs` anchor.
- Net **−149 lines**.

> Note: the DM path anchors `setStatus` to a non-assistant DM message ts
— the one piece only verifiable against the live Slack API. Channel
@-mentions already carry a real `thread_ts`.
2026-06-25 16:09:13 +02:00
Alem Tuzlak b3680616f4 fix(bot-slack): use native setStatus thinking indicator instead of hourglass
Replace the homemade animated ⏳ placeholder (a posted
message with setInterval-cycled dots) with Slack's native
assistant.threads.setStatus "is thinking..." indicator on every thread-anchored
reply -- channel @-mentions, channel threads, DMs, and the assistant pane -- not
just the pane. Slack now accepts setStatus under chat:write (not only
assistant:write), so it works for channel-based apps. Flat DMs carry the inbound
message ts as the status anchor. assistant:false opts out everywhere.
2026-06-25 15:34:05 +02:00
Ran Shemtov 5a516c827f chore: release monorepo v1.61.2 (#5696)
## Release monorepo v1.61.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.61.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.61.2`
   - Creates git tag `monorepo/v1.61.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-06-25 10:10:02 +02:00
ranst91 bb69f55c98 chore: release monorepo v1.61.2 2026-06-25 07:54:33 +00:00
tylerslaton f42dddc00e chore: release bot-slack v0.1.0 2026-06-24 19:10:00 +00:00
Tyler Slaton 6d9a9ecffd fix: add configurable Slack response routing (#5684)
## Summary

- Add Slack `respondTo` routing options with mention-only channel thread
replies by default.
- Keep DMs and assistant pane conversational while allowing legacy
owned-thread continuation by config.
- Update the Slack example config, manifests, docs, and Discord README
trigger wording.

## Why

Slack bots should stay quiet in channel threads unless explicitly
mentioned or configured for continuation. This makes Kite-style bots
less noisy by default while preserving the old behavior for teams that
want it.

## How

- Resolve `respondTo` once in `SlackAdapter.start()` and pass the
resolved policy into the Slack listener.
- Gate `app_mention`, `message.im`, and non-DM thread replies against
that policy.
- Add listener coverage for defaults, disabled routes, in-channel
mention replies, and legacy thread continuation.

## Test Plan

- `pnpm exec nx run @copilotkit/bot-slack:test --
src/__tests__/slack-listener.test.ts`
- `pnpm exec nx run @copilotkit/bot-slack:test`
- `pnpm exec nx run @copilotkit/bot-slack:check-types`
- `pnpm exec nx run slack-example:test`
- `pnpm exec nx run slack-example:check-types`
- targeted `pnpm exec oxfmt --check ...`
- targeted `git diff --check ...`
2026-06-24 12:07:30 -07:00
Tyler Slaton 3afb050ebe feat(bot-slack): configure response routing 2026-06-24 11:53:55 -07:00
Sam Julien ced9846472 fix(web-inspector): read telemetry package version from metadata 2026-06-24 10:55:49 -07:00
Sam Julien 9dffd143f4 fix(web-inspector): type thread telemetry props 2026-06-24 10:52:18 -07:00
Sam Julien ca59098e3a add inspector Threads PLG state 2026-06-24 10:52:18 -07:00
Tyler Slaton 947fe2142a fix(core): preserve agent-level headers instead of overwriting them (#5635) (#5637)
Fixes #5635.

## What

Headers set directly on an `HttpAgent` registered via
`agents__unsafe_dev_only` were silently replaced by the provider
headers. Per-agent auth headers (like an `Authorization` for a
self-hosted backend) got dropped, causing 401s.

## Why

`AgentRegistry.applyHeadersToAgent` did `agent.headers = {
...core.headers }`, a full overwrite. The run handler and the react-core
`useAgent` hook did the same. So an agent built with its own headers
lost them on registration, on every `setHeaders`, and before each
request.

## Fix

Merge instead of replace. The registry captures each agent's own headers
once (in a WeakMap, before the first apply) and rebuilds `{
...ownHeaders, ...coreHeaders }`. Core wins on key conflicts, which
keeps the existing "provider headers are authoritative" and logout/clear
behavior. All header application now routes through one method,
`CopilotKitCore.applyHeadersToAgent`, so runs never clobber per-agent
headers.

Vue and Angular benefit too: they dispatch runs through `core.runAgent`
/ `connectAgent`, so the merge is re-applied before every request.

## Tests

- core: 3 new cases in `core-headers.test.ts` (preserve, merge,
retain-across-setHeaders); existing overwrite and clear tests still
pass.
- react-core: new `use-agent-provider-headers.e2e.test.tsx` with a real
provider and an HttpAgent that has its own headers.

Verified locally: format, lint, full core + react-core suites, and both
builds.
2026-06-24 07:45:37 -07:00
Ben Taylor fbf154cb0d feat(core,react-core): add unarchiveThread to v2 thread store + useThreads (#5624)
## Summary

Adds `unarchiveThread(id)` to the v2 thread store (`@copilotkit/core`)
and the `useThreads` hook (`@copilotkit/react-core/v2`), restoring an
archived thread via the existing generic `PATCH /threads/:id { archived:
false }` update path — no new runtime endpoint. Mirrors `archiveThread`
across the store and hook.

This is the durable, architecture-independent piece extracted from the
threads-drawer effort. The drawer UI itself is being restarted as a
framework-agnostic **CopilotDrawer** (Lit web component + React/Angular
wrappers) under a separate spec; this hook method stands on its own and
is needed regardless.

## Testing

TDD. New core store test (`PATCH … { archived: false }`) and
`useThreads` hook test; full suites green (core, react-core).
2026-06-24 08:56:08 -05:00
Benjamin Taylor f90231f4fd feat(core,react-core): add unarchiveThread to thread store and useThreads
Restores an archived thread via the existing generic PATCH /threads/:id
update path with { archived: false } — the same mechanism example apps
already use for restore — so no new runtime route is required. Mirrors
archiveThread across the core thread store and the v2 useThreads hook.
2026-06-24 08:44:40 -05:00
Ran Shemtov 5d31ebbfb2 Merge branch 'main' into claude/stupefied-northcutt-12d382 2026-06-24 15:29:16 +02:00
Alem Tuzlak 16c546b87e fix(bot-discord): resolve reactions against the conversation target channel
addReaction/removeReaction resolved the channel from messageRef.channelId only,
unlike Slack and Telegram which fall back to the target channel. The shipped
example reacts with `{ id }` (no channelId) — the channel is meant to come from
the thread's reply target — so on Discord fetchSendable("") threw, the call
returned { ok: false }, and the 👀/✅/⚠️ acks silently never fired (contradicting
the example's "works on Slack, Discord, and Telegram alike" claim).

Fall back to the target channel when the reacted ref carries no channelId, for
parity with the other two adapters. Adds a regression test.
2026-06-24 14:25:28 +02:00
Alem Tuzlak c52985889e Merge branch 'main' into feat/bot-surface-parity 2026-06-24 12:38:39 +02:00
Tyler Slaton 2291fc545f fix(react-core): resolve interrupt typecheck failures 2026-06-23 21:08:37 -07:00
Tyler Slaton f330e9b795 fix(runtime): fail loud on malformed approval request 2026-06-23 20:56:33 -07:00
Tyler Slaton a13c3ee663 chore: merge main into PR 5480 2026-06-23 20:50:16 -07:00
Jordan Ritter ec646bbf4f Merge remote-tracking branch 'origin/main' into chore/remove-harness-legacy-ssot
# Conflicts:
#	showcase/scripts/railway-envs.generated.json
#	showcase/scripts/railway-envs.ts
2026-06-23 17:56:18 -07:00
github-actions[bot] 3284bc863f style: auto-fix formatting 2026-06-23 22:34:58 +00:00
Tyler Slaton 75611b272c chore: merge main into PR 5480 2026-06-23 15:32:09 -07:00
Austin Merrick 4ba201b5c4 fix: repair check-types across all packages and gate it in CI
Repairs TypeScript check-types across the monorepo and adds a CI gate so
regressions are caught going forward:

- core: bundler module resolution and strict-mode fixes
- sdk-js: bundler module resolution; keep codegen, formatter, packaging working
- react-core: fixes across components, hooks, and tests
- react-native: restore catch binding referenced by TypeError cause
- runtime: repair check-types and bound AI SDK schema inference
- web-inspector: nodenext import extensions, export Anchor
- remaining packages and node example: assorted check-types repairs
- deps: add missing type-only devDependencies
- license context driven from /info licenseStatus
- ci: run check-types in the static quality workflow

Squashed from 12 commits for a single, easily-revertable change.
2026-06-23 15:26:47 -07:00
Austin Merrick 30bd6d8f0e test(core): pin remove/re-add baseline; doc + assertion polish
CR round 2 follow-ups (no behavior change):
- Add a core-headers regression test proving the agentOwnHeaders baseline
  stays pristine across remove + re-add (the WeakMap is intentionally not
  cleared on removal; clearing would re-capture polluted headers).
- Correct the stale `headers` config doc ("appended" -> merged on top of each
  HttpAgent's own headers, core wins).
- Tighten the e2e no-provider-headers assertion to toEqual.
2026-06-23 15:22:11 -07:00
Tyler Slaton 006c62592e chore: release monorepo v1.61.1 (#5645)
## Release monorepo v1.61.1

**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.61.1`
   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.61.1`
   - Creates git tag `monorepo/v1.61.1`
   - 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-06-23 14:21:47 -07:00
Benjamin Taylor c2d38f4b6a refactor(runtime): resolve license token once in base; add env-fallback integration test
Addresses PR review feedback:
- Resolve the license token once (option ?? COPILOTKIT_LICENSE_TOKEN) into a
  protected readonly field on BaseCopilotRuntime, and have
  CopilotIntelligenceRuntime's licenseChecker reuse it. Collapses the duplicated
  resolution and structurally enforces that telemetry attribution and feature
  gating can never disagree, instead of relying on a "keep in sync" comment.
- Add an integration test for the env-var-only path (no licenseToken option) —
  the exact self-hosted scenario this PR targets — proving the env-resolved
  token reaches lambdaClient.send through a real request. Kept in its own file
  so the process-wide telemetry singleton (last-write-wins) can't false-pass it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 16:11:26 -05:00
Benjamin Taylor 7fb1600da5 test(runtime): integration-cover license token → sink for all endpoints/modes
Adds genuine end-to-end coverage beyond the SSE-via-Express case:
- SSE via the Hono adapter
- SSE via the framework-agnostic fetch handler (what node + custom adapters wrap)
- Intelligence mode end-to-end (real CopilotIntelligenceRuntime, WS runner stubbed)

Each constructs a real runtime (so the base-class setLicenseToken runs), drives a
real request through the adapter, and asserts the token reaches lambdaClient.send
on oss.runtime.copilot_request_created.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 16:11:26 -05:00
Benjamin Taylor b5a435f0c4 fix(runtime): set telemetry license token for v2 SSE runtimes
Only CopilotIntelligenceRuntime called telemetry.setLicenseToken in its
constructor; BaseCopilotRuntime and CopilotSseRuntime did not. As a result,
self-hosted SSE users got anonymous runtime telemetry (no telemetry_id) even
with a license token configured — and those events were additionally throttled
to the 5% anonymous sample rate, leaving runtime telemetry_id stuck at ~1%.

Hoist the licenseToken resolution (option ?? COPILOTKIT_LICENSE_TOKEN env
fallback) and telemetry.setLicenseToken call into BaseCopilotRuntime so SSE and
Intelligence runtimes attribute telemetry identically. Remove the now-redundant
duplicate from CopilotIntelligenceRuntime (its licenseChecker stays).

Tests cover every construction path into the endpoints:
- runtime-license-telemetry.test.ts: SSE/Intelligence direct + CopilotRuntime
  shim (both delegates) x {explicit option, env fallback, none}; asserts the
  token is set exactly once (guards against a double-set after the hoist).
- sse-license-telemetry.integration.test.ts: end-to-end proof the token rides
  to lambdaClient.send through a real Express endpoint request.
- copilot-runtime-license-telemetry.test.ts: regression guard for the v1
  CopilotRuntime path (already worked, previously untested).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 16:11:26 -05:00
tylerslaton 410d34001d chore: release monorepo v1.61.1 2026-06-23 21:00:28 +00:00
Tyler Slaton 56615da485 ci: reduce release delay, fix flakey test, use devops bot token (#5644)
## Summary
- Mint a GitHub App token for the stable release workflow and reuse it
for PR creation and follow-up API calls
- Disable lefthook during automation commits so release PR generation
does not depend on local developer hooks
- Relax the CopilotChat perf regression test to assert correctness
without a hard 5s wall-clock check

## Testing
- Unit/UI test updated to allow longer async rendering while still
verifying 100 messages render successfully
- Not run (not requested)
2026-06-23 13:56:33 -07:00
Tyler Slaton 83b21df925 Stabilize release PR token and chat perf test 2026-06-23 13:51:35 -07:00
Mike Ryan 3e4c515252 fix: preserve legacy thread endpoint behavior 2026-06-23 12:10:01 -07:00
Alem Tuzlak 268b39e3e0 feat(bot): cross-platform surface parity — reactions, ephemeral, modals
Add capability-gated surface methods across the bot packages so one handler
degrades gracefully per platform:
- thread.react / thread.unreact — emoji reactions (Slack, Discord, Telegram)
- thread.postEphemeral — only-you messages with a required DM fallback
- openModal (interaction/command-scoped) — structured forms (Slack rich,
  Discord text-only), omitted on surfaces with no live interaction trigger

Spans @copilotkit/bot-ui, bot, bot-slack, bot-discord, bot-telegram with
per-adapter capability flags. Discord: open modals from slash-command
interactions, and never clear registered commands on an empty publish.
2026-06-23 20:49:07 +02:00
Austin Merrick 6bfb12267f docs(core): document header merge/clear contract; pin clear-baseline test
CR round 1 follow-ups (no behavior change):
- Document on setHeaders + applyHeadersToAgent that the merge baseline is the
  agent's construction-time headers, so setHeaders can override but cannot
  remove a per-agent header (the agent's own value re-surfaces on clear), and
  that dynamic updates go through setHeaders, not direct agent.headers mutation.
- Tighten the agentOwnHeaders field comment (captured on first apply, never
  re-captured) and the applyHeadersToAgent method doc.
- Add a core-headers test pinning the clear-reveals-baseline contract.
- Soften the two useAgent test-mock comments: they are an additive stand-in,
  not a faithful model of core's frozen baseline.
2026-06-23 11:43:21 -07:00
Mike Ryan 3452657416 fix: update thread capability test fixtures 2026-06-23 11:36:58 -07:00
Mike Ryan 0187ec250a fix: address thread capability review feedback 2026-06-23 11:33:48 -07:00
Mike Ryan d906171c26 fix: honor thread endpoint capabilities 2026-06-23 11:33:10 -07:00
Mike Ryan db09796809 fix: gate thread endpoints by runtime capability 2026-06-23 11:32:30 -07:00
Alem Tuzlak 43fdba74aa feat: AG-UI standard interrupt support in useInterrupt + BuiltInAgent
Adds the AG-UI standard interrupt flow (RUN_FINISHED outcome:interrupt + resume array) alongside the legacy on_interrupt path.

- core: forward the standard resume array through runAgent.
- react-core / vue / react-native: useInterrupt handles standard interrupts with resolve()/cancel(), surfaces the primary + full interrupt set, and persists each resolved tool-backed interrupt as a tool-result message so multi-turn conversations stay well-formed (no dangling tool call -> no tool-call loop).
- runtime BuiltInAgent: native interrupts for the aisdk + tanstack factory paths via each SDK's needsApproval primitive (tool-approval-request / CUSTOM approval-requested -> outcome:interrupt); classic interrupt-tool emission + ctx.interrupt() factory primitive; idempotent resume injection mapped to each SDK's native tool-result; getCapabilities advertises humanInTheLoop.interrupts.
- docs: document standard interrupt support.

Verified across core/react-core/runtime unit suites and a real-model multi-turn run on both aisdk and tanstack.
2026-06-23 20:14:17 +02:00
Austin Merrick e0dfe56940 fix(web-inspector): forward core headers on owned thread store requests (#5596)
## Problem

Fixes #5581.

When `enableInspector={true}` and `useThreads()` is **not** mounted, the
inspector creates its own thread store per agent
(`ensureOwnedThreadStore`). That store initialized its context with
empty headers:

```ts
store.setContext({
  runtimeUrl: core.runtimeUrl,
  headers: {}, // ← ignores headers configured on <CopilotKit>
  agentId,
});
```

So the inspector's `/threads` requests omitted the headers configured on
`<CopilotKit>` (e.g. `X-CSRF`, `Authorization`). In environments that
enforce CSRF/auth checks this returns **HTTP 403**; in lax local envs it
200s but still sends no headers. This is the inspector-side counterpart
to the `useThreads()` fix in #5300.

## Solution

1. Source the headers from `core.headers` when the owned store's context
is created.
2. Add an `onHeadersChanged` subscriber that re-applies headers to all
owned stores when the host updates them at runtime (e.g. a refreshed
auth/CSRF token via `core.setHeaders`). This mirrors `useThreads()`,
which re-dispatches the context whenever `core.headers` change, so the
owned stores' requests stay authorized.

Headers are spread (`{ ...core.headers }`) to match the existing pattern
in `use-threads.tsx` and produce a fresh mutable object. Stores
registered by `useThreads()` are untouched — only inspector-owned stores
are affected.

## Testing

Added two regression tests in
`packages/web-inspector/src/__tests__/web-inspector.spec.ts` (stub
`globalThis.fetch`, drive the owned store via the agents-changed path):

- the owned store's `/threads` request carries `core.headers`;
- an `onHeadersChanged` update re-applies the new headers on the next
request.

`pnpm --filter @copilotkit/web-inspector test` → 34 passed. Verified the
first test fails when the fix is reverted. Lint (oxlint) and formatting
(oxfmt) clean on the changed files.
2026-06-23 11:13:45 -07:00
Austin Merrick 59f96620bb fix(core): preserve agent-level headers instead of overwriting them (#5635)
HttpAgent headers configured directly on an agent registered via
agents__unsafe_dev_only were silently replaced by core headers, dropping
per-agent auth headers and causing 401s against self-hosted backends.

Core headers are now merged ON TOP of each agent's construction-time
headers (captured once in a WeakMap before the first apply), with the
core-level value winning on a key conflict. Header application is
centralized in CopilotKitCore.applyHeadersToAgent so the run handler and
the react-core useAgent hook share one merge path and never clobber
per-agent headers.
2026-06-23 10:33:32 -07:00
Nathan 🔶 Tarbert 518c12e984 fix: reject pending human-in-the-loop promise on run abort (#5554) (#5633)
## Summary

Fixes #5554

When a backend agent calls a frontend tool via
`renderAndWaitForResponse` (the `useHumanInTheLoop` hook) and the run is
aborted (`stopAgent`/`abortRun`) while the form is still pending, the
handler promise was settled only by `respond()` — so on abort it either
hung forever or silently resolved to an empty string. The backend
received an empty `tool_call_result` (no error), which downstream agent
logic interpreted as "no input" — a silent state corruption.

## Root cause

`useHumanInTheLoop`
(`packages/react-core/src/v2/hooks/use-human-in-the-loop.tsx`) created
its handler promise capturing only `resolve`, and ignored the
`AbortSignal` that the core `RunHandler` already passes to tool handlers
(`packages/core/src/core/run-handler.ts`). An aborted run therefore
never settled the pending promise; an `undefined` result is stringified
to `""` (run-handler.ts) → silent empty tool result.

## Fix

Honor the existing `AbortSignal` in the HITL handler:
- If the signal is already aborted when the handler runs, reject
immediately.
- Otherwise attach a one-shot `abort` listener that rejects the pending
promise with an explicit `Error("Human-in-the-loop interaction
aborted")`.
- `respond()` detaches the listener before resolving, so a normal
response is unchanged and abort cannot fire after a normal resolve.

Core's existing catch path converts the rejection into an explicit error
tool result instead of a silent `""`. Scoped to the one hook; no
protocol change, no new timeout API, and **unmount is deliberately not
touched** (to avoid regressing reconnect/remount-resume).

## Tests added


`packages/react-core/src/v2/hooks/__tests__/use-human-in-the-loop.e2e.test.tsx`
— drives a HITL tool to the executing state, aborts the run without
calling `respond()`, and asserts an explicit non-empty error surfaces
(via `onToolExecutionEnd`) rather than a silent empty result. Fails
before the fix (handler hangs), passes after.

## Checklist

- [x] Failing test written and confirmed failing before the fix
- [x] Fix applied, test passes
- [x] Full `@copilotkit/react-core` suite passes (1291 passed;
reconnect/remount tests green)
- [x] Build succeeds (`nx build @copilotkit/react-core`)
- [x] Formatter passes
2026-06-23 13:20:19 -04:00
Alem Tuzlak 5ecdee36b8 feat(bot): pluggable StateStore persistence + cross-platform transcripts
Adds a durable persistence layer for @copilotkit/bot, replacing the
in-memory-only ActionStore with a pluggable StateStore.

- StateStore interface (kv/list/lock/dedup/queue) with a shared
  conformance suite; MemoryStore default plus @copilotkit/bot-store-redis
  and @copilotkit/bot-store-postgres backends.
- createBot({ store }): typed per-thread state via Standard Schema,
  action snapshots persisted through the store, per-conversation turn
  lock (onLockConflict drop|force), and inbound-event dedup keyed on a
  stable eventId. ActionStore is kept as a deprecated alias.
- Cross-platform transcripts (bot.transcripts + identity resolver) with
  age-bounded retention (prune on append + filter on read), and
  runAgent({ transcript: true }) to auto-inject history and capture the
  reply.
- createBot({ components }) re-registers components so durable actions
  re-fire after a restart; restart-durability demo in examples/slack.
- Dedup is marked seen only after the turn lock is acquired, so a turn
  dropped on lock-conflict does not burn its eventId (no lost retries).
- Release lockstep: bot-store-redis/postgres version with bot + bot-ui.
2026-06-23 18:33:38 +02:00