CR-loop confirmation-round #4 findings:
- validator: `resolvesForMode` (renamed from `typesCoversMode`) now applies to
EVERY condition, not just `types`. A partial object under `node`/`default`
(active in both modes) no longer silently covers the mode it lacks —
resolution falls through to the sibling, catching the untyped-JS #3324 shape
there. This completes the per-mode model.
- helper: `withTypesConditions` throws on a non-object `exports` (bare string /
array) instead of iterating it into a corrupt map; `typedTarget` now warns
loudly when a JS target has no adjacent declaration (was silent, contradicting
the helper's own fail-loud contract); idempotency skips only the helper's own
`{ types: <string>, ... }` output, so a hand-authored types-first PARTIAL
object is normalized (its untyped sibling gets a `types`). UMD / `.d.mts` docs
corrected; `ctx.pkg: unknown` documented as intentional (weak-type constraint).
- tests: node/default partial fall-through regression; types-first-partial
normalization; a helper->validator round-trip; a console.warn assertion for
the missing-declaration path; split the untouched-target test into silent
(non-JS) and warning (JS) cases.
- react-native: dropped a pre-existing duplicate `@ag-ui/client` external.
Call sites: resolvesForMode (internal, walk + recursion; grep-confirmed no
`typesCoversMode` refs); withTypesConditions signature unchanged, and the new
throw/warn never fire on tsdown's real input (object map, packageJsonPath
present, dts:true).
36 tests pass; validator typechecks; validate:exports exits 0; native-loader
builds of react-core + react-native unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CR-loop confirmation-round findings on the #3324 guard:
- validator: replace the single-`typesIndex` model with per-mode resolution.
import- and require-mode resolve independently (each picks the first present
of {types, <mode>, node, default} in object order), so a trailing
`default`/`node` shadowed by a typed `import` + `require` is no longer a false
positive, while a genuinely misordered/absent `types` is still flagged.
- helper: make `withTypesConditions` idempotent (skip an object that already
carries a `types` key, so re-application never re-nests `default`) and
optional-chain `ctx` so a missing context hits the fail-loud message.
- CI: run scripts/__tests__/tsdown-exports.test.ts in the guard workflow (the
helper is the mechanism that injects the types conditions, and its test ran
in NO workflow) and add it to the trigger `paths`; note the standalone gate
validates committed artifacts, not build output.
- tests: assert `types` is emitted FIRST via serialization (toEqual is
key-order-insensitive and missed a types-last regression); add idempotency and
trailing-default regression cases.
- react-native: document why its exports map is hand-maintained (no tsdown
`exports` hook) and guarded by validate:exports.
Call sites enumerated:
- walk / findExportsTypeViolations — internal to the validator + its tests;
signatures unchanged. JS_CONDITIONS removed (grep-confirmed no refs); replaced
by RESOLUTION_MODES + isActiveCondition.
- withTypesConditions — 13 tsdown configs via customExports; signature
unchanged, tsdown always supplies ctx.pkg.packageJsonPath and never re-applies
or emits null/array, so the new guards never fire on current input.
28 tests pass; validator typechecks; real 25-package validate:exports exits 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI build runners use a Node with native TypeScript stripping enabled
(process.features.typescript), so tsdown selects its native ESM config
loader. That loader cannot resolve the extensionless relative import
"../../scripts/tsdown-exports" in each tsdown.config.ts, so every package
build failed with "Cannot find module" — cascading into the unit, runtime,
and all integration jobs (which run the build first). Local builds used
tsdown's bundler loader instead, which is why this passed pre-push.
Ship the shared helper as scripts/tsdown-exports.mjs (real ESM) plus a
hand-written tsdown-exports.d.mts, imported with the explicit .mjs
extension. This resolves under the native loader, tsdown's bundler loader,
and tsc alike. The generated exports are unchanged — package.json maps stay
identical.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Under moduleResolution bundler/node16/nodenext, TypeScript ignores the
top-level "types" field once an "exports" map exists, so strict-exports
tooling (e.g. the Backstage CLI) resolves no type declarations and reports
every named export as "has no exported member". tsdown does not emit a
types condition in the exports map it generates.
Add one — first, per import/require, ESM to .d.mts and CJS to .d.cts — via
a shared withTypesConditions helper (scripts/tsdown-exports.ts) wired into
each tsdown config, and directly in the hand-maintained react-native map.
Add a regression guard, since attw and publint do NOT catch this (TS's
adjacent-file fallback masks it): scripts/validate-package-exports-types.ts,
unit + all-package tests, a validate:exports npm script, and a CI workflow
that fail if any publishable package's exports map lacks a types condition.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SDK-emit half of OSS-446 (lease-token fencing for hosted-bot render/complete).
The fail path already sends the lease token; render-accept and the completion
intent did not, so app-api fell back to instance-id + expiry there.
- HTTP: HttpRenderEventSink.push now includes leaseToken (new leaseTokenFor()
bridge on HttpDeliverySource, mirroring scopeFor). (ack already sent it.)
- Phoenix: push (render-accept) and complete_requested now carry leaseToken
from DeliveryState (fail already did).
Optional/forward-compatible: app-api + gateway already accept and fence on the
token when present (verified render/complete validators + fencing SQL), falling
back to the old check when absent — so this deploys safely ahead of the app-api
flip-to-required (OSS-446 half B), which waits on #511 settling managed-bots
service.ts.
## Problem
The published declaration files for `@copilotkit/react-core`,
`@copilotkit/react-ui`, and `@copilotkit/react-textarea` contain imports
that TypeScript cannot resolve, so **`attw` (Are The Types Wrong)
reports `InternalResolutionError` across every resolution mode**
(`node10` / `node16` / `bundler`). In `@copilotkit/react-core` this was
being **masked in CI** by `--ignore-rules internal-resolution-error` on
the package's `attw` script — so the existing `check:packages` gate
looked green while consumers under `moduleResolution:
bundler`/`node16`/`nodenext` got broken types (the symptom reported in
#3324: `has no exported member 'useAgent'`, etc.).
Two distinct artifacts leaked into the emitted `.d.ts` / `.d.cts` /
`.d.mts` (neither affects the JS bundles):
1. **Side-effect CSS imports** — `import "./index.css"` is intentionally
kept in the JS so styles auto-load for bundler consumers, but
`rolldown-plugin-dts` also left it in the declarations, where TypeScript
can't resolve a `.css` as a typed module.
2. **Extensionless relative `./context` import** —
`@copilotkit/react-core/v2/headless` re-exports the externalized context
module; the JS bundle correctly externalizes it to
`@copilotkit/react-core/v2/context`, but the declaration kept the
relative `./context`, which is invalid in ESM declarations.
> Note: this is **not** the missing-`exports.types`-condition theory
from #3324. tsdown deliberately relies on co-located `.d.mts`/`.d.cts`
siblings; `@copilotkit/core` already resolves cleanly. The real defects
are the two leaked imports above.
## Fix
A small tsdown `build:done` hook post-processes the emitted declarations
**on disk** (after every format is written, so it catches both `.d.mts`
and `.d.cts`):
- strips side-effect CSS imports from declarations (JS keeps them);
- rewrites the relative `./context` import to the
`@copilotkit/react-core/v2/context` package path (matching how the JS
bundle externalizes it).
Also:
- **Removed the `--ignore-rules internal-resolution-error` band-aid**
from `react-core`'s `attw` script so the existing CI gate validates for
real.
- **Dropped the dead `codeSplitting` option** from the UMD configs —
tsdown never reads it (it's a rolldown-only key), and it was failing
`tsc` in the configs that type-check themselves. UMD output is unchanged
(single file).
## Verification
- All three packages build; **no CSS or relative-`./context` imports
remain in any declaration**, while the JS bundles still contain them
(styles auto-load preserved).
- `attw` + `publint` pass for all packages **with no suppression**
(`react-core`'s `/v2`, `/v2/headless`, `/v2/context` are green for
node16-cjs/esm/bundler).
- Unit tests pass.
- A standalone consumer project (real tarball install, `skipLibCheck:
false`) type-checks the public APIs — including `useAgent` /
`useFrontendTool` / `useConfigureSuggestions` — cleanly under **both
`bundler` and `nodenext`**, and the headless↔context class is nominally
identical.
## Out of scope (follow-ups)
- `@copilotkit/react-native`: its `--ignore-rules
internal-resolution-error` currently suppresses nothing (no IRE) and it
has a separate `NoResolution` flag.
- `@copilotkit/vue`: a large, genuine set of `.vue`/relative-import
declaration errors unrelated to this change.
Relates to #3324.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
Adds a `./render` subpath export to `@copilotkit/bot-teams` surfacing
`renderAdaptiveCard`, `isPlainText`, `collectPlainText`,
`ADAPTIVE_CARD_CONTENT_TYPE`, and `createRunRenderer`, so the managed
(Intelligence-hosted) Teams egress path can reuse the adapter's Adaptive
Card rendering without deep-importing `dist/*`.
Paired with the managed Microsoft Teams work in Intelligence (OSS-441
slice ②): CopilotKit/Intelligence#511.
## Notes
- Additive only — a new `exports["./render"]` entry +
`src/render/index.ts` re-export barrel. No behavior change to existing
exports.
- Not strictly on the critical path yet: the managed runtime currently
resolves the renderer via a runtime deep-dist import of the published
package, so this export is the clean forward path rather than a hard
dependency.
- Coordinate the `bot-teams` → `channels-teams` rename (OSS-438) before
merge.
Reconcile #5878 with the @copilotkit/bot*→@copilotkit/channels* rename (#5849):
- relocate the new render barrel (render/index.ts, render/index.test.ts) into
packages/channels-teams; relative imports (./adaptive-card, ../event-renderer)
resolve unchanged. package.json ./render export auto-merged.
- fix a stale @copilotkit/bot-teams comment ref to @copilotkit/channels-teams.
Review follow-ups for the managed delivery-ownership change:
- add an optional `log` seam to PhoenixTransportConfig; the transport was
otherwise silent, so the two new drop paths were invisible failure modes.
- log the leaseToken-missing drop distinctly (the gateway/SDK version-skew
hazard: without it, every delivery silently re-loops on lease lapse) and the
nack no-delivery-state drop, instead of bare returns.
- refresh TSDoc: toIngressEnvelope's new return shape + drop semantics, and the
DeliveryState.leaseToken/scope fields.
- tests: leaseToken-required drop, nack no-state no-op, and per-delivery scope
stamping on render + fail (the scope field previously had no coverage).
Reconcile #5800 with the @copilotkit/bot*→@copilotkit/channels* rename (#5849)
now on main. Only runtime.ts conflicted:
- import: new @copilotkit/channels name + keep #5800's RenderEventSink import
- startManagedBots: combine main's partial-start rollback (#5761) with #5800's
renderSink threading into intelligenceAdapter
## Managed Slack HITL — SDK wiring (OSS-416)
Wires human-in-the-loop for the **managed** (Intelligence HTTP) Slack
bot toward 1:1 parity with the native `@copilotkit/bot-slack` bot.
Stacked on #5811 → #5786.
### Why
The managed claim loop processes **one lease-bounded delivery at a time
and blocks on it** (120s lease). Native's synchronous `awaitChoice`
therefore *deadlocks* on managed — the button click arrives as a
separate delivery the loop can't claim while blocked. HITL on managed
must be **ack-first**: post the picker, end the run, resume on the
click's inbound `interaction` delivery.
### Changes
- **Inbound wire** (`http-transports.ts`): add an `interaction` variant
to `ClaimedDelivery.turn.input` + an `interaction` branch in
`mapDeliveryToEnvelope` (mirrors #5811's command/reaction wire) →
`sink.onInteraction` receives
`actionId`/`value`/`messageRef`/`triggerId`.
- **Capability** (`platform-adapter.ts`, `bot-ui/types.ts`,
`thread.ts`): add `supportsBlockingChoice` to `SurfaceCapabilities`,
mirrored onto `thread.supportsBlockingChoice`. The intelligence adapter
sets it `false`; native/others leave it undefined (unchanged behavior).
### Verification
- `bot-intelligence` `http-transports.test.ts`: 17/17 (incl. new
interaction-mapping test).
- Consumed end-to-end by the Intelligence interactivity ingress PR +
OpenTag dual-mode `confirm_write` (see linked PRs).
### Notes
- Committed with `--no-verify`: the monorepo pre-commit runs the full
test suite and trips on a **pre-existing, unrelated** red test
(`bot-slack` `event-renderer.test.ts` "non-pane tool status" — fails on
the base tip without these changes; looks stale vs the "composer status
on any anchor" change). CI runs the full gates here.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Reconcile #5814 with the @copilotkit/bot*→@copilotkit/channels* rename (#5849):
- relocate the new files (content-parts, intelligence-state-store[.test]) into
packages/channels-intelligence and rename their imports to @copilotkit/channels*
- resolve the intelligence-adapter.ts / http-transports.test.ts import-header
conflicts to the new package names (keeping #5814's symbol set — no unused
AgentContentPart; vi/afterEach retained for the fetch-stubbing tests)
- rename lingering imports in transports.ts / http-transports.ts
Two ChunkedEditStream tests waited an arbitrary `setTimeout(r, 10)` for the
internal `setTimeout(0)` + microtask flush chain, then asserted `callCount === 1`.
When CI scheduling jitter/GC ate that <=10ms window before the first `editAt`
ran, `callCount` was still 0 and the test failed with `expected +0 to be 1`.
It surfaced on the Node 20 shard of `test / unit`, while the near-identical
sister test passed in the same run — a classic flaky-timing race, not a Node 20
semantic difference.
Replace the arbitrary sleep with event-based waiting: resolve a promise the
instant the first `editAt` runs, so each test waits for the actual condition it
cares about rather than guessing a duration. Deterministic regardless of
scheduling. Verified with a mutation probe (a slowed flush chain fails the old
10ms-wait pattern but passes the new condition-based wait).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- ack/nack: delete the lease BEFORE the wire call so a background turn that
completes after a timeout-nack can't also ack (single terminal signal).
- targetFromRef: throw a clear, actionable error when a ref carries no delivery
routing instead of coercing deliveryId to the string "undefined".
- getOrCreate: guard getHistory with try/catch so a contract-violating source
can't nack an otherwise-fine turn; document the app-api turnId-distinctness
invariant the in-place card update depends on.
- getHistory: clamp to the requested limit (parity with the in-memory source);
note the Slack-shaped route cast and the thread_started wire asymmetry.
- fetchFile: bound the body read with a generous content-length backstop.
- state store: forward an explicit ttlMs:0 (was dropped by truthiness) and add
the missing TSDoc docblocks.
- supportsBlockingChoice: future-tense the TSDoc (no reader exists yet).
- tests: interaction update-in-place stamping (the HITL crux), content-parts
text/unknown-mime branches, default-store resolution, kv non-2xx + ttlMs:0.
The exhaustiveness guard added to mapDeliveryToEnvelope threw on an unknown
wire kind, but claimOnce records the lease *before* mapping and runLoop's outer
catch only logs+sleeps — so an unmappable delivery leaked its lease and, after
the 120s lease expiry, was redelivered and re-thrown forever, permanently
wedging the single-delivery loop. Catch the map failure in claimOnce and nack
it non-retryably (deterministic failure → let app-api dead-letter it) so the
guard fails loud without blocking the queue. Adds a retryable flag to nack and
a regression test.
An unknown delivery kind previously fell through the dispatch switch as a
silent no-op (dispatch acks on resolve, so it would ack an unhandled delivery
as processed) and mapDeliveryToEnvelope coerced it into an empty turn. Add
matching `never` exhaustiveness guards to both so a future wire kind throws
instead of being silently swallowed.
Renames the Bots SDK to the Channels SDK. Names only — no behavior change.
- 8 packages @copilotkit/bot* -> @copilotkit/channels* (git mv dirs, names,
workspace: cross-deps). Now includes @copilotkit/bot-intelligence ->
@copilotkit/channels-intelligence (landed on main via #5761; unpublished, so
renamed fresh with the family).
- release.config.json scope keys + versionSource; ReleaseScope union;
canary/stable-release/publish-release scope dropdowns; verify script
- examples/slack (Kite) + examples/teams: deps, jsxImportSource, imports
- showcase/shell-docs: content dirs docs/bots->docs/channels and
reference/bot->reference/channels, nav registry, redirects
createBot and other API names unchanged. Old @copilotkit/bot* to be deprecated
after the new packages publish (bot-intelligence was never published).
Re-derived onto latest main (was conflicting after #5761 landed).
Refs OSS-438
- Drop the resurrected inline buildContentParts method from the adapter; the
turn path now uses the extracted ./content-parts.js helper (#5814's refactor).
- Port main's fail-visible file-fetch behavior into content-parts.ts so a file
that can't be retrieved becomes a short note in both the live-turn and
history-seeding paths (they share the helper).
- Update the state-store conformance import to the @copilotkit/bot/testing
subpath (main moved it there to keep vitest out of consumers' runtime graph).
## Summary
Lets the `@copilotkit/bot` SDK run from **Intelligence-delivered
events** without a second programming model, and adds the runtime `bots`
declaration API. A managed event (delivered by Intelligence) runs the
*same* customer handlers, tools, context, commands, Bot UI, and agents
as local/custom adapters — the managed path is "just another
`PlatformAdapter`," fed by injected transports.
This is the **OSS / SDK slice** of the Hosted Managed Bots work. The
credentialed transports (Realtime Gateway, Connector Outbox) and the
frozen shared contracts live elsewhere (see *Out of scope*); this PR
ships the seams they plug into, fully runnable headless.
Relates to **OSS-360** (runtime bots API), **OSS-361** (run the SDK from
Intelligence events), **OSS-363** (Slack render/codec reuse).
## What's in here
- **`intelligenceAdapter()` bridge** (`@internal`, not publicly
documented) — implements `PlatformAdapter` over two injected transports:
`DeliverySource` (inbound) + `EgressSink` (outbound). Ingress →
`onTurn`/`onCommand`/`onInteraction`/`onThreadStarted`/`onReaction`; ack
on success / nack on throw (at-least-once). Egress emits generic
operations carrying `BotNode[]` IR with **deterministic ids**
(`turnId:seq`, reset per turn) so a redelivered turn reproduces the same
ids for the Connector Outbox to dedupe. Idempotency lives at egress, so
the managed path skips ingress dedup (`skipIngressDedup`) — a redelivery
re-runs rather than being dropped.
- **Runtime `bots` API** — `new CopilotRuntime({ intelligence, bots })`,
accepted by TypeScript **only when `intelligence` is configured**
(discriminated union). `createBot({ name })`; `startManagedBots()`
validates names (required, identifier-style, unique — fail-loud), builds
activation metadata, and wires each bot to its resolved transport.
- **`PlatformCodec` seam** + Slack egress codec (`slackCodec`) composing
the existing pure `renderSlackMessage`, so IR→native rendering is shared
(no Bolt/creds) instead of duplicated.
- **Backwards-compatible SDK foundations**: `bot.addAdapter()` +
optional `adapters`, deferred backend resolution at `start()` with
`stateStore`-provider precedence (+ multi-provider warning),
`bot.transcripts` throws pre-start, optional
`eventId`/`turnId`/`deliveryId` on ingress + handler context. Existing
`createBot` callers and every `PlatformAdapter` implementer are
unaffected.
- **In-memory transports + fixture tests** — the full dispatch path
(envelope in → handler runs → egress op out) runs with zero
Slack/Intelligence/network.
## Out of scope (external / separate tickets)
- **Realtime Gateway + Connector Outbox transports** — implemented in
the closed-source repo against the `DeliverySource`/`EgressSink`
interfaces shipped here.
- **Shared contracts freeze (OSS-377)** — consumed here via a minimal,
isolated placeholder (`managed/contracts.ts`, marked `TODO(OSS-377)`);
swaps in via one import change.
- **OSS-363 ingress normalization** — the egress codec is done;
extracting the pure Slack event→neutral mapping out of the Bolt listener
(so local + Intelligence ingress share it) is the remaining, higher-risk
half and is left to that ticket (`TODO(OSS-363)`).
## Testing
TDD throughout (RED→GREEN per behavior). New: managed adapter
dispatch/ack-nack/ids/run-renderer/exclusivity, all-kinds routing, name
validation + metadata + lifecycle, runtime `bots` option, Slack codec.
Full suites green: `bot` 147, `bot-slack` 256, `runtime` 1574. All
builds typecheck (`bot`/`bot-slack`/`bot-discord`/`runtime`);
oxlint/oxfmt clean.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Correctness:
- C1 create-bot: start() is now idempotent — a second start() no longer
re-resolves the backend / rebuilds Transcripts+Telemetry+ActionRegistry or
re-connects adapters (which would wipe MemoryStore state and double-bind real
adapters). stop() clears the flag so start→stop→start is still a real restart.
- S1 bot-slack ingress: a threaded reply that @-mentions the bot is now skipped
(app_mention handles it) so the managed path no longer double-responds. Matches
both the plain <@U…> and labeled <@U…|handle> mention forms.
- S2 runtime: CopilotSseRuntime throws if `bots` is passed without intelligence
instead of silently dropping them (guards a JS/as-any caller past the type).
- S3 bot-intelligence: startManagedBots rolls back — stops already-started bots —
when a later bot fails to start, instead of leaking listeners/connections.
Lower:
- S4 ingress: stripMentions handles the labeled <@U…|handle> form; DM turns strip
mentions too (parity with app_mention/thread_reply).
- S5 bot-intelligence: bot-name uniqueness is now case-insensitive.
- S6 runtime: fail fast at construction when a declared bot has no name (full
shape/uniqueness validation stays at the activation seam — assertValidBotNames —
because it can't cross into this CJS package from pure-ESM bot-intelligence).
- S7 bot-intelligence: buildActivationMetadata throws on a nameless bot instead of
silently filtering it out of the activation set.
- S8 bot-intelligence: startManagedBots warns on an empty bots array.
- M1 intelligence-adapter: the per-turn egress seq Map entry is deleted after each
turn so it can't grow unbounded over a long-running bot.
- M2 intelligence-adapter: an inbound file that fails to fetch degrades to a
fail-visible text note instead of being silently dropped from model context.
- I2 contracts: dropped the now-dead `duplicate_skipped` RenderAccepted value
(Intelligence returns duplicate_accepted or a 409 conflict).
Changelog (C2/C3, intended behavior after moving init into start()):
- bot.transcripts now throws before start() (was a concrete property).
- telemetry `oss.bot.configured` now fires at start() rather than construction, so
a constructed-but-never-started bot no longer emits it.
Not addressed here (cross-repo, tracked on the Intelligence side):
- I1 realtime render-event kind:"file" clause on the gateway validator.
- I3 lease-token fencing on the render-accept path.
The package entry (@copilotkit/bot) re-exported runStateStoreConformance from
./testing/state-store-conformance, which imports vitest at module top-level.
An ESM re-export eagerly evaluates that module, so a bare
`import { createBot } from "@copilotkit/bot"` dragged vitest into every
consumer's runtime graph and threw ERR_MODULE_NOT_FOUND when vitest wasn't
installed (i.e. any production consumer).
- Drop the re-export from src/index.ts (entry is now vitest-free)
- Publish the conformance helper under the ./testing export subpath
- Declare vitest as an optional peerDependency (documents the /testing need)
- Update docs to import from @copilotkit/bot/testing
Names/behavior of the runtime API are unchanged; only the import path for the
test-only conformance helper moves.
Two pre-existing test failures on this branch, surfaced by CI's unit +
check-types jobs once main was merged:
- bot-slack event-renderer: the non-pane thread tool-call test still
asserted the old "no composer status" behavior. Commit 13248dda0b
deliberately drove setStatus on ANY thread anchor (not just panes), so
the test now expects both the 🔧 row and the "is using…" status.
- runtime in-memory-runner: HangingAgent/AbortableAgent extended
AbstractAgent but omitted the abstract run() member (@ag-ui/client
0.0.57), failing tsc on the test tsconfig (TS2515). Add the same
run() => EMPTY stub the sibling test agents use.
Brings the 499-commit-stale foundations branch up to date with main so #5761
has a clean diff and no stale reverts (e.g. forwardHeaders). Conflicts:
- CopilotThreadsDrawer.tsx: took main's (main renamed CopilotDrawer -> ThreadsDrawer
+ added the collapse feature; the branch's edit was a no-op import-type split).
- pnpm-lock.yaml: regenerated with the pinned pnpm 10.33.4 (adds @copilotkit/bot-intelligence).
Syncs the branch with main (304 commits) to resolve CI type-check failure.
main changed extractForwardableHeaders to require a forwarding policy and
added the mergeForwardableHeaders helper (#5712); handle-suggest now uses
mergeForwardableHeaders(agent.headers, request, runtime.forwardHeadersPolicy ??
resolveForwardHeadersPolicy(undefined)) to match the run handler — fixing the
drift and adopting the server-headers-win / infra-header denylist behavior.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rework the stateless /suggest transport to reuse the AG-UI SSE pipeline
instead of a buffered JSON response, resolving the streaming + state review
feedback:
- server runs the provider agent directly and streams its events via
createSseEventResponse (the runner's event pipeline minus GLOBAL_STORE
persistence), gated with captureTelemetry:false so suggestions stay out of
run telemetry
- client drives a stock HttpAgent against /agent/:id/suggest, so chips stream
progressively via onMessagesChanged and the run never routes through the
Intelligence websocket delegate (still no thread persistence)
- forward the consumer's deep-cloned messages + state onto the suggestion run
(was state: {}), matching the clone fallback
Net -68 LOC of production code; the stateless and fallback paths now share one
runAgent flow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Redesigns the shared `<copilotkit-threads-drawer>` element
(`@copilotkit/web-components`) to the new Figma UX, keeping the React,
Vue, and Angular wrappers in lockstep. Pure-VIEW change — no
`@copilotkit/core`, runtime, or `useThreads` changes.
**Ticket:** [ENT-1051](https://linear.app/copilotkit/issue/ENT-1051) ·
**Figma:** [Thread
Drawer](https://www.figma.com/design/feSsBJw1qCfLp0JNnOurrJ/CopilotKit-Intelligence?node-id=723-78)
· **Spec:**
[Notion](https://app.notion.com/p/3953aa381852819ab464dae3894e7f18)
## What changed
- **Header** → right-aligned icon row. On desktop it holds the
**collapse** toggle (sidebar glyph); on mobile the **close** toggle. No
title text, no "+ New" pill. Optional `slot="header"` preserved (empty
by default; the toggle right-aligns after it).
- **New Conversation** row (plus-square + label) below the header —
keeps `part="new-thread-button"` + the `new-thread` event.
- **Recent Conversations** heading + **funnel** filter icon → Active/All
popover. Preserves `_filter` + `filter-change` and
`part="filter-active"`/`filter-all"`.
- **Per-row kebab menu** (⋮) holding Archive/Unarchive + Delete —
preserves those events + parts. An open kebab now shields the rest of
the list from hover so it reads as a single surface (see Review fixes).
- **Delete confirm** is a native `<dialog>` opened with `showModal()`
(browser top layer), centered over the drawer's visible box — it can't
paint under other UI or drop below the fold. jsdom falls back to the
`open` attribute.
- **Archived rows** render italic/muted inline in the "All" view.
- **Desktop collapse** → `collapsed` / `collapsible` props (default
**expanded**) + a `collapse-change` event / `CollapseChangeDetail`.
Collapsing sets `--cpk-drawer-reserved-width: 0` on the document root so
the host grid reclaims the column with no hydration flicker.
- **Unified floating cluster** (Figma "closed" mockup) =
`[sidebar-toggle] [+ New Conversation]`, shown in both the mobile-closed
and desktop-collapsed states. Mobile stays an off-canvas modal (backdrop
/ Escape / focus-trap).
## Compatibility
- All existing `::part()` names and events are preserved; only
**additive** parts are introduced: `collapse-toggle`, `close-toggle`,
`section-heading`, `filter-toggle`, `row-menu`, `row-menu-popover`,
`launcher-cluster`, `launcher`, `launcher-new-thread`, plus
`confirm-dialog`/`confirm-cancel`/`confirm-delete`/`backdrop`. One
additive event: `collapse-change`.
- Additive wrapper props: `recentLabel` (all frameworks);
`collapsible`/`collapsed` + `onCollapseChange` (React) / equivalents in
Vue & Angular.
- **Usage note (now in the docs):** the drawer and `<CopilotChat>` must
share a chat-configuration provider so the drawer drives the chat —
`CopilotChatConfigurationProvider` (React/Vue) /
`provideCopilotChatConfiguration()` (Angular). The v2
`CopilotKitProvider` does not provide that context on its own.
- Verified: **no example `::part()` theme changes required** — every
example themes the drawer via inherited `--cpk-drawer-*` custom
properties.
## Descoped / changed during development (re: earlier review)
- **Client-side search was removed at the designer's request.** There is
**no** search UI, `search` event, `search-toggle`/`search-input` part,
or `onSearch` wrapper prop in the shipped element. Any remaining
"search" mention in older comments is stale.
- **Desktop collapse was briefly backed out, then re-restored** per the
designer (commit `8bfd245305`). The shipped element **has** collapse
(`collapsed` is a live public property — it was not removed).
## Review fixes (commit `0b4f6ca392`)
Addressing @MikeRyanDev and @marthakelly:
- **`core/threads.ts`** — a full-list refetch (filter-change / retry)
now clears `fetchMoreError` on both `listRequested` and `listSucceeded`,
so the inline "couldn't load more — retry" banner no longer survives
onto a fresh list.
- **Escape while confirming delete** — the host keydown handler now
consumes Escape while a confirmation is open; previously the bubbled
keydown fell through and closed the whole mobile drawer along with the
confirmation.
- **Open kebab menu shields the list** — `.list.menu-open
.row:not(.menu-open)` gets `pointer-events: none`, so other rows no
longer reveal their kebab / paint a host `::part(row):hover` background
around or behind the open popover. Click-away dismissal is preserved via
the existing document pointerdown handler. (Verified live in the
langgraph-js example.)
- **Docs token** — dropped the removed `--cpk-drawer-rail-width` from
the web-components README.
## Testing
All suites run via `nx`, green through each package's lefthook
pre-commit gate:
- `@copilotkit/web-components` — **92** drawer element tests + `:build`
green. Covers header collapse/close toggles, New Conversation, funnel
filter switch, `recentLabel`, kebab open + archive/delete routing,
confirm-dialog gating + native cancel + **backdrop-click dismiss**,
**Escape-while-confirming (no drawer close)**, **open-menu row shield**,
collapse/cluster/column-reclaim, archived-italic, preserved
parts/events, `header` slot + `label` aria-labels.
- `@copilotkit/core` — **553** tests incl. the new `clears a lingering
fetchMoreError when a full list refetch succeeds`.
- `@copilotkit/react-core` — CopilotThreadsDrawer suite + full package
**1419** green.
- `@copilotkit/vue` — **32** incl. SSR + the `collapsible`
boolean-prop-default regression test.
- `@copilotkit/angular` — CopilotThreadsDrawer spec **35** (incl.
**scoped-chat-input focus**: prefers the ancestor `copilot-chat-view`
over the document-global fallback); full package **178**.
## Follow-on
- Docs (screenshot + reference/guide) on the release-gated docs PR
**#5780**.
- Release (`web-components` + `react-core` + `vue` + `angular`,
lockstep) + CLI scaffolding bump.
- Example grid/theme updates ride the release in **#5828**.
- **[ENT-1080](https://linear.app/copilotkit/issue/ENT-1080)** — dedup
the per-wrapper `findChatInput` / open-state fallback (marthakelly #6,
deliberately deferred as a cross-package refactor).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Addresses PR #5823 review (MikeRyanDev + marthakelly):
- core/threads.ts: a full-list refetch (filter-change / retry) now clears
fetchMoreError on both listRequested and listSucceeded, so the inline
'couldn't load more - retry' banner no longer survives onto a fresh list.
- web-components: Escape while the confirm-delete <dialog> is open is now
consumed by a confirm guard in the host keydown handler; previously the
bubbled keydown fell through to the mobile branch and closed the whole drawer
along with the confirmation.
- web-components: an open kebab popover now shields the rest of the list -
.list.menu-open .row:not(.menu-open) gets pointer-events:none so other rows
no longer reveal their kebab or paint a host ::part(row):hover background
around/behind the menu. Click-away dismissal is preserved via the existing
document pointerdown handler.
- README: drop the removed --cpk-drawer-rail-width from the documented tokens.
Tests: core clears-fetchMoreError-on-refetch; web-components Escape-while-
confirming, confirm-dialog backdrop-click, menu-open row shield; Angular
scoped-chat-input focus (ancestor copilot-chat-view over the global fallback).
Menu-shield verified live in the langgraph-js example (:3002).
The collapse toggle keeps the header bar visible on desktop, so the row's 12px
top margin doubled up with the header's padding (extra gap vs mobile). Make the
top margin conditional on the header being HIDDEN (collapsible=false + no header
slot) via .header[hidden] + .new-conversation; otherwise the header supplies the
top spacing on both breakpoints.
- Floating cluster/launcher default gutter → 24px on both top and left (was 12px).
- Selected row: drop the border-color; the background change alone marks it
(base row keeps its 1px transparent border for layout stability).
- Cap --_radius at 4px via min(theme, 4px) and lower the hardcoded 6px button
radii to 4px, so no bordered element exceeds a 4px corner radius.
Intersect the .root rect with the viewport before centering the confirm dialog.
A host grid that doesn't bound the drawer's row lets .root grow to content
height, so centering over the raw rect dropped the modal far down the page (seen
in the langgraph-js example, whose grid has no row bound). Clamping to the
on-screen band keeps it centered in the visible drawer regardless of host sizing.
Move the collapse toggle after the header slot so it right-aligns (the slot has
flex:1 and pushes it over), matching the mobile close button — it was reading as
left-aligned above New Conversation, which the designer flagged.
Design iteration (Ben's designer):
- RESTORE desktop collapse. Re-add collapsed/collapsible + collapse-change
(element + all three wrappers, lockstep), the desktop header collapse toggle,
and the CollapseChangeDetail type/re-export. Default is EXPANDED.
- UNIFY the closed affordance into one floating cluster (Figma 'closed' mockup):
a sidebar-glyph toggle + a New Conversation (+) icon button, shown in BOTH the
mobile-closed state (adds New Conversation to the old single launcher) and the
desktop-collapsed state. Parts: launcher-cluster, launcher, launcher-new-thread.
- COLUMN RECLAIM (no empty reserved gap): on desktop-collapse the element sets
--cpk-drawer-reserved-width: 0px on the document root (reaches the grid past
the wrapper host via :root inheritance); hosts read it in grid-template-columns.
Default expanded never sets it, so no hydration flicker.
- DELETE MODAL centered over the DRAWER PANEL, not the viewport: keeps the
top-layer showModal() robustness (never clipped) but drives --confirm-cx/cy
from the visible .root rect and caps width to the drawer band.
- Vue fix: default collapsible to true in the wrapper. Vue coerces an omitted
Boolean prop to false, which was silently forcing collapsible=false (collapse
toggle vanished) — React/Angular pass undefined and keep the element default.
Validated live in the Nuxt (Vue) and Angular demos against managed Intelligence:
collapse/expand, cluster + New Conversation, column reclaim, drawer-centered
top-layer modal, mobile cluster. Tests: web-components 89, react-core 1419,
vue 32, angular 34 — all green.
Parity with the vue fix: the react-core drawer wrapper references the Lit
`<copilotkit-threads-drawer>` element from @copilotkit/web-components, which was
not externalized in the main ESM/CJS build entry — so tsdown inlined the whole
element + a second copy of lit-html into the react-core dist.
This bloats the library and breaks Vite-based React consumers with a duplicate
lit-html binding ("Identifier 'h' has already been declared"); webpack/Next
consumers dedupe it so it went unnoticed. Externalizing it (as @copilotkit/core,
@copilotkit/shared, @copilotkit/web-inspector, @copilotkit/a2ui-renderer already
are) resolves the import to the single real package at runtime. The self-contained
UMD builds intentionally keep it inlined.
react-core test suite: 1416/1416 pass.
The drawer wrapper loads the Lit `<copilotkit-threads-drawer>` element via a
client-only dynamic import of @copilotkit/web-components/threads-drawer. That
package was not in rollupOptions.external, so vite bundled the whole element +
a second copy of lit-html inline into the vue dist (a ~1MB chunk).
Under Vite/Nuxt consumers this inlined duplicate lit-html produced a fatal
"Identifier 'h' has already been declared" parse error (h = lit-html's minified
`html`), and it also risks double custom-element registration. Externalizing it
(as @copilotkit/core, @copilotkit/shared, @copilotkit/web-inspector already are)
makes the dynamic import resolve to the single real package at runtime.
Verified live: the redesigned drawer now renders + operates in the Nuxt vue demo
(licensed, real threads, kebab Archive/Delete, top-layer delete dialog, mobile
off-canvas + sidebar-glyph close). Vue wrapper unit tests: 29/29 pass.