Fixes#5961 (OSS-609).
## The bug
Both LangGraph auth pages told self-hosted readers to wrap their graph
in `CopilotKitRemoteEndpoint`. That path is retired and fails two ways
against the stack the reporter used (`copilotkit==0.1.94`,
`ag-ui-langgraph==0.0.4x`, Python 3.12):
```
import LangGraphAgent -> ImportError: cannot import name 'LangGraphAgent' from 'copilotkit'
execute_agent -> AgentExecutionException: Agent 'sample_agent' failed to execute:
'LangGraphAGUIAgent' object has no attribute 'execute'
```
(Reproduced locally against the `langgraph-fastapi` example's venv —
output above is verbatim.)
## The fix
`showcase/shell-docs/src/content/docs/auth.mdx` (Self-hosted tab of the
`auth_pattern: langgraph` section) and
`showcase/shell-docs/src/content/docs/integrations/langgraph/auth.mdx`
now document the supported pattern: **serve the AG-UI endpoint
yourself**, validate in a FastAPI dependency (401 before the graph
runs), and bake the resolved user into a **per-request**
`LangGraphAGUIAgent(config={"configurable": {"auth_user": user}})` so
nodes read an already-verified identity off `RunnableConfig` — no raw
token in the graph, no shared agent carrying another request's identity.
The gate-only variant (`FastAPI(dependencies=[Depends(current_user)])` +
`add_langgraph_fastapi_endpoint`) is documented for readers who only
want unauthenticated traffic rejected.
Two adjacent bugs on the same pages, fixed here because they break the
same walkthrough:
- **The frontend channel was wrong.** The pages said to pass
`properties={{ authorization: userToken }}` and claimed it "is forwarded
as a Bearer token". Nothing in `packages/` converts properties into
headers — `properties` reach the agent as AG-UI `forwardedProps` (run
payload data). The v2 runtime *does* forward the inbound `authorization`
header (and custom `x-*`) onto the agent call, so `headers={{
Authorization: ... }}` is the channel that actually works, for both
Platform and self-hosted.
- **The Platform user key was wrong.**
`config["configuration"]["langgraph_auth_user"]` →
`config["configurable"]["langgraph_auth_user"]` (matches
`langgraph/pregel/main.py` and `langgraph_api/worker.py`).
## Testing
**1. Doc snippets extracted verbatim from the MDX and executed** (a
script pulls the `main.py` + node code blocks out of each page, stubs
only `validate_your_token`, and drives them with `TestClient`; run under
the `examples/integrations/langgraph-fastapi` venv — `copilotkit
0.1.94`, `ag-ui-langgraph 0.0.41`, Python 3.12):
```
# docs/auth.mdx
PASS no header -> 401 {"detail":"Missing bearer token"}
PASS bad token -> 401 {"detail":"Invalid token"}
PASS valid token -> 200
PASS no RUN_ERROR
PASS node read user_123 off RunnableConfig
PASS node read role 'member'
PASS run completed
MESSAGES_SNAPSHOT: [{"id": "None", "role": "assistant", "content": "hello user_123"}]
ALL DOC-SNIPPET CHECKS PASSED
# docs/integrations/langgraph/auth.mdx — same script, same 7 checks
ALL DOC-SNIPPET CHECKS PASSED
```
**2. Gate-only variant**
(`FastAPI(dependencies=[Depends(current_user)])` + stock
`add_langgraph_fastapi_endpoint`):
```
no token -> 401 {"detail":"Missing bearer token"}
valid token -> 200 True True
PASS gate-only pattern (401 without token, run proceeds with token; identity NOT injected)
```
The trailing `True True` is `RUN_FINISHED` present **and** the node
seeing `nobody` — i.e. the gate works but no identity lands on the
config, exactly as the docs now say.
**3. Header forwarding actually reaches a LangGraph deployment** — the
claim behind the new `headers` guidance. Pointed a real
`@ag-ui/langgraph` `LangGraphAgent` at a local stub server, set
`agent.headers` the way `configureAgentForRequest` does, and recorded
what arrived:
```
[ { "url": "/assistants/search", "auth": "Bearer end-user-token", "apiKey": "server-side-key" } ]
PASS: authorization forwarded to the deployment
```
Both the end-user token and the server-side key arrive, which is why the
"server-configured headers win on collision" note is accurate.
Runtime-side breadth is already covered by
`packages/runtime/src/v2/runtime/__tests__/agent-utils-header-forwarding.test.ts`
("authorization header IS forwarded").
**4. Docs render checks** — both pages compile as MDX (`@mdx-js/mdx`
`compile()`), and every `python` block on both pages parses
(`ast.parse`), including the ones I didn't touch.
## Follow-up
**The DIY endpoint is deliberate but temporary.**
`add_langgraph_fastapi_endpoint` exposes no per-request seam (no
`dependencies` passthrough, no config/agent factory), and `endpoint.py`
is byte-identical in 0.0.41 and 0.0.42 — so owning the route is
currently the only way to get a verified identity onto
`config["configurable"]`. Adding that seam upstream in
`ag-ui-protocol/ag-ui` is tracked as **OSS-760**; when it lands, both
pages collapse back to the helper form and the DIY route stays only as
an escape hatch.
The broader "document request-scoped auth for
`add_langgraph_fastapi_endpoint`" ask in #3177 is now substantively
answered by these pages; leaving that issue open pending a maintainer's
call on whether it wants an SDK-level hook rather than the DIY endpoint.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Nine /integrations/<fw>/* links were rewritten to their canonical URLs.
Checking production, they were never broken — seo-redirects.ts 301s that
whole retired surface, so a reader always landed on the right page. That
made them cosmetic, and cosmetic changes do not belong in a PR whose
value is the defects around them.
Removing them takes this from 22 files to 15, all of which are things a
reader actually hits. The canonicalization is still worth doing; the
checker now reports it as its own advisory (legacy-redirect-links) so it
is tracked rather than lost.
Twenty-four defects a reader would hit, each verified against the tree
rather than pattern-matched. Found while root-causing PDX-313.
Links written as filesystem paths instead of served URLs (9). The docs
router serves integration pages at /<framework>/<slug>; the
/integrations/ prefix is the content directory, not a route. All nine
corrected URLs were confirmed to resolve.
Tutorial cross-links to directories with no index.mdx (4). loadDoc
resolves <slug>.mdx or <slug>/index.mdx, so /tutorials/ai-todo-app was a
404 — the page is at /overview.
Snippet components used with props but never imported (7). These fall
through to stubWithPartial in the global mdx-registry, which drops props
"on the floor" by design, so framework="pydantic-ai" never reached the
partial and the shared snippet rendered untailored. Importing the
partial restores prop flow. The mastra and ag2 siblings were already
correct; the seven broken ones are all in authored trees.
Dead YouTubeVideo imports (2). The component is provided globally by
mdx-registry.tsx and four other pages render it with no import at all;
these two imported a module that has never existed in the repo.
Stale byoc-* demo ids (2). Renamed to declarative-* in 70e2fb13c8
(2026-05-10, "rename byoc-* slugs to declarative-*"); the docs were
never updated. Only the three registry ID references per page change
here — snippet_cell, InlineDemo, IntegrationGrid.
Left alone deliberately: the runtimeUrl/agent code samples on the
hashbrown and json-render pages. The API routes were renamed to
copilotkit-declarative-*, but the agent ids were not renamed
consistently — declarative-hashbrown's demo uses
agent="declarative-hashbrown-demo" while declarative-json-render's still
exports AGENT_ID = "byoc_json_render". Guessing would ship a broken
copy-paste sample, so that needs an owner.
Mechanical repairs found while auditing the pydantic-ai docs. Each was
verified against the tree; nothing here is a content rewrite.
- Delete `quickstart/pydantic-ai.mdx` + its `meta.json`. `seo-redirects.ts`
already routes `/pydantic-ai/quickstart/pydantic-ai` ->
`/pydantic-ai/quickstart` (rule F6), and adk got the same treatment (F7).
pydantic-ai was the only framework still carrying a `quickstart/`
subdirectory alongside the canonical `quickstart.mdx`.
- `human-in-the-loop/agent.mdx`: link to the canonical quickstart directly
instead of the redirected legacy path, and point the starter link at
`examples/integrations/pydantic-ai` — `examples/coagents-starter-pydantic-ai`
does not exist.
- `docs-links.json`: `subagents.shell_docs_path` was `/multi-agent/subagents`,
which has no page. The real page is `/multi-agent-flows`, which the
entry's own `og_docs_url` already pointed at.
- `headless-simple/chat.tsx`: the console tag said `langgraph-python` inside
the pydantic-ai package. This sits in an `@region` block, so it is pulled
into docs as a snippet. 11 other integrations carry the same copy-paste;
they are left for the fleet sweep.
- `examples/showcases/pydantic-ai-todos/README.md`: `uv run src/main.py` ->
`uv run main.py` (there is no `src/main.py` in that tree), and the stated
Python floor now matches `agent/pyproject.toml` (`>=3.13`).
- `examples/canvas/pydantic-ai/README.md`: Python 3.8+ was unrunnable —
`agent/agent.py` uses PEP 604 unions. Aligned to the sibling tree that
pins the same `pydantic-ai-slim==2.22.0`.
Children of `CopilotSidebar` cannot use `height: 100%`. Both wrappers the
sidebar puts around your app -- `.copilotKitSidebarContentWrapper` and
`.copilotKitModalChildrenWrapper` (which had no CSS rule at all) -- are
auto-height blocks, so a percentage height on a child has no definite
containing block and collapses to content height.
Add an opt-in `fullHeightChildren` prop that gives the content wrapper a
one-viewport height and lets the children wrapper fill it. It is opt-in
because the content wrapper wraps the entire consumer app, and giving
every react-ui sidebar user a flex column with a fixed height would
reflow apps that never asked for it.
The height is a viewport unit, not `100%`: `100%` only resolves when
every ancestor (html/body/#root) also declares a height, which react-ui
neither sets nor can guarantee, so it would silently no-op in a stock
Next.js app. `min-height: 0` on the children wrapper clears the flex-item
`min-height: auto` floor so tall content scrolls inside the child instead
of stretching the wrapper past the viewport.
Fixes#261
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Ashish Shaw <77574570+ashish4143@users.noreply.github.com>
## Summary
`mcpApps.servers` entries that carry `includeTools` or `excludeTools`
are currently accepted even though the pinned
`@ag-ui/mcp-apps-middleware` package has no option for them. The runtime
then ignores the keys, so tools an operator intended to restrict remain
available. This change rejects that configuration instead of allowing a
silent no-op.
## What CopilotKit owns
- `mcpApps.servers` configuration and `agentId` scoping.
- Projection of selected servers into `MCPAppsMiddleware`.
- Reporting unsupported configuration before middleware construction.
Discovery, model-emitted tool execution, frontend-proxied execution,
server identity, and tool provenance belong to
`@ag-ui/mcp-apps-middleware`.
## Changes
- Extract the server projection into `resolveMcpAppsServers`, which
scans all configured entries for defined policy keys, filters by
`agentId`, strips only `agentId`, and forwards other fields unchanged.
- Return a configuration error naming the unsupported key, server,
pinned middleware version, owning package, and issue when a policy key
is supplied.
- Add tests for agent scoping, field forwarding, malformed and empty
values, undefined spread values, constructor avoidance, and the existing
HTTP error path.
- Document the ownership boundary and add a runtime changeset.
## Why the filter stays external
The pinned package is version `0.0.3`. It owns the private server maps,
UI-tool discovery, model-emitted execution, and frontend proxy
execution. A CopilotKit middleware could observe only one of those paths
and would have to duplicate private server identity and tool provenance.
The complete `includeTools` and `excludeTools` implementation belongs in
the external package, where one predicate can cover discovery and both
execution paths.
## Current behavior
Plain JavaScript or JSON configuration can supply `excludeTools:
["delete_account"]` without a TypeScript excess-property check. The
runtime currently accepts the configuration, constructs
`MCPAppsMiddleware`, and leaves the tool available. The new behavior
returns an HTTP 500 through the existing runtime error path, names the
unsupported key and dependency, and does not construct the middleware.
## Follow-up
The counterpart change in `@ag-ui/mcp-apps-middleware` should add the
fields to the per-server configuration, preserve absent versus empty
include lists, resolve server identity through its existing maps, and
apply one predicate after UI-resource discovery and before model-emitted
and proxied tool execution. Once that version is released, CopilotKit
can remove the rejection and pass the fields through unchanged.
## Related issue
Refs #5930.
The cross-repository ownership split follows the proposal in
https://github.com/CopilotKit/CopilotKit/issues/5930#issuecomment-5128722524.
This PR does not close the issue.
## Test plan
- [x] `pnpm -C packages/runtime exec vitest run
src/v2/runtime/__tests__/mcp-apps-servers.test.ts
src/v2/runtime/__tests__/mcp-apps-middleware-integration.test.ts`
passed, 2 files and 20 tests
- [x] `pnpm -C packages/runtime exec vitest run` passed, 129 files and
1,836 tests
- [x] `pnpm exec nx run @copilotkit/runtime:check-types` passed
- [x] `pnpm exec oxlint` and `pnpm exec oxfmt --check` passed on changed
TypeScript files
- [x] `pnpm check:plugin-skills` passed
- [ ] `CI green for static / quality and test / unit on Node 20, 22, and
24`
## Summary
The A2UI integration docs still use helper names and wire keys from
before the Python SDK's v0.9 API. The examples now match the current
SDK, while the A2A page clearly labels its cloned starter's v0.8
compatibility contract.
## Changes
- Update the generic, DeepAgents, and LangGraph A2UI pages to current
helper names, wire keys, and operation order.
- Reconcile the A2A page to the cloned starter's v0.8 payload and defer
its v0.9 migration.
- Remove unsupported `action_handlers=` and `dataContextPath` claims
from the advanced examples.
- Use the exported `createA2UIMessageRenderer` `onAction` interceptor in
the React guides.
## Out of scope
Migrating `examples/integrations/a2a-a2ui/` from its v0.8 renderer and
operation list requires source and example changes outside this
documentation-only target.
## Related PRs and Issues
Addresses the v0.9 documentation portion of #4821.
The corrected names follow `sdk-python/copilotkit/a2ui.py`; the A2A page
follows the cloned starter's current v0.8 contract. Closed partial work
is tracked in https://github.com/CopilotKit/CopilotKit/pull/5854.
## Test plan
- [x] Documentation search passed. The v0.9 pages contain no stale
Python helper names, current wire names are present, and the A2A
compatibility page is labeled.
- [x] Diff validation passed. Only the eight named MDX files changed.
- [ ] Shell-docs typecheck, lint, and formatting were unavailable
because this worktree has no installed `node_modules`; CI will run them
on the PR.
- [ ] CI green (`static / quality`, `test / unit` on Node 20/22/24).
The self-hosted LangGraph auth guides told readers to wrap their graph in
`CopilotKitRemoteEndpoint`. That path is retired and fails two ways against
the current SDK (copilotkit 0.1.94 / ag-ui-langgraph 0.0.4x):
* `from copilotkit import ... LangGraphAgent` -> ImportError (the export is
`LangGraphAGUIAgent`)
* `CopilotKitRemoteEndpoint.execute_agent()` calls `agent.execute(...)`, but
`LangGraphAGUIAgent` only defines `run(...)` -> AgentExecutionException:
'LangGraphAGUIAgent' object has no attribute 'execute'
Replace both with the supported pattern: serve the AG-UI endpoint yourself, let
a FastAPI dependency validate the forwarded `Authorization` header (401 before
the graph runs), and bake the resolved user into a per-request
`LangGraphAGUIAgent(config={"configurable": {...}})` so nodes read an
already-verified identity off `RunnableConfig`. Also document the gate-only
variant that keeps `add_langgraph_fastapi_endpoint`.
Two adjacent fixes on the same pages:
* the frontend channel is `headers={{ Authorization }}`, not
`properties={{ authorization }}` — the runtime forwards `authorization`
(and custom `x-*`) onto the agent call, while `properties` are delivered as
AG-UI `forwardedProps` and are never turned into a Bearer credential
* the Platform user lands in `config["configurable"]["langgraph_auth_user"]`,
not `config["configuration"][...]`
Fixes#5961
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Documents the now-bounded in-memory runner for users: the maxThreads /
maxRunsPerThread / maxBytes limits and their defaults, the precise eviction
model (LRU threads, per-thread run-cap, cross-thread byte ceiling enforced at
run completion), and the onConcurrentRun throw/supersede option. Clarifies that
the store is a process-global singleton shared by every runner, that dedup
weakens past the run cap, and points at the first-party SqliteAgentRunner for
durable or multi-instance deployments. Adds a troubleshooting entry for the
in-memory eviction warning.
Co-Authored-By: Claude <noreply@anthropic.com>
Follows the maintainer's Correction #2 on issue 6363. An exact version in a
docs install command is the same rot as the starlette==0.45.3 pin it replaced:
it goes stale silently and nobody re-resolves prose. The 2.22.0 the docs shipped
was already a version behind current the day it was written.
- docs install lines use pydantic-ai-slim[ag-ui,openai]>=2,<3, which constrains
the dep the pages actually care about and fails loudly at the v3 boundary
- ag-ui-protocol drops out of the docs lines entirely; no doc snippet imports
ag_ui, so naming it there was the transitive-dep noise the correction is about
- starlette>=0.46.2 stays, because the v2 snippets import Starlette directly.
A floor with no ceiling cannot force a downgrade, so it does not recreate the
silent backtrack
- examples/showcases/pydantic-ai-todos moves to a range in pyproject.toml and
relocks; the uv.lock is what reproduces
- examples/canvas/pydantic-ai keeps exact pins: it has no lockfile, so
requirements.txt is its only reproducibility artifact
Smoke-tested the open question from the issue: starlette 1.x works on
pydantic-ai v2. All 8 doc pages pass on 2.23.0 + starlette 1.3.1 and on
2.23.0 + starlette 0.52.1, so Jordan's <1.0 guard can be dropped rather
than raised.
Agent.to_ag_ui(), AGUIApp and the pydantic_ai.ag_ui module were removed in
Pydantic AI v2. The docs installed pydantic-ai unpinned, so anyone following
the quickstart got 2.22.0 and failed first at dependency resolution
(starlette==0.45.3 conflicts with the >=0.46.2 the ag-ui extra needs) and then
at AttributeError.
- 8 doc pages under showcase/shell-docs .../integrations/pydantic-ai serve the
agent from a Starlette route via AGUIAdapter.dispatch_request
- StateDeps moves from pydantic_ai.ag_ui to pydantic_ai.ui
- stateful snippets build StateDeps per request; dispatch_request writes the
client's state into deps.state, so a shared instance leaks state between users
- install commands exact-pin pydantic-ai-slim==2.22.0 and ag-ui-protocol==0.1.19
- examples/canvas/pydantic-ai and examples/showcases/pydantic-ai-todos ported
and pinned, todos relocked
- skills/copilotkit-integrations reference updated to the same shape
showcase/integrations/pydantic-ai is deliberately untouched; it is tracked
separately.
**Root page.** The channel and agent-backend pickers are gone, and the copy
action moves beside the supporting sentence, under the heading. The pickers had
stopped earning their place: the guide asks which platform and framework the
developer wants, so choosing here asked the same question twice and changed
nothing about what got copied. `ActivationSelect` and its option plumbing go with
them. The setup-guide link becomes one aside alongside OpenTag rather than a
per-selection route.
**Per-framework pages.** The accordion is gone. It existed to keep a twenty-line
prompt out of the way; the payload is now a single action, so a disclosure cost a
click and revealed nothing. The panel keeps the in-content idiom it shares with
`OpsPlatformCTA` — neutral surface, `--border`, accent on a small glyph — and the
button reads "Copy prompt" like every other surface.
`docs.channels_activation_prompt_expanded` retires with the disclosure that was
its only trigger.
Replaces the skill-install pointer with a fetch of one hosted file:
Read https://copilotkit.ai/channels-guide.md and help the user build
their first channel
The guide lives at `public/channels-guide.md` on the marketing site and owns the
whole workflow. It asks the developer which platform and which agent framework
they want, which is why this pointer passes neither.
That is what resolves the review's blocking issue rather than papering over it.
Interpolating the picker's channel and backend meant these pages promised
coverage on a skill's behalf — and the skill it named is scoped to Slack, to the
provider half, and to an OpenTag checkout, so the Teams road pointed at a
workflow that does not exist and the backend picker implied nineteen it never
claimed. A pointer that names nothing cannot overpromise, and the guide handles
selection itself.
Consequently `CHANNELS_ONBOARDING_SKILL`, its install command, and the
per-selection prompt builders are gone: one constant serves every surface.
Supporting copy that said the prompt "installs the onboarding skill" or offered
a "tailored" prompt is corrected — neither is true now.
Two changes from review.
**The disclosure is back.** The panel is the shared featured `<Accordion>`
again — reused, not restyled — so the overview stays compact when collapsed and
a reader can expand to read the exact prompt before copying it. Copy-only made
the payload opaque, which was the problem #6356 set out to fix. The component
still owns the Slack/Teams switch and the analytics; the container is markup.
**The treatment is now token-only.** The featured variant carried a saturated
`--accent` tile and an accent-mixed gradient. `copilotkit-ui-theme` names a
purple accent bar or stripe as a known wrong direction, and
`copilotkit-branding` scopes accent to restrained, atmospheric use with
gradients behind content rather than as the contrast layer — the old treatment
was both at once, on a docs `--accent` that resolves to violet. It now matches
the in-content panel idiom already in `OpsPlatformCTA`: `--bg-elevated`,
`--border`, `--shadow-control`, and accent carried only by a small glyph and the
hover state. Padding drops to `p-4` like every other docs panel, so a collapsed
prompt no longer pushes the page's own introduction below the fold. Verified in
light and dark.
Also adds `docs.channels_activation_prompt_expanded`. The disclosure is where a
funnel loses people and neither `viewed` nor `promptCopied` can see it: someone
who never opened the panel is indistinguishable from someone who opened it and
walked away.
The prompt inside wraps instead of scrolling. The docs' usual code block scrolls
horizontally, which is right for code and wrong here — it hid half the prompt
behind the overflow, defeating the point of letting people read it first.
The clipboard write and the capture call shared one try block, so a PostHog
client that threw reported "Copy blocked" for a prompt already sitting on the
clipboard. Only the write decides what the reader is told; capture moves behind
the same isolated helper the activation strip already uses, and the impression
observer uses it too instead of its own inline catch.
Two regression tests: capture throwing after a resolved write still shows
"Copied", and a rejected write still shows "Copy blocked" without emitting a
copy event.
Also drops 16 lines of `dev: true` lockfile churn picked up from an `npm install`
in the review worktree — no dependency actually changed.
The prompt is not on the page, so the button is the whole point of the panel. It
now sits directly under the heading instead of off to the right, where it read
as trailing furniture.
"Copy the prompt and paste it into your coding agent" also had nothing to point
at once the text stopped being rendered. The supporting line now says only what
the skill does.
Two changes.
**Impressions.** Both docs entry points emitted a copy event and nothing else,
so the copy count had no denominator: a surface nobody scrolls to and a surface
everybody ignores were indistinguishable. `docs.channels_activation_viewed`
fires once per surface on the first intersection at 50%, from an
IntersectionObserver rather than on mount, since both sit below the fold. The
`surface` values move into a shared `CHANNELS_ACTIVATION_SURFACES` map so the
two docs roads — and copilotkit.ai/channels, which sends its own event name with
the same property — stay separable inside one funnel.
The observer is guarded on `typeof IntersectionObserver`. An impression is never
worth breaking a render for, and this repo's jsdom tests do not define it.
**The prompt is no longer rendered.** The panel offers the prompt through the
copy button alone. The supporting line says so explicitly rather than saying
"paste this" next to nothing.
Adopts the visual language from the featured-Accordion work — accent panel,
terminal mark, eyebrow, prominent copy action — for the shared Channels entry
point, and drops the disclosure it was attached to. The accordion existed
because the payload was twenty lines; the payload is now one sentence, so
hiding it behind "Open & copy prompt" costs a click and buys nothing.
The panel renders exactly the text the button copies. Two earlier shapes were
wrong in instructive ways: a full monospace paragraph wrapped like a rendering
bug, and a code block with the ask underneath read as a shell command with a
footnote, which made a button labelled "Copy prompt" look like it was lying.
Only the command is monospace now; the prose around it wraps like prose.
The featured Accordion variant stays in mdx-components as a shared opt-in
capability, unused for the moment.
Keeps the featured Accordion treatment from #6356 as a shared opt-in component.
Its two consumers — the Slack and Teams starter prompts on the Channels
overview — are replaced here by the shared entry-point component, because the
payload those accordions concealed is now one line and there is nothing left to
disclose.
The Channels overview page, the docs landing activation strip, the website's
/channels strip, and the channels-sdk README each carried their own copy of the
onboarding workflow — six versions across three repos. They drifted, and each
went stale against the CLI independently: developers were told to install
unnamed skills, run `copilotkit channels` (which the published CLI does not
have), and use a bare `npx copilotkit` that a cached older binary shadows.
Every surface now emits the same two sentences naming one skill, and the
workflow itself lives in that skill. Shipped here:
- `buildChannelsActivationPrompt` returns a pointer, not a workflow, built from
a single `CHANNELS_ONBOARDING_SKILL` constant.
- The overview page's two 20-line prompts — one Slack, one Teams, both hidden in
accordions — collapse into the shared `<ChannelsStartPrompt />`.
- The prompt text renders on screen instead of living only in a clipboard
payload. That is why it was missable: a copy button with an invisible payload
reads as decoration. At one line there is nothing left to hide behind an
accordion.
- Both surfaces emit `promptCopied` with a `surface` property, so the funnel can
answer which road people actually take.
Tests pin the corrections that drift produced: the skill is named, the install
is non-interactive, the CLI is `@latest`, and the overview cannot re-embed a
workflow.
## Summary
Document the draft-first Microsoft Teams setup flow across Channels
docs, skills, and the Teams adapter README.
## Why
Intelligence now offers a resumable Fast CLI path and a Guided manual
path while keeping custom branding artifacts local and separating
provider completion from runtime health.
## How
- Describe the fully scoped provisioning and resume contract.
- Replace Azure Bot and manifest-editing guidance with Teams Developer
Portal plus Entra.
- Teach both setup skills the local-only artifact and Team-installation
boundaries.
- Update documentation contract tests for the new path.