13122 Commits

Author SHA1 Message Date
Tyler Slaton 5c36df2099 chore: release monorepo v1.63.0 (#5992)
## Release monorepo v1.63.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.63.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.63.0`
   - Creates git tag `monorepo/v1.63.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.
v1.63.0
2026-07-15 15:27:42 -07:00
tylerslaton 6c354037fc chore: release monorepo v1.63.0 2026-07-15 22:18:07 +00:00
Tyler Slaton 5fa4f2813d fix(release): unblock Channels umbrella publish (#5990)
## Problem

The Channels v0.2.0 release publishes its dependency packages, then
fails before publishing the umbrella package because pnpm rejects the
freshly published dependencies under the 24-hour minimum release age
policy.

## Why

The registry-backed verifier installs the packed umbrella in an isolated
temporary consumer without carrying a Channels-family release-age
exemption into that consumer.

## Fix

Generate a pnpm workspace config for the isolated consumer that exempts
every Channels package while retaining the maturity gate for unrelated
dependencies. Add regression coverage for the complete family. Verified
with all 109 release-script tests, the registry-backed Channels
verifier, explicit lint/typecheck/format checks, and the full Nx package
build.
channels/v0.2.0
2026-07-15 14:26:45 -07:00
Tyler Slaton a5387831c0 fix(release): exempt Channels verifier from maturity gate 2026-07-15 14:11:44 -07:00
Tyler Slaton 45fa204843 chore: release channels v0.2.0 (#5989)
## Release channels v0.2.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.2.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.2.0`
   - Creates git tag `channels/v0.2.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.
2026-07-15 13:38:33 -07:00
tylerslaton add6539dcd chore: release channels v0.2.0 2026-07-15 20:29:29 +00:00
Tyler Slaton 9504e0666c refactor(channels): add batteries-included package architecture (#5948)
## Summary

- Extract the platform-neutral Channels foundation into
`@copilotkit/channels-core`.
- Make `@copilotkit/channels` the batteries-included consumer entry
point, with adapter and UI subpaths.
- Release the umbrella, core, UI, and all six adapters as one shared
`channels` version scope.
- Verify the packed consumer contract and migrate the Slack and Teams
examples to the umbrella.

## Why

Consumers should be able to install one Channels package without making
the runtime or selective integrations depend on every platform SDK.
Shipping the complete Channels family together prevents adapter/core
version drift and makes the umbrella's exact dependency set release as a
compatible unit.

## How

- Move shared bot/runtime primitives into `channels-core` and invert
adapter/runtime dependencies.
- Add exact workspace dependencies and export subpaths from the umbrella
package.
- Consolidate the existing release configuration and all three release
workflow selectors into one shared `channels` scope containing all nine
packages.
- Use `@copilotkit/channels` as the version source: the next minor
release resolves to `0.2.0` and bumps every Channels package together.
- Publish scoped packages in dependency order for both stable and canary
releases: UI, core, adapters, then the umbrella.
- For a stable Channels release, publish the UI/core/adapters first, run
the registry-backed packed-consumer verifier against those newly
published exact versions, then publish the umbrella.
- Generate Channels release notes from `channels/v*` tags rather than
the monorepo `v*` tags.
- Verify builds, type checks, tests, package artifacts, examples, and
release workflow scope synchronization locally.

### First stable release sequence

1. Bootstrap the currently unpublished `@copilotkit/channels-core`
package on npm and configure npm trusted publishing for every Channels
package against this repository's `release / publish` workflow and `npm`
environment.
2. Create and merge the `channels` minor release PR. The stable workflow
publishes the Channels family in the staged order above and validates
the packed umbrella from the registry before the umbrella is released.
3. Create and merge a subsequent `monorepo` release PR so the published
`@copilotkit/runtime` switches from the historical umbrella dependency
to `@copilotkit/channels-core`.
2026-07-15 13:12:16 -07:00
Ben Taylor 0777a26833 feat(channels-intelligence): realtime transport parity — kinds, identity, history/files, delete (#5983)
## What & why

The Realtime Gateway transport had drifted from the HTTP polling
transport, so managed channels running over the gateway behaved
differently from direct. This brings the realtime path to full parity.

Before, the realtime transport:
- built a **text-only** ingress envelope —
commands/reactions/interactions were coerced into empty turns;
- keyed conversations **per-turn** (`conversationKey = turn.id`), so
threaded follow-ups didn't share agent/session state;
- **dropped the provider actor** — `env.user` was never populated (on
*both* transports, in fact);
- implemented **none** of `fetchFile`/`getHistory`/`uploadFile`, so the
realtime path silently ran with no history and no file support;
- had **no `delete` render kind**, so `thread.delete` couldn't render
over the gateway.

## Changes

- **`claim-mapping.ts` (new, shared):** extract the claim→ingress
mapping — `ClaimedDelivery`, `conversationKeyFromReplyTarget`,
`mapDeliveryToEnvelope` — into one module both transports import, so
they can't drift again (drift *was* the bug). Adds the provider `actor`
to the claim turn and maps it to `env.user` — fixing identity on
**both** paths.
- **realtime transport:** builds the envelope via the shared mapper
(real kind discrimination, thread-stable `conversationKey`, actor→user)
instead of the inline text-only build. Fail-closed: an unmodeled
reply-target/kind is dropped+logged, not crashed.
- **`intelligence-file-history.ts` (new, shared):** extract
`fetchFile`/`getHistory`/`uploadFile` into
`IntelligenceFileHistoryClient`. These are **HTTP-only** — the gateway
relays the render-event stream but never file bytes or history. The
realtime transport gains them when configured with `appApiBaseUrl` +
`apiKey` (threaded through the launcher); without those, the methods
stay `undefined` and the adapter degrades exactly as before.
- **`delete` render kind:** add `{ kind: "delete"; ref }` to
`ChannelRenderEvent` (mirrors the frozen Intelligence contract) and
route `thread.delete` through a render frame when a render sink is
wired, like post/update/file.

## Tests

- `claim-mapping.test.ts`: actor→user, kind discrimination
(command/reaction/interaction/text), thread-stable conversationKey
(slack/teams), unmodeled-adapter throw.
- `render-events.test.ts`: realtime non-text kind + identity +
thread-stable key; file/history capability toggling on config; a
delete-render-frame test.
- Full suite (160) + `check-types` + `build` green.

## Companion

This is the CopilotKit SDK half of the managed-transport parity work;
the Intelligence-side half (gateway `render_event/1` validator for
file/delete, DB CHECK, Connector Outbox `chat.delete`, actor on the
claim) landed separately. Together they close the direct-vs-managed
parity matrix.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-15 14:41:41 -05:00
Benjamin Taylor a8407f84fd fix(channels): address Tyler CR — wire app-api URL through runtime activation + map user name (OSS-476)
- [P1] History/files were absent on the NORMAL managed path: defaultActivateChannel
  never forwarded the app-api HTTP URL, so the transport (which installs
  fetchFile/getHistory/uploadFile only when appApiBaseUrl is set) ran without
  them for Channels started by the CopilotRuntime handler — only manual
  low-level launcher callers got file/history. Thread intelligence.ɵgetApiUrl()
  through ChannelActivationConfig.apiUrl → defaultActivateChannel →
  startChannelsOverRealtimeGateway({ appApiBaseUrl }). The launcher + transport
  already accepted it.
- [P2] Managed turns exposed the provider profile under a non-public `displayName`
  field, leaving PlatformUser.name undefined. Map env.user.displayName -> name
  (parity with the direct Slack adapter, which populates `name`).

Tests: deriver returns apiUrl; defaultActivateChannel forwards appApiBaseUrl to
the launcher opts; onMessage sees message.user.name. channel-activation-config +
channels-intelligence (170) green; runtime build type-checks. (channel-manager.test
executes in CI — local vitest hits the known optional-peer-dep resolution flake.)
2026-07-15 13:36:36 -05:00
Sam Julien cc107b776a docs: refresh stale contributing commands, langgraph RunnableConfig imports, and Anthropic model IDs (#5982)
## What & why

Consolidates three community docs fixes that were each opened against
the retired `docs/content/docs/` tree (now just a symlink to
`showcase/shell-docs/`), so none of them could merge as-is even though
the underlying doc bugs are still live. This re-applies them against the
current shell-docs source and, where the original proposals had drifted,
uses commands verified against the current repo.

### 1. Contributing / package-linking guides — supersedes #3509
Turborepo has been **fully removed** from the repo (no `turbo`
dependency, no `turbo.json`), but every contributing guide still
instructs `turbo run …`. Across the root guide, all per-integration
copies, and the shared snippets:
- Drop the "Turborepo v2.x installed globally" prerequisite; bump pnpm
to **v10.x** to match the root `packageManager` (`pnpm@10.33.4`).
- Reframe the monorepo as a pnpm workspace orchestrated by **Nx**.
- Replace commands with equivalents verified against the current root
`package.json` / Nx targets:
  - `turbo run build|dev|format|lint` → `pnpm run build|dev|format|lint`
- `turbo run link:global` / `unlink:global` → `pnpm exec nx run-many -t
link:global` / `unlink:global`
- per-package dev (`turbo run dev --filter=…`) → `pnpm exec nx watch
--projects=packages/<name> -- pnpm run build`
- also fixed a pre-existing stale reference: `pnpm run example-dev` →
`pnpm run dev:examples` (the real script)

### 2. Anthropic model IDs — supersedes #3656
`built-in-agent/model-selection` listed dotted IDs that aren't valid;
hyphenate `claude-3-7-sonnet`, `claude-opus-4-1`, `claude-3-5-haiku`.

### 3. LangGraph `RunnableConfig` imports — supersedes #4069
Twelve langgraph reference pages annotate `config: RunnableConfig` in
Python snippets without importing it. Add `from langchain_core.runnables
import RunnableConfig` to each such block. (Tutorial pages were
intentionally left out to avoid disrupting their step-by-step
narrative.)

## Credit
Thanks to the original authors whose fixes this incorporates:
@electricalen (#3509), @Abubakar-01 (#3656), and @Koushik-Salammagari
(#4069). Those PRs can be closed in favor of this one once merged.

## Testing
- `grep` confirms **0** remaining `turbo run` / `Turborepo` / `turbo@2`
references and **0** remaining `example-dev` references across the docs
content.
- All replacement scripts/targets verified present in the root
`package.json` (`build`, `dev`, `format`, `lint`, `dev:examples`) and as
per-package Nx targets (`link:global`, `unlink:global`).
- Confirmed **0** langgraph Python blocks remain that use
`RunnableConfig` without importing it (excluding the
intentionally-skipped tutorial pages).
- Spot-checked insertion placement/indentation in both deeply-nested
(`interrupt-flow.mdx`) and flat (`auth.mdx`) code blocks.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-15 11:11:54 -07:00
Benjamin Taylor 237cbf4cf9 chore(channels): CR hygiene — real test coverage + doc/log cleanups (OSS-476)
Trivial items from the CR confirmation rounds (no behavior change to production
paths):

- intelligence-adapter.test.ts: `delete source.getHistory` was inert (getHistory
  is a prototype method; delete only removes own props), so the "no getHistory"
  branch was never actually exercised. Shadow with an own `undefined` instead so
  the `source?.getHistory?.()` short-circuit is genuinely tested.
- in-memory-transports.ts: mirror the production `limit <= 0 -> []` guard in
  InMemoryDeliverySource.getHistory (was `slice(-limit)` → returns ALL for 0).
- realtime ack(): log the empty-turn (no accepted frames) drop so the OSS-491
  redelivery pile-up is diagnosable (every other drop path here logs).
- http-transports.ts: remove two orphaned JSDoc comments dangling over
  ClaimResponse (leftovers from the file/history extraction).
- intelligence-adapter.ts: correct an inaccurate op-id comment (mintOp is the
  ${turnId}:${seq} source; the render path keys on ${turnId}:${slot}:${seq}).

channels-intelligence 170 tests + check-types green.
2026-07-15 12:58:53 -05:00
Tyler Slaton 5811cab177 fix(channels): align rebase with channel API 2026-07-15 10:29:21 -07:00
Benjamin Taylor 2718474229 fix(channels): second CR round — graceful realtime stop() + parity/hygiene (OSS-476)
From the 7-agent CR confirmation round:

- RealtimeGatewayTransport.stop() now halts intake (a `stopped` guard in
  handleDeliveryAvailable — the session exposes no `off` to detach the
  DELIVERY_AVAILABLE listener) and DRAINS the in-flight delivery (awaits the
  serial `processing` chain) before clearing state, so a turn settling at stop
  time still sends its terminal signal instead of silently no-oping and
  redelivering. Mirrors HttpDeliverySource.stop().
- Realtime nack() truncates the reason to 500 chars (parity with HTTP).
- getMessages drops empty content parts before join(" ") so a read_thread
  transcript isn't corrupted with doubled/leading/trailing spaces (a test had
  enshrined "part one  part two").
- Removed dead imports (buildContentParts, AgentContentPart, ChannelFileRef)
  left in http-transports.ts after the file/history extraction.

Deferred (delivery-contract / design; need coordination — folded into OSS-491):
timeout-nack can redeliver a still-running turnId (overlap); realtime push()
no-state fallback vs HTTP fail-loud (documented intentional — parity Q); the
empty-turn completion signal.

Tests: stop() drains in-flight + ignores post-stop deliveries; getMessages
assertion corrected. channels-intelligence 170 tests + check-types green.
2026-07-15 12:21:53 -05:00
Tyler Slaton eed52e13c7 refactor(release): preserve configured publish order 2026-07-15 10:13:19 -07:00
Tyler Slaton 3b22abbbd1 fix(release): stage channels publishing 2026-07-15 10:13:18 -07:00
Tyler Slaton 17213998a1 chore(release): unify channels version scope 2026-07-15 10:13:18 -07:00
Tyler Slaton 02a93c3b8a docs(channels): clarify umbrella release availability 2026-07-15 10:13:18 -07:00
Tyler Slaton fad2aed6c2 test(channels): verify packed umbrella consumers 2026-07-15 10:13:17 -07:00
Tyler Slaton 8f6f97f231 docs(channels): dogfood the umbrella in channel examples 2026-07-15 10:13:17 -07:00
Tyler Slaton 163d0e99b8 chore(release): add channels core and umbrella scopes 2026-07-15 10:12:23 -07:00
Tyler Slaton 3dba09b7c7 feat(channels): add the batteries-included umbrella 2026-07-15 10:12:23 -07:00
Tyler Slaton 06fb2ed34a refactor(channels): extract the platform-neutral core 2026-07-15 10:11:55 -07:00
Benjamin Taylor f126acfb43 docs: address review — real example dev path/script, root auth imports, pnpm 10, langgraph self-contained fences
Addresses @samjulien's review on #5982:

1. Contributing example command: the guide changed into a nonexistent
   `examples/next-openai` and ran `dev:examples` (a workspace build/watch
   script that never starts a server). Point it at the real
   `examples/v1/next-openai` package and its `example-dev` (`next dev`)
   script, which actually serves http://localhost:3000/presentation.
   Fixed in the shared snippet + all per-integration copies.

2. LangGraph auth: langgraph variants are docs_mode: generated, so the
   `/auth` route renders the root `docs/auth.mdx`, not the framework copy.
   Add the missing `from langchain_core.runnables import RunnableConfig`
   to the two Python blocks in the root source that route renders.

3. Self-contained fences: add the import to the non-tutorial
   `langgraph/shared-state/predictive-state-updates.mdx` Python fence and
   the `snippets/integrations/langgraph/frontend-tools.mdx` fence, so the
   zero-missing-import claim holds for every non-tutorial langgraph block.

4. Contributor prerequisites: bump the `docs-contributions` guides from
   pnpm 9 to pnpm 10 to match the code-contributions requirement.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 12:08:29 -05:00
github-actions[bot] c2e2b4c0fd style: auto-fix formatting 2026-07-15 17:08:16 +00:00
Benjamin Taylor 54fa303477 fix(channels): address adversarial CR findings on the realtime transport (OSS-476)
Pre-merge 7-agent CR of #5983 surfaced several realtime-transport defects (all
in channels-intelligence). Fixes:

- Poison-payload re-lease loop: an unmappable delivery (unmodeled reply-target
  adapter / unknown input.kind) with a valid lease was logged + dropped, so
  app-api re-leased the identical payload forever. It now fails NON-retryable
  (dead-letter), mirroring the HTTP path. nack() gains a `retryable` param.
- Double-terminal-signal race: realtime ack()/nack() deleted delivery state
  AFTER the wire push, so a per-turn-timeout nack could race a late dispatch ack
  and emit BOTH fail + complete_requested. Now delete-before-push (XOR).
- Concurrent dispatch: deliveries were handled fire-and-forget; now processed
  serially (parity with the HTTP runLoop) so an in-flight redelivery can't reset
  the shared per-turn seq counter or run two turns of one conversation at once.
- actor.displayName was carried through the claim mapper then dropped in
  dispatchTo (`{ id }` only) — now forwarded to handlers (OSS-476 identity).
- fetchFile enforced MAX_INBOUND_FILE_BYTES only against declared content-length;
  the actual read was unbounded. Now streams and aborts past the cap.
- getHistory returned the FULL history for limit<=0 (slice(-0)) and hydrated file
  bytes for over-returned messages it then discarded. Now caps to the most recent
  `limit` BEFORE hydrating; limit<=0 -> [].
- stream() posted an empty text frame for an empty stream; now skips the post.
- HTTP withTimeout/defaultSleep timers now unref() (parity with realtime).
- Corrected the inaccurate thread_started comment in claim-mapping.ts.

Deferred to OSS-491 (delivery-terminal-signal contract, needs app-api
coordination): an empty turn (reaction/command that posts nothing) has no valid
completion signal (acceptedThrough requires >=1) and redelivers; and the
run_error swallow on the HTTP-fallback render path.

Tests: poison non-retryable, single-terminal XOR, serial dispatch, displayName
forwarding, getHistory limit<=0 + cap. channels-intelligence 169 tests + check-types green.
2026-07-15 12:06:50 -05:00
Benjamin Taylor bc883e89c0 feat(channels-intelligence): realtime transport parity — kinds, identity, history/files, delete (OSS-476)
The realtime-gateway transport had drifted from the HTTP transport: it built a
text-only ingress envelope that coerced commands/reactions/interactions into
empty turns, keyed conversations per-turn (breaking threaded follow-ups),
dropped the provider actor identity, and implemented none of
fetchFile/getHistory/uploadFile (so the realtime path silently ran with no
history and no file support). It also had no `delete` render kind. This brings
the realtime path to full parity with direct.

- claim-mapping: extract the claim→ingress mapping (ClaimedDelivery,
  conversationKeyFromReplyTarget, mapDeliveryToEnvelope) into a shared module
  both transports use, so they cannot drift again. Add the provider `actor` to
  the claim turn and map it to `env.user` (fixes identity on BOTH paths).
- realtime transport: build the envelope via the shared mapper — real kind
  discrimination, thread-stable conversationKey, actor→user — instead of the
  text-only inline build. Fail-closed: an unmodeled reply-target/kind is
  dropped+logged, not crashed.
- file/history: extract fetchFile/getHistory/uploadFile into a shared
  IntelligenceFileHistoryClient (HTTP-only — the gateway never relays bytes or
  history). The realtime transport gains them when configured with
  `appApiBaseUrl` + `apiKey` (threaded through the launcher); absent that, the
  methods stay undefined and the adapter degrades exactly as before.
- delete render kind: add `{ kind: "delete"; ref }` to ChannelRenderEvent
  (mirrors the frozen Intelligence contract) and route thread.delete through a
  render frame when a render sink is wired (OSS-420), like post/update/file.

Tests: shared-mapper unit tests (actor→user, kind discrimination,
conversationKey, unmodeled-adapter throw); realtime tests for non-text kind +
identity + thread-stable key and file/history capability toggling; a
delete-render-frame test. Full suite + check-types + build green.
2026-07-15 11:41:01 -05:00
Ben Taylor 183b0d9dc9 fix(channels): realtime-transport reliability hardening + residual bot-naming scrub (closes OSS-485, OSS-482) (#5972)
Combines the two OSS-473 follow-ups (originally #5972 + #5973, now
folded here) into one PR for a single review/merge. Base is `main`
(OSS-473/#5963 has merged). Reviewable **commit-by-commit** — the
mechanical rename is isolated in commit 1; the behavior changes are
commits 2–4.

## Commit 1 — `refactor(channels)`: scrub residual internal "bot" naming
(closes OSS-485)

Mechanical, naming-only, **no behavior change** — the last internal
"bot" vestiges left out of 473's atomic telemetry-surface commit.

- `bot` local variable → `channel` in `create-channel.ts` internals +
the ~15 tests that exercise it.
- `BotNode` type → `ChannelNode` in `@copilotkit/channels-ui` and
**every** importer (channels,
slack/teams/discord/telegram/whatsapp/intelligence adapters, slack/teams
examples — 256 refs).
- `botName` adapter-SPI option → `channelName` on `AdapterStartContext`
+ its `create-channel` caller + the one consumer
(`IntelligenceAdapter.start` ctx), in one change so the SPI can't drift.
(Phoenix wire contract was already `channelName` as of 473.)
- Stale `bot-ui`/`bot-slack` comment refs →
`channels-ui`/`channels-slack`; `Symbol.for("copilotkit.bot-ui.*")` →
`"copilotkit.channels-ui.*"`.
- **Kept** (out of scope): the platform `isBot?` author flag (unrelated
semantic) and example `bot` instance vars / "demo bot" prose
(user-facing).

## Commits 2–4 — `fix`: realtime-transport reliability & parity
hardening (closes OSS-482)

The self-contained observability/robustness subset of the OSS-473 CR
follow-ups (`packages/channels-intelligence` transport +
`packages/runtime` manager). Each hardening ships with a test that fails
without the fix.

- **`emit()` fail-loud** (`intelligence-adapter.ts`) — returned a
synthetic `MessageRef` on `{ ok: false }`, acking a failed
post/update/delete as success (silent egress drop). Now throws → the
delivery is nacked/retried.
- **Realtime delivery dispatch** (`realtime-gateway-transport.ts`) — the
`void handleDeliveryAvailable(...)` fire-and-forget had no error
boundary and no deadline. Replaced with a `.catch` + a bounded per-turn
timeout that nacks + logs on failure/timeout (parity with the HTTP
`runLoop`).
- **`ChannelManager.stop()` per-handle timeout** — a wedged
`handle.stop()` hung teardown/SIGTERM forever. Each is now bounded by
`stopHandleTimeoutMs` (default 5000); on timeout it's logged and
abandoned so other entries still stop.
- **`ChannelManager.ready({ timeoutMs })`** — a set-wide timeout
discarded an erroring channel's reason when a sibling hung. The deadline
is now **per channel**, so the `AggregateError` carries both the real
activation error **and** a named timeout for each hanging channel.
- **Forward `ChannelManager` `log` down** to the launcher/transport so
transport-level drop diagnostics (e.g. a version-skew
missing-`leaseToken` outage) aren't silent in the managed path.
- **`examples/slack/.env.example`** — OpenAI-only `AGENT_MODEL`
guidance; removed never-read `ANTHROPIC_API_KEY`/`GOOGLE_API_KEY`;
blanked the presence-gated `LINEAR_API_KEY`/`NOTION_*` placeholders (a
non-blank value wires a broken MCP); documented `NOTION_MCP_PORT`; added
WhatsApp to the adapter list.

### Already landed in OSS-473 (not re-done here)
The reconnect **"gave-up → error" escalation** is fully implemented on
`main`: `realtime-gateway.ts` bounds the reconnect window
(`reconnectGiveUpMs`, default 60s) and emits a terminal `gave_up`; the
launcher exposes `onStateChange`;
`ChannelManager.registerConnectionObserver` maps `gave_up → error`
(covered by `realtime-gateway.test.ts` +
`channel-manager-reconnect.test.ts`).

### Explicitly EXCLUDED — owned by OSS-474 / OSS-476
Left untouched (overlap in-flight work): no cross-turn history on the
realtime path (per-turn `conversationKey`) → OSS-474/476; empty-turn
`ack()` redelivery livelock → OSS-474/475/476; non-text parity
(command/interaction/reaction) → OSS-476; `thread.delete()` render frame
→ OSS-476 (PR #554); durable StateStore on the managed realtime path →
OSS-474/476.

## Testing
- **DoD grep** — `grep -rn "\bbot\b\|botName\|BotNode" packages/channels
packages/channels-ui` (`.ts`/`.tsx`, excl. `dist/`) → **0**; `BotNode`
gone repo-wide.
- **check-types** green across all 8 channels packages +
`@copilotkit/runtime`.
- **tests** green: channels 155, channels-ui 23, slack 277, teams 88,
discord 193, telegram 149, whatsapp 73, channels-intelligence 144,
runtime `channel-manager` 40; slack-example 63, teams-example 2. Full CI
matrix (incl. `unit` 20.x/22.x/24.x) green on this branch.
- Each 482 hardening has a test that fails pre-fix (no silent ack;
dispatch nacks on throw/timeout; `stop()` resolves + logs on a wedged
handle; `ready()` aggregate preserves the real reason;
`defaultActivateChannel` forwards `log`).

Closes OSS-485. Closes OSS-482.
2026-07-15 11:29:09 -05:00
Benjamin Taylor a758faa19d fix(channels): CR follow-ups — honest stopped status + correct .env WS port (OSS-482)
Two fixes from the pre-merge adversarial CR of this PR (both pre-existing,
flagged as in-subject):

- ChannelManager.status() reported overall "online" for a manager stopped
  BEFORE activate() (e.g. SIGTERM during startup): `entries` is empty, so the
  empty-set fold returned "online" — a torn-down manager reading healthy. Now
  short-circuits to "stopped" when `this.stopped`, matching the documented
  status() contract. New red-green test covers the stop()-before-activate() case.

- examples/slack/.env.example: COPILOTKIT_INTELLIGENCE_WS_URL example was
  ws://localhost:4401, but derivation is a scheme-only swap of the :4201 API URL
  (→ ws://localhost:4201) and 4401 is used nowhere — a user uncommenting it hit a
  dead port. Corrected to :4201 and clarified the derivation note.
2026-07-15 10:50:20 -05:00
Benjamin Taylor 07c1ad7ef1 docs: refresh stale contributing commands, langgraph RunnableConfig imports, and Anthropic model IDs
Consolidates three stale-docs fixes that were opened against the retired
`docs/content/docs/` tree (now a symlink) and so could no longer merge:

- Contributing/package-linking guides (root + all integration copies +
  shared snippets): Turborepo is fully removed from the repo (no dep, no
  turbo.json). Drop the Turborepo prerequisite, bump pnpm to v10.x to match
  `packageManager`, describe the monorepo as a pnpm workspace orchestrated by
  Nx, and replace `turbo run <task>` with verified equivalents:
  `pnpm run build|dev|format|lint`, `pnpm exec nx run-many -t (un)link:global`,
  `pnpm exec nx watch` for a single package, and `pnpm run dev:examples`
  (the real script; `example-dev` did not exist). Supersedes #3509.
- built-in-agent/model-selection: hyphenate the Anthropic model IDs
  (`claude-3-7-sonnet`, `claude-opus-4-1`, `claude-3-5-haiku`). Supersedes #3656.
- langgraph reference docs: add the missing
  `from langchain_core.runnables import RunnableConfig` import to Python code
  blocks that annotate `config: RunnableConfig`. Supersedes #4069.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 09:18:02 -05:00
renovate[bot] afcd74a587 chore(deps): update zizmorcore/zizmor-action action to v0.6.0 (#5981)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[zizmorcore/zizmor-action](https://redirect.github.com/zizmorcore/zizmor-action)
| action | minor | `v0.5.7` → `v0.6.0` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the [Dependency
Dashboard](../issues/592) for more information.

---

### Release Notes

<details>
<summary>zizmorcore/zizmor-action (zizmorcore/zizmor-action)</summary>

###
[`v0.6.0`](https://redirect.github.com/zizmorcore/zizmor-action/compare/v0.5.7...v0.6.0)

[Compare
Source](https://redirect.github.com/zizmorcore/zizmor-action/compare/v0.5.7...v0.6.0)

</details>

---

### Configuration

📅 **Schedule**: (in timezone America/Los_Angeles)

- Branch creation
  - "before 9am every weekday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/CopilotKit/CopilotKit).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNTkuMiIsInVwZGF0ZWRJblZlciI6IjQzLjI1OS4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-07-15 12:55:38 +00:00
Benjamin Taylor 8eda1d64aa docs(examples): correct slack .env.example env accuracy (OSS-482)
Pre-existing .env.example issues noted in the OSS-473 CR:

- AGENT_MODEL example was `anthropic/claude-sonnet-4.5`, but runtime.ts is
  OpenAI-only (web search is an OpenAI hosted tool; it strips a leading
  `openai/` and passes the rest to `openaiText`). Use an OpenAI example and say
  so.
- Removed ANTHROPIC_API_KEY / GOOGLE_API_KEY — never read by this runtime.
- LINEAR_API_KEY, NOTION_TOKEN and NOTION_MCP_AUTH_TOKEN were non-blank
  placeholders, but runtime.ts turns the Linear/Notion MCPs ON purely on the
  presence of LINEAR_API_KEY / NOTION_MCP_AUTH_TOKEN — a placeholder wires a
  broken MCP. Blanked them (they're optional integrations, like the commented
  Discord/Telegram/WhatsApp creds).
- Documented NOTION_MCP_PORT (the `pnpm notion-mcp` sidecar port, default 3001;
  must match NOTION_MCP_URL).
- Added WhatsApp to the header list of supported adapters.
2026-07-15 07:44:35 -05:00
Benjamin Taylor ed37e418ca fix(runtime): bound ChannelManager.stop(), surface ready() AggregateError on hang, forward log to transport (OSS-482)
Three ChannelManager observability/robustness hardenings from the OSS-473 CR.

stop() per-handle timeout: stopEntry awaited handle.stop() unbounded, so a
wedged stop() (e.g. a socket.disconnect that never returns) hung teardown — and
thus SIGTERM shutdown — forever. Each handle.stop() is now bounded by
stopHandleTimeoutMs (default 5000); on timeout it is logged and abandoned so
every other entry still reaches `stopped`.

ready() surfaces the real reason on hang: ready({ timeoutMs }) wrapped the whole
`allSettled` in one timeout, so when one channel settled to `error` while a
sibling hung, it rejected with only a generic timeout and DISCARDED the erroring
channel's reason. The deadline is now applied PER CHANNEL, so the AggregateError
carries both each failed channel's real reason AND a named timeout for each
still-hanging channel.

Log forwarding: the manager's `log` reached activation-level events only; the
default engine (defaultActivateChannel → startChannelsOverRealtimeGateway) never
passed it down, so transport-level drop diagnostics (e.g. a version-skew
missing-leaseToken outage) were silent in the managed path. `log` is now
forwarded down to the launcher/transport.

Note: the reconnect "gave-up → error" escalation from the same CR is already
implemented end-to-end in OSS-473 (realtime-gateway.ts reconnectGiveUpMs +
ChannelManager.registerConnectionObserver) and is not re-done here.

Tests: stop() resolves + logs a timeout when handle.stop() never settles;
ready() aggregate contains both a real activation error and the hung sibling's
named timeout; defaultActivateChannel forwards its log sink to the launcher opts.
2026-07-15 07:44:35 -05:00
Benjamin Taylor 2b4b19cf3b fix(channels): fail loud on egress failure + bound realtime delivery dispatch (OSS-482)
Two transport reliability hardenings on the managed realtime path, from the
OSS-473 CR.

emit() fail-loud: IntelligenceAdapter.emit() returned a synthetic MessageRef on
`{ ok: false }`, acking a failed post/update/delete as success (silent egress
drop on the HTTP-fallback path). It now throws with the failure code so the
failure propagates up the render/run path and the delivery is nacked/retried.

Realtime delivery dispatch: `session.on(delivery.available)` fired
`void this.handleDeliveryAvailable(...)` with no error boundary and no per-turn
deadline — an onDelivery rejection became an unhandled rejection (silent drop)
and a hung handler pinned the delivery forever. Replaced the `void` with a
`.catch` and wrapped the turn in a bounded per-turn timeout that nacks + logs on
failure/timeout (parity with the HTTP runLoop's turnTimeoutMs).

Tests: egress `{ ok: false }` makes thread.post throw (no silent ack); an
onDelivery throw and an onDelivery that exceeds deliveryTimeoutMs both nack
(channel.delivery.fail.v1) and log.
2026-07-15 07:41:17 -05:00
renovate[bot] 9ab25acda1 chore(deps): update zizmorcore/zizmor-action action to v0.6.0 2026-07-15 12:40:42 +00:00
Benjamin Taylor 483880583f refactor(channels): scrub residual internal "bot" naming → "channel" (closes OSS-485)
Follow-up to the OSS-473 clean-break rename (#5963). Scrubs the last
internal "bot" vestiges deliberately left out of 473's atomic
telemetry-surface commit. Naming-only, no behavior change.

- `bot` local variable → `channel` in create-channel.ts factory internals
  and the ~15 test files that exercise it.
- `BotNode` type → `ChannelNode` in @copilotkit/channels-ui and every
  importer (channels, slack/teams/discord/telegram/whatsapp/intelligence
  adapters, and the slack/teams examples).
- `botName` adapter-SPI option → `channelName`: renamed on
  AdapterStartContext, its create-channel caller, and the one adapter that
  reads it (IntelligenceAdapter.start ctx) in the same change so the SPI
  cannot drift. The Phoenix wire contract was already `channelName` (473).
- Stale `bot-ui`/`bot-slack` package refs and "bot core"/"the bot" prose in
  channels/channels-ui comments → `channels-ui`/`channels-slack`/"channel".
- `Symbol.for("copilotkit.bot-ui.*")` → `"copilotkit.channels-ui.*"`.

Deliberately kept: the platform `isBot?` author flag (an unrelated
"is this message author a bot account" semantic), and example `bot`
instance variables / "demo bot" prose (user-facing, out of this ticket's
internal-symbol scope).

Validation: DoD grep returns zero internal bot symbols in
packages/channels + packages/channels-ui; check-types + test green across
all 8 channels packages (1093 tests) and both examples.
2026-07-15 07:39:50 -05:00
Ben Taylor 0b00f6ea37 fix(channels-intelligence): Teams thread history (getHistory + getMessages) (#5969)
## Problem
On the managed Teams path the agent always reported "no earlier
messages" / "I don't see the image in this thread."
`HttpDeliverySource.getHistory` was **Slack-only**: it keyed off
`threadTs` and returned `[]` for any route without one. A Teams route is
`{ adapter: 'teams', tenantId, conversationId }` (no `threadTs`), so it
short-circuited *before making any request* — starving **both** history
mechanisms on Teams:
- `agent.messages` seeding (`conversationStore.getOrCreate`), and
- the `read_thread` tool (via `thread.getMessages()`).

## Fix
- **`getHistory` is now adapter-aware** (mirrors
`conversationKeyFromReplyTarget`'s per-adapter switch): Slack keys off
`teamId`/`channel`/`threadTs`; Teams sends `adapter=teams` + `tenantId`
+ `conversationId`, matching app-api's
`teams:{tenantId}:{conversationId}` thread_key. app-api's
`/api/channels/history` route already accepts this shape. **Slack query
and order are unchanged.**
- **Add `getMessages` to the adapter** so `thread.getMessages()` (the
`read_thread` tool) reads reconstructed history via the transport and
maps it to `ThreadMessage[]`. Without it `Thread.getMessages()` returns
`[]` and thread-reading tools (summaries, "what was in the image") see
nothing even when history exists.

## Tests
- New: Teams-shaped `getHistory` query, and the
missing-`tenantId`/`conversationId` short-circuit (no request).
- All 115 `channels-intelligence` tests pass; `check-types` clean.

## Notes
Verified end-to-end against a live managed Teams bot as a dist patch
before porting to source (history seeding + `read_thread` both start
returning the thread's messages). The app-api counterpart (Teams
ingress: reactions, inline-media/Graph file ingest, slash commands) is a
separate PR in the Intelligence repo.
2026-07-15 07:39:17 -05:00
Ben Taylor a4b80247f2 feat(runtime): handler owns managed channels activation & lifecycle (OSS-473) (#5963)
## Summary

Makes the normal `createCopilotRuntimeHandler` own Managed Channels
activation, readiness, reconnect, and shutdown. A developer declares a
Channel next to their agents in an existing `CopilotRuntime`, adds
`intelligence`, and mounts the normal handler — **no separate launcher,
no gateway URLs, no org/project/channel/runtime-instance IDs**.

```ts
const support = createChannel({ name: "support", agent: () => supportAgent });
const runtime = new CopilotRuntime({ agents, intelligence, identifyUser, channels: [support] });
export const handler = createCopilotRuntimeHandler({ runtime, basePath: "/api/copilotkit" });

await handler.channels.ready({ timeoutMs: 10_000 });
handler.channels.status();
await handler.channels.stop();
```

Implements the CopilotKit-SDK half of the Managed Channels SoT
**workstream A**. Closes OSS-473.

## What changed

- **`channels:` runtime option** on the Intelligence runtime (public
alias replacing the internal `bots` field); the SSE runtime rejects it.
- **`ChannelManager`** (`packages/runtime/.../core/channel-manager.ts`)
owns the managed lifecycle: lazy, idempotent `activate()`; `ready({
timeoutMs })`; `status()` (`connecting | online | setup_required |
reconnecting | stopped | unmanaged | error`); idempotent, resilient
`stop()`. Activation config (`wsUrl`, `apiKey`, `projectId`,
`channelName`, `provider`, `runtimeInstanceId`) is **derived from the
`intelligence` config + the declared channel** — `projectId` is parsed
from the `cpk-{projectId}_` API-key prefix, and the managed `provider`
is declared **per-Channel** via `createChannel({ provider })` (type
`ManagedChannelProvider`, defaults to `slack`; `teams` is
gated/coordinated — the gateway accepts only `slack` at join today, so
`teams` is SDK-ready but not GA until Intelligence OSS-450/#511 lands) —
so no infrastructure IDs are supplied by the developer.
- **`createCopilotRuntimeHandler` returns a callable object** — for an
Intelligence runtime with declared channels the `channels` control is
**non-optional** (`((req) => Promise<Response>) & { channels:
ChannelsControl }`; optional for SSE / channel-less runtimes).
Activation is **lazy and serverless-safe**: handler creation opens
**no** connection; the persistent gateway socket opens on the first
`await handler.channels.ready()`, so a Fetch host that cannot own a
listener (Cloudflare Workers, Next.js App Router) never opens one. Call
`ready()` once at startup on a long-running host. Idempotent per-runtime
via a `WeakMap`; additive and non-breaking; propagated through the
node/express/hono endpoint wrappers.
- **Clean-break rename** `createBot → createChannel` / `Bot → Channel`
(and `CreateChannelOptions`, `ChannelHandler/Component/Tool/Command`,
`defineChannelTool/Command`) across `@copilotkit/channels`,
`-intelligence`, the adapter packages, `@copilotkitnext/teams`, and the
examples — no public `Bot` aliases, per the SoT.
- **`examples/slack`** managed path rewritten to the no-launcher DX
(`createChannel` + `channels:` + `createCopilotNodeListener` +
`handler.channels.stop()` on SIGTERM/SIGINT); the six `INTELLIGENCE_*`
launcher env vars removed.
- The `channels-intelligence` realtime launcher is kept working and is
now driven internally by the handler.

## Reconnection & connection health (note for reviewers)

The actual reconnect/rejoin is **delegated to the Phoenix connection
layer**, not a runtime-managed backoff loop. Verified against the live
gateway contract (Intelligence #511): Phoenix's `Socket` auto-reconnects
and auto-rejoins, which re-runs the gateway `join/3` →
`record_heartbeat` (re-registers the listener), and `terminate/2`
releases the dead socket's lease. A manager-level re-activation loop
would be both **redundant** and **incorrect** (the `Channel` is
single-start — re-running `addAdapter`/`start` throws).

What the handler owns is **observation of that connection**. The session
exposes `onStateChange` and the manager reflects it in `status()`: a
dropped socket **or a Phoenix channel-level close/error** moves the
channel to `reconnecting`; a successful (re)join moves it back to
`online` — so `online` means the managed path can currently send. A
**bounded give-up window** (`reconnectGiveUpMs`, default 60s) surfaces a
prolonged outage as `error`, but it is **recoverable**: if Phoenix
rejoins afterwards the channel returns to `online` — the state always
tracks the live transport, never latching. Join rejections are
**classified per-Channel** from the gateway's declaration states:
genuinely unconfigured/waiting states (`adapter_setup_required`,
`*_waiting_for_runtime`, `no_channels_yet`, …) surface as
`setup_required` (and `ready()` resolves); a `runtime_conflict` or a
`*_failed`/hard state surfaces as an **error** and is never silently
downgraded to setup-required. `ready()` keeps one-shot semantics — it
settles on the initial activation outcome; later health transitions move
only `status()`.

## Testing

Unit + integration: handler-owned activation with config derived purely
from `intelligence` (asserts **no** org/channelId on the wire); **first
request does not trigger activation**; `ready`/`status`/`stop`; a
socket-drop path; duplicate-name fail-loud; setup-required surfacing;
the default engine's opts mapping + module-not-found path. `build` +
`check-types` + `test` green across `@copilotkit/channels`,
`-intelligence`, and `runtime` (1726 tests).

## Residual risk / verification

The dynamic `import()` of `@copilotkit/channels-intelligence`, the real
Phoenix socket join, and the example's node listener are proven against
fakes but not a live gateway. A **packaged-SDK → Realtime Gateway →
provider e2e smoke** is recommended before production reliance (SoT
workstream D; pairs with landing Intelligence #511).

## Follow-ups (out of this PR's activation-lifecycle subject)

Filed separately: managed transport parity/reliability — cross-turn
history (OSS-436), command/interaction/reaction ingress over the
realtime path (OSS-416/419/434), `thread.delete()` egress, empty-turn
redelivery, egress-failure fail-loud, durable StateStore on the managed
path, and the reconnect-observability / `stop()`-timeout hardenings.
Docs update tracked in #5925. Id-less gateway topic in OSS-480.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

## Adapter transport model (clarified per review)

This PR is **exclusive per Channel**, not per platform, and there is no
simultaneous coexistence. **Any** developer-supplied **direct** adapter
on a Channel declared in `channels:` makes the **whole** Channel
`unmanaged` and skips it for managed activation — regardless of platform
— rather than dual-activating, because attaching the managed gateway
transport beside a direct adapter would open two live connections to the
provider and deliver every event twice (`assertExclusive` enforces the
adapter exclusivity). The skipped Channel is not silently "healthy": it
is reported with an explicit **`unmanaged`** status (a runtime whose
only Channel is direct reads `overall: "unmanaged"`, never `online`),
and the handler neither starts nor stops it — the developer owns its
`channel.start()`. Having the handler own the lifecycle of a direct
Channel too (either-or per Channel) is **OSS-486**; true managed+direct
coexistence on one Channel is **OSS-484**.
2026-07-15 07:38:00 -05:00
Benjamin Taylor 9886d0b3f9 refactor(channels): rename ChannelProvider to ManagedChannelProvider, gate teams in docs (OSS-473) 2026-07-15 07:15:07 -05:00
Benjamin Taylor 2a72ca15e3 docs(runtime): describe channel skip as exclusive per Channel, not per platform (OSS-473) 2026-07-15 07:15:06 -05:00
Benjamin Taylor 0e5dc5f97c fix(channels-intelligence): make gave_up sticky until a successful rejoin (OSS-473) 2026-07-15 07:15:06 -05:00
Benjamin Taylor 4cc85a945b docs(channels): use channel vocabulary in the quickstart, not bot (OSS-473) 2026-07-15 07:15:06 -05:00
Benjamin Taylor fbf35ac593 fix(runtime): defer channel activation to ready() so the Fetch handler is serverless-safe (OSS-473) 2026-07-15 07:15:06 -05:00
Benjamin Taylor 5042266824 fix(runtime): declare managed provider per-Channel instead of a hard-coded slack default (OSS-473) 2026-07-15 07:15:06 -05:00
Benjamin Taylor 6fb748c368 fix(runtime): report skipped direct-adapter channels as unmanaged, not healthy (OSS-473) 2026-07-15 07:15:05 -05:00
Benjamin Taylor decd4c20d5 fix(channels-intelligence): truthful connection-health for managed channels (OSS-473)
Classify per-channel state on channel_declaration_unavailable rejects so a
runtime_conflict is a hard error rather than being downgraded to setup_required;
make gave_up recoverable (a later rejoin restores online); and route Phoenix
channel-level close/error through the same health transition as socket drops.
2026-07-15 07:15:05 -05:00
Benjamin Taylor 496bb57e15 refactor(channels): rename telemetry surface oss.bot.* → oss.channel.* (OSS-473) 2026-07-15 07:15:05 -05:00
Benjamin Taylor 68349bc1f8 fix(runtime): non-optional handler.channels for intelligence runtimes with declared channels (OSS-473)
The PR's documented snippet — `await handler.channels.ready(...)` with no
`!`/`?.` — did not type-check under strict TS because
`createCopilotRuntimeHandler` always returned `channels?: ChannelsControl`.

Encode channel-presence at the type level:

- runtime.ts: `CopilotRuntime` is now a `const` typed as `CopilotRuntimeConstructor`
  (backed by an internal `CopilotRuntimeShim` class; behavior unchanged). A
  class constructor cannot vary its return type across overloads, so the two
  construct-signature overloads live on the constructor interface: `intelligence`
  + a non-empty `channels` tuple returns a `RuntimeWithDeclaredChannels`-branded
  runtime; every other config (SSE, intelligence-without-channels, empty
  `channels: []`, or a non-literal `Channel[]` variable) stays unbranded. The
  brand is a phantom (compile-time-only) property. `export interface CopilotRuntime`
  preserves the name as a type for existing `runtime: CopilotRuntime` / `as
  CopilotRuntime` sites.
- fetch-handler.ts: overload `createCopilotRuntimeHandler` — a branded runtime
  (unless `activateChannels: false`, constrained to `true | undefined`) returns
  the new `CopilotRuntimeFetchHandlerWithChannels` (non-optional `channels`);
  everything else keeps the optional shape. Opting out of activation honestly
  falls through to the optional overload.
- Added a compile-time type test (checked by `tsc --noEmit`, the `check-types`
  gate). It probes the optionality modifier structurally (`{} extends Pick<T,K>`)
  rather than for `undefined`, since this package compiles `strict: false`.
  Confirmed it fails pre-change on the required-channels assertion and passes
  after. Dropped the now-unnecessary `!` in handler-channels.test.ts.

Call sites: the second overload is byte-identical to the former single signature,
so every `createCopilotRuntimeHandler` caller (node/express/hono endpoints,
integration servers, examples) and every `new CopilotRuntime` site resolves
unchanged; only inline non-empty-`channels` construction gains the (strict
supertype-assignable) branded type. Verified via a clean full-package check-types.
2026-07-15 07:15:05 -05:00
Benjamin Taylor 08d14dcc8c fix(runtime): reachable setup_required + connection-health status from the real gateway (OSS-473)
P1#2 — reachable setup_required on the PRODUCTION engine path.
connectRealtimeGateway no longer flattens every join rejection into a
generic Error. A join `.receive("error", reason)` whose reason is a known
setup-required code (`channel_declaration_unavailable`, and defensively
`adapter_setup_required` / `not_configured`) now rejects with a
distinguishable `RealtimeGatewaySetupRequiredError` (`code === "SETUP_REQUIRED"`,
raw reason preserved). ChannelManager already detects that code, so an
unconfigured managed provider now degrades to `setup_required` (ready()
resolves) instead of `error`. All other reasons keep the generic error and
the socket-leak teardown is unchanged.

P1#3 — status() reflects real connection health instead of `online` forever.
ConnectedRealtimeGatewaySession exposes `onStateChange(cb)` over
`RealtimeGatewayConnectionState` (`online` | `reconnecting` | `gave_up`),
driven by the real Phoenix seams: an unexpected socket drop → `reconnecting`;
a successful (re)join (the join-push recHooks survive Phoenix `resend`, so
`"ok"` re-fires on every auto-rejoin) → `online`; and a BOUNDED give-up —
Phoenix retries forever, so a `reconnectGiveUpMs` window (default 60000, runs
from the first drop of an episode, cleared on rejoin) elapsing while still
reconnecting → `gave_up` (terminal). Our own disconnect() stays silent.
ChannelManager wires this in place of the log-only onClose breadcrumb:
`reconnecting`→status reconnecting, `online`→online, `gave_up`→error; a
stopped manager/entry ignores late events. computeOverall now ranks
`error > reconnecting > setup_required > connecting > online`. ready() keeps
its one-shot semantics (settles on the initial outcome); later health
transitions move only status(). Docs updated to state `online` means
currently-sendable.

Wording: the direct-adapter skip comment/log now states delivery is
exclusive-per-platform (managed OR direct per platform, not both — attaching
both would double-deliver) with true coexistence tracked in OSS-484. Skip
behavior unchanged.

Call-sites for the changed signatures:
- connectRealtimeGateway error shape: only caller is
  startChannelsOverRealtimeGateway (realtime-gateway-launcher.ts:216); it
  awaits and lets the rejection propagate, so the setup-required error flows
  through unchanged (no branch to update).
- new ConnectedRealtimeGatewaySession.onStateChange: passed through in
  startChannelsWithGatewaySession and startChannelsOverRealtimeGateway
  (realtime-gateway-launcher.ts); added to ChannelsHandle (runtime.ts) and the
  manager's local ChannelsHandle view (channel-manager.ts); exported from
  index.ts. RealtimeGatewaySession (base, no observer) consumers
  (realtime-gateway-transport.ts) unaffected.
- manager onClose→state transitions: registerOnClose renamed to
  registerConnectionObserver; sole caller is the online settle handler in
  activate().
2026-07-15 07:15:05 -05:00
Benjamin Taylor 6ab2d57ff3 fix(runtime): skip managed activation for channels carrying a direct adapter (OSS-473)
Honors the SoT "never infer managed intent from a direct adapter" rule.
Channel.adapters is a new additive read-only member; verified no consumer
constructs Channel literals (only createChannel does).
2026-07-15 07:15:05 -05:00
Benjamin Taylor 04a1b10e26 fix(runtime): safe-integer projectId, trim adapter, named error classes, RC7 guard test (OSS-473) 2026-07-15 07:15:04 -05:00