13718 Commits

Author SHA1 Message Date
Benjamin Taylor fdcc0150ab fix(channels-intelligence): classify channel_not_declared as setup-required
A runtime that declares a managed channel the Intelligence project does not have
crashed startup: ready() rejected and the channel settled to error. Creating the
channel and attaching credentials is the setup step every new channel goes
through, so this was the first run of every managed channel, before the happy
path.

app-api had no declaration-response token for "the project has no channel by that
name", so it sent runtime_not_declared. That token means the inverse on a join —
a channel exists but no runtime declared it, while on a join the runtime is the
one declaring. classifyJoinError read it literally, which was correct, and so
treated a benign setup gap as a hard failure.

app-api now sends channel_not_declared for that case (Intelligence#615). Adding it
to SETUP_REQUIRED_CHANNEL_STATES lets the channel settle to setup_required and
ready() resolve. runtime_not_declared keeps its literal reading and stays a hard
error, as do runtime_conflict, platform_setup_failed, delivery_failed,
egress_failed and runtime_offline.

This also answers the TODO(gateway-owner) the classifier carried, which asked
whether runtime_not_declared was ever benign. It was, but not for the reason the
comment guessed: the server was overloading the token, not racing. Replaced the
TODO with what the answer turned out to be.

npx vitest run src/realtime-gateway.test.ts: 21 pass

33 failures elsewhere in this package are pre-existing on origin/main (a stale
@copilotkit/channels build missing the runtime symbol) and unrelated.
2026-07-28 12:49:37 -05:00
Ben Taylor 3b9d2c27b5 fix(channels-intelligence): surface an unreachable gateway host instead of hanging the connect (#6191)
Closes OSS-623 (split out of OSS-621's acceptance item 3).

## The bug

A managed Channel whose `wsUrl` points at a wrong or unreachable host
sat in `connecting` for the caller's full settle budget and then
rejected with `did not settle in time` — no indication that the *host*
was the problem. A wrong **API** host fails fast with a clean HTTP
error; a wrong **WS** host hung.

Three failure modes collapsed into that one opaque timeout: DNS that
does not resolve, a refused/failed TCP or TLS connect, and a host that
serves HTTP with no Phoenix socket mounted at the endpoint (the prod
split-plane trap — `api.intelligence` and `realtime.intelligence` are
separate hosts).

## Why it hung

Two Phoenix behaviors compound, and the second is why the existing join
timeout did not save us:

1. `Socket` retries a failed **first** connect forever and reports
transport errors as reconnect churn, so nothing distinguishes "still
handshaking" from "this host will never work."
2. The join push's own `timeoutMs` is silently defused.
`Channel.onError` calls `joinPush.reset()` while the channel is joining
(phoenix 1.8.4 `channel.js`), which nulls `refEvent` and `off`s the
reply binding — the armed timeout then fires into a dead event name and
no `"timeout"` recHook ever runs.

Net: the connect await hung *indefinitely*; the 30s in the ticket was
purely `ChannelManager.ready({ timeoutMs })`'s deadline, which is why
the message was a stopwatch.

## The fix

**Watch the initial connect separately from reconnects**, keyed off
whether the socket has ever opened (phoenix passes exactly that as
`socket.onError`'s third argument, `establishedConnections`):

- **Never opened** → reject with a new `RealtimeGatewayUnreachableError`
naming the endpoint and the cause, and tear the socket down so it stops
retrying a host that will not answer.
- **Opened, then dropped** → unchanged; the existing `reconnecting` /
`gave_up` machine owns it.

**Get the cause from HTTP, because the socket does not have it.** This
is the part an adversarial review of the first commit corrected. Node's
global WebSocket (undici — the production default) dispatches the *same*
detail-free `ErrorEvent` for all three modes: `"Received network error
or non-101 status code."`, no OS code, an inner `Error` with nothing
more. Browsers are deliberately worse. So on the failure path only, we
probe the same origin **and path** over http(s) with `fetch`, which does
discriminate — verified against the real network:

| target | WebSocket says | `fetch` says |
|---|---|---|
| nonexistent host | network error or non-101 | `cause.code = ENOTFOUND`
|
| closed port | network error or non-101 | `cause.code = ECONNREFUSED` |
| `https://example.com/runner` | network error or non-101 | **HTTP 404**
|

A transport that *does* expose an OS code (the `ws` package, custom
transports) is trusted directly and skips the probe; a successful
connect issues no request at all.

**Failing fast vs. tolerating a boot race** is split by what the
diagnosis found:

- **The host does not resolve** → fail immediately. Measured against the
real network: **25ms**, message `the host does not resolve (getaddrinfo
ENOTFOUND …)`. This is the OSS-621 case (`realtime.copilotkit.ai` has no
DNS record at all).
- **Anything else** → wait out `connectTimeoutMs` (default **30s**,
plumbed through the launcher). A `ChannelManager` marks an activation
failure `error` **permanently, with no retry**, so a shorter window
would convert a rolling restart of the gateway into a dead Channel where
the old forever-retry would have recovered.

Per acceptance item 4, the error deliberately does **not** carry the
`SETUP_REQUIRED` marker. An unreachable host is a config problem, but
`setup_required` is a benign settled state that lets `ready()` resolve —
a typo'd `wsUrl` must reject loudly, not go quiet-green.

## What the messages look like now

```
realtime gateway unreachable: the socket never connected to wss://oss623-does-not-exist.copilotkit.ai/runner
  (the host does not resolve (getaddrinfo ENOTFOUND oss623-does-not-exist.copilotkit.ai))
  — check that the Intelligence wsUrl points at the gateway host          [25ms]

realtime gateway unreachable: the socket never connected to ws://127.0.0.1:45999/runner
  (the host refused the connection (connect ECONNREFUSED 127.0.0.1:45999))
  — check that the Intelligence wsUrl points at the gateway host          [full window]

realtime gateway unreachable: the socket never connected to wss://example.com/runner
  (the host answered HTTP 404 for https://example.com/runner, so it resolves and accepts
   connections — only the WebSocket handshake failed, so the wsUrl path may not be the
   gateway's socket endpoint)                                            [full window]
```

## Tests

Rebuilt around the shapes the real implementations emit, with the undici
shape as the default case (the first version's fake fed a `ws`-shaped
coded error undici never produces, which is what hid the dead code):

- The host does not resolve → fails in <1s despite a 5s window, probes
host **and path**, names the cause, carries `code ===
"GATEWAY_UNREACHABLE"` and the underlying `cause`, is not
`SETUP_REQUIRED`, socket closed.
- Refused → waits out the window, then names `ECONNREFUSED`.
- Host answers HTTP 404 → reported as a handshake/path problem, not a
missing host.
- A transport that exposes an OS code → fails fast with **no** probe
issued.
- A connect that hangs with no transport event at all → bounded by the
connect window (not a 60s join timeout), and the deadline itself starts
the probe so a cause is still reported.
- Neither transport nor probe yields a cause → still names the endpoint.
- **The shipped probe against a real closed loopback port** — no seam,
no DNS, no egress.
- A gateway that fails once and accepts on retry still connects (the
boot race).
- A healthy connect issues no diagnostic request.
- The connect deadline is disarmed after a successful connect — a later
drop is still a reconnect episode.

Before the fix, four of these hang until vitest's own 5s timeout — the
bug reproduced directly.

## Verification

`check-types` clean; full package suite 195/195; oxlint + oxfmt clean;
lefthook pre-commit (whole package suite + `check:packages`) green. All
three failure modes additionally verified end-to-end against the real
network and the real `fetch`, not just fakes.

## Known adjacent case, not in scope

A socket that opens and *then* drops before the initial join reply hits
the same defused-join-timeout behavior (2 above) and still relies on the
caller's settle deadline. That path can genuinely self-heal via
Phoenix's rejoin, so it keeps the retry; bounding it is a separate call
from OSS-623's "never connected once."

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-28 12:33:11 -05:00
David McKay b2f4b47a2d docs(banking): refresh the demo README to match what shipped
#6136 changed the demo substantially and left the README describing an older
build. Corrections:

- It documented CopilotPopup; the chat is a left-docked CopilotSidebar rendering
  two columns (thread rail + conversation) with the app's nav rail on the right.
- It called the learned over-limit memory `procedural`; the code writes
  `operational`.
- It said the Glass Engine Memory tab was "top-k semantic recall, not a full
  enumeration". It is now a complete enumeration ("Stored memories (N)"),
  aggregated across the demo's identities, with semantic search as a separate
  box.
- It listed per-run reset as a future follow-up. It ships, gated behind
  PRESENTER_RESET_ENABLED, and also re-seeds memory and returns to the bare root.
- It described showTransactions as a useFrontendTool; it is a useComponent so it
  persists in the transcript.
- The "change my Visa PIN from the team page" example described a navigation
  fallback; PIN entry is now a component in the chat and the agent never sees the
  digits.

Adds what was missing: the eight-pill demo script, the /charges page, the PDF/Q2
multimodal beat, seeded memory (and why the over-limit procedure is deliberately
NOT seeded), the components-not-markdown-tables rule, per-team chart colour, and
the two incompatible helper families in actions.ts that make destructuring `ok`
report a success as a failure.

All three screenshots are replaced — they showed the old right-docked chat, so
they contradicted the first paragraph.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 09:22:11 -07:00
Tyler Slaton 1d1ca9ea8b fix(docs): correct the documented Intelligence endpoints for Channels (#6189)
Fixes the Channels docs/examples pointing at an Intelligence host that
does not serve the API, and the websocket URL guidance that can never
produce a working prod value. Linear:
[OSS-621](https://linear.app/copilotkit/issue/OSS-621).

## The two bugs

**1. The documented host does not serve the API.** Probed every
plausible path, not just `/`:

| URL | Result |
| --- | --- |
| `api.copilotkit.ai` — `/`, `/api`, `/api/health`, `/health`,
`/api/threads`, `/api/v1/threads` | **404 on all**, `server:
awselb/2.0`, `content-length: 0` — an ALB with no target-group rule
behind it |
| `realtime.copilotkit.ai` | **no DNS record at all** |
| `api.intelligence.copilotkit.ai/` and `/api/health` | 200,
`x-powered-by: Express` |
| `api.intelligence.copilotkit.ai/api/threads` | **401** — a real,
auth-gated endpoint |
| `realtime.intelligence.copilotkit.ai/runner/websocket` | **403** —
mounted and auth-gated (a 404 would mean unmounted) |

So the documented host is not merely returning 404 at the root — nothing
is routed there on any path, and it is not the app (no `x-powered-by`).
The working pair, matching the CLI's baked-in prod defaults and
`gitops/environments/prod/values.yaml`, is
`https://api.intelligence.copilotkit.ai` +
`wss://realtime.intelligence.copilotkit.ai`.

**2. `wsUrl` was documented as derivable from `apiUrl`.** Prod splits
the API and realtime planes across *different hosts*, so a scheme-only
swap yields `wss://api.intelligence.copilotkit.ai` — wrong host. That
failure is silent: a wrong `apiUrl` returns a clean HTTP error, but a
wrong `wsUrl` sits in `connecting` until the settle timeout and reports
only "did not settle in time". The derive is not even correct locally,
where the API and gateway are on different ports (4201 vs 4401) and the
swap preserves the port. It is essentially never right, so it is deleted
rather than relabelled.

Demonstrated end to end rather than asserted — running `examples/teams`
with the WS URL unset:

```
# on main: derive(https://api.intelligence.copilotkit.ai) -> wss://api.intelligence.copilotkit.ai   (wrong host, 30s hang)
# on this branch:
exit code: 1
Missing COPILOTKIT_INTELLIGENCE_WS_URL.
  export COPILOTKIT_INTELLIGENCE_URL=https://api.intelligence.copilotkit.ai
  export COPILOTKIT_INTELLIGENCE_WS_URL=wss://realtime.intelligence.copilotkit.ai
The API and websocket URLs are DIFFERENT hosts (api.… vs realtime.…), so
the websocket URL cannot be derived from the API URL — set both.
```

## Scope note

The ticket listed 8 sites; the actual blast radius was 30 across 22
files. Beyond the ticket's list:

- **Five more channels package READMEs** — `channels`, `channels-core`,
`channels-discord`, `channels-slack`, `channels-teams` (the ticket named
only telegram + whatsapp).
- **The live product docs** —
`showcase/shell-docs/src/content/docs/channels/` taught the broken
derive in 8 files. These escaped the ticket's grep because their host
was already a `your-intelligence-url` placeholder; only bug 2 was
present. This is the surface developers actually read.
- **A generated skills mirror** — `skills/runtime/` is produced from
`packages/runtime/skills/runtime/` by `pnpm sync:plugin-skills`; fixing
one without the other leaves the bug live and fails the
`check-plugin-skills` gate.
- `skills/copilotkit-debug/references/runtime-debugging.md` and a third
occurrence in `client.ts`.

## Acceptance item 4 — resolved, chain verified

The ticket flagged a contradiction: `realtime-gateway.ts` documented
`wss://gateway.example/socket` while the runtime skill listed `/socket`
as a mistake. **The skill is right**, confirmed by tracing the real
runtime path rather than inferring it:

1. `channel-activation-config.ts:145` — `const wsUrl =
intelligence.ɵgetRunnerWsUrl()`, i.e. base + `/runner`.
2. → `channel-manager.ts:237` `wsUrl: config.wsUrl` →
`startChannelsOverRealtimeGateway` → `connectRealtimeGateway`.
3. `realtime-gateway.ts:253` hands that to Phoenix's `Socket`, which
appends `/websocket`.
4. The gateway mounts exactly `/runner` and `/client`
(`realtime_gateway/endpoint.ex:10,17`). There is no `/socket`.

The two docs survived contradicting each other because they describe
**different layers**: the public `wsUrl` is a bare base, while
`connectRealtimeGateway` receives the already-derived runner URL. Its
doc comment and the test fixtures move to `/runner` and now name the
layer. Independently, `get-runtime-info.ts:93` uses `ɵgetClientWsUrl()`,
which confirms the `/info` sample in the debug skill correctly keeps its
`/client` suffix — only the host there was wrong.

## Acceptance item 3 — split out

Failing loudly instead of hanging is a behavior change in the launcher's
connect path that overlaps OSS-622's error classification, so it is
[OSS-623](https://linear.app/copilotkit/issue/OSS-623) rather than
smuggled into a docs fix.

## Verification

- 44/44 gateway tests in `packages/channels-intelligence`;
`examples/slack/app/managed.test.ts` passes; `examples/teams` typechecks
clean.
- `nx run-many -t test,publint,attw` across the 15 affected projects
passed via the pre-commit hook; full CI green (38 checks, including
`build-check (shell-docs)`).
- Behavior proven by running the example, not by mocks (above).
- 45 commits behind `main` at time of review with **zero overlap** on
changed files, and the diff covers every dead-host and derive site
present on *current* `main`.
- No logic changes in either published package — `client.ts` and
`realtime-gateway.ts` are JSDoc/field-comment only. Behavior changes are
confined to the three example apps.

## Self-review pass

An adversarial pass over this PR tried to falsify its central claim
(that `api.copilotkit.ai` does not serve the API) by probing non-root
paths — the evidence got stronger, not weaker. It also cleared a
suspected regression: three `channels-intelligence` files read
`COPILOTKIT_INTELLIGENCE_URL` without the WS var, but they are the
HTTP-only transport path and need no socket URL. Two genuine gaps it
*did* find are fixed in the last commit: the five platform pages had
lost deployment neutrality (managed hosts now carry a self-hosted note),
and `index.mdx`/`mcp.mdx` referenced the new variable without telling
the reader where it comes from.

**One product decision for the reviewer:** `api.copilotkit.ai` is a live
ALB that routes nothing. If it is meant to become the public API alias,
this PR is documenting the wrong long-term string and the ALB wants a
listener rule instead. I used the host the product actually hands users
today (CLI prod defaults + gitops).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-28 08:46:39 -07:00
Alem Tuzlak 9a501d5e0d fix(showcase/built-in-agent): repair six real-LLM defects D6 was masking (#6200)
Six defects reported on staging. All six reproduce; five share one
cause.

## The shared cause

`built-in-agent/src/app/api/copilotkit/route.ts` maps **~20 demos to the
same prompt-less `createBuiltInAgent()`**. The reference wires each to
its own graph **and its own system prompt** (28 graphs in
`langgraph-python/langgraph.json`).

aimock replays a scripted tool-call sequence keyed on `userMessage` +
`context`, so D6 is green whether or not the model could have reasoned
its way there. This is `showcase/GOTCHAS.md` #8 verbatim — including its
stated "structural tell": *grep the integration's `route.ts` for demos
left in the generic fallthrough that LGP wires to a dedicated graph.*

## Per-defect

| Demo | Symptom | Root cause |
|---|---|---|
| `gen-ui-tool-based` | charts plot zeros | No prompt. The assistant
says so itself: *"I used placeholder values since no sales figures were
provided."* Nondeterministic — one run gave a real 0..220 axis, the next
`0 1 2 3 4` with no bars, which is how it passed review. |
| `gen-ui-agent` | wall of text, steps frozen at "step 1 of 4" |
`set_steps` + its `/steps` STATE_DELTA already worked; nothing told the
model to *walk* pending→in_progress→completed. |
| `subagents` | left panel always empty | `grep -rn delegations src/ \|
grep -v demos/subagents` → **no output**. Frontend reads
`agent.state.delegations`; the converter handles `/steps` and `/notes`
and has no `delegations` case. Chat works because the tools do run. |
| `declarative-json-render` | raw JSON for pills 2 & 3 | Intercepted the
SSE: **the wire is already one closing brace short** — nothing is
dropped in transport, and the client parser correctly requires a
balanced object. Pill 1 only works because the model copies the prompt's
worked example verbatim (`\$1.24M`, `+18% vs Q2`). Model-level JSON
enforcement had been removed with the note *"the system prompt already
enforces JSON-only output"* — it does not. |
| `a2ui-recovery` | 5 identical cards | Reuses declarative-gen-ui's
prompt with no "call once" constraint, while the pills literally ask it
to *"self-correct a malformed first attempt"*. The retry loop is
**inside** the tool and returns on first valid pass, so every supervisor
retry is pure duplication. |
| `declarative-gen-ui` | D4 though it works live | **The inverse case.**
Badges: `UI ✓ BE ✓ 1P ✗ D6 —` (*"gated — blocked by a lower rung"*). Not
a live bug — a fixture bug. See below. |

## The D4 cap (`declarative-gen-ui`, `a2ui-recovery`)

Both demos' secondary design-call fixtures gated on
`match.responseFormat: \"json_object\"`. That matcher **can never match
this backend**:

- The agent talks to the OpenAI *Responses* API, where JSON mode is
`text.format`.
- aimock has **no `text.format` handling at all** —
`responsesToCompletionRequest` forwards only a top-level
`response_format` (absent in 1.19.1, forwarded in 1.37.4), a key the
Responses API doesn't accept and this client doesn't send.
- So `effective.response_format?.type` is always `undefined` and
`router.ts` skips the fixture → the design call never matched → surface
never painted → D5 red, D6 never ran.

Re-keyed onto `match.toolName`, which aimock *does* normalize out of a
Responses request (`responsesToolsToCompletionsTools`): the outer call
declares `generate_a2ui`, the in-tool design call declares nothing.
Tool-less secondary fixtures moved last, because several pills' brief is
a substring of the pill text (`\"Build my Q2 revenue summary …\"`
contains `\"Q2 revenue summary\"`) and would otherwise win the outer
request.

## Verification

**Reproduced live** (staging, real LLM): the zero-axis chart plus its
self-incriminating message, the unbalanced JSON on the wire, and the
reference rendering the *same pill* correctly for contrast.

**Two mutation-verified test suites** — both fail with the fix reverted:

- `aimock-a2ui-routing.test.ts` drives aimock's **real `matchFixture`**.
On the old fixtures 14/16 cases fail with `no fixture matched the
secondary design call for brief \"…\"` — the D5 red, reproduced as a
unit test.
- `tanstack-factory.test.ts` covers the `/delegations` and `/steps`
deltas (3 delegation tests fail with the branch disabled; the 2 controls
still pass).

**No regressions:** `tsc --noEmit` is 61 errors before *and* after (all
pre-existing — `gpt-5.4` missing from the adapter's model union, zod
v3/v4 skew); oxlint clean on changed files; the repo-wide fixture
validator passes (858 assertions).

`text.format` was verified by reading the request mapping in the
**pinned** `@tanstack/ai-openai@0.15.6` → `@tanstack/openai-base@0.9.2`:
`modelOptions` is spread straight into `responses.create()`,
`validateTextProviderOptions` only inspects
`metadata`/`conversation`/`previous_response_id`, and the adapter sets
`text.format` itself only when an `outputSchema` is passed (none here,
so nothing is clobbered).

## What is NOT verified

**The four prompt/`text.format` changes have not had a real-LLM
click-through** — that needs an OpenAI key this environment doesn't
have, and D6/aimock cannot verify them by construction (that's the whole
point of gotcha #8). Reasoning is documented inline at each site. This
area has burned the repo before: a previous `response_format` attempt
made the call return an empty string "verified against real OpenAI",
which is why `text.format` — the Responses API's own param — is used
instead. **Worth a live click-through on the staging deploy before this
is considered closed.**

## Deliberate non-goals

- **`a2ui-recovery` only demonstrates recovery under aimock.** The
heal/exhaust branches need a designer LLM that emits invalid surfaces on
demand; a real one succeeds on attempt 1. The single-call constraint
fixes the 5-card bug and makes it honest; making recovery visible live
needs deliberate fault injection — a product decision, recorded in
`PARITY_NOTES.md`.
- **The reference pins `openai:gpt-4o-mini`** (`gen_ui_agent.py:92`).
Flagged, not touched.

## Docs

`GOTCHAS.md` gains built-in-agent as a second worked instance of #8
(including that the masking runs *both* ways, and that `1P ✗ D6 —` means
gated, not failing), plus the `text.format` and dead-`responseFormat`
traps. `PARITY_NOTES.md` records that the named-agent registry is
**not** prompt-neutral, and that any new `state.<slot>` needs a
converter branch.
2026-07-28 17:32:20 +02:00
Alem Tuzlak 1d5cdb35cf fix(showcase/built-in-agent): repair six real-LLM defects D6 was masking
Every affected cell was D6-green (or D4-gated) while the deployed demo was
visibly broken. The common cause is GOTCHAS #8: aimock replays a scripted
tool-call sequence keyed on userMessage + context, so the fixture answers a
question the model was never asked.

Prompt gaps — ~20 demos shared one prompt-less `createBuiltInAgent()` where the
reference wires each to its own graph AND its own system prompt. Adds
`createBuiltInAgent({ systemPrompt })` + `demo-prompts.ts`, ported from the
reference graphs:

- gen-ui-tool-based plotted zeros; the assistant said "I used placeholder values
  since no sales figures were provided". Nondeterministic — some runs invented
  real values, which is how it passed review.
- gen-ui-agent published its plan once then narrated, freezing the progress card
  on step 1 of 4.
- a2ui-recovery painted five identical cards: nothing constrained the supervisor
  to one `generate_a2ui` call, and the pills literally ask it to "self-correct".
  The retry loop lives inside the tool, so supervisor retries are duplication.

subagents' delegation panel was permanently empty — the frontend reads
`agent.state.delegations` and no code emitted that slot. The converter now emits
a `/delegations` delta per sub-agent result (whole-array `add`, since initial
state is `{}` and strict fast-json-patch rejects unresolvable paths while
@ag-ui/client swallows the throw), and ports the reference's
`_MAX_CRITIQUE_ITERATIONS = 1` cap.

declarative-json-render dumped raw JSON for any prompt the model couldn't crib
from the worked example. Captured the SSE: the wire is already one closing brace
short, so nothing is dropped in transport — model-level enforcement had been
removed on the grounds that "the system prompt already enforces JSON-only
output". It does not. Restores it via the Responses API's `text.format`, the
param the removed `response_format` maps to, verified against the pinned
@tanstack/ai-openai@0.15.6 -> @tanstack/openai-base@0.9.2 request mapping.

declarative-gen-ui and a2ui-recovery were the inverse: capped at D4 (UI ✓ BE ✓
1P ✗ D6 gated) while working live. Their secondary design-call fixtures gated on
`match.responseFormat`, which aimock can never satisfy here — it has no
`text.format` handling and only forwards a top-level `response_format` the
Responses API doesn't accept. Re-keyed onto `match.toolName` (the outer call
declares `generate_a2ui`, the in-tool design call declares nothing) with the
tool-less fixtures moved last, since several pills' brief is a substring of the
pill text.

Tests (both mutation-verified — they fail with the fix reverted):
- aimock-a2ui-routing.test.ts drives aimock's real `matchFixture`; 14/16 cases
  fail on the old fixtures with "no fixture matched the secondary design call".
- tanstack-factory.test.ts covers the `/delegations` and `/steps` deltas.

Typecheck unchanged at 61 pre-existing errors; oxlint clean on changed files.

NOT verified live: the four prompt/`text.format` changes need a real-LLM
click-through, which needs a key this environment doesn't have. Reasoning and
the exact request mapping are documented inline.
2026-07-28 16:49:43 +02:00
Alem Tuzlak ffcab5fb64 fix(showcase/ms-agent-dotnet): copy manifest.yaml into the runtime image (#6197)
## Symptom

Every `ms-agent-dotnet` cell on staging dropped to **D3** after #6130
merged:

| Rung | Error |
|---|---|
| D6 Parity (Reference) | `chat input not found: waitForSelector
locator('[role="textbox"]') to be visible` |
| D5 1P (Single Pill) | `timeout after 600000ms` |
| D4 BE (Agent) | `page.type: Timeout 59981ms exceeded waiting for
locator('textarea')` |

## Root cause

Not a chat/agent bug — **every `/demos/*` page is a 500 error
boundary**, so no chat input ever mounts. Verified live:

```
$ GET https://showcase-ms-agent-dotnet-staging.up.railway.app/demos/agentic-chat
Application error: a client-side exception has occurred
[showcase] Uncaught error: An error occurred in the Server Components render.
```

`/` still returns 200 (statically prerendered) — which is why the
service line stays green and the cells look merely "stuck at D3".

#6130 added `src/middleware.ts`, which sets the `x-pathname` header.
That header is the thing that makes `src/app/demos/layout.tsx`'s
`generateMetadata()` get past its `if (!slug) return` early-out and
actually call `loadDemoIndex()` — a request-time
`readFileSync(process.cwd()/manifest.yaml)`. The Dockerfile's runner
stage never copied `manifest.yaml` → ENOENT on every demo route.

Before the middleware existed the header was always absent, so the
manifest read never ran. The middleware didn't break the layout; it
*activated* a path that had never executed in production.

`langgraph-python` hit this exact bug and fixed it with the same
one-line `COPY` (its Dockerfile carries a comment describing this
failure verbatim). A sweep confirms `ms-agent-dotnet` was the only
integration shipping `middleware.ts` without it:

```
langgraph-fastapi  middleware=yes copy=yes
langgraph-python   middleware=yes copy=yes
ms-agent-dotnet    middleware=yes copy=no   <-- broken
(all others)       middleware=no            <-- header absent, read never runs
```

## Fix

1. `COPY --chown=app:app manifest.yaml ./` in the runner stage, with the
same explanatory comment as the reference.
2. `showcase/scripts/__tests__/runtime-manifest-copy.test.ts` — ratchets
the invariant: any integration shipping `middleware.ts` whose demos
layout reads `manifest.yaml` must copy it into the image.

## Verification

- Confirmed the live 500 on `/demos/agentic-chat` and confirmed
`langgraph-python` staging renders the manifest-driven title (`LangGraph
(Python) - Pre-Built: CopilotChat`) on the same route — isolating the
missing COPY as the only difference.
- Ratchet test mutation-verified: passes with the fix, fails when the
`COPY` line is removed.
- Not container-built locally (the .NET SDK stage is expensive and the
change mirrors a proven line); the deployed staging image is the real
check.

## Note, separate from this PR

D3 rendered **green** while every demo page was a 500. Whatever D3
probes, it does not detect an error-boundary page — that is red-masking
worth a follow-up.
2026-07-28 15:35:08 +02:00
Benjamin Taylor e0c7500d6b fix(channels-intelligence): diagnose an unreachable gateway over HTTP, not from the socket
Adversarial review of the previous commit against the real transport found the
cause-carrying half of it was dead code. Node's global WebSocket (undici) — the
production default — dispatches the SAME detail-free ErrorEvent for a host that
does not resolve, a refused port, and a host answering HTTP with no socket
mounted: "Received network error or non-101 status code.", no OS code, an inner
Error with nothing more. So the non-retryable code set never matched (measured:
NXDOMAIN burned the full 8s window instead of failing fast) and the reported
transport error was the same useless string for every cause. The tests passed
only because their fake fed a ws-package-shaped error undici never produces.

Get the cause from a source that has it: on the failure path only, probe the
same origin AND path over http(s) with fetch, which does distinguish the modes
(verified against the real network: ENOTFOUND / ECONNREFUSED / HTTP 404). A
transport that DOES expose an OS code is still trusted directly and skips the
probe; a successful connect issues no request at all. NXDOMAIN now fails in
~25ms rather than after the whole window, and the HTTP-answered case reports
that the host is up and only the handshake failed — the prod split-plane trap.

Also raise the default connect window 10s -> 30s. Because a supervising
ChannelManager marks an activation failure `error` permanently with no retry,
the old default could convert a rolling restart of the gateway into a
permanently dead Channel, where the previous forever-retry would have recovered.

Tests are rebuilt around the shapes the real implementations emit, including one
that exercises the SHIPPED probe against a closed loopback port (no DNS, no
egress), one asserting a healthy connect probes nothing, and coverage for the
GATEWAY_UNREACHABLE code and preserved cause.

Refs OSS-623
2026-07-28 08:14:40 -05:00
Alem Tuzlak 734294155a fix(showcase/ms-agent-dotnet): copy manifest.yaml into the runtime image
PR #6130 added `src/middleware.ts`, which sets the `x-pathname` header that
`src/app/demos/layout.tsx`'s `generateMetadata()` reads. That header is what
makes the layout actually call `loadDemoIndex()` — a request-time
`readFileSync(process.cwd()/manifest.yaml)`. The Dockerfile's runner stage
never copied `manifest.yaml`, so every `/demos/*` route now 500s with "An
error occurred in the Server Components render" (ENOENT), while the
statically-prerendered home page keeps returning 200.

On the dashboard that reads as "service is up, every cell pinned at D3": D4
fails on `page.type` waiting for `textarea`, D5 times out, D6 fails on
`waitForSelector('[role="textbox"]')` — the chat input never mounts because
the page is an error boundary.

langgraph-python hit this exact bug and fixed it with the same one-line COPY;
ms-agent-dotnet is the only integration shipping `middleware.ts` without it.
Adds a ratchet test over that invariant (mutation-verified: fails with the
COPY removed).
2026-07-28 15:10:44 +02:00
Benjamin Taylor ad67e9f4d6 docs(channels): keep the Channels pages deployment-neutral and self-sourcing
Two gaps from the previous commit, found reviewing it.

The five platform pages previously carried a `your-intelligence-url`
placeholder, which was neutral about where Intelligence runs. Replacing it with
the managed hosts read as "this is the endpoint" to a self-hosted reader, and
only the quickstart said otherwise; every env block now notes that self-hosted
deployments substitute their own two hosts.

index.mdx and mcp.mdx are code-only pages with no env block, so after requiring
a second variable they referenced COPILOTKIT_INTELLIGENCE_WS_URL without ever
telling the reader where it comes from. Both now point at the dashboard and the
quickstart's env block.

Refs OSS-621
2026-07-28 08:07:29 -05:00
Benjamin Taylor c03cb1e215 chore(channels-intelligence): drop the changeset
Changesets are no longer part of the release flow for this repo.
2026-07-28 08:03:01 -05:00
Alem Tuzlak 809bb72e72 feat(showcase): bring ms-agent-dotnet to D6 (frontend parity + shared-state-streaming, a2ui-recovery, threadid) (#6130)
## What

Brings the **ms-agent-dotnet** (Microsoft Agent Framework .NET) showcase
integration from D5 to **D6**, using **langgraph-python** as the
north-star reference.

### 1. Frontend parity with langgraph-python
Restores near-identical frontends where ms-agent-dotnet had drifted,
while **preserving the load-bearing .NET adaptations** (per the showcase
iron rules — differences belong in fixtures/minimal backend, not the
shared frontend):
- Root shell: `globals.css` (Tailwind `@theme` block + brand green),
manifest-driven index `page.tsx`, `layout.tsx`, new `middleware.ts`
(`x-pathname`), `tsconfig` include.
- `declarative-gen-ui` subtree restored (fixes divergent pill testids
the shared probe asserts).
- Doc-snippet `@region` markers, import-style normalization, `subagents`
revert, stale-file cleanup, `auth` inspector flag.
- **Kept** (load-bearing, not reverted): `parse-json-result` 3-layer
unwrap, multimodal legacy-shim, tool-based `hitl` (MAF has no
`interrupt()`), `agent-config` `properties=`.

### 2. shared-state-streaming → per-token (removed from
`not_supported_features`)
`write_document`'s `document` arg now streams into `state.document`
per-token via a `createSharedStateStreamingAgent` route shim (mirrors
the proven `createGenUiAgent` bridge, with a partial-JSON string
decoder), since the .NET AG-UI host has no `predict_state_config`.

### 3. a2ui-recovery cell (new)
First MS-Agent-Framework implementation of the A2UI
validate→retry→`a2ui_recovery_exhausted` recovery loop. Because the MAF
AG-UI adapter can't emit the custom `ACTIVITY_SNAPSHOT{status:"failed"}`
the exhausted card needs, it's a **raw-SSE `MapPost` endpoint**
(`RecoveryAgent.cs`) — the same adapter-bypass pattern already shipped
for `/multimodal`. Adds the demo frontend, API route, deterministic
aimock fixture (heal seq0-invalid→seq1-valid; exhaust always-invalid),
and a unique per-slug `PROMPTS` entry in the shared probe.

### 4. threadid-frontend-tool-roundtrip demo (parity)
Added for demo-set parity (reuses the `frontend_tools` passthrough; not
a D6-scored feature, mirroring the reference).

`gen-ui-interrupt` / `interrupt-headless` remain honestly quarantined
(upstream `@copilotkit/react-core` `useInterrupt` resume-path bug — not
a backend gap).

## Verification
- Code was authored in parallel worktree-isolated slots, each
cross-verified against the reference + the shared probe contracts; the
a2ui-recovery fixture was cross-checked against
`RecoveryAgent.ValidateComponents`.
- Local D6 harness: the image builds and the stack + probes run, but
**full local green was blocked by Windows-only harness friction**
(`core.symlinks=false` breaks `stage_shared`'s `[ -L ]` materialization;
`--direct` doesn't context-scope the `x-aimock-context` header so
context-keyed a2ui fixtures miss). These are environmental, not code
issues. **Relying on CI's Linux harness (real symlinks + fleet worker)
for authoritative D6.**

## Follow-up (not in this PR)
- `stage_shared()` should also materialize Windows symlink-as-file
entries (detect a regular file whose content is a relative path), so
forced local rebuilds work on `core.symlinks=false` checkouts.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-28 13:09:06 +02:00
lukasmoschitz eca1d741c8 fix(showcase): align langgraph-fastapi to north-star langgraph-python (OSS-582) (#6184)
## Summary

Aligns the **langgraph-fastapi** showcase integration to the north-star
**langgraph-python** (OSS-582 — "Align LangGraph (FastAPI) Showcase
demos and code"). Moves the integration onto the product-centric demo
set: backend agent graphs, runtime wiring, frontend chrome, the
demo-browser overview (`manifest.yaml`), and aimock fixtures.

Linear: OSS-582.

## What changed

**Backend agents (`src/agents/src/`)**
- Ported the missing dedicated graphs: `agentic_chat`, `gen_ui_agent`,
`gen_ui_tool_based`, `shared_state_streaming`.
- v2 `create_agent` ports + config alignment for `agent_config_agent`,
`headless_complete`, `reasoning_agent`, `tool_rendering_agent`,
`tool_rendering_reasoning_chain_agent`, `a2ui_dynamic`.

**Runtime wiring**
- `route.ts`: wired the canonical demos to their dedicated graphs and
removed them from the generic `sample_agent` fallthrough loop (this is
what made several demos behave correctly against a real LLM — see
below); added `recursion_limit`.
- `langgraph.json`: registered the 3 new graphs.
- Renamed the dedicated API routes
`copilotkit-byoc-{hashbrown,json-render}` →
`copilotkit-declarative-{hashbrown,json-render}`.

**Overview / content**
- `manifest.yaml`: demos + features aligned to LGP (names, descriptions,
tags, order, canonical ids; deprecated aliases migrated; phantom
`hitl-in-chat-booking` removed; `shared-state-streaming` un-quarantined
now that it works). Integration identity (name/slug/logo) preserved. The
demo-browser overview is now card-for-card identical to
langgraph-python.
- Restored two missing `@region` markers (factory-automation snippet
extraction).

**Frontend chrome**
- `globals.css`, `layout.tsx`, `middleware.ts` (was missing),
`declarative-gen-ui/*` + `sales-context.ts`, `beautiful-chat`;
`Dockerfile` now copies `manifest.yaml` (fixed an RSC crash on every
`/demos/*`).

**aimock fixtures (`aimock/d4|d6/langgraph-fastapi/`)**
- D6 fixture fixes for the previously-red cells (stale `turnIndex`
gates, cross-file substring shadowing, missing `chunkSize`, prompt
narrowing).

**Showcase tooling / docs**
- Fixed harness services racing on a shared image tag
(`docker-compose.local.yml`).
- `GOTCHAS.md` #8: documents that aimock D6 can be green while a demo is
broken against a real LLM (fixtures replay scripted tool calls
regardless of which graph ran), plus how to catch it.
- `PARITY_NOTES.md`: sanctioned divergences (a2ui-recovery per-slug
prompt isolation; declarative-json-render scoped-test divergence).

## Verification

- **D6 sweep: 38 green / 1 red.** The single red is **a2ui-recovery**,
which reproduces **identically on the north star** (shared Python
`ag_ui_langgraph` recovery loop; mastra's TS impl is green). It is not a
fastapi defect and is tracked as a separate PR against the north star.
Documented in `PARITY_NOTES.md`.
- `validate-manifests` (manifest → registry, all 20 integrations) —
green.
- `validate-routes --all` (runtime-route wiring) — green.
- Build + TypeScript typecheck (`next build` via the Docker image build)
— green.
- `@region` audit — all region pairs balanced and LGP-consistent (0
orphans/typos/mismatches).
- Live manual QA against real OpenAI (`:3102` vs `:3100`) confirmed the
key demos (reasoning-custom, shared-state-streaming, gen-ui-tool-based,
agentic-chat).

## Notable finding

Several demos were D6-green but broke against a real model because they
fell back to the generic `sample_agent` instead of their dedicated graph
— the aimock fixture masked the wiring bug by replaying scripted tool
calls. This PR fixes the wiring and documents the gap (GOTCHAS #8).
D6-green is necessary but not sufficient for graph/tool-dependent demos;
a real-LLM click-through is required.

## Deferred / follow-ups

- **a2ui-recovery** — shared north-star defect in the Python
`ag_ui_langgraph` recovery loop; separate PR against the north star.
- **d5-byoc probe** — always sends the hashbrown pill even on the
json-render page (fleet-wide harness limitation); tracked as a
follow-up. The gating `byoc` grid cell is green.

## Scope note

Only `langgraph-fastapi` integration code, its aimock fixtures, and
showcase tooling/docs changed. **langgraph-python (the north star) was
not touched.**
2026-07-28 12:49:57 +02:00
renovate[bot] 2732476db3 chore(deps): update docker/login-action action to v4.5.2 (#6194)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [docker/login-action](https://redirect.github.com/docker/login-action)
| action | patch | `v4.5.1` → `v4.5.2` |

---

### Release Notes

<details>
<summary>docker/login-action (docker/login-action)</summary>

###
[`v4.5.2`](https://redirect.github.com/docker/login-action/compare/v4.5.1...v4.5.2)

[Compare
Source](https://redirect.github.com/docker/login-action/compare/v4.5.1...v4.5.2)

</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:eyJjcmVhdGVkSW5WZXIiOiI0My4yODAuMCIsInVwZGF0ZWRJblZlciI6IjQzLjI4MC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-07-28 10:25:38 +00:00
renovate[bot] a36772ffe5 chore(deps): update docker/login-action action to v4.5.2 2026-07-28 09:10:42 +00:00
Ran Shemtov b51cfc2725 fix(showcase): PNI-100 relaxed-turnIndex re-emit — hitl-in-app + frontend-tools-async (#6183)
## What

Under aimock's **default (relaxed) turn-index selection**, a fixture
whose emit leg is keyed on `turnIndex:0` gets re-served on the
**post-tool-result turn** — `selectByTurnIndex` treats a
behind-the-count scripted turn (`turnIndex < assistantCount`) as a
stronger signal than an unpositioned narration fixture. So returning a
frontend-tool result re-fires the tool instead of narrating the outcome,
and the turn never settles.

This is a **test-infra (fixture-selection) bug, not a bridge/runtime
bug**: `@ag-ui/mastra` loops back correctly on real OpenAI and under
strict aimock.

## Fixtures fixed

**hitl-in-app** (`request_user_approval`) — re-key the 3 pill emit legs
from `turnIndex:0` to `hasToolResult:false`. Matches the emit turn;
excluded on the post-approval turn (where the `toolCallId` narration
wins).

**frontend-tools-async** (`query_notes`) — add `hasToolResult:false` to
the 4 emit legs (3 pills + the `project planning` catch-all). The
`toolCallId` narration legs round-trip fine (verified on the wire: emit
id `call_d5_query_notes_*` matches the tool-result id on turn 2) and are
unchanged. `query_notes` ids exist only in this file — no
duplicate/shadow.

`subagents.json` reviewed and left as-is — its `turnIndex:1/2` legs are
intentional server-side step sequencing, not the client-re-POST pattern.

## Why safe

- `hasToolResult` is **per-turn** in current aimock
(`currentTurnHasToolResult` scans messages after the last user index),
so the guard is multi-pill-safe: each pill click is a fresh user turn.
(577 `hasToolResult:true` legs across 258 fixture files already depend
on this and are green on `ghcr:latest`, confirming the deployed aimock
is per-turn.)
- No `turnIndex` dependence on the emit decision → correct under both
strict and relaxed aimock.
- `--validate-on-load` passes for both files.

## Validation (relaxed / default aimock — the failing mode)

Live-verified in-browser against a local mastra run on aimock:
- **hitl-in-app**: refund approve → narration; multi-pill (approve
refund → escalate) → each pill gets its own dialog and settles.
- **frontend-tools-async**: project-planning pill → single result card +
narration (was: double card + `toLowerCase` error).

## NOT in this PR (separate follow-up)

**open-gen-ui / open-gen-ui-advanced** also break under relaxed aimock,
but the cause is different and messier: their real fixtures live in
`gen-ui-open.json` / `gen-ui-open-advanced.json` (already
`hasToolResult`-gated, no `turnIndex` → no PNI-100 bug). A **stale
duplicate** set of `generateSandboxedUi` entries in
`gen-ui-tool-based.json` (orphan `call_d5_*` ids, superseded by the real
`call_d6_mastra_ogui_*` fixtures) overlaps those pills' userMessages and
re-serves on turn 2, poisoning the real flow. Additionally
`gen-ui-open.json` has no per-pill turn-2 narration. Fixing that cleanly
= remove the stale duplicates + add narration legs to the real files;
tracked separately, not bundled here.

## Follow-ups

- Durable fleet fix is aimock-side: relaxed selection should prefer a
more-specific match over a `turnIndex`-only fixture.
- gen-ui stale-duplicate cleanup (above).

Linear: PNI-100
2026-07-28 11:09:38 +02:00
Ran Shemtov 0f52c5260e Merge branch 'main' into claude/youthful-williams-bf7746 2026-07-28 11:07:36 +02:00
Sam Julien 417eb746d2 docs(react-native): expand into a production guide (#6144)
## What

Expands the React Native docs page from a "get to first chat" quickstart
into a full production guide, matching the depth of the Slack/Teams
frontend pages. The existing quickstart Steps are unchanged; the new
material is added after them.

### Production sections added
- **Headless imports** — `@copilotkit/react-native/headless` for a
custom-UI app, avoiding the chat/attachment native peer deps the default
barrel re-exports.
- **Metro configuration for release bundles** —
`unstable_enablePackageExports` + browser-first
`unstable_conditionNames` so the transitive `jose` dependency resolves
to its browser build (fails only at release-bundle time otherwise).
- **Polyfills** — documents the real auto-installed barrel + granular
subpaths, including the `crypto` polyfill (backed by `Math.random`) and
the recommendation to install a secure RNG for production.
- **Provider options** — the full `CopilotKitProvider` prop surface:
`runtimeUrl`, `headers` (auth), `credentials`, `properties`, `onError`,
`debug`, `defaultThrottleMs`, `useSingleEndpoint`.
- **Runtime and model wiring** — the string model form,
`OPENAI_BASE_URL` (and Anthropic/Google equivalents) for
OpenAI-compatible endpoints, `createCopilotNodeListener` zero-dep
serving, and `BuiltInAgent` options.
- **Connecting from a device or bench** — LAN IP, Android emulator
`10.0.2.2`, `adb reverse` for USB, and Android release cleartext HTTP
via `expo-build-properties`.
- **Frontend tools and generative UI** — `useFrontendTool` with
zod/Standard Schema, the send-a-turn recipe, `useAgent` read-only
subscribe + `threadId`, discriminated-union params, and variable-length
`ScrollView` rendering.
- **Run lifecycle and UI** — `isRunning` outliving the visible compose,
and gating overlays/reveal motion accordingly.
- **Voice and speech-to-text** — DIY STT with a graceful-fallback
warning for de-Googled/automotive hardware.
- Refreshed **What's included** and **Known limitations**, plus a **Next
steps** section.

### Dependency on runtime / react-native fixes

Three of the four fixes this guide relies on are now **merged to
`main`**, so it documents them as current behavior:

- `@copilotkit/react-native/headless` subpath (headless provider + hooks
without chat/attachment deps) — #6142 ✅ merged
- `resolveModel` honoring `OPENAI_BASE_URL` / `ANTHROPIC_BASE_URL` /
`GOOGLE_GENERATIVE_AI_BASE_URL` for the string model form — #6140 ✅
merged
- union / `anyOf` / `oneOf` tool-param schemas surviving JSON-schema→zod
conversion — #6143 ✅ merged

The fourth — `useAgent` accepting an optional `threadId` (#6141) — is
**still open**. The guide therefore does **not** document it as working;
it is listed under **Known limitations** instead, and will be promoted
once #6141 lands.

Every API, export, env var, and behavior in the page was independently
re-verified against the package source (three parallel source audits
over `packages/{react-native,react-core,runtime}`). That pass turned up
one overstatement, fixed in 6eefe2b: only `/headless` and the root
barrel side-effect-import the polyfill barrel —
`@copilotkit/react-native/components` does not, so the "all three
surfaces auto-install" wording was corrected.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-27 20:02:36 -07:00
tylerslaton 17564afd2b chore: release monorepo v1.64.0 2026-07-28 02:40:43 +00:00
David McKay 9ebada7cd8 fix(runtime): emit activity snapshot before any delta in open generative UI middleware (#5392)
## Summary

Fixes the silent failure where `generateSandboxedUi` (Open Generative
UI) intermittently renders an empty gray box — the agent says "Done — I
built the calculator…" but no sandboxed UI appears. Reported from a live
demo against https://langgraph-py.examples.copilotkit.ai/ (Linear:
CPK-7634).

## Root cause

`ArgsParser` in `open-generative-ui-middleware.ts` only emitted the
activity-creating `ACTIVITY_SNAPSHOT` when it parsed the
**`initialHeight`** param. The LLM controls the key order of the
streamed tool-call JSON, so whenever `initialHeight` was not the first
key (or was omitted), every `css`/`html`/`jsFunctions` `ACTIVITY_DELTA`
was emitted **before the activity message existed**. `@ag-ui/client`
silently drops `ACTIVITY_DELTA` events whose `messageId` has no prior
snapshot, so all UI content was lost; the late snapshot then created an
activity containing only `{initialHeight, generating}`.

Captured from production (failing run, event order on the wire):
`ACTIVITY_DELTA /css → /cssComplete → /html (127 chunks) → /htmlComplete
→ /jsFunctions → … → ACTIVITY_SNAPSHOT {initialHeight: 760} → … →
/generating: false`

Result: 760px empty gray box, zero console errors, all requests 200.
Whether a given click works is pure LLM key-order roulette — which is
why the demo failed twice in a row and then worked.

## Fix

- Emit the snapshot lazily before the **first** delta of any kind
(guarded, still emitted exactly once).
- When `initialHeight` parses after the snapshot has gone out, deliver
it as a `/initialHeight` delta instead.
- Happy path (`initialHeight` first) is byte-identical to current
behavior; all 18 existing tests pass unchanged.

## Tests

3 new regression tests, including one that replays the exact production
failure ordering (`css → html → jsFunctions → initialHeight →
jsExpressions → placeholderMessages`) and asserts the reconstructed
activity content is complete. Red before the fix, green after. Full
`@copilotkit/runtime` suite: 1528 tests pass.

## Follow-ups (out of scope)

- `@ag-ui/client` drops `ACTIVITY_DELTA` for unknown `messageId`
silently — a `console.warn` there would have surfaced this immediately.
Worth an upstream issue.
- Pre-existing `check-types` failures/OOM in `packages/runtime` (398
errors in unrelated test files on main) — unrelated to this change.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-27 16:52:18 -07:00
David McKay 2864973051 Banking demo: ChatGPT-style shell, gen-UI beats, and durable-memory self-learning (#6136)
Upgrades the Northwind Finance banking demo
(`examples/showcases/banking`) into a presentable, end-to-end demo.
Every beat below was driven live and verified against real app state
(API reads, not just "the agent said so").

## The demo: 8 suggestion pills, in order

1. **Show the spending trend** — chart in chat
2. **Change my card PIN** — interactive PIN component *in the chat*:
card picker + four masked auto-advancing digits. The agent never asks
for or receives the digits, so the PIN stays out of the transcript and
the model's context
3. **What's on my screen?** — answers from what the page actually
renders
4. **Show me the 10 most expensive charges** — new `/charges` page,
agent navigates pre-filtered and stack-ranked
5. **Prep the Q2 spend report** — one click stages a real PDF invoice;
gpt-5.4 reads it and folds its 5 line items ($62,800) into the filed
report's figures and charts
6. **Summarize our spend** — recalls a stored *preference* and applies
it unasked; the recalled preference drives the component's props, so
memory changes the UI's structure
7. **I don't recognize the Delta charge** — recalls a stored *procedure*
and executes it: flag → alert → note
8. **Approve the $15,000 AWS charge** — learns a *new* procedure: record
→ watch the officer demonstrate → save to durable memory. On a **fresh
thread** it then applies that procedure to a different over-limit charge
unaided

Beats 6–8 are deliberately sequenced: already-knows-a-preference →
already-knows-a-procedure → learns-a-new-one.

## Shell and UI

- **Chat docked left, ChatGPT-style**: `[ threads 200 ][ conversation
480 ][ app ][ nav rail ]`. The app's icon rail moved right via flex
`order`, so nav still precedes `<main>` in the DOM
- The chat surface runs ChatGPT's neutral palette; the banking app keeps
its violet identity
- **Tool activity** renders as ChatGPT-style expandable lines (chevron
reveals the real call); internal plumbing (AG-UI state deltas) is
filtered out
- **Never markdown tables** — tabular data goes through components; a
one-row table is blocked structurally, since a single-record table is
narration, not data
- Assistant markdown scoped and retuned for a narrow column
- Filed reports are now a dashboard (KPI tiles + 3 charts + decision
rows), not a memo
- One colour per team across every chart, keyed to the team rather than
its index
- Report uses ranked bars, not a donut: an attached invoice pushes one
team to 96% and a donut collapses to a single ring

## Notable fixes worth a reviewer's eye

- **Anything a recalled procedure can call must be registered
globally.** Four action tools were registered on the cards route, so a
saved procedure only worked from one page and failed elsewhere with "the
required action tools aren't available"
- **An unanswered approval card poisons the thread.** The procedure's
closing step was human-in-the-loop; skipping it left an unresolved tool
call and the next message died with `Tool result is missing for tool
call …`. That step now runs without a gate — approvals that move money
keep theirs
- **Two families of helpers in `actions.ts`** and they are not
interchangeable: `openPolicyException` / `finalizePolicyException` /
`changeTransactionStatus` return `{ok, data/error}`; `changePin` /
`addNoteToTransaction` resolve with the updated record. Destructuring
`ok` off the second family reports success as failure
- **A stale canvas surface hid the page.** The canvas is derived from
the last `a2ui-surface` message and replaces the page body until
dismissed; the dismiss effect keyed on `pathname` only, so a `?tab=`
switch left it covering the page
- Suggestion pills ignore clicks while a run is in flight (a
double-click orphaned the previous run's tool call)

## Verification

Full 8-beat sequence run in one unbroken session from a clean reset,
checked against the API: charges navigate correctly; the report files
with 5 invoice additions; the Delta note content changes from its seeded
value; the teach arc captures all 6 demonstrated steps and persists the
`EXC-BOARD-APPROVED` procedure (memory 3 → 4); a fresh thread then
recalls it. Typecheck and lint clean; 0 console errors on load, both
light and dark.

## Not included on purpose

The `@copilotkit/license-verifier` `pnpm patch` (local workaround for
the "Invalid license token" banner) is left uncommitted — it patches a
dependency for every install and isn't demo scope.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-27 16:52:03 -07:00
David McKay cee8fcfcbb Merge branch 'main' into banking-demo-upgrades 2026-07-27 16:24:16 -07:00
David McKay 66a6af5d3e feat(banking): ChatGPT-style shell, gen-UI beats, and durable-memory self-learning
Turns the Northwind Finance banking demo into a presentable end-to-end demo.
Every beat was driven live and verified against real app state, not just the
agent's own claims.

The demo is eight suggestion pills, in order: show a chart; change a card PIN in
an interactive in-chat component; ask what is on screen; open the 10 most
expensive charges; prep the Q2 report from an attached PDF invoice; summarize
spend using a remembered preference; handle an unrecognized charge using a
remembered procedure; and finally learn a brand-new procedure by watching the
officer clear an over-limit charge. The last three are sequenced deliberately:
already-knows-a-preference, already-knows-a-procedure, learns-a-new-one — and on
a fresh thread the newly learned procedure is recalled and applied to a
different charge unaided.

Shell and UI:
- Chat docks LEFT as two columns (thread rail + conversation) with the app's
  icon rail moved right via flex `order`, so nav still precedes <main> in the
  DOM. The chat surface runs ChatGPT's neutral palette while the app keeps its
  violet identity.
- Tool activity renders as ChatGPT-style expandable lines; internal AG-UI
  plumbing is filtered out of the transcript.
- Tabular data always renders as components, never markdown tables; a one-row
  table is blocked structurally because a single-record table is narration.
- Filed reports are a dashboard (KPI tiles, three charts, decision rows) rather
  than a memo, with one colour per team across every chart and ranked bars in
  place of a donut that collapsed when an invoice pushed one team to 96%.
- PIN entry happens in the chat component; the agent never asks for or receives
  the digits.

Fixes worth review attention:
- Anything a RECALLED procedure can call must be registered globally. Four
  action tools were registered on the cards route, so a saved procedure only
  worked from one page.
- An unanswered human-in-the-loop card poisons the thread: skipping it left an
  unresolved tool call and the next message failed with "Tool result is missing
  for tool call ...". The procedure's closing step no longer gates; approvals
  that move money still do.
- actions.ts has two helper families that are not interchangeable —
  openPolicyException / finalizePolicyException / changeTransactionStatus return
  {ok, data/error}, while changePin / addNoteToTransaction resolve with the
  updated record. Destructuring `ok` off the second family reports success as
  failure.
- A stale canvas surface hid the page: it is derived from the last a2ui-surface
  message and replaces the page body until dismissed, but the dismiss effect
  keyed on pathname only, so a ?tab= switch left it covering the page.
- Suggestion pills ignore clicks while a run is in flight, since a double-click
  orphaned the previous run's tool call.

Deliberately NOT included: the @copilotkit/license-verifier pnpm patch (local
workaround for the "Invalid license token" banner) stays uncommitted, because it
patches a dependency for every install and is not demo scope.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 16:22:36 -07:00
David McKay 6632e48a89 Merge branch 'main' into david/fix-cpk-7634-activity-snapshot-order 2026-07-27 15:21:27 -07:00
David McKay d892b7975c Merge branch 'main' into docs/react-native-production-guide 2026-07-27 15:20:23 -07:00
Benjamin Taylor ef21c12b6d fix(channels-intelligence): surface an unreachable gateway host instead of hanging the connect
A managed Channel whose wsUrl points at a wrong or unreachable host sat in
connecting for the caller's full settle budget and then reported only a
timeout. Phoenix retries a failed FIRST connect forever and reports transport
errors as reconnect churn, and the join push's own timeout never fires in that
state (Channel.onError calls joinPush.reset() while joining, detaching the
reply binding the armed timeout would have triggered), so the connect await
hung indefinitely with no indication that the host was the problem.

Watch the initial connect separately from reconnects, keyed off whether the
socket has EVER opened: a socket that never opened rejects with a new
RealtimeGatewayUnreachableError naming the endpoint and the underlying
transport error, while one that opened and later dropped stays with the
existing reconnect/give-up state machine. NXDOMAIN fails immediately (a host
that does not exist cannot start existing); every other cause is given the new
connectTimeoutMs window so a still-booting gateway is not turned into a
permanently errored Channel — the supervising ChannelManager does not retry
activation.

The error deliberately does NOT carry the SETUP_REQUIRED marker: a
misconfigured host must reject ready(), not resolve as setup_required.

Refs OSS-623
2026-07-27 17:11:51 -05:00
Benjamin Taylor 3b842ccf1b docs(channels-intelligence): name the gateway socket path the code actually uses
ConnectRealtimeGatewayOptions.wsUrl was documented as
`wss://gateway.example/socket`, while the runtime skill lists `/socket` as a
mistake because it is not a real path. The skill is right, and the gateway
mounts exactly `/runner` and `/client` — there is no `/socket`.

The two docs were describing different layers, which is how the contradiction
survived: the public wsUrl a developer configures is a bare base, and the
Intelligence client appends `/runner` before it reaches connectRealtimeGateway,
after which Phoenix appends `/websocket`. So this option is the fully-resolved
runner URL, and the doc comment now says so instead of implying developers
should write `/socket` anywhere. Test fixtures follow.

Refs OSS-621
2026-07-27 16:46:49 -05:00
Benjamin Taylor 8fb739fc53 docs(channels): stop teaching a websocket URL derive that cannot work
The published Channels docs configured the Intelligence client with
`wsUrl: apiUrl.replace(/^http/, "ws")` in all eight pages. Because the API and
realtime planes are separate hosts, that yields a URL serving no socket, and
the failure is a silent 30s hang rather than an error — so this was the most
harmful copy of the bug: it is the surface developers actually read.

Every page now reads COPILOTKIT_INTELLIGENCE_WS_URL from the environment
alongside the API URL, and the six env blocks that previously showed only a
`your-intelligence-url` placeholder document both hosts with the values that
work against the managed service. The quickstart gains a short paragraph on why
the second host is required and what going wrong looks like.

Refs OSS-621
2026-07-27 16:46:39 -05:00
Benjamin Taylor eba97178bc fix(examples): require the Intelligence websocket URL instead of deriving it
The Slack and Teams examples derived COPILOTKIT_INTELLIGENCE_WS_URL from
COPILOTKIT_INTELLIGENCE_URL with a scheme-only swap when it was unset, and
documented it as optional. That derive preserves host and port, so it is only
correct on a deployment that puts both planes behind one host+port — which is
neither prod (api.intelligence… vs realtime.intelligence…) nor local dev (4201
vs 4401). In practice it was always wrong, and wrong in the worst way: the
resulting join hangs in `connecting` for 30s and reports only a timeout.

All three call sites (slack native, slack managed, teams) now require both
URLs explicitly and deriveWsUrl is gone. The Teams startup error names both
vars and says why one cannot be computed from the other.

managed.test.ts previously deleted the WS var to exercise the derive; it now
sets a host+port deliberately different from the API URL, so the test encodes
that the planes are deployed apart rather than assuming they are not.

Refs OSS-621
2026-07-27 16:46:28 -05:00
Benjamin Taylor 3c1c85937b fix(docs): point Intelligence docs at the hosts that actually serve prod
Every documented Intelligence endpoint used api.copilotkit.ai for both the
REST and websocket planes. That host returns 404, and the realtime host it
implied (realtime.copilotkit.ai) has no DNS record at all, so a developer
copying any of these snippets could not connect.

The real managed endpoints are split across two hosts, matching what the CLI
bakes in as its prod defaults and what gitops deploys:

  apiUrl: https://api.intelligence.copilotkit.ai
  wsUrl:  wss://realtime.intelligence.copilotkit.ai

Because the planes are separate hosts, wsUrl cannot be produced from apiUrl by
swapping the scheme, so each site now says so explicitly. The runtime skill's
"common mistakes" section gains that as a third failure mode, and the
CopilotKitIntelligenceConfig JSDoc documents it on the field itself — a wrong
apiUrl fails fast with an HTTP error, but a wrong wsUrl only hangs until the
settle timeout, which is what made this expensive to diagnose.

skills/runtime is the generated mirror of packages/runtime/skills/runtime,
regenerated with pnpm sync:plugin-skills. skills/copilotkit-debug is
standalone; its /info sample keeps the /client suffix, which is correct there
because that response carries the already-derived client URL.

Refs OSS-621
2026-07-27 16:44:38 -05:00
Mike Ryan 033b0ae25a Make Angular docs a first-class docs surface (#6138)
## Summary

Angular is now a first-class docs surface instead of a small frontend
island:

- give `/angular` the full shared documentation information
architecture, including deployment, troubleshooting, What's New,
Channels, CLI, telemetry, backend setup, Runtime, agentic protocols, and
CopilotKit Enterprise Intelligence
- keep frontend and backend selection orthogonal, preserving the
selected backend when switching React ↔ Angular
- share frontend-independent content once and swap only frontend-owned
sections, avoiding an Angular × backend × topic copy matrix
- resolve React-owned topics to Angular-native task guides and canonical
redirects instead of showing React hooks or provider setup
- render Angular-specific branches correctly in pages and `.mdx`/LLM
output, including nested shared snippets
- source Angular examples from runnable Showcase regions for tools,
agent config, sub-agent delegation, headless UI, interrupts,
authentication, and Mastra activity renderers
- add Angular-native shared-state, HITL, generative UI, auth, thread,
troubleshooting, metadata, canonical, search, and sitemap coverage
- add a complete `injectThreads` API reference so the threads journey
has no dangling handoff

## Guardrails

- exact React → Angular information-architecture parity test across the
root and all 20 visible backends; every React destination maps to a
published Angular destination
- exhaustive Angular × every visible backend render test: every page
resolves, and rendered Markdown contains no React package, hook,
provider, or unexpanded frontend/snippet components
- shared Runtime, Intelligence, deployment, troubleshooting, Channels,
and operational routes stay canonical instead of being duplicated for
every backend
- backend-owned pages appear under a backend only when that backend
genuinely changes the content
- frontend selector links preserve backend context
- Showcase source-region tests ensure Angular snippets remain tied to
runnable code
- shell-docs image contract ensures Angular source is staged and bundled
before the production build

## Validation

- `npm run test` in `showcase/shell-docs` — 42 files / 209 tests
- `npm run typecheck` in `showcase/shell-docs`
- `npm run lint` in `showcase/shell-docs` — clean apart from existing
warnings
- `npm run build` in `showcase/shell-docs` — 222 static pages
- `pnpm nx run @copilotkit/showcase-scripts:test --skip-nx-cache` — 72
files / 2,324 tests
- `pnpm nx run @copilotkit/showcase-angular-host:typecheck
--skip-nx-cache`
- clean root-context `showcase/shell-docs/Dockerfile` build — all
content generators, TypeScript, and 222 static pages
- production-server smoke checks for Angular root, deployment,
troubleshooting, What's New, Channels, telemetry, LangGraph agent
config/sub-agents, API reference, `.mdx` output, canonical redirects,
and sitemap
- rendered `.mdx` audit confirms shared backend guidance and
Showcase-derived Angular snippets are present with no React
imports/hooks or raw conditional tags

The broad `verify-shell-docs:fast` audit remains red on the repository's
existing whole-tree dead-link/content-shape baseline; the same audit is
red on `main`. Branch-specific tests and production routes are green.

Draft intentionally; mark ready only when requested.
2026-07-27 10:06:03 -07:00
Mike Ryan dacc667355 fix(docs): resolve Angular overview guide links 2026-07-27 09:38:01 -07:00
Mike Ryan 852990ccd1 fix(docs): select Angular quickstart branch before MDX 2026-07-27 09:38:01 -07:00
Mike Ryan f43682b3db test(showcase): align Angular staging assertion 2026-07-27 09:38:01 -07:00
Mike Ryan b7e83d7c1f docs: make Angular journeys capability-aware 2026-07-27 09:38:01 -07:00
Mike Ryan 8a2844b571 docs: enforce angular-only documentation routes 2026-07-27 09:38:01 -07:00
Mike Ryan 9988c23890 docs: render shared concepts for Angular 2026-07-27 09:38:00 -07:00
Mike Ryan cc8689e21f fix(showcase): bundle Angular docs source in image 2026-07-27 09:38:00 -07:00
Mike Ryan a87f1c9a30 docs: close Angular information architecture gaps 2026-07-27 09:38:00 -07:00
Mike Ryan db732d3966 docs: complete Angular threads API journey 2026-07-27 09:38:00 -07:00
Mike Ryan 38333d3625 docs: enforce Angular parity across backend guides 2026-07-27 09:38:00 -07:00
Mike Ryan 92e5c31332 docs: align Angular metadata with rendered variants 2026-07-27 09:37:59 -07:00
Mike Ryan fcf2357c25 docs: add Angular-native content and source-backed snippets 2026-07-27 09:37:59 -07:00
Mike Ryan c569aa595d docs: make Angular share the full information architecture 2026-07-27 09:37:59 -07:00
David McKay 6eefe2b551 docs(react-native): correct polyfill auto-install claim for /components
Independent re-verification against packages/react-native source found the
page overstated polyfill auto-install: only /headless (and the root barrel,
via its /headless re-export) side-effect-imports the polyfill barrel.
src/components/index.ts imports no polyfills, so a consumer importing only
from @copilotkit/react-native/components does not get them auto-installed;
it relies on the provider imported alongside it. Corrects the 'all three
surfaces auto-install' and 'every entry point installs these automatically'
statements accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 09:35:53 -07:00
Tyler Slaton 47ce2720a6 fix(release): make verify:runtime-package pass on channels release PRs (#6186)
Fixes OSS-616

## Problem

`scripts/release/verify-runtime-package.ts` **cannot pass on a channels
release PR**, so the `unit (20.x)` leg is red by construction on exactly
the PRs the gate exists to protect — and gets merged past.

1. `packages/runtime/package.json` declares
`@copilotkit/channels-intelligence: workspace:*`.
2. `pnpm pack` rewrites `workspace:` to the workspace's *current*
version — on a release PR, the freshly-bumped one (e.g. `0.3.0`).
3. The temp consumer then has to resolve
`@copilotkit/channels-intelligence@0.3.0` **from npm**.
4. That version doesn't exist yet. This PR is what publishes it.

Real CI output from #6185:

```
ERR_PNPM_NO_MATCHING_VERSION  No matching version found for
@copilotkit/channels-intelligence@0.3.0 while fetching it from https://registry.npmjs.org/
This error happened while installing the dependencies of @copilotkit/runtime@1.63.2
The latest release of @copilotkit/channels-intelligence is "0.2.1". Published at 7/17/2026
```

Red instances: **#6185** (`channels/v0.3.0`) and **#6025**
(`channels/v0.2.1`, merged red on 2026-07-17). #5989 (`v0.2.0`) passed
only because the hard runtime dependency landed the next day in
`fea464de52`.

## Fix

`verify-channels-umbrella.ts` already solved this — its own comment
describes the problem verbatim. Same approach applied here: pack the
whole first-party `workspace:` closure locally and pin it through pnpm
`overrides` so nothing unpublished is resolved over the network.

The packing helpers (`packPackage`, the transitive `workspace:` walk)
move into `scripts/release/lib/pack-workspace.ts`, shared by both
scripts instead of duplicated. `verify-channels-umbrella` behavior is
unchanged — it just calls the extracted helpers.

**The check is not weakened.** It still asserts the packed runtime
declares `@copilotkit/channels-intelligence` as a real dependency and
that the package loads through both ESM and CJS. A new guard fails
loudly if channels-intelligence ever stops being a `workspace:` dep, so
a silent fallback to registry resolution can't creep back in. Runtime
dependency ranges were not touched.

## Testing

All runs under Node 20 in a clean worktree off `origin/main`.

**1. Reproduced the failure** — applied the exact 9 version bumps from
`release/publish/channels/v0.3.0`, then ran the pre-fix script from
`origin/main`:

```
The latest release of @copilotkit/channels-core is "0.2.1". Published at 7/17/2026
Command failed: pnpm install --ignore-scripts
```

**2. Fixed script, same bumped state** — acceptance criterion 1:

```
$ pnpm run verify:runtime-package
OK: packed runtime installs @copilotkit/channels-intelligence and loads through ESM and CJS.
EXIT=0
```

**3. Still fails when the dependency is removed** — deleted
`@copilotkit/channels-intelligence` from
`packages/runtime/package.json`:

```
packed runtime must install @copilotkit/channels-intelligence as a dependency
EXIT=1
```

**4. Still fails on a genuine ESM load break** — `throw` at the top of
`packages/channels-intelligence/src/index.ts`:

```
Error: simulated ESM load break
Command failed: pnpm exec node --experimental-import-meta-resolve --input-type=module --eval ...
EXIT=1
```

**5. Still fails on a genuine CJS load break** — `throw` at the top of
`packages/runtime/src/v2/index.ts`:

```
Error: simulated CJS load break
Command failed: pnpm exec node --eval require("@copilotkit/runtime");
EXIT=1
```

**6. Umbrella check unaffected by the helper extraction:**

```
$ pnpm run verify:channels-umbrella
OK: local Channels snapshot is exact, compatible, singly resolved, and TSX-consumable.
```

**7. Release script tests** — new
`scripts/release/__tests__/verify-runtime-package.test.ts` models the
release-PR case (bumped local versions absent from the registry) and
asserts no workspace dependency is left registry-resolvable. The vitest
include glob was extended to cover `__tests__/`:

```
Test Files  10 passed (10)
     Tests  137 passed (137)
```

**8.** `oxfmt --check` clean, `oxlint` 0 warnings / 0 errors, `tsc
--strict --noEmit` clean on all four touched scripts.

All mutations from steps 1 and 3–5 were reverted; the final diff touches
`scripts/release/` only.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-27 09:32:04 -07:00
Benjamin Taylor 4a9837e176 fix(release): pack runtime workspace deps locally in verify-runtime-package
`verify:runtime-package` could not pass on a channels release PR. `pnpm pack`
rewrites the runtime's `workspace:` ranges to the workspace's current version,
so on a release PR the temp consumer tried to resolve the freshly-bumped
`@copilotkit/channels-intelligence` from npm — the version this very PR is
about to publish. It failed `unit (20.x)` by construction on #6185 and #6025.

Apply the fix `verify-channels-umbrella` already uses: pack the whole
first-party `workspace:` closure locally and pin it through pnpm `overrides`.
The packing helpers move to `lib/pack-workspace.ts` so both scripts share one
implementation instead of duplicating it.

The contract is unchanged: the packed runtime must still declare
channels-intelligence as a real dependency, and it must still load through
both ESM and CJS.
2026-07-27 11:10:32 -05:00
Ben Taylor 8180a9160d chore: release channels v0.3.0 (#6185)
## Release channels v0.3.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.3.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.3.0`
   - Creates git tag `channels/v0.3.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.3.0
2026-07-27 11:09:22 -05:00
BenTaylorDev 0e19eb2d5d chore: release channels v0.3.0 2026-07-27 15:43:19 +00:00
Lukas Moschitz 5d3dac1298 fix(showcase): restore langgraph-fastapi gen-ui-open.json fixtures (CI: aimock-fixtures)
CI 'Showcase: Validate' failed: aimock-fixtures.test.ts asserts every d6/*.json
yields >=1 fixture, but gen-ui-open.json had been emptied to a _meta stub (0
fixtures) by an earlier open-gen-ui shadowing fix that consolidated the pills
into gen-ui-tool-based.json.

Restore gen-ui-open.json as a byte-for-byte context-swap of the canonical
langgraph-python file (context -> langgraph-fastapi): 4 fixtures for the '3D
axis' and 'Inline expression evaluator' pills, LGP's clean shape
(hasToolResult:false leg-1, distinct toolCallId terminals). No shadowing loop:
each file's leg-1/terminal pair is anchored by its own distinct toolCallId, so
the done-signal always resolves.

Verified: aimock-fixtures.test.ts 839/839 green locally; D6 gen-ui-open,
gen-ui-open-advanced, gen-ui-custom all green (two real runs each).
2026-07-27 17:28:26 +02:00