## What does this PR do?
Closes the naming and documentation half of
[OSS-881](https://linear.app/copilotkit/issue/OSS-881). Paired with
CopilotKit/Intelligence#890, which adds `copilotkit verify` and tightens
the evaluation rubric.
### 1. One name for the Intelligence key
**Three** names for one value were live in CopilotKit's own
documentation, and following the wrong one with a CLI-provisioned
project yields an undefined key:
| Name | Where | Code readers |
| --- | --- | --- |
| `INTELLIGENCE_API_KEY` | what `copilotkit project select` writes; all
34 integration examples; the docs site | 34 |
| `COPILOTKIT_INTELLIGENCE_API_KEY` | 7 Channels package READMEs +
packaged skills | **0** |
| `COPILOTKIT_API_KEY` | `examples/slack`, `examples/teams`, and the
TSDoc on `CopilotKitIntelligence` itself | 2 |
`INTELLIGENCE_API_KEY` wins — it is the name the CLI provisions, and
changing it would break every scaffolded project in the wild.
- `COPILOTKIT_INTELLIGENCE_API_KEY` is **retired outright**. Nothing
ever read it, so there is nothing to keep compatible.
- `COPILOTKIT_API_KEY` stays **readable as a deprecated alias** in the
two examples that consume it, so an existing `.env` keeps working, and
is documented as deprecated everywhere it appears.
The third name was the worst placed: it was in the TSDoc on
`CopilotKitIntelligence`, which is what an IDE shows on hover.
This was not only untidy. The CLI's own `channels-preflight` accepts
`INTELLIGENCE_API_KEY` or `COPILOTKIT_API_KEY` — **not**
`COPILOTKIT_INTELLIGENCE_API_KEY`, the name the Channels READMEs told
people to set. So following a Channels README verbatim made `copilotkit
channels` warn that no runtime API key was present while the key sat
visibly in `.env`. After this PR the documented name is one preflight
accepts.
> [!NOTE]
> `NEXT_PUBLIC_COPILOTKIT_API_KEY` is a **different value** — the legacy
Copilot Cloud public key — and is deliberately left alone.
### 2. A real defect, not just naming skew
`skills/runtime/references/intelligence-mode.md` documented
`organizationId` as a `CopilotKitIntelligence` option, sourced from two
further env names (`COPILOTKIT_INTELLIGENCE_ORG_ID`,
`COPILOTKIT_ORG_ID`).
`CopilotKitIntelligenceConfig` has no such field — the copy-pasteable
sample it appeared in **would not compile**. Removed from the samples,
and the prose telling readers to fetch a value for it corrected. That
file is the only place those two names ever existed, which is very
likely why the failing validation run reported that "the runtime reads
`COPILOTKIT_INTELLIGENCE_API_KEY` and `COPILOTKIT_INTELLIGENCE_ORG_ID`".
### 3. Publish the Intelligence wiring
The wiring instructions existed only inside
`node_modules/@copilotkit/runtime/skills/`, and the only docs pages
mentioning `CopilotKitIntelligence` at all were the two Channels
frontends — so a developer on the plain web path had no page to reach it
from.
Adds **`/premium/connect-your-runtime`**: the wiring itself, how to
confirm the credential is actually consumed, the self-hosted
both-URLs-or-neither rule, and a troubleshooting table. Linked into both
navs, and the skills reference now points at the published page.
### 4. A guard so it cannot drift back
`scripts/validate-intelligence-env-names.ts` (`pnpm
check:intelligence-env-names`), wired to lefthook and a new workflow.
The workflow is **intentionally unfiltered**. The two workflows that
would otherwise cover this both filter: `plugin-skills-check` by
`paths:`, and `static/quality` by `paths-ignore: examples/**` — which is
exactly where the deprecated alias lives. Scoping the job would re-open
the hole it exists to close. Legitimate alias sites live in
`ALIAS_ALLOWLIST`.
## Related PRs and Issues
- [OSS-881](https://linear.app/copilotkit/issue/OSS-881) — needs
**both** PRs; neither closes it alone
- CopilotKit/Intelligence#890 — items 1 and 4 (`copilotkit verify` +
rubric contract 1.3.0)
## Verification
- Full lefthook pre-commit ran green: `check-plugin-skills`, `lint-fix`,
the new `check-intelligence-env-names`, and `test`/`publint`/`attw`
across **25 projects**.
- `examples/slack` `managed.test.ts` extended to cover **both** the
canonical name and the alias fallback, and proven non-vacuous — removing
the fallback turns the new test red.
- The drift guard proven non-vacuous the same way: reintroducing a
retired name fails it, exit 1.
- `oxfmt` and `oxlint` clean on every file touched (0 errors).
## 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
🤖 Generated with [Claude Code](https://claude.com/claude-code)
After the first chat, web quickstarts now tell the reader to open Inspector and confirm Agents, AG-UI Events, and Threads. Feature pages that map to a shipped pane get a short Callout. Angular links the Inspector install page first. Vue sets show-dev-console=auto so the overlay appears on localhost. The inspector-docs skill keeps pane-to-docs pointers in sync when Inspector panes change.
Three names for one value were live in CopilotKit's own documentation, and
following the wrong one with a CLI-provisioned project yields an undefined
key:
- `INTELLIGENCE_API_KEY` — what `copilotkit project select` writes, used by
all 34 integration examples and the docs site.
- `COPILOTKIT_INTELLIGENCE_API_KEY` — the seven Channels package READMEs and
the packaged skills. Nothing ever read it.
- `COPILOTKIT_API_KEY` — the Slack and Teams examples, and the TSDoc on
`CopilotKitIntelligence` itself, which is what an IDE shows on hover.
`INTELLIGENCE_API_KEY` wins, because it is the name the CLI provisions and
changing it would break every scaffolded project in the wild.
`COPILOTKIT_INTELLIGENCE_API_KEY` is retired outright — no code read it.
`COPILOTKIT_API_KEY` stays readable as a deprecated alias in the two
examples that consume it, so an existing `.env` keeps working, and is
documented as deprecated everywhere it appears.
The skills reference also documented `organizationId`, sourced from a fourth
and fifth env name, as a `CopilotKitIntelligence` option. It is not one:
`CopilotKitIntelligenceConfig` has no such field, so the copy-pasteable
sample it appeared in would not compile. Removed from the samples, and the
prose that told readers to fetch a value for it corrected.
The Intelligence wiring itself was published only inside
`node_modules/@copilotkit/runtime/skills/`, and the only docs pages showing
`CopilotKitIntelligence` were the two Channels frontends — so a developer on
the plain web path had no page to reach it from. Adds
`/premium/connect-your-runtime`, which covers the wiring, how to confirm the
credential is actually consumed, and the self-hosted two-URL rule.
`scripts/validate-intelligence-env-names.ts` keeps this from drifting back.
It runs unfiltered in CI on purpose: the two workflows that would otherwise
cover it filter paths, and static/quality ignores `examples/**` — exactly
where the deprecated alias lives.
## What broke
The regression was introduced by commit fec70d086 (feat(angular):
checkpoint 2 - core and package), merged through PR #6109 as b07482da5.
That commit established Angular 20 as the package’s compiler and support
floor, but the demo remained on Angular 21 after 8b13fbcb7 (build:
update ng).
It also introduced the packed smoke runner without canonicalizing macOS
temporary paths, allowing /var/... and /private/var/... to resolve
inconsistently.
## Why I made this change
I moved the demo back to Angular 20 so it exercises the lowest supported
Angular version, aligned the Angular 20 dependencies and support
contract on
20.3.27, and canonicalized the packed consumer directory before starting
the SSR server.
This keeps the demo, package metadata, tests, and lockfile consistent
while making the packed smoke test reliable across symlinked temporary
directories.
## Changes
- Align the Angular demo with the Angular 20 support floor.
- Update Angular 20 dependencies and support-policy tests to 20.3.27.
- Resolve the packed consumer directory to its real path before
launching SSR.
Adds scripts/telemetry/ (emit-fragment.ts + extract.ts) and two CI workflows
that generate CopilotKit's telemetry-registry fragments and open path-limited
PRs into CopilotKit/oss-path-to-production:
- runtime (bespoke catalog): reads the AnalyticsEvents type map for event names
+ properties, scans capture() sites for call_sites, fails loud if the v1/v2
catalogs diverge. Triggered on stable monorepo release.
- docs (callee mode): extracts inline posthog.capture literals from
showcase/shell-docs (drops $-reserved events). Triggered on push to main
touching showcase/shell-docs/**.
Both are content-gated: the fragment is left untouched (and no PR opened) when
the event set is unchanged, so releases/edits don't churn the registry. Cross-
repo token follows the least-privilege recipe (no owner, bare repositories,
contents+PR write); mint gated on a job-level env var. zizmor clean (one
justified cache-poisoning suppression). 13 unit tests; tsc + oxlint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Channels onboarding workflow is served at
https://copilotkit.ai/channels-guide.md, and every other entry point now
copies one line that points there (#6357 for the docs surfaces). Coding
agents invoked through a skill had no such pointer, so they fell back to
setup-slack-channel, which is scoped to Slack, to the provider half, and to
an OpenTag checkout.
channels-setup is a pointer, not a copy. The workflow stays in one place and
is corrected there instead of drifting against the CLI on its own schedule.
Verifying the fetch is the substance of the file. The site answers unknown
paths with a "Page not found" HTML page under HTTP 200, so a status check
proves nothing: an agent that keys on the status code gets a marketing 404
and improvises the workflow from memory. The skill checks the body instead
-- markdown, the guide's H1, five Phase headings -- and stops if any fail.
Standalone skills are not generated from packages/*/skills, so the slug is
registered in RESERVED_LIFECYCLE_SLUGS. Without that entry the sync treats
the directory as an orphan and deletes it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
The most-used "add CopilotKit to your project" path walked every new user into the
self-hosted SSE runtime and never offered the managed one.
`CopilotIntelligenceRuntime`, `CopilotKitIntelligence`, the required
`identifyUser`, and the hosted environment values all appeared in this skill's
reference files but were wired by no step, so the skill could describe managed
Intelligence without ever producing it.
Step 2 now chooses the runtime mode before any runtime code is written, because
the mode changes how the runtime is constructed and retrofitting it means
rewriting the file. Managed Intelligence is the recommended default and now has
real wiring. Self-hosted SSE stays fully documented as a deliberate opt-out with
its prerequisites and its tradeoff stated plainly at the point of choice -- the
open-source packages are published and MIT-licensed, so obscuring the alternative
would not prevent its use and would cost credibility on everything around it.
Step 6 becomes the actual Intelligence step rather than a telemetry aside. It
separates the two credentials that setup mistakes usually conflate: the
server-side project API key, which is a secret and must never take a
NEXT_PUBLIC_/VITE_ prefix, and the public license key, which is a project
identifier meant to reach the client.
It also fixes a command that does not exist. Both this skill and
references/telemetry-setup.md instructed `npx copilotkit auth`; the command is
`login`, and `project select` is what provisions the project.
The new copilotkit-channels skill covers the code half of a managed Channel: the
declaration, the long-running host requirement, and the awaited
`listener.channels.ready()` call. Activation is lazy on every host, so a runtime
that omits that call serves HTTP, reports no error, shows an encouraging badge in
the dashboard, and answers nothing -- the failure the skill exists to prevent. It
states the managed-versus-self-hosted boundary up front, since both product
families use the words "channels" and "Slack".
A standalone skill must be registered in RESERVED_LIFECYCLE_SLUGS. Without an
entry the sync script treats it as an orphan and deletes it, so the test now pins
that requirement with the reason.
## What changes
This replaces the managed Channels adapter and transport stack with live
sessions over Realtime Gateway.
- Channels use the standard AgentRunner and canonical AG-UI history for
each turn.
- One admitted delivery runs one prompt; multiple agent calls run in
order; concurrent calls fail with a bounded protocol error.
- Slack and Teams reuse their native renderers to emit destination-free
provider effects.
- Files, rich controls, interaction handlers, and provider cursors cross
the live-session protocol with bounded payloads.
- The old claim mapping, HTTP fallback, render batches, listener
election, in-memory transport, and legacy adapter code are removed.
- The public `@copilotkit/channels` umbrella remains limited to public
provider adapters; the managed launcher stays in
`@copilotkit/channels-intelligence`.
Companion service PR and kind proof:
https://github.com/CopilotKit/Intelligence/pull/638
## Why
Managed delivery must use the same AgentRunner path as other
Intelligence runs. SDK code emits provider-neutral effects; the trusted
Gateway owns credentials, destinations, admission, retries, and terminal
outcomes.
## Validation
- `pnpm nx run-many -t build,check-types,test -p
@copilotkit/channels-core,@copilotkit/channels-intelligence,@copilotkit/channels-slack,@copilotkit/channels-teams,@copilotkit/channels,@copilotkit/runtime`
— 31 tasks passed
- `pnpm nx test @copilotkit/channels-teams` — 89 passed
- `pnpm vitest run scripts/release/lib/channels-umbrella.test.ts` — 8
passed
- `pnpm verify:channels-umbrella` — packed snapshot, dependency
resolution, and TSX consumer passed
- affected package pre-commit tests, publint, and API type checks passed
- changed-file Prettier and `git diff --check` passed
## Known unrelated check
`pnpm nx build demo` now compiles past the prior Channels telemetry
dependency leak, then fails on the existing AG-UI 0.0.51 versus 0.0.57
private `_debug` type mismatch.
After the npx fix each publish is ~4.7s and almost entirely a registry
round-trip, so a 26-package scope=all canary still spent ~125s waiting
serially. Publish 4 at a time (CANARY_PUBLISH_CONCURRENCY=1 restores
serial for debugging).
This weakens no ordering invariant. prerelease.ts's own header already
documents that the cross-scope graph has cycles (runtime ->
channels-intelligence, channels-core -> core), so no serial order avoided
publishing a package before the same-run version it pins.
Per-package output is captured and replayed as one block rather than
inherited, since a pool would otherwise interleave several npm publishes
line-by-line — and that log is the only forensic record when a canary
half-publishes. Every package is attempted even if others fail, so one
report names all of them; main() now exits non-zero on failure rather
than letting an unhandled rejection pass the step.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The canary flow took ~11.5 min steady-state (and 20 min in an observed
run). Measured from run 30473499191, the time went to five avoidable
places rather than to real work.
1. `npx --yes npm@11.15.0 publish` ran per package, and npx re-resolves
the spec against the registry on EVERY invocation: ~16s of each
package's ~21s. A 9-package channels canary paid ~2.4 min of pure npx
overhead; a 16-package monorepo release paid over 4 min. Hoist the
pinned npm into lib/npm-cli.ts, install it once into a throwaway
prefix, and reuse the binary.
2. publish-release.yml was the only workflow in the repo with no pnpm
store cache, so all three jobs installed 4608 packages cold every
time. Usually ~45s each, but registry-bandwidth bound and heavy
tailed: the observed run spent 9m08s here on tarballs arriving at
2-49 KiB/s. Add the same node-version-keyed cache the rest of CI uses.
3. The notify job ran for canaries only to compute "post nothing" — the
builder already returns should_post=false for mode=prerelease and the
self-watchdog is already gated off. ~85s of dead work on the critical
path, since canary.yml waits for the whole run. Skip the job, keeping
it reachable for a python_publish dispatch.
4. The build job fetched full history for canaries, which need none (no
tag, no GH Release, no release-note commit range, and `nx run-many`
resolves no merge base). That rode along in the 837 MiB workspace
artifact too. Shallow-fetch prereleases; stable keeps depth 0 because
its publish job pushes tags out of that artifact's .git.
5. Two smaller ones: the artifact was gzipped and then re-deflated into
the artifact zip (compression-level: 0), and the orchestrator's
run-discovery loop slept 6s before its first poll.
Verified: 143 release-script tests pass (6 new for the npm-cli helper),
actionlint + shellcheck + the scope-dropdown guard are clean, the
prerelease dry-run path still enumerates all 9 channels packages, and a
live probe confirms the helper installs npm 11.15.0 once (3.2s) and
memoizes thereafter (0ms).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`verify:runtime-package` could not pass on a channels release PR. `pnpm pack`
rewrites the runtime's `workspace:` ranges to the workspace's current version,
so on a release PR the temp consumer tried to resolve the freshly-bumped
`@copilotkit/channels-intelligence` from npm — the version this very PR is
about to publish. It failed `unit (20.x)` by construction on #6185 and #6025.
Apply the fix `verify-channels-umbrella` already uses: pack the whole
first-party `workspace:` closure locally and pin it through pnpm `overrides`.
The packing helpers move to `lib/pack-workspace.ts` so both scripts share one
implementation instead of duplicating it.
The contract is unchanged: the packed runtime must still declare
channels-intelligence as a real dependency, and it must still load through
both ESM and CJS.
Adversarial pass on the previous commit. The warning only inspected
`workspace:` ranges, so it missed the OTHER way a cross-scope pin goes stale: a
literal version range naming a package in another scope. `bumpPackages` rewrites
literal ranges for in-scope packages only, so such a pin survives every bump —
`scope=all` publishes the canary and the artifact still resolves the dependency's
last stable release, silently, which is the exact failure this warning exists to
surface.
`findCrossScopeWorkspaceDeps` becomes `findCrossScopePins`, reporting both shapes
tagged with a `reason`, and the literal case carries its own remedy (convert to
`workspace:`) instead of the useless "re-run with scope=all".
No such pin exists in the tree today — every cross-scope edge is `workspace:` —
so this closes a latent hole rather than a live one, in the one place a future
refactor would reintroduce it.
Also documents the multi-scope partial-failure mode in prerelease.ts: the
cross-scope graph has cycles, so no publish order avoids a package shipping
before the same-run version it pins, and npm's no-republish rule means a failed
run must be retried under a new suffix.
Two release-tooling defects turned a pair of canary publishes into a broken
combination for consumers (a canary runtime resolving the last STABLE
channels-intelligence, which still called the removed `channel.addAdapter`).
1. Canary versions were prereleases of an ALREADY-PUBLISHED version. A stable
release leaves the working tree on the version it just published, and
computePrereleaseVersion appended `-canary.<id>` to exactly that, so the
canary sorted BELOW its own release (`0.2.1-canary.x < 0.2.1`): the `canary`
dist-tag pointed behind `latest`, and no dependent range could ever resolve
it. Base the canary on the next unreleased version instead (patch bump,
reusing computeNextStableVersion's prerelease rule).
2. A canary published one scope at a time, but the scopes are only independent
on the version axis. `@copilotkit/runtime` carries
`"@copilotkit/channels-intelligence": "workspace:*"`, and `pnpm pack`
resolves that against the working tree — so a `monorepo` canary pinned the
channels family to its last stable release even when the commit changed both
sides of the contract. Add a prerelease-only `all` selector that bumps and
publishes every scope from one commit under one shared canary id, and warn
loudly when a single-scope canary leaves a cross-scope pin behind.
`all` is a selector, never a scope: stable releases stay single-scope (their
tag, release branch, and npm/Slack links all derive from one scope name), which
publish-release.yml enforces in both jobs and the dropdown guard enforces per
workflow.
The local `verify:channels-umbrella` check packs the Channels family and
installs a throwaway consumer, overriding only the family tarballs. The
family depends on monorepo-versioned packages (`@copilotkit/core`,
`@copilotkit/shared`) via the `workspace:` protocol, which `pnpm pack`
rewrites to the workspace's current version. On a release PR that version
is the freshly-bumped one that isn't on the registry until the release
publishes, so the consumer install fell through to npm and failed with
ERR_PNPM_NO_MATCHING_VERSION — the unit(20.x) red that blocked every
monorepo release PR (e.g. #5992, #6019) on its own not-yet-published
version.
Pack those workspace siblings locally too and pin them as consumer
overrides, so local mode never races the registry against our own
in-flight release. Siblings are discovered transitively via the
`workspace:` protocol so the list never drifts as the family's internal
dependencies change.
Verified by bumping shared+core to an unpublished version: the old script
fails with ERR_PNPM_NO_MATCHING_VERSION, the fixed script reports OK.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The packed Channels umbrella (and the runtime-package verify) install
first-party packages straight from the registry, so a just-published
@copilotkit/* version fails pnpm's 24h minimumReleaseAge check — this is
what reddened `unit (20.x)` on channels PRs. The enumerated exclude lists
drift every time an internal dependency is added (main already had to bolt
on @copilotkit/channels-core, then #6012 added @copilotkit/core + shared).
Wildcard the @copilotkit/* scope (org-owned, same publish pipeline — no
outsider can inject a package, and it stops the drift). Keep @ag-ui
enumerated rather than wildcarded: it's a separate upstream org, so we don't
extend immediate-install trust to its entire scope. Third-party deps stay
fully gated. Applied to both the generated consumer workspace and the
repo-root .npmrc.
Verified: `pnpm run verify:channels-umbrella` installs the packed umbrella
with no ERR_PNPM_NO_MATURE_MATCHING_VERSION; umbrella unit tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>