## Summary
- Backport the website messaging from CopilotKit/website#398 into the
shell-docs Slack and Microsoft Teams frontend pages.
- Replace the stale waitlist/managed-only framing with "get early
access" copy that presents CopilotKit Enterprise Intelligence as the
self-hosted or cloud-hosted production layer around the open source Bot
SDK.
- Frame Slack and Teams as frontends for agents built on any harness or
framework, while reserving production-layer terminology for CopilotKit
Enterprise Intelligence.
- Address browser review annotations on both pages: remove filler in the
opener, avoid setup-heavy lead copy, use "open source" without a hyphen,
add the full CopilotKit Enterprise Intelligence name to the CTA titles,
and keep CTA telemetry surfaces intact.
- Update the shell-docs nav test expectation so it matches the current
root IA, where Threads lives under Build Chat UIs rather than the
generated Intelligence Platform section.
## Validation
- `npm run lint` from `showcase/shell-docs` (passes with existing
warnings)
- `npm run typecheck` from `showcase/shell-docs`
- `npm run test` from `showcase/shell-docs`
- `npm run build` from `showcase/shell-docs`
## Notes
- Hydrated Git LFS assets locally with `git lfs pull` so the shell-docs
public asset tests could read real PNG bytes.
## What
Adds `examples/showcases/banking/stop-demo.sh` — the teardown companion
to the existing `run-demo.sh`.
## Why
`run-demo.sh` detaches everything except the Next.js dev server:
- `docker compose up -d --wait` (detached stack)
- native Metal TEI via `nohup … & disown` (Apple Silicon only)
- `exec pnpm dev` (the only foreground process)
So Ctrl-C stops *only* the dev server and silently leaves the docker
stack (`banking-memory`) and the host embedder on `:7067` running. There
was no one-command way to bring those down. This script fills that gap
and mirrors `run-demo.sh`'s conventions (same `say`/`ok` helpers, same
header-comment style, idempotent).
## What it does
Tears down, idempotently, in order:
1. Next.js dev server on `:3000` (defensive — usually already gone via
Ctrl-C)
2. docker compose stack (project `banking-memory`), **containers only**
by default so a re-run of `run-demo.sh` reuses the built composite image
+ seeded Postgres
3. native Metal TEI on `:7067` (Apple Silicon; the host process docker
doesn't manage) — SIGTERM, then SIGKILL for anything that ignores it
## Flags
- `--purge` — also delete the docker volumes (postgres/redis/minio/tei
model cache) for a full clean-slate reset
- `--keep-tei` — leave the slow-to-warm native embedder running when
only bouncing the stack
## Testing
- `bash -n stop-demo.sh` — syntax clean
- `shellcheck stop-demo.sh` — clean, no warnings
- `./stop-demo.sh --help` renders the banner correctly
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Problem
`@copilotkit/bot`'s package entry re-exports `runStateStoreConformance`
from `./testing/state-store-conformance`, which does `import { describe,
it, expect, ... } from "vitest"` at module top-level. In ESM a static
re-export **eagerly evaluates** the re-exported module, so a plain:
```ts
import { createBot } from "@copilotkit/bot";
```
drags `vitest` into the consumer's runtime module graph and throws
`ERR_MODULE_NOT_FOUND: Cannot find package 'vitest'` for any consumer
that doesn't have vitest installed (i.e. every production consumer).
`vitest` is only a devDependency.
Surfaced while smoke-testing the package rename (OSS-438) — but it's a
pre-existing bug on `main`, independent of that rename.
## Fix
- **Drop the re-export from `src/index.ts`** → the package entry is now
vitest-free.
- **Publish the helper under a `./testing` subpath**
(`@copilotkit/bot/testing`) — test tooling lives off the runtime entry,
the standard pattern.
- **Declare `vitest` as an optional `peerDependency`** so consumers of
`/testing` get the right signal.
- **Docs** updated to `import { runStateStoreConformance } from
"@copilotkit/bot/testing"`.
Only the import path of the test-only conformance helper changes; the
runtime API is untouched.
## Verification
- Static import trace: the entry graph is 12 runtime modules, **none**
import vitest; every vitest importer is a `.test.js` (not in the graph)
or `testing/state-store-conformance.js` (only reachable via `/testing`).
- `@copilotkit/bot` builds; **143/143** tests pass; `publint` + `attw`
clean (the internal conformance test imports the helper by relative
path, unaffected).
## Coordination
Touches `packages/bot` on `main`. The OSS-438 rename PR (#5849) renames
this package to `@copilotkit/channels`; that PR re-derives from `main`
before merge, so it will absorb this fix automatically. If #5849 merges
first, this rebases onto `packages/channels` mechanically.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
run-demo.sh detaches everything except the Next.js dev server (docker
compose up -d, native Metal TEI via nohup/disown, then exec pnpm dev), so
Ctrl-C on the dev server leaves the docker stack and the host embedder
running. stop-demo.sh brings those leftovers down in one command.
Tears down, idempotently:
- the Next.js dev server on :3000 (defensive; usually gone via Ctrl-C)
- the docker compose stack (project banking-memory), containers only by
default so a re-run reuses the built image + seeded data
- the native Metal TEI on :7067 (Apple Silicon; the host process docker
doesn't manage), SIGTERM then SIGKILL
Flags: --purge also drops volumes for a clean slate; --keep-tei leaves the
slow-to-warm embedder running when only bouncing the stack.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
## Release monorepo v1.62.3
**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.62.3`
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.62.3`
- Creates git tag `monorepo/v1.62.3`
- 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.
Both entrypoints run under set -e. The tail `wait -n $AGENT_PID $NEXTJS_PID`
returns non-zero on the PRIMARY designed exit path (137 = size-gate/watchdog
SIGKILL of the agent tree, or an agent crash), so set -e aborted the script AT
that line — making EXIT_CODE=$?, the entire 'which process exited with code N'
diagnostic, and the final `exit $EXIT_CODE` dead code on exactly the
interesting exits. Capture the code with `EXIT_CODE=0; wait -n ... || EXIT_CODE=$?`
so the diagnostic and explicit exit run and preserve the exact code (incl. 137);
the container-restart path is unchanged.
Same class: langgraph's LANGGRAPH_SIZE_THRESHOLD_MB was used in
`[ "$DIR_SIZE_MB" -ge "$threshold" ]` with no numericity guard, so a
non-integer operator override made the test error and silently no-op the size
gate every cycle. Validate the threshold the same way DIR_SIZE_MB already is
(numeric case guard + 'size guard inactive' WARNING, then skip safely).
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.
The health-watchdog armed its 3-strike/~90s kill counter immediately with no
startup-grace window. langgraph-typescript has a 180s grace precisely to keep a
slow cold start from being killed mid-boot into a restart loop. Now that the
tree-kill makes the strands kill effective (the orphan bug previously made it
cosmetic), a slow tsx cold start (>90s) would be genuinely killed and loop.
Port langgraph's grace mechanism verbatim (GRACE=180, no env override, poll
every 5s, exit 0 on agent death during startup, arm anyway if grace elapses),
adapted to strands' :8000/health probe.
Two tightly-related defects in the size-gated restart machinery in
entrypoint.sh:
1. _watchdog_check_size_once validated the du/awk result only for
emptiness, not numericity. A non-integer value (junk du output, a
transient read error, a test-seam stub) reached the
`[ "$DIR_SIZE_MB" -ge ... ]` comparison and threw "integer expression
expected"; sitting inside an `if`, set -e was suppressed so the test
evaluated false and the size gate was SILENTLY skipped with no
warning (unlike the empty-string branch). Now match ^[0-9]+$ via a
case and emit the same "size guard inactive" WARNING, so the gate can
never silently disappear.
2. The size sub-loop used `_watchdog_check_size_once || break`, treating
ANY non-zero (including a transient check error) as a kill and
permanently ending the monitor for the container's lifetime. Now
break ONLY on the real-kill signal (rc==1) — preserving the
kill -> wait -n -> container-restart -> boot-purge contract — while a
transient non-zero keeps the monitor live and re-checks next cycle.
Verified RED->GREEN against the real entrypoint in node:22-slim with a
stubbed du seam: non-numeric du now warns and keeps the gate active; a
transient error no longer permanently disables the loop.
The tree-kill enumerated agent descendants in a single /proc snapshot then
killed. A child that forks a new child (or reparents) between the scan and the
kill escaped the walk, reparented to PID 1, and kept the agent port bound —
defeating the tree-kill's whole purpose of freeing the port before the
container restart.
Replace the single snapshot with a BOUNDED re-scan loop: keep the root alive as
the walk anchor, re-enumerate and SIGKILL live descendants deepest-first each
pass (up to 5 passes, 0.2s apart) until a scan comes back empty, then kill the
root last. Killing the root FIRST would immediately reparent every descendant to
PID 1 and make them unreachable by the root-anchored PPID walk, so root-last is
required for the re-scan to reap late/mid-walk descendants. A descendant that
fully daemonizes (double-fork to PID 1) before we reach it remains out of reach
— documented as an inherent limit of PPID-based reaping without job control; the
agent's npm->node tree does not daemonize.
Also document why the ${stat##*) } PPID parse is safe against a comm containing
") " (longest-prefix to the last ") " always lands on the true terminator).
Applied identically to langgraph-typescript (:8123) and strands-typescript
(:8000). Proven via local RED-GREEN in node:22-slim: pre-fix leaks a mid-walk
escapee (port stays bound), post-fix reaps it (0 orphans, port freed).
The cleanup() EXIT/SIGTERM trap in both langgraph-typescript and
strands-typescript entrypoints did a bare `kill $AGENT_PID`. Because
$AGENT_PID is the outer process-substitution subshell (not the real
npm->node server), this reaped only the subshell and orphaned the node
server (reparented to PID 1, still holding :8123 / :8000) on every
graceful/SIGTERM shutdown -- e.g. every Railway redeploy/rollover.
Route cleanup() through the existing _kill_agent_tree helper (as the
size-watchdog and health-strike kill sites already do), and move the
_agent_descendants/_kill_agent_tree helpers above cleanup()/the trap so
they are defined whenever the trap can first fire.
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).
strands-typescript/entrypoint.sh carries the identical latent trap fixed in
langgraph-typescript by this branch. The agent is launched through a process
substitution — `cd /app/src/agent && npm start &> >(awk …) &` — so
$AGENT_PID (=$!) is the outer subshell wrapping that pipeline, NOT the npm→node
tree it forks (`npm start` runs `node --import tsx server.ts`, which stays a
child of npm, not an exec-replacement). The health-strike `kill -9 $AGENT_PID`
therefore reaps only the subshell; npm and node reparent to PID 1 and KEEP
RUNNING, still bound to :8000. `wait -n` never observes the real server die →
container never restarts → the frontend proxies to a dead-but-not-restarted
agent forever (edge 502s). strands-typescript has no size-gate, so this only
fires after the 3-strike (~90s) health counter exhausts, but it is a real
latent footgun with the same root cause.
Fix: reuse the /proc-based `_kill_agent_tree` helper (node:22-slim ships
neither ps nor pgrep, and job control is off so a group kill would take out
the whole entrypoint) at the single health-strike kill site. The whole
npm→node tree now dies, :8000 is freed, `wait -n` returns and the container
restarts.
Red-green proven in a real node:22-slim container against the exact
process-sub → npm → node structure: RED (bare kill -9 $AGENT_PID) leaves node
orphaned and :8000 still LISTENing; GREEN (_kill_agent_tree) reaps the tree,
0 orphans, port freed.
The size-gated watchdog added in ef103f5f5 does `kill -9 $AGENT_PID`
expecting the container to exit and Railway to restart (re-running the
boot-purge). But $AGENT_PID is the process-substitution subshell wrapping
`cd /app/src/agent && npm start &> >(awk …)`, NOT the npm→node tree it
forks. A single-PID kill reaps only that subshell; npm and node reparent
to PID 1 and keep running, still bound to :8123 with the bloated in-memory
state resident. The frontend then proxies to a dead-but-not-restarted
agent and the edge 502s forever.
Fix: add a /proc-based `_kill_agent_tree` (node:22-slim ships neither ps
nor pgrep, and job control is off so a group kill would take out the whole
entrypoint) and use it at both watchdog kill sites (size gate + health
strikes). The whole npm→node tree now dies, :8123 is freed, `wait -n`
returns, the container restarts and re-runs boot-purge — preserving the
original RangeError-prevention intent.
Documents the new **CopilotDrawer** prebuilt threads drawer (component
PR #5746).
## Changes
- **Guide** — `docs/prebuilt-components/copilot-drawer.mdx` (new) +
registered in `prebuilt-components/meta.json`. React drop-in
(`<CopilotDrawer />` beside `<CopilotChat />`, zero active-thread
wiring), props table, customization (slots / `::part`s /
`--cpk-drawer-*` tokens), Angular usage, license.
- **Threads how-to** — `snippets/shared/threads/threads.mdx` (shared →
propagates to ~10 integration threads pages + `/threads`): a callout
recommending `CopilotDrawer` as the prebuilt path and framing the
`useThreads` steps as the **headless** alternative (kept, not removed) +
Next-steps cross-links.
- **Reference** — `reference/components/CopilotDrawer.mdx` (React) and
`reference/angular/components/CopilotDrawer.mdx` (Angular),
hand-authored with `<PropertyReference>`.
## Validation
`npm run build` in `showcase/shell-docs` is green locally — compiled
successfully, 215/215 static pages generated, no MDX errors. All
internal links verified to resolve on `main`.
## Why draft / release-gated
Merges **with or after** the drawer release
(`@copilotkit/web-components` published +
`react-core`/`@copilotkit/angular` > 1.61.2 containing `CopilotDrawer`).
Until then the documented imports don't exist in a published package.
## Follow-ups (out of scope)
- Live `<InlineDemo>` of the drawer (needs a demo-registry entry) — uses
plain code blocks for now.
- Angular `injectThreads` / `CopilotChatConfiguration` reference pages
(named without links here; they don't exist on `main` yet).
Spec: https://app.notion.com/p/38f3aa381852814e8884e3153d3d4688 ·
Tracking: ENT-1021
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Stacked on #5761 (base: `alem/oss-360-sdk-foundations`). The SDK side of
the Hosted Bots **realtime** path, per the source-of-truth Notion plan
and the OSS-395 frozen contract.
## What this delivers
### OSS-403 — extract a Bolt-free Slack renderer
(`@copilotkit/bot-slack/render`)
`createRunRenderer` no longer takes a `WebClient`; it takes an injected
`SlackRenderTransport` (the 3 credentialed ops it actually calls —
`setStatus` / `postMessage` / `updateMessage`; the 4 native streaming
ops were already injected via `NativeStreamTransport`). New `./render`
subpath export exposes the transport-agnostic renderer plus the
already-pure IR→Block Kit / modal / mrkdwn helpers, so the gateway-side
**Connector Outbox (OSS-404)** can drive the *identical* renderer for
managed replies — 1:1 UX parity without forking. The native Slack
adapter wraps its own `WebClient` into the transport.
- **Zero behavior change** to the native bot — 272 `bot-slack` tests
pass, check-types green.
### OSS-402 — stream RenderEvents over the realtime gateway
The managed adapter (`@copilotkit/bot-intelligence`) now mints semantic
render frames (`run_started` / `text_delta` / `text_end` / `tool_start`
/ `tool_end` / `interrupt` / `run_error` / `finalize`) and streams them
through a new `RenderEventSink`, assigning a monotonic `seq` per
`(turnId, slot)` and awaiting a durable `render_accepted` receipt for
each. Ordering is guaranteed by a serial chain with `seq` fixed at
enqueue time (so it holds regardless of AG-UI callback scheduling); a
rejected push surfaces at drain and nacks the delivery.
`PhoenixRealtimeTransport` (implements `DeliverySource` +
`RenderEventSink`) speaks the frozen OSS-395 contract exactly:
`render_event` frames → `render_accepted` receipts, then
`delivery.complete_requested` (completion **intent**, carrying
`acceptedThrough` high-water pointers) — **never** a committed
`delivery.ack` (app-api owns ack), and **no Slack credentials** in the
SDK. The Phoenix `Socket`/`Channel` boilerplate is expressed against a
minimal injected `HostedBotChannel` so the protocol is fully unit-tested
with a fake channel.
When no realtime sink is wired, the renderer falls back to translating
frames into `post` ops on the `EgressSink`, so the existing HTTP demo
keeps posting plain-text replies.
## Tests
- `@copilotkit/bot-slack`: 272 pass (native renderer behavior preserved
through the transport swap).
- `@copilotkit/bot-intelligence`: 43 pass (37 baseline + 6 new) — frame
ordering + `turnId:slot:seq`, completion-after-receipt, and the
**never-self-ack** invariant.
- `@copilotkit/runtime` check-types green (fixes the pre-existing
`get-runtime-info` `bots` red flagged on #5761).
## Follow-ups (not in this PR)
- **Gateway `complete_requested` handler (Intelligence #466):**
`sdk_channel.ex` currently routes `render_event` / `ack` / `fail` but
not `complete_requested`; it needs a `handle_in` clause +
`app_api_client.complete_delivery` + a matching app-api completion
route. Coordinating with @TylerSlaton since it's his file / shared
branch (no local Elixir toolchain to verify here).
- **OSS-404 Connector Outbox** consumes `@copilotkit/bot-slack/render`
to render frames → real Slack messages; end-to-end realtime parity isn't
demoable until it lands.
- Discrete `EgressSink` posts (command/interaction replies) →
`post`/`update` render frames (needs a per-`(turn, slot)` seq shared
with the run renderer).
> **Update (SSE rework — addresses review):** `/suggest` now **streams
AG-UI SSE** instead of buffering a JSON `{ messages }` response, and the
run **forwards the consumer's messages + state**. Server-side it reuses
`createSseEventResponse` (the runner's event pipeline minus
`GLOBAL_STORE` persistence, `captureTelemetry:false`); client-side it
drives a stock `HttpAgent` at `/agent/:id/suggest`, so chips fill in
progressively via `onMessagesChanged` and the run never touches the
Intelligence websocket delegate. The stateless and fallback paths now
share one `runAgent` flow (net −68 LOC of production code). The `## How`
/ behavioral notes below are updated to match.
## What & why
Dynamic suggestions were implemented as a **full shadow-thread agent
run** (`SuggestionEngine.generateSuggestions` clones the provider agent
onto a throwaway `threadId` and calls `runAgent`). Against a
thread-persisting backend (CopilotKit Intelligence) every reload
materialized a **listed, auto-named thread** (plus gateway events + a
run lock), flooding the thread drawer. Root cause is the design: a
suggestion is a **stateless structured completion**, not a persisted
conversation.
This makes it one. A suggestion now runs the provider agent **directly**
behind a dedicated `POST /agent/:agentId/suggest` handler — no thread,
lock, gateway, runner-store, or name-gen — for **all**
CopilotKit-runtime backends. The debris is structurally impossible
rather than hidden.
## How
- **`get-runtime-info`** advertises a `suggestions` capability on `GET
/info` (`RuntimeInfo.suggestions`).
- **`handleSuggestAgent`** (new) runs the resolved provider agent
directly and **streams its AG-UI events as SSE** via
`createSseEventResponse` — the runner's event pipeline
(`agent.runAgent({ onEvent })` + `finalizeRunEvents`) **minus** the
`GLOBAL_STORE` persistence that backs SSE-mode thread listings.
`captureTelemetry:false` and no debug bus keep suggestions out of run
telemetry/the inspector. Header-forwarding only; no middleware.
- **Router**: `POST /agent/:agentId/suggest` wired through
`fetch-router`/`fetch-handler` (multi- and single-route), with
`assertNever` exhaustiveness guards.
- **core `SuggestionEngine`**: when the runtime advertises `suggestions`
(and transport isn't single-route), it seeds a stock `HttpAgent`
(pointed at `/agent/:id/suggest`, credentials via a fetch wrapper) with
the consumer's deep-cloned messages + state and `runAgent`s it — a plain
`HttpAgent` only speaks REST SSE, so it never routes through the
Intelligence websocket delegate. Otherwise it falls back to the
clone+`runAgent` path. Both paths share one flow; abort-aware; failures
logged.
## Testing
Unit + integration across both packages (**504 core + 1587 runtime
green**, `check-types` clean): the **no-thread-leak** proof (real
`InMemoryAgentRunner`, asserts the suggest thread never appears in the
live listing), progressive-streaming + state-forwarding assertions,
cross-mode SSE handler behavior, error/abort robustness (Safari
`DOMException` + undici non-`AbortError`), header/credential forwarding,
multi-route `200` + `GET→405`, and single-route/`suggestions:false`
fallback.
Validated end-to-end against a running runtime + real `CopilotKitCore`
client over HTTP (local built packages):
- **In-memory managed threads:** a normal `/run` creates exactly 1
listed thread; **4 dynamic-suggestion reloads add 0**. Chips stream
progressively (`onSuggestionsChanged` 0→1→2); the `/suggest` request
carries the consumer's state.
- **Live hosted CopilotKit Intelligence** (`mode: "intelligence"`, real
gateway): `/info` advertises the capability, suggestions stream via the
stateless path, and the **hosted thread listing is byte-identical
(50→50) after 6 reloads** — the drawer's actual data source is
untouched.
## Compatibility
**Migrations:** none. No schema/DB change; self-hosted deployments need
no coordinated update — the change is entirely in `@copilotkit/core` +
`@copilotkit/runtime`. No release flag.
**Additive + capability-gated — version skew is safe both directions:**
- **New core + old runtime** (no `/suggest`, capability absent):
`core.suggestions` is `undefined` → client uses today's clone+`runAgent`
path.
- **Old core + new runtime**: the new optional `RuntimeInfo.suggestions`
field is ignored; the `/suggest` endpoint sits unused.
- **New + new**: stateless `/suggest`.
`RuntimeInfo.suggestions` is a new **optional** field and the
`/agent/:agentId/suggest` route (+ `RouteInfo`/`METHOD_NAMES` entries)
are purely additive. No exported symbol was removed or renamed.
**Behavioral notes (matched new versions):**
- Dynamic suggestions **stream progressively** over SSE (chips fill in
as the provider emits them), matching the pre-`/suggest` UX.
- Suggestion runs no longer flow through the run handler, so they create
**no thread** (the fix) and won't appear in `oss.runtime.*` run
telemetry.
- Single-route transport and non-CopilotKit AG-UI backends keep the
clone+`runAgent` fallback.
**⚠️ Deploy note:** a reverse proxy that path-allowlists CopilotKit
routes must add `POST /agent/:agentId/suggest`. If the capability is
advertised but that path is blocked, suggestions silently no-op — the
client does **not** fall back on a `/suggest` error (by design, to avoid
re-introducing the thread flood).
## Review
Landed through a full CR loop (3 rounds / 21 agent-reviews + a
bucket-(c) promotion audit) — bucket (a) converged to zero. Known
pre-existing follow-ups tracked separately (out of scope):
`resolveLicenseStatus` grace-period ordering and
`AgentRegistry.getAgent` using `in` vs `hasOwnProperty` for reserved
ids.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
- Add zodState import and wrapper in TypeScript state schema example
- Show state emission as chatNode function with proper signature
- Align Deep Agents pattern with LangGraph state rendering docs
- Fixes FAC-63: ensures custom state properties appear in STATE_SNAPSHOT events
- Add zodState import to in-app-agent-read.mdx and in-app-agent-write.mdx
- Wrap language field with zodState() to ensure it appears in AG-UI and useAgent state
- Matches pattern used in predictive-state-updates.mdx
Fixes: FAC-48
- Replace self.state with state in manual predictive state updates example
- Add missing colon in step_progress_tool function definition
- Align Python examples with working code patterns from examples/v1/travel/agent
Fixes FAC-53
The prior preview paired the real <copilotkit-threads-drawer> with a hand-coded
mock chat panel (from the screenshot harness), so the chat half wasn't an actual
CopilotKit component — which is what the reviewer flagged. Re-captured with the
real default <CopilotThreadsDrawer> + <CopilotChat> together, stock (untheme d)
light styling, showing a thread's replayed conversation.
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>
- /threads page title -> 'Headless Threads' to distinguish the headless
useThreads path from the prebuilt drawer (slug kept, so no inbound links
break; sidebar label follows the frontmatter title). (samjulien #9)
- prebuilt-components index: the 'saved conversations' line now leads with the
drop-in CopilotThreadsDrawer and offers Headless Threads as the DIY path, plus
a companion-sidebar mention in 'Pick a surface' — the drawer was absent from
the prebuilt landing page.
- chat page: same, point at the prebuilt drawer first, headless second.
- Remove 'rename' from the prebuilt CopilotThreadsDrawer capability claims
(guide, React reference, shared Threads callout) — the row kebab only does
archive/unarchive + delete. Add an explicit note that rename is available via
the headless useThreads path. (MikeRyanDev)
- Reference CSS parts list now matches the shipped element: adds row/row-active,
collapse-toggle, close-toggle, backdrop, launcher-cluster, launcher-new-thread,
load-more, fetching-more, fetch-more-error, fetch-more-retry, licensed,
licensed-cta; grouped by area. (MikeRyanDev)
- Rewrite the 'no threadId state / no onSelect plumbing' line to stand on its own
by contrasting with a hand-rolled sidebar. (samjulien)
Stand-in for a live showcase example (out of scope for this PR): a real
screenshot of <CopilotThreadsDrawer> beside <CopilotChat>, rendered from the
v2 react demo against the Intelligence platform. Embedded as a <Frame> preview
right under the intro.
Match the final component name. Renames the guide and reference pages
(copilot-drawer.mdx -> copilot-threads-drawer.mdx, CopilotDrawer.mdx ->
CopilotThreadsDrawer.mdx), their slugs/URLs, the nav meta entry, the
data-testid default, the <copilotkit-threads-drawer> element mention, and
all prose/import references. The --cpk-drawer-* CSS tokens and ::part names
are unchanged.
Per review: remove the onUnlicensed prop, the unlicensed slot, and the
unlicensed/unlicensed-cta parts from the guide + reference so neither
humans nor agents surface them. Replace with a single neutral line:
threads require Intelligence; a locked view shows without a license key.
- Guide: lead with the user benefit (less reference-y opening), reframe the
headless useThreads alternative to stand alone, add the OpsPlatformCTA
sign-up callout (per review).
- Add threads.mdx (shared-snippet include) for a2a, adk, agent-spec,
deepagents + register each under Intelligence Platform in meta.json.
Per review: defer the Angular drawer docs. Removes the Angular reference
page, the guide's Angular section, and Angular cross-links; keeps the
React guide + reference + the Threads how-to callout.
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)
## Problem
The **Shell script tests (bats + shellcheck)** job in
`showcase_validate.yml` installs `bats` with:
```yaml
sudo apt-get update
sudo apt-get install -y bats
```
GitHub's `ubuntu-latest` runner image preconfigures third-party apt
repos (Microsoft / `azure-cli`, pointing at `packages.microsoft.com`)
for preinstalled tooling. `apt-get update` refreshes **every**
configured repo, not just the ones a job needs. When one of those
Microsoft repos serves invalid release metadata:
```
E: Failed to fetch https://packages.microsoft.com/.../InRelease Clearsigned file isn't valid, got 'NOSPLIT'
##[error]Process completed with exit code 100.
```
`apt-get update` exits non-zero and — because the step runs under `bash
-e` — the whole step aborts before `bats` installs. This fails the job
even though `bats` comes from Ubuntu's own `universe` repo, which is
unaffected. It's a runner-image / external-repo outage, not anything in
the shell tests.
## Fix
This job only needs Ubuntu packages, so remove the unused third-party
repos before updating:
```yaml
sudo rm -f /etc/apt/sources.list.d/*microsoft* /etc/apt/sources.list.d/*azure-cli*
sudo apt-get update
sudo apt-get install -y bats
```
Ubuntu's archive (where `bats` lives) is in the base `sources.list` and
is untouched, so `bats` still installs; `apt-get update` no longer
refreshes the broken, unused Microsoft repos, so it stops failing the
job.
## Testing
- Change is confined to the `Install bats` step of the
`shell-script-tests` job. The rest of the job (shellcheck + the bats
suite) is unchanged.
- CI on this PR exercises the modified step directly — a green
`shell-script-tests` run here confirms the install path.
🤖 Generated with [Claude Code](https://claude.com/claude-code)