Closes OSS-646. Split out of OSS-641 as the unambiguous half. This PR does **not** change when activation happens — whether the long-running wrappers should auto-connect stays open on OSS-641. ## Why `createCopilotRuntimeHandler` builds the `ChannelManager` but opens no connection; activation is lazy, triggered by the first `channels.ready()`. That is deliberate (`fbf35ac59`, OSS-473) — Cloudflare/Next isolates freeze and recycle per request, so cold starts would mint conflicting listeners. Two things were left inconsistent with it: 1. `endpoints/node.ts` still documented the pre-`fbf35ac59` world — "the same `ChannelsControl` surface the underlying fetch handler **activates at creation time**" — and labelled the one required call as `// Optional:`. That's the TSDoc developers and coding agents see in-editor, and it contradicted every channel-package README. Same failure class as OSS-634. 2. `68349bc1f` gave the fetch handler a branded overload so `handler.channels.ready()` type-checks without `?.`, but the node wrapper never got it — so every call site, including our own example and all nine showcase docs pages, was written defensively. ### A live consequence, found en route `examples/slack/app/managed.ts` never called `ready()`. It built the runtime, mounted the listener, logged `[channel] started managed Channel "…"`, and only ever called `stop()` — so since activation went lazy it has connected nothing while reporting success. It was written against exactly the creation-time model the TSDoc described. Fixed here, with a regression assertion. ## What changed - **Types** — `createCopilotNodeListener` gets the branded overload pair mirroring `createCopilotRuntimeHandler`: a runtime with at least one declared Channel yields non-optional `.channels`; `activateChannels: false` and channel-less runtimes keep the optional shape. Adds `NodeCopilotListenerWithChannels`; both listener types are now exported from `@copilotkit/runtime/v2/node`. - **Docs** — node/express/hono TSDoc corrected: creation opens no connection, `ready()` is what activates, and it is required on a long-running host. Same stale claim fixed in the three example comments and `examples/slack/README.md` that repeated it. - **Call sites** — `?.` dropped from `examples/slack`, `examples/teams`, both READMEs, and the nine `showcase/shell-docs` channel pages. ## Deliberate scope choices, called out - **Express/Hono keep an optional `.channels`.** Only their TSDoc is corrected here. Their own type docs name Node as the lifecycle-owning surface and attach `.channels` best-effort, so the branded overload is Node-only for now; `endpoints-channels.test.ts` still uses `!` for those two. Say the word if the overload should extend to them. - **The non-optional shape requires a literal `channels` tuple** (`readonly [Channel, ...Channel[]]`). A runtime built from a dynamically-assembled `Channel[]` is unbranded and still needs `?.`. Now stated in the node TSDoc. - **`examples/slack/app/managed.ts` now exits nonzero if activation fails**, where before it stayed up serving HTTP with nothing connected. Intentional — fail loud, and it matches `index.ts`. Note that `ready()` resolves for `setup_required`, so a declared-but-unprovisioned channel still logs as started. - **Signal handlers are registered before awaiting activation** in `managed.ts`, so a Ctrl-C inside the 30s activation window still tears the Channel down instead of hitting Node's default handler. ## Verification - **Type contract, red → green:** the new `KeyIsRequired<typeof listener, "channels">` assertion in `handler-channels-types.test.ts` failed to compile before the overload (`error TS2344: Type 'false' does not satisfy the constraint 'true'`) and passes after. - **Example bug, red → green:** stashing only `managed.ts` fails the new guard with `expected "vi.fn()" to be called once, but got 0 times`. - **Strict-null proof:** `slack-example` and `teams-example` both `tsc --noEmit` clean under `strict: true` with the `?.` removed. This matters because the runtime package compiles with `strict: false`, so its own type test can only probe the optionality modifier structurally. - Runtime channel suites 54/54; slack example 63/63. - **Coverage limit:** the `managed.ts` guard is mocked — it proves the example *calls* `ready()` with a bound, not that a Channel connects. Nothing in CI exercises a real gateway connect for these examples. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Shell Docs
showcase/shell-docs is the Next.js app that builds and serves
docs.copilotkit.ai. Author CopilotKit product documentation here, not in the retired
top-level docs/ app.
Run Locally
Shell-docs is a standalone npm-based app. You do not need a root install just to run the docs app locally.
cd showcase/scripts
npm install
cd ../shell-docs
npm install
npm run dev
The local dev server runs on port 3003.
http://localhost:3003
The shell-docs npm lifecycle generates registry, demo-content, setup-content, and search
data before dev, build, and typecheck.
Validate Changes
Run these from showcase/shell-docs:
npm run build
npm run typecheck
npm run test
For repo-level CI parity, prefer Nx when a shell-docs target is available in the current checkout and root dependencies are installed. For normal shell-docs local development, the npm commands above are the canonical path.
Authoring Recipes
Showcase-Driven Framework Docs
Showcase-driven frameworks use docs_mode: generated. The docs are assembled from showcase
registry/generated data, demos, source regions, shared/root MDX, snippets, and sparse
framework overrides.
To update showcase-driven docs:
- Edit the showcase source of truth: manifests, demos, feature coverage, source regions, or registry inputs.
- Edit shared/root MDX only when the change applies across generated frameworks.
- Add sparse framework overrides only for real framework-specific differences.
- Do not hand-edit generated files under
src/data/frameworks/. - Validate routes, sidebar state, search results, snippets, and framework switching.
Authored Framework Docs
Authored frameworks use docs_mode: authored. The framework owns an MDX tree under
src/content/docs/integrations/<docsFolder>/ with a meta.json sidebar.
To update authored docs:
- Check
getDocsFolder()insrc/lib/registry.ts; the URL slug and folder name may differ. - Edit the MDX page under
src/content/docs/integrations/<docsFolder>/. - Update that folder's
meta.jsonwhen adding, removing, or moving pages. - Reuse shared snippets from
src/content/snippets/when content should stay consistent across frameworks. - Validate the framework route, sidebar, search result, and any shared snippet render.
Reference Docs
Edit API reference pages under src/content/reference/.
The v2 reference does not use meta.json; navigation is generated by walking the tree and
reading each page's title and description frontmatter. Only the legacy reference/v1/
tree uses meta.json.
Snippets
Reusable snippets live under src/content/snippets/. Snippets may be rendered by root docs,
authored framework pages, and showcase-driven framework pages, so keep them general unless
the path is intentionally framework-specific.
Frontend Applicability
Frontend routes use page-level applicability metadata, independent from where the content is authored. A page can be authored MDX, showcase-generated content, mirrored protocol docs, or reference content and still be universal across frontends.
Use the frontend field in page frontmatter or meta.json when a root doc should appear in
non-React frontend docs:
universal— render the same page under/<frontend>/....frontend-variant— render only when a matching page exists undersrc/content/docs/frontends/<frontend>/....hide— omit the page from frontend-scoped docs.
Do not use "showcase-driven" as a proxy for frontend availability. Showcase derivation is an authoring/source detail; frontend applicability controls routing and sidebar inclusion.
AG-UI Mirrored Docs
AG-UI protocol docs are authored upstream in ag-ui-protocol/ag-ui. The
src/content/ag-ui/ tree is a downstream mirror rendered on the CopilotKit docs host.
Change AG-UI docs upstream first, then sync the mirror back into shell-docs.
Top-Level Docs Symlink
The repository's top-level docs/ path is a symlink to showcase/shell-docs/ for
contributor muscle memory. It is not a separate docs app. Do not recreate the old
docs/content/docs/ tree; author CopilotKit docs in showcase/shell-docs/src/content/.