## Summary
- add provider-aware coding-agent entry points to the Channels overviews
at `/slack` and `/teams`
- present both onboarding flows in the site’s existing collapsed
`Accordion` pattern, with fenced text blocks that provide the standard
`Copy Text` control
- make each prompt start from an empty directory instead of assuming an
existing CopilotKit agent
- use the Slack onboarding skill as the Slack provider source of truth
and the published Teams documentation as the Teams provider source of
truth
- add regression coverage for provider scoping, onboarding order, safety
requirements, and end-to-end verification gates
## User experience
Both Channels overview routes now open with **Start building with your
coding agent — copy this prompt**. The row stays collapsed until
selected, keeping the existing overview readable while giving builders a
complete prompt they can copy into a local coding agent.
The prompts share the same start-from-scratch foundation:
1. Read `copilotkit-setup` for new-application and managed-Intelligence
onboarding.
2. Read `copilotkit-channels` for the CLI-scaffolded Channel declaration
and long-running host.
3. Stop when a required generic skill is unavailable and direct the user
to `npx copilotkit@latest skills install`.
4. Use the CopilotKit CLI through `npx copilotkit@latest`; neither
prompt assumes or requires a global CLI installation.
5. Run `npx copilotkit@latest create`, asking the user for the project
name and framework rather than silently choosing them.
6. Select managed Intelligence, since Channels are unavailable in
self-hosted SSE mode.
7. Preserve the CLI-generated `channel-host.mts` and `channels.mts` as
the code half instead of creating a duplicate Channel declaration or
placing it in a serverless route.
8. Inspect actual browser capabilities and either drive the browser with
confirmation before consequential live-account changes or guide the user
one step at a time.
9. Keep credentials out of the conversation and report success only
after the provider installation, managed runtime, and real-message path
are verified.
## Slack prompt
The `/slack` prompt additionally:
- reads `setup-slack-channel` before planning
- delegates the Slack app, managed provider connection, and workspace
verification to that skill
- verifies Node.js 20 or newer
- protects Slack tokens, signing secrets, and Intelligence API keys
- requires all three `setup-slack-channel` gates before reporting
success
## Microsoft Teams prompt
The `/teams` prompt mirrors the complete start-from-scratch flow without
inventing or requiring a Teams-specific onboarding skill. It instead:
- treats `https://docs.copilotkit.ai/teams` as the provider source of
truth, following the documentation-based prompt pattern already used by
the docs-root Channels CTA
- tells the agent to select the user’s backend in the docs and follow
the linked Intelligence configuration and Teams connection pages in
order
- rejects substituting Slack setup instructions or relying on remembered
Azure Bot and Teams app setup
- verifies Node.js 22 or newer, matching the published managed Teams
runner requirement
- requires confirmation before consequential Azure, Intelligence, and
Teams account changes
- protects tokens, client secrets, and API keys
- requires the app to be installed in the intended Teams scope, the
Intelligence Channel to be Online, and a real Teams message or mention
to receive a reply before reporting success
## Provider scoping
The Channels overview source is shared by both routes. Separate
`FrontendOnly` blocks ensure that:
- `/slack` receives only the Slack skill-driven prompt
- `/teams` receives only the Teams documentation-driven prompt
- neither route leaks the other provider’s onboarding source or
instructions
## Research basis
The final flow follows responsibilities and requirements already
documented in the repository:
- `copilotkit-setup` owns new-project and managed-Intelligence
onboarding
- `copilotkit-channels` owns the generated Channels runtime and warns
against duplicate/manual Channel wiring
- `setup-slack-channel` owns the Slack provider flow and its three
success gates
- the docs-root Channels CTA already produces provider-specific
coding-agent prompts that point to the selected Shell Docs guide URL
- the Teams guide documents Node.js 22+, managed Intelligence
configuration, Azure Bot setup, installation in Teams, Online runtime
status, and real-message verification
- the CLI docs consistently use `npx copilotkit@latest`; they do not
require a global installation
- `npx copilotkit@latest skills install` is the documented skill
installer, and `npx copilotkit@latest create` is the documented
new-project scaffold
## Scope
This PR intentionally changes only:
- `showcase/shell-docs/src/content/docs/channels/index.mdx`
- `showcase/shell-docs/src/lib/__tests__/channels-docs.test.ts`
It does not add a component, alter navigation, change either provider
tutorial, modify the Intelligence browser setup flow, or claim a
Teams-specific onboarding skill exists.
The branch is intentionally presented as one commit:
- `docs(channels): add copyable onboarding prompts`
## Validation
- full shell-docs suite: `npm test -- --maxWorkers=1
--no-file-parallelism`
- full Vitest suite confirmation: `npx vitest run --maxWorkers=1
--no-file-parallelism --reporter=dot`
- focused Channels docs suite: 29 tests passed
- `pnpm exec oxfmt --check
showcase/shell-docs/src/lib/__tests__/channels-docs.test.ts
showcase/shell-docs/src/content/docs/channels/index.mdx`
- `git diff --check`
- rendered Slack verification at `http://localhost:3003/slack`: one
provider-specific prompt with `setup-slack-channel`, no Teams docs URL,
and one `Copy Text` control
- rendered Teams verification at `http://localhost:3003/teams`: one
provider-specific prompt with the Teams docs URL, no
`setup-slack-channel` reference, and one `Copy Text` control
- pre-commit and commit-message hooks passed
All test runs used one worker with file parallelism disabled to keep
memory use bounded.
`Agent.to_ag_ui()`, `AGUIApp`, and the `pydantic_ai.ag_ui` shim were removed in
Pydantic AI v2, so the example only ran on v1.
- pin `pydantic-ai-slim[ag-ui,openai]>=2.0.0` and `ag-ui-protocol>=0.1.19`
- import `StateDeps` from `pydantic_ai.ui`
- serve via `AGUIAdapter.dispatch_request` on a Starlette route
- give each request its own `deps` copy so state no longer leaks between threads
## Summary
Brings the `setup-slack-channel` skill into the set that `copilotkit
skills install` distributes, and narrows both it and
`copilotkit-channels` so they stop matching the same request.
## Why
The skill was merged into
[CopilotKit/channels-sdk#7](https://github.com/CopilotKit/channels-sdk/pull/7),
which makes it reachable from a channels-sdk checkout and nowhere else.
`copilotkit skills install` reads `CopilotKit/CopilotKit/skills`, so
until the skill lives here, no CLI user can get it.
No CLI change is needed to pick it up: skill names are free-form
(`--skill` is validated by regex only, with no allowlist) and the
default install is `--skill *`.
## How
**The skill is copied from the merged channels-sdk source**, with one
addition: a `version: 1.0.0` frontmatter field, so it matches the other
standalone skills in this repo (every one of them carries a version;
only the package-mirrored skills omit it). The body, references, and
manifest asset are otherwise unchanged. It stays browser-first and makes
no reference to the `copilotkit channels` commands, per the call to
defer those to the web app until they have more real-world usage.
**Both descriptions are narrowed** so the trigger overlap is gone:
| Skill | Owns | Hands off |
| --- | --- | --- |
| `copilotkit-channels` | The code half — declaring, wiring, and
customising a Channel. Assumes the provider app exists | First-time
Slack setup → `setup-slack-channel` |
| `setup-slack-channel` | The provider half — the Slack app, its tokens,
attaching it to a Channel | Code questions → `copilotkit-channels` |
Before this, both matched "connect my agent to Slack" and the agent
picked whichever it read first. That collision was invisible in
channels-sdk, where `setup-slack-channel` is the only skill present; it
becomes live the moment both ship in the installed set.
## Notes for reviewers
- **Scope is Slack only.** A Teams sibling is deliberately a separate
pass.
- **No existing skill content changes** — the `copilotkit-channels` diff
is its frontmatter description and nothing else.
- Two known follow-ups are tracked and intentionally not addressed here:
[channels-sdk#9](https://github.com/CopilotKit/channels-sdk/issues/9)
(CLI-capability claims, deferred by decision) and
[channels-sdk#2](https://github.com/CopilotKit/channels-sdk/issues/2)
(`build-channels-bot` staleness, unrelated).
Refs CopilotKit/channels-sdk#10
## Added during the `main` merge
Resolving the conflict surfaced a second, unrelated problem that had to
be fixed for this PR to be safe to land.
`skills/setup-slack-channel` is a **standalone** skill — it has no
`packages/*/skills/` source. `scripts/sync-plugin-skills.ts` treats any
such directory as an orphan unless it is listed in
`RESERVED_LIFECYCLE_SLUGS`, which this one was not. Verified against the
pre-fix script:
- `pnpm check:plugin-skills` → exit 1, `orphan file(s) in mirror:
skills/setup-slack-channel`
- `pnpm sync:plugin-skills` (write mode) → **recursively deleted all 8
files of the new skill**
The `plugin-skills-check` workflow's path filter does not match
`skills/setup-slack-channel/**`, so this PR would not have caught it —
it would have gone red on the next unrelated PR that touched the script
or a package skill, or silently eaten the skill on the next sync run.
Fix is two lines: add the slug to `RESERVED_LIFECYCLE_SLUGS`, and move
the paired `size` assertion in
`scripts/__tests__/sync-plugin-skills.test.ts` from 9 to 10. The test
file already documents this exact hazard in a comment.
### Conflict resolution
The only conflict was the `copilotkit-channels` frontmatter description,
which #6320 rewrote in parallel. The two sides disagreed about Teams:
this branch said the skill "assumes the provider app already exists",
while #6320 established that Teams provider setup **is** this skill's
job because the CLI or dashboard wizard performs it. The resolution
keeps this branch's code-half framing and the `setup-slack-channel`
handoff, but scopes that handoff to *first-time Slack app creation* only
— so it contradicts neither #6320's Teams sections nor the Slack
provider troubleshooting that stays in this file. Took #6320's `version:
1.1.0`.
## Release angular v0.3.1
**Scope:** `angular` | **Bump:** `patch`
---
### How this release process works
1. **This PR was created automatically** by the "release / create-pr"
workflow.
It bumped the `angular` packages to `0.3.1`
and generated AI-enhanced release notes.
2. **CI runs on this PR** — the full test suite (unit tests, lint, type
checks, build)
must pass before merging. This is the review gate.
3. **Review the release notes** in `release-notes.md` in this PR.
If a Notion draft was created, you can edit the release notes there
before merging.
4. **When this PR is merged**, the `release / publish` workflow
automatically:
- Builds all packages
- Publishes the `angular` packages to npm at version `0.3.1`
- Creates git tag `angular/v0.3.1`
- Creates a GitHub Release with the final release notes
### Before merging
- [ ] CI is green (tests, lint, types, build)
- [ ] Version bumps look correct
- [ ] Release notes are accurate (edit in Notion if a draft was created)
---
> **Do not merge until CI is fully green.** The full test suite runs
automatically on this PR.
## What
Fixes the intermittently-red `test / integration / runtime` **bun** leg.
Three commits, smallest blast radius first:
1. **`ci(runtime)`** — pin `bun-version` from `latest` to `1.3.14` so a
Bun release can't change module-resolution behaviour between runs. (Only
`bun-version: latest` in the repo.)
2. **`fix(deps)`** — **this is the actual fix.** Patch
`eventsource@3.0.7` to drop its `bun` export condition, via `pnpm patch`
+ `patchedDependencies`.
3. **`refactor(runtime)`** — module-graph hygiene: load the MCP SSE
transport lazily. Explicitly **not** a behaviour fix; commit 2 is.
## Root cause
```
TypeError: require() async module ".../eventsource@3.0.7/node_modules/eventsource/dist/index.js" is unsupported. use "await import()" instead.
at .../@modelcontextprotocol/sdk/dist/cjs/client/sse.js:4:7
at .../@ag-ui/mcp-apps-middleware/dist/index.js:1:983
at processTicksAndRejections (unknown:7:39)
```
- `eventsource@3.0.7` maps its `bun` export condition to the **ESM**
build (`dist/index.js`). Bun resolves `bun` **before** `require`, so a
CJS `require("eventsource")` receives an async ESM module and throws.
The package ships a real CJS build (`dist/index.cjs`) behind `require`,
but Bun never reaches it.
- Two CJS consumers in our graph hit this: the MCP SDK's own
`dist/cjs/client/sse.js`, and `@ag-ui/mcp-apps-middleware@0.0.3` — a
CJS-only package (`main: ./dist/index.js`, no `exports`, no `type:
module`) that `require`s that SDK path unconditionally at module load.
- **Why intermittent:** it's a load-order race. If the ESM graph fully
evaluates `eventsource` first, the later CJS `require` can be served
synchronously and the run passes; otherwise it throws.
Dropping the `bun` key makes Bun fall through to `import` for ESM
consumers (same `dist/index.js` as before — no behaviour change) and to
`require` for CJS consumers (`dist/index.cjs`, which is what they need).
Only `bun` is touched; `deno`/`source`/`import`/`require`/`default` are
left alone.
**A version bump is not an alternative:** `eventsource@4.1.0` still
ships the same `bun` → ESM mapping.
## Patch diff
`patches/eventsource@3.0.7.patch` (header abridged — the file carries
the full rationale and an explicit deletion criterion so it doesn't
become permanent by accident):
```diff
# Drops the `bun` export condition from eventsource.
# ...
# DELETE THIS PATCH WHEN: eventsource drops the `bun` condition or points it at
# dist/index.cjs, OR Bun stops preferring `bun` over `require` for CJS requires.
diff --git a/package.json b/package.json
@@ -10,7 +10,6 @@
"exports": {
".": {
"deno": "./dist/index.js",
- "bun": "./dist/index.js",
"source": "./src/index.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs",
```
Root `package.json` gains:
```json
"patchedDependencies": { "eventsource@3.0.7": "patches/eventsource@3.0.7.patch" }
```
This repo had no `patches/` precedent (it uses `pnpm.overrides`), so
this sets one — hence the minimal one-line patch and the documented
removal criterion.
## Red-green proof
All four states. Local runs are the **same command on the same
machine**, differing only by whether the patch is applied. Bun 1.3.14,
macOS arm64, run from `packages/runtime`:
```sh
bun test src/v2/runtime/__tests__/integration/bun/bun-servers.integration.test.ts
```
A single green run proves nothing here — it's a race — so both local
states are N=20.
### 1. CI-RED
- This branch before the patch, run
[30835752558](https://github.com/CopilotKit/CopilotKit/actions/runs/30835752558)
@ `5456e308b9` — `runtime / node` success, **`runtime / bun` failure**:
```
4 | const eventsource_1 = require("eventsource");
TypeError: require() async module "/home/runner/work/CopilotKit/CopilotKit/node_modules/.pnpm/eventsource@3.0.7/node_modules/eventsource/dist/index.js" is unsupported. use "await import()" instead.
0 pass
1 fail
```
- Also on `main` @ `26a23bbf3a`, run
[30825667393](https://github.com/CopilotKit/CopilotKit/actions/runs/30825667393)
— same leg, same failure.
### 2. LOCAL-RED (eventsource UNPATCHED, N=20)
```
run 1: 72 pass 0 fail
run 2: 0 pass 1 fail
run 3: 0 pass 1 fail
run 4: 0 pass 1 fail
run 5: 0 pass 1 fail
run 6: 0 pass 1 fail
run 7: 0 pass 1 fail
run 8: 0 pass 1 fail
run 9: 0 pass 1 fail
run 10: 72 pass 0 fail
run 11: 0 pass 1 fail
run 12: 0 pass 1 fail
run 13: 72 pass 0 fail
run 14: 0 pass 1 fail
run 15: 0 pass 1 fail
run 16: 72 pass 0 fail
run 17: 0 pass 1 fail
run 18: 72 pass 0 fail
run 19: 0 pass 1 fail
run 20: 0 pass 1 fail
LOCAL-RED TOTAL: pass=5 fail=15 (out of 20)
```
### 3. LOCAL-GREEN (eventsource PATCHED, N=20)
```
run 1: 72 pass 0 fail
run 2: 72 pass 0 fail
run 3: 72 pass 0 fail
run 4: 72 pass 0 fail
run 5: 72 pass 0 fail
run 6: 72 pass 0 fail
run 7: 72 pass 0 fail
run 8: 72 pass 0 fail
run 9: 72 pass 0 fail
run 10: 72 pass 0 fail
run 11: 72 pass 0 fail
run 12: 72 pass 0 fail
run 13: 72 pass 0 fail
run 14: 72 pass 0 fail
run 15: 72 pass 0 fail
run 16: 72 pass 0 fail
run 17: 72 pass 0 fail
run 18: 72 pass 0 fail
run 19: 72 pass 0 fail
run 20: 72 pass 0 fail
LOCAL-GREEN TOTAL: pass=20 fail=0 (out of 20)
```
**5/20 → 20/20.**
### 4. CI-GREEN
The `test / integration / runtime` bun leg on this PR is the
load-bearing evidence. See checks below.
## Clean-install verification
A patch that only works incrementally is worthless in CI, so this was
verified from scratch — every `node_modules` in the workspace deleted,
then `pnpm install --frozen-lockfile`:
- Install exited **0** with `--frozen-lockfile` (lockfile is
self-consistent; no drift).
- Exactly one `eventsource` entry in the store, and it is the patched
one:
`node_modules/.pnpm/eventsource@3.0.7_patch_hash=427032a8df76e38f39988ff5fb919a02ccb70eb8a235e2b484007e4bebb1e67e/`
- Resolved `package.json` in the store after clean install:
`{"deno":"./dist/index.js","source":"./src/index.ts","import":"./dist/index.js","require":"./dist/index.cjs","default":"./dist/index.js"}`
— `bun` absent, everything else intact.
- Lockfile records it deterministically:
`patchedDependencies.eventsource@3.0.7` with `hash: 427032a8...` and
`path: patches/eventsource@3.0.7.patch`, and the dependency edge
resolves as `eventsource@3.0.7(patch_hash=427032a8...)`.
- `--frozen-lockfile` accepted the lockfile verbatim (it does not
rewrite), so the lockfile is self-consistent with the manifests.
- The comment header on the patch file does not break pnpm's patch
applier.
- **Lockfile diff is scoped to eventsource — 9 lines, 3 hunks, nothing
else.** An earlier revision of this branch carried incidental drift
(`vue-component-type-helpers` 3.3.8→3.3.9 and a `vite` peer-range
narrowing) picked up by a non-frozen install; that has been reverted so
the diff contains only the patch wiring.
## Tests
All from `packages/runtime`, with the patch applied:
| Suite | Command | Result |
|---|---|---|
| Full runtime suite | `pnpm exec vitest run` | **130 files / 1835 tests
passed**, 0 failed |
| Node integration (other CI leg) | `pnpm exec vitest run
src/v2/runtime/__tests__/integration/node-servers.integration.test.ts` |
**153 passed** |
| MCP + SSE transport | `pnpm exec vitest run
src/agent/__tests__/mcp-servers-integration.test.ts
src/agent/__tests__/mcp-clients.test.ts
src/v2/runtime/__tests__/mcp-apps-middleware-integration.test.ts` | **3
files / 22 passed** |
| Bun integration | `bun test .../bun-servers.integration.test.ts` |
**20/20** (was 5/20) |
Non-Bun consumers are unaffected by construction — Node never reads the
`bun` export condition — and the Node suites above confirm it. The SSE
path stays covered: `mcp-servers-integration.test.ts` exercises
`mcpServers: [{ type: "sse", url }]`, so it executes the new `await
import()`, which sits **outside** the `try/catch` that swallows
per-server connection failures.
## Module-graph proof for commit 3
Commit 3 is hygiene, so it gets its own narrower proof. Probe: Bun
populates `require.cache` with the resolved path of every module
actually loaded, so importing one module and inspecting that cache shows
whether `eventsource` entered the graph. Two controls run every time so
it can't pass vacuously.
```ts
const target = process.argv[2]!;
await import(target);
const keys = Object.keys(require.cache).filter(
(k) => /eventsource/.test(k) && !/eventsource-parser/.test(k),
);
console.log(`${target}\n eventsource loaded: ${keys.length > 0 ? "YES" : "NO"}`);
```
| Module | before commit 3 | after commit 3 |
|---|---|---|
| `@copilotkit/shared` (negative control) | NO | NO |
| `@modelcontextprotocol/sdk/client/sse.js` (positive control) | YES |
YES |
| `../src/agent/index.ts` (subject, non-SSE path) | **YES** | **NO** |
Both controls hold steady; only the subject flips. Measured on its own,
commit 3 does **not** move the bun pass rate (5/20 before, 3/20 after
within noise) — which is exactly why commit 2 exists.
## Typing
No `as any`, no `@ts-ignore`. `const { SSEClientTransport } = await
import(...)` keeps the class fully typed — TypeScript resolves
dynamic-import types statically. `packages/runtime/tsconfig.json`
already sets `"module": "es2022"` with the comment *"so dynamic import()
typechecks"*, so the pattern is anticipated.
Two adjacent bare `let` declarations (`transport`, `mcpClient`) gained
explicit annotations (`MCPTransport | undefined`, `MCPClient`) because
editors surface them as implicit-any suggestions. Both pre-existed on
`main`. Verified: `tsc --noEmit` clean; `tsc --noEmit --strict` error
set **identical to baseline** (3 pre-existing unrelated `TS2769`s);
`oxlint` warnings **unchanged from baseline** (2, both pre-existing).
`SSEClientTransport` is `@deprecated` in SDK 1.29.0 in favour of
`StreamableHTTPClientTransport`. That deprecation pre-exists on `main`
and is left alone: `type: "sse"` is documented public config, SSE and
Streamable HTTP are different wire protocols, and the SDK's own note
says clients "may need to support both transports during the migration
period." Migrating is a user-facing change for its own PR.
## Gates run
- `pnpm exec oxfmt --check packages/runtime/src/agent/index.ts` — clean
- `pnpm exec oxlint packages/runtime/src/agent/index.ts` — 0 errors, 2
warnings (both pre-existing on `main`)
- `pnpm nx run @copilotkit/runtime:check-types` — pass
- `pnpm exec commitlint --from HEAD~3 --to HEAD` — pass
- `pnpm install --frozen-lockfile` from a fully wiped workspace — exit 0
## Release monorepo v1.66.0
**Scope:** `monorepo` | **Bump:** `minor`
---
### How this release process works
1. **This PR was created automatically** by the "release / create-pr"
workflow.
It bumped the `monorepo` packages to `1.66.0`
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.66.0`
- Creates git tag `monorepo/v1.66.0`
- 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.
## Release channels v0.7.0
**Scope:** `channels` | **Bump:** `minor`
---
### How this release process works
1. **This PR was created automatically** by the "release / create-pr"
workflow.
It bumped the `channels` packages to `0.7.0`
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 `channels` packages to npm at version `0.7.0`
- Creates git tag `channels/v0.7.0`
- 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.
The previous pass fixed three of the sites and missed the ones that do not
contain the string `is_enabled` — a grep keyed on that phrase could not see
either.
`references/slack-workspace-and-app.md:109` is the one that mattered. It sat in
a table headed "Settings that must stay as the manifest sets them" and said
Interactivity must be `disabled`, 50 lines below the corrected prose saying the
opposite. That is worse than the bug it replaced rather than equivalent to it:
the prose only told a reader a feature was broken, whereas a normative settings
table under a keep-this-as-is heading gets acted on mechanically — and acting on
it turns interactivity off, causing the exact failure the rest of the fix
prevents. It now names the enabled state and the interactivity request URL
(`/api/channels/adapters/slack/interactivity`, verified against Intelligence
`libs/channels-setup/src/slack.ts:14-18`), and says modals stay undelivered
either way.
`assets/slack-app-manifest.yaml:33-34` carried the same claim in a comment —
"the managed path does not deliver interactive payloads at all." Not raised in
review; found by re-grepping for `nteractiv` instead of the specific phrasing.
The comment now distinguishes the direct-adapter shape (interactivity enabled,
no request URL) from the managed one (enabled with an Intelligence request URL),
which is the distinction the whole file exists to draw.
Swept for every remaining variant of the claim; none left.
Closes the `openUrl` XSS reported against `@a2ui/web_core`
([GHSA-72qq-p3r5-f7wq](https://github.com/a2ui-project/a2ui/security/advisories/GHSA-72qq-p3r5-f7wq),
CVSS 9.3).
## The vulnerability
`@a2ui/web_core` <= 0.10.1 passed an agent-supplied `openUrl` argument
straight to `window.open()` with no scheme allowlist:
```js
// basic_catalog/functions/basic_functions.js — 0.9.0
if (args.url && typeof window !== 'undefined' && window.open) {
window.open(args.url, '_blank'); // no scheme check
}
```
A malicious agent could emit a Button whose `functionCall` named a
`javascript:` URI; clicking it executed arbitrary script in the host
application's origin. The Basic Catalog is the default, so no
non-default configuration was required to be exposed.
## Why it reached us
We pinned `0.9.0` **exactly**, as a runtime `dependencies` entry of two
published packages — so downstream users could not upgrade out of it
without an `overrides` entry:
| Published package | Path to the vulnerable version |
|---|---|
| `@copilotkit/a2ui-renderer` | direct pin `0.9.0` |
| `@copilotkit/vue` | direct pin `0.9.0` |
| `@copilotkit/react-core` | → `a2ui-renderer` |
We import the sink deliberately (`BASIC_FUNCTIONS`) in four places
across the React, Lit, and Vue catalogs, and add no sanitisation of our
own. Note the advisory enumerates three affected renderers (React, Lit,
Angular); we ship a fourth, Vue, that upstream did not list.
## The change
Bump to `0.10.4`, which adds a strict http/https allowlist plus
`noopener,noreferrer`. This is a drop-in upgrade: `0.10.4` still exports
the `./v0_9` and `./v0_9/basic_catalog` entrypoints we import, and of
the `v0_9` surface (165 → 194 exports) the only symbol removed is
`FrameworkSignal`, which is referenced nowhere in this repo.
`showcase/angular` and `examples/v2/angular/demo` are private; those
bumps are hygiene only.
## Tests
Adds 6 regression tests over the React and Lit renderers, which reach
the sink independently of each other. They assert that `javascript:` and
`data:` URIs never reach `window.open`, that https URLs still open with
`noopener,noreferrer`, and that a blocked scheme leaves the surface
mounted rather than escaping into the click handler.
These were verified to be non-vacuous: pinned back to `0.9.0`, **5 of
the 6 fail**, including direct confirmation that `javascript:alert(1)`
reaches `window.open` through our own renderer on the vulnerable
version.
Full suites green: `a2ui-renderer` 22, `vue` 1074, `react-core` 1471,
`runtime` 1835, `angular` 292, `react-native` 251. Builds and
type-checks clean.
## Behaviour change worth knowing
`0.10.x` changes `openUrl`'s failure mode: the old code silently no-op'd
on a bad URL, the patched one throws `A2uiExpressionError` for a
non-http(s) scheme. That throw does **not** reach the render path —
`web_core`'s own `evaluateFunctionReactive` already catches it and
routes it to `surface.dispatchError`. Confirmed by exercising a real
click in both renderers: nothing escapes, no uncaught error, the surface
stays mounted.
## Follow-ups (not in this PR)
- **`@copilotkit/angular@0.3.0` remains exposed.** It pins
`@copilotkit/a2ui-renderer@1.63.2`, which pins the vulnerable `0.9.0`.
It needs a release after `a2ui-renderer` publishes, or Angular users
stay on the vulnerable transitive.
- **Blocked actions are invisible.** The resulting `EXPRESSION_ERROR` is
emitted on `surface.onError`, which no renderer subscribes to — so an
agent probing `javascript:` URIs is blocked completely silently, with no
log or telemetry. Surfacing it is a cross-renderer API decision,
deliberately kept out of a security bump.
## Summary
This PR reconciles the Channels documentation with the released Channels
SDK and Runtime behavior, recent CopilotKit and Intelligence changes,
and the launch review of the provider docs.
The scope is intentionally limited to Channels guides, provider routing,
Channels SDK reference pages, and regression coverage in
`showcase/shell-docs`.
## Why
The existing docs mixed older package versions and pre-launch
assumptions with newer SDK behavior. In particular:
- examples still referenced Channels SDK `0.5.0` / `0.6.0` and Runtime
`1.64.2`
- lifecycle guidance did not distinguish eager Node/Express startup from
lazy Hono/generic Fetch startup
- Slack setup omitted current interactivity and file-scope requirements
- routing, welcome-event, reply-continuation, event-sanitization, clone,
and file-identifier behavior had changed or lacked precise documentation
- identity and Memory were not documented as a complete provider-scoped
journey
- scaling and HITL guidance overstated public configuration and
cross-backend support
- Channels CLI support is not yet part of the released public setup path
and should not be presented as available
## What changed
### Released versions and setup paths
- updates Channels examples to SDK `0.6.1` and Runtime `1.65.0`
- keeps the Intelligence browser wizard as the documented released setup
path
- excludes Channels CLI instructions until that support ships in a
public release
- preserves the latest browser-based Teams wizard details for draft
creation, branding/package handling, permissions, installation state,
and runtime verification
- updates Slack setup to use a bot token plus Signing Secret, enable
Interactivity, and include `files:read` / `files:write`
- tells existing Slack app owners where to obtain the current generated
manifest, when to reinstall, and when to refresh the bot token
### Identity and Memory
- adds one shared `Identity and Memory` source guide exposed only
through provider-scoped routes:
- `/slack/identity-and-memory`
- `/teams/identity-and-memory`
- does not introduce a channel-agnostic documentation surface
- separates conversation, current provider actor, and canonical
application user
- provides provider-specific Slack and Teams identity mapping examples
without leaking the other provider's identifiers into the rendered page
- documents safe per-run user/project Memory grants, unlinked-user
behavior, and approval resume subjects
- replaces the incomplete approval snippet with a complete
registered-component callback example
- adds a verification sequence for multi-actor conversations, unlinked
actors, and initiator-versus-actor approval behavior
- corrects the `channel_memory_unavailable` description to reflect the
runtime/adapter capability check
- cross-links the guide from both provider quickstarts,
thread/transcript guidance, and SDK reference content
### Runtime lifecycle and routing
- documents that Node and Express listeners start the Channel eagerly
- documents lazy startup for Hono and generic Fetch adapters
- clarifies when `ready()` is optional versus required
- places signal-handler registration before readiness
- corrects connecting-status expectations
- documents the single-path routing rules for mentions and ordinary
messages
- describes `onWelcome` as a lifecycle event rather than a synthetic
user message
### SDK reference corrections
- documents reply-continuation defaults and truncation behavior:
- 11,000 UTF-8 bytes
- 20 messages
- narrows `sanitizeAgentEvents` guidance to the known `parentMessageId`
repair
- documents per-turn agent cloning, including factory results, and the
`0.6.1` warning behavior
- distinguishes `assetId` from provider `fileId` in `Thread.postFile()`
- adds a current direct Slack adapter example
- updates command and reaction coverage for the current SDK surface
- keeps detailed option semantics in the `createChannel` function
reference and links to it from the `Channel` class page to reduce
duplication
### Production and HITL guidance
- removes the nonexistent public `maxConcurrentDeliveries` option
- describes the runtime's internal bounded delivery capacity without
presenting it as user configuration
- names the validated managed approval path precisely: a DeepAgent
tool-driven `on_interrupt` that returns from delivery and resumes on a
later interaction
- gives other backends an explicit real-provider verification checklist
instead of implying universal native AG-UI interrupt compatibility
- clarifies that event sanitization does not create framework interrupt
support
### Regression coverage
- adds route tests for both provider-scoped identity-and-memory pages
- adds documentation assertions for versions, lifecycle, Slack manifest
requirements, routing, SDK behavior, scaling/HITL claims,
provider-specific identity examples, verification steps, and cross-links
- keeps a broad guard preventing unreleased Channels CLI commands from
entering the public setup journey without enumerating future command or
configuration details
- makes wording assertions tolerant of normal MDX line wrapping
## Rebase reconciliation
The branch is rebased onto current `origin/main` (`0c72359cbd`). The
rebase included overlapping changes to the Intelligence setup guide. The
resolution retains the newer browser-based Teams setup improvements
while excluding the CLI-only path so the page remains aligned with the
released public setup surface.
## User impact
Readers get setup instructions that match the released product, more
precise lifecycle and SDK behavior, and a complete provider-scoped
identity/Memory journey. Existing Slack users can find and apply the
current generated manifest, approval implementers get a testable
compatibility boundary, and users are not directed to unreleased CLI
commands.
## Scope boundaries
This PR does **not**:
- change Channels SDK or Runtime implementation code
- document unreleased CLI commands
- introduce channel-agnostic Channels pages
- modify unrelated documentation
- commit generated registry, catalog, setup-content, or search-index
churn
## Validation
From the repository root:
- `pnpm exec oxfmt --check showcase/shell-docs/next.config.ts
showcase/shell-docs/src/lib/channel-guide-routes.ts
showcase/shell-docs/src/lib/__tests__/channel-guide-routes.test.ts
showcase/shell-docs/src/lib/__tests__/channels-docs.test.ts`
- `git diff --check origin/main...HEAD`
- publication-safety scan of added lines for credentials, private Slack
references, internal email addresses, and private keys
- scan confirming Channels CLI commands and unreleased setup details are
absent from public Channels docs and regression tests
From `showcase/shell-docs`:
- `npm run lint` — 0 errors; existing package warnings remain unchanged
- `npm run typecheck`
- `./node_modules/.bin/vitest run --maxWorkers=1 --no-file-parallelism`
— 54 files, 368 tests passed
- `npm run build` — production build completed; 222 pages generated
Local rendered-route verification:
- `/slack/identity-and-memory` — HTTP 200
- `/teams/identity-and-memory` — HTTP 200
- `/slack/intelligence` — HTTP 200
- `/teams/intelligence` — HTTP 200
The worktree is clean, and the local review server remains available at
`http://localhost:3003`.
Addresses review on #6340.
Interactivity is no longer disabled on the managed path. The shared generator
emits `interactivity.is_enabled: true` with an Intelligence-hosted request URL
(Intelligence `libs/channels-setup/src/slack.ts:209-211`) and the ingress
handles `block_actions` (`apps/app-api/src/routes/channels-routes.ts:868`), so
HITL buttons and selects do fire. The skill's own bundled manifest asset already
said `is_enabled: true`, so the prose contradicted the file shipped beside it.
What is still undelivered is `slash_commands` (absent from the generator) and
`view_submission` (`apps/app-api/src/channels/slack-ingress.ts:1050`), so the
`onCommand` / `onModalSubmit` warnings stay. Corrected in all three places that
claimed otherwise, and the troubleshooting entry now tells the reader a dead
button is a real failure rather than a capability limit.
Browser-only framing is now a routing instruction rather than an architectural
claim, since `copilotkit channels add` does create the Channel and attach the
adapter. Same outcome, but it no longer contradicts `--help` for an agent that
was told to check it.
Trigger scope is narrowed in the frontmatter description instead of rewriting
Phase 0. The phases assume OpenTag conventions (`app/channel.tsx`, `app/env.ts`,
`INTELLIGENCE_CHANNEL_NAME`, an agent on port 8123), which are not what
`copilotkit init` scaffolds — naming that in the description keeps the skill from
firing on any "connect my agent to Slack" once it installs into customer repos.
Also widens the plugin-skills-check path filter to `skills/**` rather than
adding the one new slug. The orphan scan reads the whole mirror, so enumerating
individual directories is what let this PR's own blocker go untested and would
have armed it again for the next standalone skill.
## What does this PR do?
Slack data visualization blocks were listed in the native catalog, but
the JSX surface emitted a non-Slack `visualizations` field and had no
typed `chart` prop. This PR makes `Slack.Block.DataVisualization`
produce the payload that Slack documents.
- Adds public types for pie, bar, area, and line chart payloads.
- Requires `title` and `chart` at compile time and runtime.
- Checks Slack limits for titles, labels, segments, series, points,
categories, axis labels, positive pie values, unique series names, and
exact category coverage.
- Rejects more than two data visualization blocks in one message.
- Preserves the chart through direct Slack `chat.postMessage` delivery
and managed Intelligence delivery.
- Documents a small `defineChannelComponent` weather chart example.
Slack contract:
https://docs.slack.dev/reference/block-kit/blocks/data-visualization-block/
## Review path
1. Start with `packages/channels-slack/src/native.ts` for the public JSX
and TypeScript contract.
2. Read `packages/channels-slack/src/data-visualization.ts` for runtime
checks.
3. Read `packages/channels-slack/src/native-data-visualization.test.tsx`
for the weather component through the real adapter boundary.
4. Read
`packages/channels-slack/src/native-data-visualization-validation.test.ts`
for provider limit and cross-field cases.
5. Read
`packages/channels-intelligence/src/delivery-provider-elements.test.ts`
for the managed effect payload.
## Test coverage
- All four chart variants serialize to exact Slack JSON.
- Invalid nested payloads fail before a network request.
- Two charts pass and three charts fail.
- Negative values pass for series charts; non-positive values fail for
pie charts.
- Compile-time tests reject missing fields, unsupported chart types,
wrong chart shapes, and children.
- A `defineChannelComponent` weather card reaches
`SlackAdapter.client.chat.postMessage` with the expected Block Kit
request.
## Validation
- `NX_DAEMON=false pnpm nx run-many -t test,check-types,build
--projects=@copilotkit/channels-slack,@copilotkit/channels-intelligence`
- `NX_DAEMON=false pnpm nx run-many -t publint,attw
--projects=@copilotkit/channels-slack,@copilotkit/channels-intelligence`
- `NX_DAEMON=false pnpm verify:channels-umbrella`
- `pnpm check:channel-native-catalogs && pnpm
audit:channel-native-catalogs`
- `pnpm exec oxfmt --check packages/channels-slack/README.md`
This checkout has no `docs:check` script. The changed README passes the
repo formatter, and its executable example is covered by the adapter
test.
## Related PRs and Issues
- Follows the native Slack JSX surface merged in
https://github.com/CopilotKit/CopilotKit/pull/6331
## Checklist
- [x] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [x] If the PR changes or adds functionality, I have updated the
relevant documentation
- [x] Allow edits by maintainers is enabled
GHSA-72qq-p3r5-f7wq (CVSS 9.3). web_core <= 0.10.1 passed an agent-supplied
`openUrl` argument straight to `window.open()` with no scheme allowlist, so a
Button whose `functionCall` named a `javascript:` URI executed arbitrary script
in the host origin when a user clicked it. The Basic Catalog is the default, so
no non-default configuration was required to be exposed.
We pinned 0.9.0 exactly, as a runtime dependency of two published packages
(@copilotkit/a2ui-renderer, @copilotkit/vue) and transitively of
@copilotkit/react-core and @copilotkit/angular, so downstream users could not
upgrade out of it on their own. 0.10.4 keeps the ./v0_9 and
./v0_9/basic_catalog entrypoints we import; the only symbol dropped from v0_9
is FrameworkSignal, which we never referenced.
Add regression tests over both renderers that reach the sink independently
(React and Lit). They assert that javascript: and data: URIs never reach
window.open, that https URLs still open with noopener,noreferrer, and that a
blocked scheme leaves the surface mounted rather than escaping into the click
handler. Verified they fail against 0.9.0 and pass against 0.10.4.
Every other standalone skill in RESERVED_LIFECYCLE_SLUGS carries one; only
the package-mirrored skills (runtime, react-core, a2ui-renderer) omit it.
This makes the new skill consistent with its cohort.
Nothing enforces the field, so this is convention rather than a gate. It does
mean the skill is no longer byte-identical to the channels-sdk source, so the
PR description is updated to say "unchanged except for an added version field"
rather than claiming a clean diff -r.
Resolves the one conflict in skills/copilotkit-channels/SKILL.md, where main
(#6320) rewrote the same frontmatter description this branch narrowed.
The two sides disagreed on Teams. This branch scoped the skill to the code half
and said it "assumes the provider app already exists"; main established that
Teams provider setup *is* this skill's job, because the CLI or dashboard wizard
performs it. Kept the branch's code-half framing and the handoff to
setup-slack-channel, but scoped that handoff to first-time Slack app creation
only, so it no longer contradicts main's Teams sections or the Slack provider
troubleshooting that stays in this file. Took main's version 1.1.0.
Also registers setup-slack-channel in RESERVED_LIFECYCLE_SLUGS. Standalone
skills have no packages/*/skills source, so without an entry the sync script
treats the directory as an orphan: `pnpm check:plugin-skills` fails, and
`pnpm sync:plugin-skills` deletes all 8 files of the new skill. Verified both
behaviours against the pre-fix script before fixing. The paired size assertion
in the unit test moves 9 -> 10.