14031 Commits

Author SHA1 Message Date
Tyler Slaton 49b37fb195 chore: release monorepo v1.64.2 (#6280)
## Release monorepo v1.64.2

**Scope:** `monorepo` | **Bump:** `patch`

---

### How this release process works

1. **This PR was created automatically** by the "release / create-pr"
workflow.
   It bumped the `monorepo` packages to `1.64.2`
   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.64.2`
   - Creates git tag `monorepo/v1.64.2`
   - 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.64.2
2026-07-31 13:11:23 -07:00
tylerslaton 33b1312795 chore: release monorepo v1.64.2 2026-07-31 20:10:27 +00:00
Tyler Slaton 8ed703d4ae chore: release channels v0.5.0 (#6279)
## Release channels v0.5.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.5.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.5.0`
   - Creates git tag `channels/v0.5.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.
channels/v0.5.0
2026-07-31 12:46:54 -07:00
tylerslaton db8ebf5f09 chore: release channels v0.5.0 2026-07-31 12:46:39 -07:00
Maxim 4cc314b25a Reskinnable demo: the banking showcase as a shell hosting swappable skins (#6262)
Adds `examples/showcases/reskinnable-demo` — the banking showcase
restructured onto a reskinnable shell/skin architecture, shipping
**banking** and **airline** as two swappable skins.

`examples/showcases/banking` is untouched.

## What this is

One Next.js app whose entire experience — brand, theme, layout, pages,
frontend tools, a2ui catalog, agent — swaps at runtime from a floating
selector. A skin-agnostic **shell** hosts one **skin** per route segment
`/[skin]/...`.

```
src/shell/     skin-contract · provider · registries · chat/ · canvas/ · threads/
src/skins/banking/   REST-backed  (/api/banking/v1/*)
src/skins/airline/   in-memory client state
src/app/[skin]/      resolves the skin from the URL, mounted keyed by skin.id
```

The contract deliberately spans **both data substrates** — banking talks
to a REST backend, airline holds plain client state. A contract that
only ever hosted one kind of skin proves much less.

## How it was built

Started as a byte-identical clone of banking (159 tracked files, one
commit), then restructured in verifiable steps. Every intermediate step
kept the app **building and running**, not merely compiling. Most of the
diff is relocation; the genuinely new code is `src/shell/**`,
`src/skins/airline/**`, and banking's skin assembly.

Because `examples/showcases/banking` still exists, git cannot infer
renames — the diff shows all 203 files as additions. Reviewing it that
way is misleading. The real surface: **63 files are byte-identical to
banking, 90 changed (mostly import specifiers), 48 are genuinely new.**

## Decisions worth a reviewer's attention

**Banking keeps its REST backend, namespaced to the skin.** The
standalone prototype this architecture came from had replaced banking's
`/api/v1/*` with a client-side store. That was never a design decision —
the prototype was greenfield and simply never ported it (no commit
deletes it; the only deletion in its history is two stub skins). Since
every route under it is banking domain — cards, transactions,
exceptions, policies, reports, users, dev/reset — it moved to
`/api/banking/v1/*` rather than being discarded. Mounting one skin's
whole backend at the app root would contradict the premise that skins
are self-contained.

**`v1` is retained** even though it is vestigial (it arrived with the
pre-monorepo demo; there is no `v2` and nothing negotiates on it) so the
path shape is otherwise unchanged.

**The routing cutover is one commit on purpose.** Banking's pages *were*
the Next.js routes, so moving them, adding `/[skin]` routing, swapping
the registry off the placeholder, assembling the skin object and cutting
the shell's banking coupling had to land together — any split produces a
commit that builds and serves 404s.

**Four contract fields were added** beyond the prototype's version, each
closing a gap that would otherwise make a shipped banking feature
inexpressible:
- `toolLabels` — tool-activity chip labels are skin-domain strings
- `chatHeaderActions` — a skin contributes buttons to the shared chat
header (banking's invoice paperclip)
- `onSuggestionSelect` — a suggestion pill may stage an attachment and
drive the composer instead of sending text
- corrected OGUI semantics — surfaces render full-region on the canvas.
The prototype's "inline in the chat" note described a *published-SDK*
limitation that does not apply on `workspace:*`.

## Bugs fixed along the way

Several were pre-existing in banking and only surfaced by moving the
code:

- **`/cards?operation=change-pin` opened nothing.** Card operations are
registered on the index route, but banking's `/cards` re-exported the
*dashboard*, which ignores `?operation=`. Both `/` and `/cards` now
resolve to the skin base, so the PIN dialog actually opens.
- **The presenter-reset test asserted a pre-#6136 shape.** That route
also forgets the default persona (3 ids, not 2) and re-seeds; the test
was never updated, and its seeding dependency was unmocked so it
executed real network code.
- **Two memory smoke scripts posted to
`/api/copilotkit/agent/default/run`** — no `default` agent exists once
agents are keyed per skin. Nothing in the toolchain reads those `.mjs`
files, so no build or test could have caught it.
- **Both Docker stacks shared a compose project name**
(`banking-memory`), so containers *and named volumes* were shared:
`docker compose down` in either app destroyed the other's memory stack.
- **The skin selector covered the chat and swallowed clicks** on its
toggle. Now positioned from a `--nw-chat-width` custom property the chat
publishes, in a `pointer-events: none` strip — structurally unable to
intercept.
- **A dark-mode leak and a token-ownership leak**: `.dark` persisted
across skin switches into a light-only skin's shared chrome, and the
shell's `@theme inline` block still carried banking-violet shadow
values.
- **971 lines of dead code dropped** — `threads-drawer`, `threads-panel`
and their CSS module were superseded by `chat-inbox` in #6136 and
imported by nothing.

## Verification

- `nx run reskinnable-demo:build` green; **45 unit tests pass, 0 fail**;
`oxlint` 0 errors; `eslint` clean
- **6/6 Playwright smoke**, including 5 new tests covering reskinning
itself — the index redirect, both skins rendering their own chrome,
unknown-skin 404, and switching in both directions. Each was verified by
*deliberately breaking the app and watching the test fail*, because the
first draft of these assertions passed against a broken airline (the
shell selector renders every skin's brand on every page, so a brand-text
check proved nothing).
- Route behaviour confirmed over HTTP: `/` → 307 → `/banking`;
`/banking{,/cards,/charges,/dashboard,/team}` and `/airline` → 200;
unknown skin → 404
- The agent prompt was verified byte-identical to banking's (19,382
chars, compared programmatically) — it encodes demo behaviour including
the never-markdown-tables rule
- All 8 of #6136's demo beats traced end-to-end, plus the three
regressions it fixed (globally-registered action tools, non-HITL closing
step, the two non-interchangeable `actions.ts` helper families)
- Reviewed by a 7-lens pass scoped to the delta-from-banking. It found 4
significant issues, all fixed here: the runtime route applied banking's
identity scheme to every skin; per-user memory scoping depended on an
accidental effect ordering; dark mode leaked into a light-only skin's
shared chrome; and the skin selector overlapped skin navigation below
~1190px. One reported finding was investigated and **rejected** as
factually wrong rather than applied.

### How to read the diff

Because `examples/showcases/banking` still exists, git cannot infer
renames and shows all 203 files as additions. Reviewing it that way is
misleading:

- **63 files are byte-identical to banking** — no review needed
- **90 changed**, most only in their import specifiers
- **48 are genuinely new** — `src/shell/**`, `src/skins/airline/**`,
banking's skin assembly, `/[skin]` routing

The 8 commits are grouped by area of concern and read in order:
workspace → shell → banking-as-a-skin → airline → routing → tests → docs
→ tooling.

## Isolation from the banking demo

This app vendors the same Intelligence stack as banking with the same
seeded persona ids, so it is isolated on two independent axes:

**Ports** — banking's `7050/7053/715x` shifted by +200
(`7250/7253/725x`). Without this, `pnpm dev` here while banking's stack
was up attached silently to banking's backend: same memory buckets, and
the presenter reset button clearing the neighbour's demo state. Verified
live — the stack comes up as project `reskinnable-demo-memory` with its
own volumes and all six ports bound, banking untouched. The native Metal
TEI on `:7067` stays deliberately shared: same version and model give
byte-identical embeddings and it holds no demo state.

**Organization** — a different seeded `cpk` key, so this app resolves to
`haus-von-haskell` rather than banking's `casa-de-erlang`. Org comes
from the authenticated key and `seed.sql` already provisions three orgs
for exactly this, so it is a key swap with no backend change. This axis
matters because ports are a local convention that copying banking's
`.env` over this one silently undoes, whereas the org key still holds
when someone does.

Verified against a freshly seeded stack: a memory written under one key
is invisible to the other **even for an identical user id**, and the
app's own reset seeded 3 memories into org 2 that org 1 cannot see.
Neither demo can read or delete the other's memories.

The presenter reset and the runtime now also name the backend they
resolved (`apiUrl` in the reset response, one startup log line), so a
misconfiguration is visible rather than silent.

**One inherited doc claim corrected.** `.env.example` warned that
non-seeded ids `403` against the Intelligence stack. Measured, they do
not — `GET`/`POST /api/memories` returns 200/201 for an unseeded id and
for a nonsense one, the scope being created on demand. It mattered
because `DEMO_DEFAULT_USER_ID` is absent from `seed.sql`, so the warning
implied the unpinned interactive config the same file recommends was
broken. It is not. **banking still carries the claim** and deserves the
same correction.

## Known follow-ups (not blockers)

- `/banking/<unknown>` returns HTTP 200 while rendering the 404 UI —
`notFound()` in a client subtree, after the status is committed. Correct
UX, wrong status code; a clean fix needs a server-component validation
boundary.
- `e2e/ogui-routing.spec.ts` is `test.describe.fixme`. It clicks 7
suggestion pills, 6 of which #6136 had already removed from the
registered set — it has been broken on `main` since July, unnoticed
because **no CI workflow runs banking's e2e at all**. Rebuilding it
against the new catalog is real work, and the OGUI/a2ui routing it
guarded currently has no e2e coverage.
- Banking's own copy of the `dev/reset` test has the same stale
assertion and deserves a one-file fix.
- The two demo stacks still share host ports 7050/7053, so they cannot
run simultaneously (a loud "port already allocated", not silent
corruption). Moving them needs lockstep edits across compose, env,
playwright and `src` test defaults.
- Airline has no dark palette and renders no theme toggle.

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

https://claude.ai/code/session_01VgnriWLrxhfKnK8g1gHa5S
2026-07-31 21:32:42 +02:00
github-actions[bot] 04b445a04d style: auto-fix formatting 2026-07-31 18:58:10 +00:00
Maxim a40427a6f3 docs(reskinnable-demo): state the cardsRef sync trade-off honestly
The previous comment presented the effect-based ref sync as merely avoiding
the react-hooks/refs lint rule. It is a trade-off: the render reads
cardsRef.current, and an effect writes after commit, so a card mutation
landing while the PIN card is open can leave the picker one render behind.
Records why that is accepted and what fixing it properly would require.
2026-07-31 20:55:38 +02:00
Maxim 08e10deb59 Merge branch 'blitz/banking-genui-replay/report' into blitz/reskinnable-demo-port/integration 2026-07-31 20:51:24 +02:00
Maxim e95ff4c996 Merge branch 'blitz/banking-genui-replay/tools' into blitz/reskinnable-demo-port/integration 2026-07-31 20:51:23 +02:00
Maxim 52461637ff feat(reskinnable-demo): port PIN, charges-confirm and note polish to the banking skin
Replays three generative-UI changes from the banking showcase (#6259) into
the reskinnable demo's banking skin:

- setCardPin keys its collapsed render on the tool result and remembers
  answered calls in a module-scope map, so a resolved PIN change survives
  re-render. Drops the [cards] dependency in favour of a ref, which is what
  stopped the tool tearing down mid-flight.
- showCharges becomes human-in-the-loop: it asks before moving the user's
  whole screen, and routes through the skin-scoped base prefix.
- Notes about reported charges are prefixed with a red alert emoji.
2026-07-31 20:49:05 +02:00
Maxim 3a2ea211a0 feat(reskinnable-demo): curate the banking report charts to the final trio
Replays the report chart curation from the banking showcase (#6259, #6263)
into the reskinnable demo's banking skin. The three columns become spend
breakdown, spend over time and largest charges — the last of which answers
"which line items drive the total", a question a three-team aggregate
cannot. Additions carry a policyId so their bars colour by owning team.
2026-07-31 20:46:45 +02:00
Maxim e5b54d81b8 Merge branch 'blitz/banking-genui-replay/seed-policy' into blitz/reskinnable-demo-port/integration 2026-07-31 20:41:01 +02:00
Maxim 83d3bfa31a Merge branch 'blitz/banking-genui-replay/seed-memo' into blitz/reskinnable-demo-port/integration 2026-07-31 20:41:00 +02:00
Maxim 5af07bf489 Merge branch 'blitz/banking-genui-replay/charges-tint' into blitz/reskinnable-demo-port/integration 2026-07-31 20:41:00 +02:00
Maxim 109583caf4 Merge branch 'blitz/banking-genui-replay/prompt' into blitz/reskinnable-demo-port/integration 2026-07-31 20:40:59 +02:00
Maxim 37cf227d8b Merge branch 'blitz/banking-genui-replay/top-charges' into blitz/reskinnable-demo-port/integration 2026-07-31 20:40:59 +02:00
Maxim d9d50139e7 Merge branch 'blitz/banking-genui-replay/pin-card' into blitz/reskinnable-demo-port/integration 2026-07-31 20:40:59 +02:00
Maxim 936dbd7ffb Merge branch 'blitz/banking-genui-replay/nav-card' into blitz/reskinnable-demo-port/integration 2026-07-31 20:40:58 +02:00
Maxim a6c5c93238 feat(reskinnable-demo): tint the charges Sort and Show selects when set
Ports the brand-tint treatment for explicitly-set filter selects from the
banking showcase charges page (#6259) into the reskinnable demo's banking
skin, so an agent-applied filter reads as deliberate.
2026-07-31 20:39:59 +02:00
Maxim ec00c42c50 feat(reskinnable-demo): rebalance seeded policy limits in the banking skin
Ports the seed rebalance from the banking showcase (#6259) into the
reskinnable demo's banking skin so the report's spend-share donut shows a
meaningful split rather than three near-equal slices.
2026-07-31 20:38:50 +02:00
Maxim 9eef856d29 feat(reskinnable-demo): add TopChargesChart to the banking skin
Ports the ranked largest-charges chart from the banking showcase (#6263)
into the reskinnable demo's banking skin. The report-card wiring that
consumes it lands separately.
2026-07-31 20:37:21 +02:00
Maxim 4b4b937599 feat(reskinnable-demo): seed the alert-emoji note convention in the banking skin
Ports the red-alert-emoji instruction from the banking showcase seeded
memories (#6259) into the reskinnable demo's banking skin, so recalled
memory drives the same note formatting.
2026-07-31 20:37:05 +02:00
Maxim a83afc6db8 feat(reskinnable-demo): add prose house-style rules to the banking prompt
Ports the "format prose the same way every time" instruction block from
the banking showcase agent prompt (#6259) into the reskinnable demo's
banking skin, where the prompt lives in agent.ts rather than the route.
2026-07-31 20:36:29 +02:00
Maxim 92756f4a41 feat(reskinnable-demo): add PinChangedCard to the banking skin
Ports the resolved-state PIN card from the banking showcase (#6259) into
the reskinnable demo's banking skin. The consumer wiring lands separately.
2026-07-31 20:36:27 +02:00
Maxim c8fd81a4fe feat(reskinnable-demo): add NavigateConfirmCard to the banking skin
Ports the confirm-before-navigate chat card from the banking showcase
(#6259) into the reskinnable demo's banking skin. The showCharges
human-in-the-loop wiring that consumes it lands separately.
2026-07-31 20:36:02 +02:00
Maxim 916f4b7f24 Merge remote-tracking branch 'origin/main' into blitz/reskinnable-demo-port/integration 2026-07-31 20:31:11 +02:00
Mike Ryan 62eb3bf1f8 fix(channels): start Slack streams with first text (#6278)
## Summary

- Send the first bounded Slack text delta in stream.start for direct and
managed delivery.
- Keep later appends, continuation messages, and empty-stream cleanup
behavior.
- Add managed delivery integration tests for first-text, empty-chunk,
and empty-stream paths.

## Root cause

Slack received stream.start without text, so it rendered Thinking...
until a separate append arrived.

## Tests

- pnpm nx run-many -t test,check-types,build -p
@copilotkit/channels-slack @copilotkit/channels-intelligence
--skip-nx-cache
- pnpm nx test @copilotkit/react-core --skip-nx-cache
- pre-commit test, publint, and attw checks for 12 affected projects
2026-07-31 10:37:58 -07:00
Mike Ryan eb6c8df0ad fix(channels): start Slack streams with first text 2026-07-31 10:26:41 -07:00
Maxim acd8d0715a feat(reskinnable-demo): isolate memory by organization as well as port
Points this app at the stack's second seeded organization
(haus-von-haskell) instead of the one banking uses (casa-de-erlang). Org is
resolved from the authenticated cpk key, and seed.sql already provisions three
orgs for exactly this, so it is a key swap with no backend change.

Ports and orgs isolate on independent axes and the second one matters: ports are
a local convention that copying banking's .env over this one silently undoes,
whereas the org key still holds when someone does. Verified against a freshly
seeded stack — writing under one key is invisible to the other even for an
identical user id, so neither demo can read or delete the other's memories.

Also corrects an inherited claim. banking's .env.example warns that non-seeded
ids 403; measured, they do not — GET/POST /api/memories returns 200/201 for an
unseeded id and for a nonsense one, the scope being created on demand. That
mattered because DEMO_DEFAULT_USER_ID is absent from seed.sql, so the warning
implied the unpinned interactive config it recommends was broken. It is not.
2026-07-31 19:04:05 +02:00
Ben Taylor 101fe27d80 fix(channels): contain terminal provider failures (#6269)
## Summary

- stop the Channels agent loop when a tool handler reports an
already-terminal provider delivery
- freeze managed renderer fanout while canonical ingestion records
`RUN_ERROR`
- immediately observe Slack native-stream queue failures while
preserving them for `finish()`
- treat uncertain managed file-delivery errors as terminal delivery
outcomes

## Root cause

The Core run loop converted every tool-handler exception into a
model-visible tool result. After `ChannelProviderDeliveryError` closed
the effect path, the model could continue and emit text, causing Slack
native rendering to call `slack.stream.start` against a closed delivery.

The native stream also retained that rejection in an unobserved internal
promise until `finish()`, leaving a Node unhandled-rejection window.

## Validation

- `pnpm nx run-many -t test check-types -p
@copilotkit/channels-core,@copilotkit/channels-slack,@copilotkit/channels-intelligence
--skip-nx-cache`
- `pnpm nx run-many -t build publint attw -p
@copilotkit/channels-core,@copilotkit/channels-slack,@copilotkit/channels-intelligence
--skip-nx-cache`
- `pnpm nx run-many -t test check-types build publint attw -p
@copilotkit/channels --skip-nx-cache`
- pre-commit affected-package matrix: 17 projects / 24 tasks
2026-07-31 10:09:55 -05:00
Maxim 3453b228e2 feat(reskinnable-demo): make the resolved Intelligence backend explicit
This app and the sibling banking demo vendor the same durable-memory stack with
identical seeded ids and api key, so which backend a process attached to was
invisible — a `pnpm dev` pointed at the neighbour's live stack shared its memory
buckets silently, and the presenter reset could mutate the other demo's state.
Surface the target so a human notices:

- Runtime route logs the resolved Intelligence API URL once at startup when
  memory is enabled ("[reskinnable-demo] Intelligence: <url>  (memory enabled)").
  Never logs the api key or license token.
- The destructive presenter reset now names its target: it logs a warning with
  the apiUrl and the exact user ids before forgetting, and includes the resolved
  apiUrl in both the success and error response bodies so the caller can see
  which backend was mutated. Behaviour is otherwise unchanged (same ids forgotten,
  same PRESENTER_RESET_ENABLED gate, no confirmation prompt); only observability
  improves. route.test.ts updated to assert the new apiUrl field, still exact.
2026-07-31 17:04:26 +02:00
Maxim 9fe2714d24 fix(reskinnable-demo): shift Intelligence stack host ports by +200 to isolate from banking
reskinnable-demo was cloned from examples/showcases/banking and vendors an
identical Intelligence (durable-memory) docker stack — same seeded ids
(jordan-beamson / morgan-fluxx / northwind-demo-user), same INTELLIGENCE_API_KEY
and org. Both apps identified their backend purely by address, and both pointed
at http://localhost:7050. So with banking's stack already up, a bare `pnpm dev`
here (which, unlike run-demo.sh, checks no port bind) would silently attach to
banking's backend and read/write the SAME memory buckets — worst case, the
presenter reset button forgetting the neighbour demo's memories.

Shift every published host port by +200 (705x/715x -> 725x/727x) in lockstep
across compose, env, scripts, e2e config and tests so attaching to the wrong
stack is impossible by accident:

  app-api        7050 -> 7250
  gateway        7053 -> 7253
  postgres       7156 -> 7256
  redis          7158 -> 7258
  minio API      7160 -> 7260
  minio console  7161 -> 7261
  bundled TEI    7167 -> 7267

The native Metal TEI on :7067 is left shared ON PURPOSE: it holds no demo state
(buckets live in the now-isolated postgres/redis), the same TEI version + model
yields byte-identical embeddings, and run-demo.sh reuses it when healthy rather
than forcing a second ~20x-slower model load. A comment records why.
2026-07-31 17:04:16 +02:00
Ben Taylor 468995e8f5 feat(telemetry): inspector opened event and banner surface split (OSS-566/568) (#6203)
Two related Inspector-telemetry tickets: **OSS-566** and **OSS-568**.

## OSS-566 — explicit "Inspector opened" event

There was no event recording that the panel was opened. Opens could only
be inferred from in-panel activity (~1,655/90d, a floor) or from
`banner_clicked` cta=`body` (~511), which misses the common
floating-button path entirely.

Adds `oss.inspector.opened` with:

| property | values |
|---|---|
| `open_source` | `floating_button` \| `announcement_preview` |
| `has_unseen_announcement` | whether an announcement was on screen at
open time |
| `license_status` / `runtime_mode` / `runtime_url_type` | same
segmentation the threads events already carry |
| `package_name` / `package_version` / `inspector_distinct_id` | version
segmentation |

**Restoring a persisted-open panel deliberately does not count.**
Restore assigns `isOpen` directly instead of routing through
`openInspector()`, so page reloads — and every `next dev` hot reload —
stay out of the number.

## OSS-568 — banner surface + first-class dismissal

1. **`surface` on `banner_viewed`** — `collapsed_preview` (bubble on the
collapsed widget) vs `expanded_card` (card inside the opened panel),
stamped at fire time. Dedup is now per `(banner, surface)` instead of
per banner, so opening the panel records the card impression as its own
signal.
2. **`oss.inspector.banner_dismissed`** — emitted **in addition to**
`banner_clicked { cta: "dismiss" }`, not replacing it, so dashboards
reading the `cta` value keep working. Carries `surface` too, separating
"swatted the bubble away" from "dismissed the card after opening".

Both new events clear the sink's `oss.inspector.` prefix gate, so **no
telemetry-sink deploy is needed**.

## Testing

- **`packages/web-inspector` full suite — 112 passed (4 files)**, run
locally in the worktree:
  ```
   ✓ dev/css-raw-import.spec.ts (1 test) 1ms
   ✓ src/__tests__/telemetry-egress-guard.spec.ts (3 tests) 2ms
   ✓ src/lib/__tests__/telemetry.test.ts (28 tests) 8ms
   ✓ src/__tests__/web-inspector.spec.ts (80 tests) 890ms
   Test Files  4 passed (4)
        Tests  112 passed (112)
  ```
- **New coverage**: payload shape for `opened` / `banner_dismissed`,
incl. an allow-list assertion that no content/PII key can be added
accidentally; collapsed→expanded surface sequence on open; per-surface
dedup; open attribution for both sources; no event for an already-open
panel; no event for a restored-open panel; nothing emitted when the
runtime reports `telemetryDisabled`; an open still recorded while the
runtime is disconnected.
- **`tsc --noEmit`** on `@copilotkit/web-inspector`: clean (after
building `core` + `shared` dist in the worktree).
- **`tsdown` build**: succeeds; the test-only egress-guard helper is
**not** present in `dist/`.
- **`oxfmt --check`**: clean. **`oxlint`**: 9 warnings, all
pre-existing.
- `@copilotkit/runtime` (1,760) and `@copilotkit/shared` (199) also
green — both are back on main's own test files in this PR.

## A test-only egress guard rides along

`vitest.setup.ts` installs a fetch guard that swallows requests to the
telemetry sink. This is **not** CI plumbing — it is a prerequisite for
the new events. These tests run in jsdom, where a real `fetch` exists,
and inspector telemetry is fire-and-forget, so any test that drives a
banner / threads / open path without stubbing fetch POSTs a real
`oss.inspector.*` event to the live sink, from developer machines as
well as CI. The announcement-dismissal tests were already doing this;
the new `opened` / `banner_dismissed` tests hit the same send path. No
environment variable can prevent it, because the inspector's opt-out
arrives in the runtime's `/info` response and these tests never boot a
runtime.

## Not in scope

Suppressing telemetry from CI jobs that boot real apps (**OSS-565**) was
explored on this branch and removed. It needs a mechanism that does not
depend on the `/info` handshake — the env → `/info` → core chain is
asynchronous, so an early interaction beats it. That ticket stays open
and unaddressed here.

Closes OSS-566, OSS-568.
2026-07-31 08:33:57 -05:00
Mike Ryan 29d2721775 fix(channels): contain terminal delivery failures 2026-07-30 23:20:48 -07:00
Mike Ryan 2fe47bc969 Prevent Slack actor metadata from leaking into assistant history (#6268)
## Summary

- keep participant actor metadata model-visible in Slack transcript
history
- keep own-channel assistant history equal to the provider-visible
message content
- preserve structured actor and provider message metadata returned by
`thread.getMessages()`

## Root cause

The delivery adapter prefixed every transcript entry with the untrusted
participant metadata envelope before assigning AG-UI roles. Own-channel
transcript entries were then assigned the assistant role with that
participant-style prefix still in their content, so the model received
the prefix as prior assistant output and could reproduce it on the next
turn.

## Validation

- `pnpm nx test @copilotkit/channels-intelligence --skip-nx-cache` (17
files, 117 tests)
- `pnpm nx run-many -t check-types build -p
@copilotkit/channels-intelligence --skip-nx-cache`
- repository pre-commit Nx test/publint/attw suite
2026-07-30 22:58:29 -07:00
Mike Ryan 552268d47d fix: keep participant metadata out of assistant history 2026-07-30 22:50:27 -07:00
Tyler Slaton ca5e665e98 feat(channels): implement Channels V5 (#6266)
## Summary

- add the provider-neutral V5 message operation contract with stable
logical and revision identities
- route created, updated, and deleted messages through explicit mention
and message semantics
- add delivery-scoped transcript loading with retry coalescing and
delivery thread authorization
- charge managed deliveries on first substantive work while leaving
preparation free
- handle transcript failures by surface, render Slack status, and fence
superseded runs before output
- reconcile managed files with idempotent operation identities and bound
pending delivery capacity
- propagate actor kind through prepared deliveries
- normalize Slack, Teams, Telegram, WhatsApp, and Discord turns onto the
same operation contract

## Validation

- pnpm nx run-many -t test build -p @copilotkit/channels-intelligence
@copilotkit/channels-slack
  - channels-intelligence: 117 tests passed
  - channels-slack: 320 tests passed
- pnpm nx run-many -t build test -p @copilotkit/channels-telegram
@copilotkit/channels-whatsapp @copilotkit/channels-discord
  - Telegram: 150 tests passed
  - WhatsApp: 74 tests passed
  - Discord: 194 tests passed
- pre-commit affected package test, publint, attw, binary, lint, and
commitlint checks pass
- broad affected run found unrelated existing example lint/build
failures; the branch-owned cross-adapter type failures it revealed were
fixed and rerun green
- paired Intelligence Docker E2E passed all four realtime boundary
scenarios against real Postgres, Redis, MinIO, App API, and two Gateways
with fake Runtime and fake Slack
2026-07-30 20:41:38 -07:00
Mike Ryan 8e2d7a5cda test(channels): bind canonical run delivery 2026-07-30 20:05:31 -07:00
Mike Ryan 4680d2f579 fix(channels): normalize remaining provider turns 2026-07-30 19:58:08 -07:00
Mike Ryan 81d192ad22 fix(channels): suppress exact provider self output 2026-07-30 19:51:08 -07:00
Mike Ryan 7259bae438 feat(channels): bound pending delivery capacity 2026-07-30 19:51:08 -07:00
Mike Ryan 9dc343ddf8 feat(channels): confirm managed file delivery 2026-07-30 19:51:08 -07:00
Mike Ryan ddfa6f3453 feat(channels): supersede pre-output runs 2026-07-30 19:51:08 -07:00
Mike Ryan 2a00a5a16a feat(channels): render native Slack status 2026-07-30 19:51:08 -07:00
Mike Ryan bff87d1428 feat(channels): handle transcript failures by surface 2026-07-30 19:51:07 -07:00
Mike Ryan f7437daad3 feat(channels): charge deliveries on first work 2026-07-30 19:51:07 -07:00
Mike Ryan f1eef91fed feat(channels): authorize delivery thread access 2026-07-30 19:51:07 -07:00
Mike Ryan b376c901f6 feat(channels): load delivery-scoped transcripts 2026-07-30 19:51:07 -07:00
Mike Ryan bf8abc3cef feat(channels): add V5 message operation routing 2026-07-30 19:51:07 -07:00
Ben Taylor 5a8a487df3 feat(runtime): auto-start managed Channels on long-running hosts (OSS-641) (#6258)
Resolves [OSS-641](https://linear.app/copilotkit/issue/OSS-641). Mike's
report: *"You have to `await channels.ready()` for it to connect to the
Realtime Gateway. Seems like there's some clunkiness to creating the
runtime and getting it connected."* He then picked the fix: *"I think it
should autostart in the long running wrappers."*

## What changes

**`createCopilotNodeListener` and `createCopilotExpressHandler` start
activation at creation.** A declared Channel connects because it was
declared; `channels.ready()` becomes await-and-observe rather than the
call you must remember. Failure-mode asymmetry is the argument:
forgetting `ready()` today gives you a process that serves HTTP, looks
healthy, and is silently disconnected with **zero output**, while
auto-start's worst case is an activation error in the logs.

**`createCopilotRuntimeHandler` and `createCopilotHonoHandler` stay
lazy.** The generic Fetch handler is the serverless/edge entry point —
isolates freeze and recycle per request, so separate cold starts would
mint competing listeners for the same Channel (the reason activation was
deferred in `fbf35ac59` in the first place). Hono keeps that behavior
because it is our Next.js App Router surface in practice: every route
handler in `examples/showcases/*` (banking, mcp-apps,
generative-ui-playground, oracle-agent-memory) plus the vue/nuxt demo
builds one at module scope. Its TSDoc now states why, loudly, so nobody
"finishes the job" later.

`activateChannels: false` remains the clean opt-out that opens no
socket.

## Consequence for host code: the shutdown boundary moves earlier

Signal handlers must now be registered **before the listener is
created**, not merely before `ready()`. Otherwise a Ctrl-C during the
connect window hits Node's default handler and leaks a live gateway
session. `examples/slack`, `examples/teams`, and the docs snippets are
restructured to wire teardown before the listener exists (a
`stopChannels`/`teardown` binding assigned in the same tick as
creation). **Worth calling out in the changelog** — it is the general
hazard for any user code that registers shutdown after mounting.

## Failure semantics

Fire-and-forget by necessity, since a factory is synchronous. Set-level
failures log at `error`; per-Channel failures keep their existing `warn`
breadcrumbs; an up-front misconfiguration (duplicate/missing Channel
names) now surfaces as a logged error at creation rather than a throw
out of the factory — the factory still never throws. `ready()` stays
idempotent and one-shot, so a host that *does* await it observes this
activation's outcome, including its rejection, rather than triggering a
second one.

## READMEs

Every `channels-*/README.md` quickstart built the *generic* handler and
needed `await handler.channels.ready()` — for a socket-mode Slack bot, a
request handler you construct and never serve, which is likely closer to
what actually felt clunky. All seven now use the Node listener, so they
inherit auto-start and agree with the docs-site quickstarts. No new
public surface: a bot-only `startChannels(runtime)` host was the
alternative and is deliberately not taken here.

## Testing

- **`packages/runtime` unit suite: 1815 passed / 128 files** (`npx
vitest run`), including 9 tests in `endpoints-channels.test.ts`
covering: auto-start on node + express; Hono still lazy;
`activateChannels: false` opens no socket; a failed auto-start logs
instead of leaving an unhandled rejection (asserted via an
`unhandledRejection` listener) and the reason survives to a later
`ready()`; a duplicate-name misconfig logs without throwing; and two
wrappers over one runtime activate once (the per-runtime manager cache
is load-bearing now that *construction* activates).
- **`examples/slack`: 63 passed / 12 files; `examples/teams`: 2 passed /
1 file** (`npm test` in each).
- **Typecheck clean:** `examples/slack` and `examples/teams` (`tsc
--noEmit`), plus a full `@copilotkit/runtime` tsdown build.
- **Lint/format clean:** `oxlint` reports 0 findings in every changed
file (the 21 warnings in that run are pre-existing, all in untouched
example render/tool files), `oxfmt --check` passes on all 9 changed
source files.
- **Docs:** verified no stale lifecycle claims remain (`opens no
connection` / `ready() is required` / `control surface` guards) across
`docs/channels/**` and the Slack + Teams platform guides.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-30 21:26:06 -05:00