Commit Graph

13613 Commits

Author SHA1 Message Date
Tyler Slaton 8427fa187e chore: release channels-discord v0.0.3 (#5919)
## Release channels-discord v0.0.3

**Scope:** `channels-discord` | **Bump:** `patch`

---

### How this release process works

1. **This PR was created automatically** by the "release / create-pr"
workflow.
   It bumped the `channels-discord` packages to `0.0.3`
   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-discord` packages to npm at version `0.0.3`
   - Creates git tag `channels-discord/v0.0.3`
   - 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.
channels-discord/v0.0.3
2026-07-10 15:55:57 -07:00
Tyler Slaton 5e389aab83 chore: release channels v0.1.1 (#5918)
## Release channels v0.1.1

**Scope:** `channels` | **Bump:** `patch`

---

### How this release process works

1. **This PR was created automatically** by the "release / create-pr"
workflow.
   It bumped the `channels` packages to `0.1.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 `channels` packages to npm at version `0.1.1`
   - Creates git tag `channels/v0.1.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.
channels/v0.1.1
2026-07-10 15:55:56 -07:00
tylerslaton 4c04e8028e chore: release channels-whatsapp v0.0.2 2026-07-10 22:45:18 +00:00
tylerslaton 6281beaf6f chore: release channels-telegram v0.0.4 2026-07-10 22:45:08 +00:00
tylerslaton 2893feecde chore: release channels-teams v0.1.2 2026-07-10 22:44:52 +00:00
tylerslaton 4ce3124c5c chore: release channels-slack v0.1.2 2026-07-10 22:44:52 +00:00
tylerslaton f4e19e2039 chore: release channels-intelligence v0.1.1 2026-07-10 22:44:50 +00:00
tylerslaton ceae64cf1b chore: release channels-discord v0.0.3 2026-07-10 22:44:42 +00:00
tylerslaton ea50654ec0 chore: release channels v0.1.1 2026-07-10 22:44:26 +00:00
Ben Taylor 77d43dcb1a refactor(channels-intelligence): migrate to Channels API (#5916)
## Problem

`@copilotkit/channels-intelligence` still used the retired Bot
HTTP/realtime contract and exposed Phoenix implementation details. The
managed Slack entrypoint used the same legacy API.

## Why

Intelligence now exposes a clean-break Channels contract. The SDK must
use it consistently, while keeping the Phoenix-backed transport private
behind a product-neutral Realtime Gateway API. Compatibility aliases
would hide integration mismatches.

## Fix

- Migrated HTTP paths, payloads, config, and KV state to Channel
terminology.
- Added the Realtime Gateway abstraction and Channel realtime wire
contract.
- Renamed remaining APIs/types, updated the managed Slack example, and
added forbidden-term coverage.
- Preserved framework and vendor `Bot` terminology only where it remains
semantically correct.
2026-07-10 17:29:03 -05:00
Tyler Slaton 001dda539a chore(channels-intelligence): complete channel terminology sweep 2026-07-10 14:47:06 -07:00
Ben Taylor 4970f55878 Add Inspector Threads empty-state onboarding (#5909)
## Summary

Adds production onboarding for the Inspector Threads empty state:

- Moves the Threads `Talk to an Engineer` CTA into the main inspector
tab nav when Threads is active.
- Replaces the enabled-empty `No threads yet` state with example thread
rows and a deselected overview.
- Lets users select an example thread to preview the real thread-details
UI with Timeline, Raw AG-UI Events, and State data.
- Adds a dismissible/reopenable example tour that persists dismissal in
local storage.
- Hides examples once real threads are present.
- Adds the `Learn how Threads work` and `Explore self-hosted
Intelligence` CTAs.
- Adds a deferred autoplay video preview to the enabled-empty deselected
overview.

## Telemetry

New/updated Threads events in this PR:

- `oss.inspector.threads_tab_clicked` — fires when the rendered Threads
nav tab is clicked.
- `oss.inspector.threads_locked_viewed` — fires once per inspector
instance for the locked state.
- `oss.inspector.threads_empty_enabled_viewed` — fires once per
inspector instance when Threads are enabled with zero real threads.
- `oss.inspector.threads_enabled_viewed` — fires once per inspector
instance when real threads are present.
- `oss.inspector.threads_intelligence_signup_clicked` — fires from
locked-state Intelligence signup CTAs.
- `oss.inspector.threads_talk_to_engineer_clicked` /
`oss.inspector.talk_to_engineer_clicked` — fire from Threads-specific
and shared Talk to an Engineer CTAs.
- `oss.inspector.threads_example_viewed` — fires once per example thread
shown in the empty state.
- `oss.inspector.threads_example_selected` — fires once per example
thread selection.
- `oss.inspector.threads_example_tour_started` — fires when the tour
auto-starts for the first selected example.
- `oss.inspector.threads_example_tour_step_viewed` — fires once per
example thread/tour step.
- `oss.inspector.threads_example_tour_dismissed` — fires when the user
skips the tour.
- `oss.inspector.threads_example_tour_completed` — fires when the user
finishes the tour.
- `oss.inspector.threads_example_tour_reopened` — fires when the user
clicks `Show tour` after dismissal.

Telemetry properties are limited to product metadata and funnel context:
package/version, inspector distinct IDs,
intelligence/thread-service/license/runtime status, runtime URL type,
CTA surface/type, telemetry-disabled status, thread count, example
thread ID, tour step/tab, and dismiss method. We do **not** send message
content, AG-UI event payloads, agent state, prompts, completions, or
thread bodies.

No telemetry was added for passive video loading; it is a visual
affordance rather than a user intent signal.

## Outbound Attribution

Threads onboarding CTAs now include existing `ref` attribution plus
these UTM parameters:

- `utm_source=copilotkit_inspector`
- `utm_medium=in_product`
- `utm_campaign=threads_onboarding`

Affected links are limited to Threads onboarding surfaces:

- Threads tab-nav `Talk to an Engineer`
- Threads locked-state `Sign up for Intelligence`
(`https://dashboard.operations.copilotkit.ai/sign-in`)
- Empty Threads overview `Learn how Threads work`
- Empty Threads overview `Explore self-hosted Intelligence`

The UTM params are opt-in for these Threads onboarding CTAs and do not
apply to generic announcement/banner links or locked Memories CTAs. The
inspector spec includes a regression test to keep locked Memories CTAs
free of the Threads campaign params.

## Video Asset + Performance

- The overview video uses the CDN-hosted asset at
`https://cdn.copilotkit.ai/corp-site/videos/copilotkit-generative-ui-agentic-frontend-demo.webm`
instead of committing a binary to `@copilotkit/web-inspector`.
- Verified the URL serves `200`, `Content-Type: video/webm`,
`Content-Length: 6765736`, and a CloudFront cache hit.
- `@copilotkit/web-inspector` currently only inlines CSS and SVG assets
in its package build, while larger docs/showcase media commonly lives on
hosted/CDN-style URLs.
- The video `src` is not rendered on the initial overview paint. It is
deferred until `requestIdleCallback` or a short timeout fallback, uses
`preload="metadata"`, fades in after `loadeddata`, and does not load for
`prefers-reduced-motion: reduce`.

## Validation

- `NX_TUI=false npx -y pnpm@10.33.4 nx run
@copilotkit/web-inspector:test -- web-inspector.spec.ts`
- `NX_TUI=false npx -y pnpm@10.33.4 nx run
@copilotkit/web-inspector:check-types`

<img width="1662" height="1382" alt="CleanShot 2026-07-10 at 12 01
03@2x"
src="https://github.com/user-attachments/assets/e2031570-f602-40dc-a54c-e9c7690fc0ba"
/>
<img width="1680" height="1388" alt="CleanShot 2026-07-10 at 12 01
12@2x"
src="https://github.com/user-attachments/assets/2c8db42f-3ab0-47e2-a235-ea54e8dd292b"
/>
2026-07-10 16:46:32 -05:00
Tyler Slaton 68e43fefe1 refactor(channels-intelligence): rename remaining channel APIs 2026-07-10 14:39:56 -07:00
Sam Julien b9091c40a4 fix(web-inspector): address threads onboarding review 2026-07-10 14:11:55 -07:00
Tyler Slaton ea9910ae93 refactor(channels-intelligence): introduce realtime gateway abstraction 2026-07-10 14:10:30 -07:00
Tyler Slaton 4dddabd79f test(channels-intelligence): align claim test with provider-agnostic flow 2026-07-10 13:44:11 -07:00
Tyler Slaton 21d3b8c7df Merge remote-tracking branch 'origin/main' into update-intelligence-channels 2026-07-10 13:43:04 -07:00
Tyler Slaton 67fce71690 refactor(channels-intelligence): migrate HTTP contract to channels 2026-07-10 13:25:17 -07:00
Sam Julien cc2fa9412e docs(shell-docs): use canonical import guide links 2026-07-10 13:09:41 -07:00
Tyler Slaton 5c217538ab fix(channels-intelligence): claim deliveries provider-agnostically (#5914)
## Problem

A managed bot with **both** a Slack and a Teams adapter attached only
ever received its **Slack** deliveries. Teams deliveries stayed `queued`
forever — never claimed, never sent.

## Root cause

`channels-intelligence`'s runtime claim loop (`http-transports.ts` →
`claimOnce()`) posted a per-provider filter to
`/api/bots/listener/claim`:

```ts
{
  runtimeInstanceId: this.cfg.runtimeInstanceId,
  adapters: [this.cfg.adapter], // defaults to "slack"
}
```

app-api filters claimable deliveries by that list (`$adapters IS NULL OR
bie.provider = ANY($adapters)`), so a runtime declaring only `"slack"`
is never handed the same bot's Teams deliveries.

But the managed runtime is **provider-agnostic**: it emits abstract
render frames and Intelligence renders each reply per the delivery's own
reply target. There is no reason for the runtime to constrain claims by
provider — one `intelligenceAdapter()` should serve every channel its
bot has attached.

## Fix

Drop the `adapters` field from the claim body. `adapters` is already
optional on the app-api side (absent → `NULL` → no provider filter → all
providers), so this needs no coordinated backend change.
`this.cfg.adapter` is still used for the heartbeat's declared bots and
for egress, both unaffected.

## Testing

Verified end-to-end locally against a managed Teams bot: inbound Bot
Framework JWT → claim → agent run → render → Bot Connector egress all
`succeed` with this change. Slack continues to work unchanged.
2026-07-10 12:52:32 -07:00
Benjamin Taylor 150164a4bd fix(channels-intelligence): derive conversationKey per provider (Teams-safe)
Follow-up to the provider-agnostic claim change on this branch. Now that the
runtime claims deliveries for every provider its bot has attached, Teams
deliveries flow through the same bridge — and their reply target is a distinct
shape (serviceUrl/conversationId/tenantId, no teamId/channel/threadTs). Deriving
conversationKey from Slack-only fields collapsed every Teams conversation onto
one degenerate key, and conversationKey keys the agent/session
(getOrCreate -> makeAgent), so distinct Teams conversations would share
state/memory.

Make replyTarget a discriminated union (slack|teams) and derive conversationKey
per provider: teams:{tenantId}:{conversationId}, matching Intelligence app-api's
thread_key (OSS-441 slice 2, Intelligence #511) so client and server agree on
conversation identity. Unknown adapters fail loud (the claim loop's existing
catch nacks, not wedges).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 14:34:09 -05:00
github-actions[bot] 556ba9d8c0 style: auto-fix formatting 2026-07-10 19:14:02 +00:00
Sam Julien b11fd7d8d0 fix(web-inspector): remove threads cta utms 2026-07-10 12:13:07 -07:00
Sam Julien e245a990aa fix(web-inspector): use operations sign-in for threads signup 2026-07-10 11:48:38 -07:00
Sam Julien fe144289e1 docs(shell-docs): group threads docs navigation 2026-07-10 11:46:35 -07:00
Sam Julien 3645c05435 docs(shell-docs): document threads drawer 2026-07-10 11:46:35 -07:00
Sam Julien 1be36d5898 docs(shell-docs): add thread import guides 2026-07-10 11:46:34 -07:00
Sam Julien 852d41e176 fix(web-inspector): scope threads utm links 2026-07-10 11:38:55 -07:00
github-actions[bot] feffbacb6d style: auto-fix formatting 2026-07-10 18:33:39 +00:00
Sam Julien 81c1740726 fix(web-inspector): add inspector utm attribution 2026-07-10 11:32:43 -07:00
Tyler Slaton 4de005701d feat(channels-intelligence): managed-over-Phoenix launcher + slack managed entrypoint (OSS-406 Phase 1) (#5907)
## Summary

**OSS-406 Phase 1** — the missing composition that runs a managed bot
over the **Phoenix realtime path**, plus a real consumer of it.

The realtime foundation is live on main (gateway
`hosted_bots:project:<id>` channel, Redis fan-out, app-api durable
authority + lease fencing), and the SDK primitives — `startManagedBots`,
`connectPhoenixHostedBotChannel`, `PhoenixRealtimeTransport` — exist and
are unit-tested. But **nothing composed them**, so the managed adapter
fell back to its HTTP default and Phoenix was never exercised
end-to-end. This adds the launcher **and wires an example to it** so it
isn't an unused export.

## Launcher (`channels-intelligence/phoenix-launcher.ts`)

- **`startManagedBotsOnChannel(bots, { channel, scope,
runtimeInstanceId, log? })`** — wraps an already-connected channel in a
`PhoenixRealtimeTransport` (delivery source + render sink) and starts
the bots via `startManagedBots`. Split out so the *behavior* is
unit-testable against a fake channel.
- **`startManagedBotsOverPhoenix(bots, config)`** — thin glue:
`connectPhoenixHostedBotChannel` → delegate → `disconnect()` on
`stop()`.
- **`phoenixEgress`** — fail-loud `EgressSink`. `intelligenceAdapter` is
exclusive and, with a render sink wired, routes every
`post`/`update`/run-render through it — the generic `EgressSink` must
never be hit.

## Consumer (`examples/slack/app/managed.ts`)

A **real caller** of the launcher: the *same* Slack bot as
`examples/slack/app/index.ts` — identical agent, tools, context,
commands, and turn handlers — run in **managed mode over Phoenix**
instead of the native `slack()` adapter. `index.ts` (native/self-hosted)
is left untouched; `managed.ts` holds no Slack creds and no public
endpoint, just a runtime key + a Phoenix connection. Demonstrates the
"same bot, swap the transport" thesis concretely:

```
native:   createBot({ adapters: [ slack({ botToken, appToken }) ] })   // index.ts
managed:  startManagedBotsOverPhoenix([ createBot({ … }) ], { … })     // managed.ts
```

The managed `onMention` handler passes the current message as `prompt`
to `runAgent` — see the validation note below for why this is required
on the managed path (and not on native).

## Tests

Drive a **real `createBot`** through the **full managed path** over a
fake channel:
- a Phoenix-delivered turn → handler runs → `render_event` frame →
`complete_requested` (completion **intent**, never a self-ack);
- a throwing handler → `fail` intent (no completion, no ack).

Build + 97 package tests green; `examples/slack` `managed.ts`
type-clean.

## Validation — live E2E over the real Phoenix path

This didn't just pass unit tests against a fake channel; the whole loop
was driven end-to-end on a real local stack with a **real OpenAI
backend**, and I verified the message actually traveled the websocket
path (not the HTTP fallback).

**Stack:** Intelligence `main` via docker-compose (postgres,
postgres-ops, redis, keycloak, minio, tei, realtime-gateway on `:4401`)
+ app-api on `:7050` (graphile migrations applied). Managed-bots
entitlement was granted via the managed-service path (a stub ops
entitlement endpoint + `FF_MANAGED_BOTS=true` on both app-api and the
gateway; gateway join otherwise rejects with
`disabled_by_feature_flag`).

**Provisioning:** created a managed Slack bot + attached a Slack adapter
(fixture workspace/creds) against app-api, then triggered a real
`app-mention` event through a fake-Slack provider into app-api's signed
ingress.

**What was proven:**
- app-api ingress → Redis publish → gateway leases the delivery and
pushes `delivery.available` over Phoenix → `managed.ts` (via the
launcher) receives it, runs the **real OpenAI** agent, and streams
`render_event` frames back → durable render acceptances written
(`run_started`, `text_delta`, `text_end`, `finalize`) →
`complete_requested` intent → app-api commits the ack. Delivery ended
`succeeded`.
- **It genuinely used the websocket path.** With gateway debug logging
bumped, frames showed `HANDLED hosted_bot.render_event.v1 INCOMING ON
hosted_bots:project:<id> (SdkChannel)`. All app-api delivery-side calls
originated from the gateway's HTTP client (`hackney`), with **zero
launcher-originated HTTP delivery calls**, and the launcher contains no
HTTP delivery code at all. The render frames went bot → gateway over the
Phoenix socket.

**Bug found and fixed during validation (the reason `managed.ts` passes
`prompt`):**
The first real run failed with an OpenAI `400 input.messages=0` — the
agent received **zero messages**. Root cause: `runAgent`
(packages/channels `thread.ts`) only injects a user message when
`extra.prompt` is set; otherwise it relies on the adapter's
reconstructed history. The **native** path gets away with omitting
`prompt` because the native adapter's `getHistory` rebuilds the live
thread *including* the triggering message. The **managed** path does
not: app-api's `GET /api/bots/history`
(`reconstructManagedThreadHistory`) rebuilds only *prior committed*
deliveries and structurally excludes the in-flight turn (the current
delivery is handed to the SDK as the delivery envelope, not via
history). So a managed handler that omits `prompt` drops the newest user
message — turn 1 → 0 messages → 400; turn 2+ → the agent answers a
*stale* prompt. FakeAgent unit tests never caught it because they don't
exercise the history round-trip.

Fixed here by having the managed `onMention` pass `message.contentParts
?? message.text` as `prompt`. This is a **systemic** footgun for every
managed entrypoint; it's tracked with a durable-fix recommendation
(adapter auto-injects the current message so managed handlers match
native) in **OSS-459**.

## Scope

- **This PR:** the launcher composition + its first consumer +
unit/contract coverage, **plus the live-stack E2E above** which closes
OSS-406's "realtime path is unproven" gap and surfaced/fixed the managed
`prompt` bug.
- **Deferred (OSS-459):** Teams managed entrypoint, multi-tenant
multiplexing, BYO, promote to a deployable Intelligence managed-bot
runtime app, HTTP-path deprecation, shared bot-def extraction, and the
durable managed-`prompt` fix.

Refs OSS-406.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-10 11:23:54 -07:00
Tyler Slaton 779bb3df8d fix(examples/slack): exit nonzero when managed shutdown fails 2026-07-10 11:08:29 -07:00
Benjamin Taylor 57ddcb9532 fix(channels-intelligence): enforce runtimeInstanceId on OnChannel + leak-path tests + fail-loud managed entrypoint (OSS-406 review r3) 2026-07-10 12:31:24 -05:00
Sam Julien bfa9df05bb fix(web-inspector): use CDN threads overview video 2026-07-10 10:23:03 -07:00
Alem Tuzlak 2a16becf61 fix(channels-intelligence): claim deliveries provider-agnostically
The managed runtime's claimOnce() declared `adapters: [this.cfg.adapter]`
(defaulting to "slack"), which Intelligence used to filter claimable
deliveries by provider. A runtime serving a bot with both Slack and Teams
adapters would therefore never receive the bot's Teams deliveries — they
stayed queued forever while Slack worked.

The managed runtime is provider-agnostic: it emits abstract render frames
and Intelligence renders per the delivery's own reply target. So the claim
must not filter by provider. Drop the adapter filter from the claim body;
one config-free `intelligenceAdapter()` now serves every channel its bot
has attached.

Verified end-to-end locally against managed Teams: inbound JWT -> claim ->
agent -> render -> Bot Connector egress all succeed with this change.
2026-07-10 19:06:44 +02:00
Sam Julien b2dda71bb0 fix(web-inspector): remove threads nav sheen 2026-07-10 10:03:52 -07:00
github-actions[bot] 43fa5be1a9 style: auto-fix formatting 2026-07-10 16:53:55 +00:00
Sam Julien 3c1b5187d9 feat(web-inspector): add threads onboarding motion cues 2026-07-10 09:52:45 -07:00
github-actions[bot] 2851d83a8e style: auto-fix formatting 2026-07-10 16:49:23 +00:00
Benjamin Taylor 852bd04a13 fix(channels-intelligence): single-bot guard + startup/join socket cleanup + authoritative runtimeInstanceId (OSS-406 review r2) 2026-07-10 11:48:23 -05:00
github-actions[bot] 1441768b73 style: auto-fix formatting 2026-07-10 16:42:07 +00:00
Benjamin Taylor df7d3e348f fix(channels-intelligence): fail-fast bot names + richer activation join + stop() cleanup (OSS-406 review) 2026-07-10 11:40:32 -05:00
Benjamin Taylor fec701f731 ci(showcase): raise shell-script-tests timeout to 10m to stop timeout-race flake 2026-07-10 11:40:32 -05:00
Benjamin Taylor f35f2829b8 docs(channels-intelligence): drop deprecated 'coworker' term from launcher comment 2026-07-10 11:40:31 -05:00
Benjamin Taylor 7207332d10 fix(examples/slack): managed mode must pass the current message as prompt
Managed getHistory (app-api /api/bots/history) doesn't include the in-flight
turn — unlike native adapters whose getHistory rebuilds the live thread — so
runAgent({context}) alone runs the agent with zero messages (→ provider 400).
Pass the current message (contentParts ?? text) as `prompt`, the sanctioned
mechanism for input not in the adapter's reconstructed history. Verified live:
the managed Slack bot now returns a real answer over the Phoenix loop.
2026-07-10 11:40:31 -05:00
github-actions[bot] f63578df21 style: auto-fix formatting 2026-07-10 11:40:31 -05:00
Benjamin Taylor 786d08171b feat(channels-intelligence): managed-over-Phoenix launcher + slack managed entrypoint (OSS-406 Phase 1)
The realtime primitives (startManagedBots, connectPhoenixHostedBotChannel,
PhoenixRealtimeTransport) existed but nothing composed them into a launcher, so
the managed path defaulted to HTTP and Phoenix was never actually used.

- startManagedBotsOnChannel(bots, { channel, scope, runtimeInstanceId }) — wraps
  an already-connected channel in a PhoenixRealtimeTransport (delivery source +
  render sink) and starts the bots via startManagedBots. Split out so the
  behavior is unit-testable against a fake channel.
- startManagedBotsOverPhoenix(bots, config) — thin glue: connect the gateway
  bot-IO channel, delegate, disconnect on stop().
- phoenixEgress: fail-loud EgressSink (Phoenix routes all egress through the
  render sink).
- examples/slack/app/managed.ts — a REAL consumer of the launcher: the same
  Slack bot as index.ts (agent/tools/context/commands/handlers identical) run in
  managed mode over Phoenix instead of the native slack() adapter. No native
  index.ts changes.

Tests drive a real createBot through the full managed path over a fake channel:
delivered turn → render frame → completion INTENT (never self-ack); throwing
handler → fail intent. Live-stack E2E + manual validation are the OSS-406 proof;
scale-out (Teams, etc.) is OSS-459.
2026-07-10 11:40:31 -05:00
Mark 8ed045e623 ci(showcase): guard against dead-on-load demos (runtime-route wiring check) [OSS-451 prevention] (#5884)
## What

Adds a pre-merge guard that prevents the
[OSS-451](https://linear.app/copilotkit/issue/OSS-451) **dead-on-load**
failure class from recurring:

- `showcase/scripts/validate-runtime-routes.ts` — static validator +
`validate-routes` npm script
- `showcase/scripts/__tests__/validate-runtime-routes.test.ts` (+
fixture integration) — regression coverage
- `.github/workflows/showcase_validate-wiring.yml` — runs it on every
`showcase/integrations/**` PR

> **Stacked on #5881** (the fix). Base branch is
`mark/oss-451-mastra-demos-404`; retarget to `main` once #5881 merges.
The guard is green *because* #5881 is present.

## Why

OSS-451 shipped because **nothing linked a demo page's `runtimeUrl`
string to the existence of the `/api` route it names.** The only
automatic pre-merge gate for `showcase/**` is `showcase_build_check.yml`
— a Docker *build* — and a page that references a non-existent route
compiles perfectly cleanly. So three demos went dead-on-load in
production undetected (the e2e specs that *would* have caught it run
on-demand only, post-deploy, and exclude TS-only Mastra).

## How it works

For every **shipped** demo — a demo directory whose slug is in its
integration's `manifest.yaml` `features` — the validator asserts every
`runtimeUrl` it declares resolves to a real route dir under
`src/app/api/`.

- Unshipped / experimental demos (not in `features`) and
`not_supported_features` are **skipped** — they aren't claimed to work,
so they don't fail the gate. Promoting one into `features` immediately
starts enforcing it (this is the safety net for the still-unshipped
`declarative-hashbrown` / `declarative-json-render`).
- A `validate-runtime-routes.baseline.json` can grandfather pre-existing
violations (same pattern as the pin-drift `fail-baseline.json`). **The
fleet is currently clean — 0 violations, no baseline needed.**
- Handles the shared `/api/copilotkit` route and catch-all routes
(`copilotkit-auth`, `copilotkit-voice`) by asserting the top-level route
dir exists.

## Verification

- `npm run validate-routes` → clean across all 21 integrations.
- Removing the 3 OSS-451 routes locally → the guard flags **exactly
those 3** and exits 1; restoring → exits 0.
- Regression test (5 cases): flags the shipped-demo-with-missing-route
shape, passes shared + dedicated routes, skips unshipped.
- Full `showcase/scripts` vitest suite: **2151 tests green**.
- Workflow YAML validated; actions pinned to SHA; `contents: read` only;
no secrets.

## Follow-up for a maintainer
Add **"Showcase: Runtime-Route Wiring (PR)"** to the branch-protection
required checks so it blocks merge like the build check does.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-10 09:12:08 -07:00
Tyler Slaton 4c94c10574 fix(react-core): expose lean hooks from /v2/headless to cut bundle bloat (#4893)
The main @copilotkit/react-core/v2 entry re-exports from one monolithic shared
chunk, so importing any symbol links the built-in chat-message rendering stack
(streamdown -> shiki, mermaid, cytoscape, katex) into the consumer's bundle —
~3MB gzip / ~15MB raw — with no way to tree-shake it. Custom-UI consumers who
only use hooks pay the full cost (issue #4893).

The separate /v2/headless build entry ships those hooks in their own small chunk
without that stack, but it was missing two hooks a custom UI needs and leaked
tailwind-merge.

- headless: export useCopilotKit and useRenderToolCall (both DOM-free, pull no
  chat-UI rendering stack). useDefaultRenderTool / useRenderCustomMessages /
  useRenderActivityMessage stay in /v2 — web-only markup or a2ui-renderer.
- headless: fix UseAgentUpdate (a runtime enum) being re-exported via
  `export type` — under isolatedModules that stripped its runtime binding, so
  useAgent's `updates` option was unusable from /v2/headless.
- Stop headless pulling tailwind-merge: extract the tailwind-free ref helpers
  (shallowEqual / useShallowStableRef) into lib/shallow-stable-ref and point
  CopilotChatConfigurationProvider at them.
- Add a vitest export-surface test asserting the documented hooks are runtime
  functions and UseAgentUpdate is a runtime value (the `export type` bug is
  invisible to tsc).

react-core check-types, vitest (1427), and react-native check-types pass.
Shipped dist/v2/headless.mjs imports only react, @ag-ui/client, @copilotkit/core,
@copilotkit/shared, @copilotkit/react-core/v2/context, zod — no rendering stack.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 08:10:40 -07:00
Mark 6db81b8c99 Merge branch 'main' into mark/oss-451-showcase-route-wiring-guard 2026-07-09 23:05:40 -07:00